Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright 2011 Google Inc. | 2 * Copyright 2011 Google Inc. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
| 5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
| 6 */ | 6 */ |
| 7 | 7 |
| 8 #include "GrAAHairLinePathRenderer.h" | 8 #include "GrAAHairLinePathRenderer.h" |
| 9 | 9 |
| 10 #include "GrContext.h" | 10 #include "GrContext.h" |
| (...skipping 632 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 643 /////////////////////////////////////////////////////////////////////////////// | 643 /////////////////////////////////////////////////////////////////////////////// |
| 644 | 644 |
| 645 namespace { | 645 namespace { |
| 646 // position + edge | 646 // position + edge |
| 647 extern const GrVertexAttrib gHairlineBezierAttribs[] = { | 647 extern const GrVertexAttrib gHairlineBezierAttribs[] = { |
| 648 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBind ing}, | 648 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBind ing}, |
| 649 {kVec4f_GrVertexAttribType, sizeof(SkPoint), kGeometryProcessor_GrVertexA ttribBinding} | 649 {kVec4f_GrVertexAttribType, sizeof(SkPoint), kGeometryProcessor_GrVertexA ttribBinding} |
| 650 }; | 650 }; |
| 651 }; | 651 }; |
| 652 | 652 |
| 653 bool GrAAHairLinePathRenderer::createLineGeom(const SkPath& path, | 653 bool GrAAHairLinePathRenderer::createLineGeom(GrDrawTarget* target, |
| 654 GrDrawTarget* target, | 654 GrDrawState* drawState, |
| 655 GrDrawTarget::AutoReleaseGeometry* arg, | |
| 656 SkRect* devBounds, | |
| 657 const SkPath& path, | |
| 655 const PtArray& lines, | 658 const PtArray& lines, |
| 656 int lineCnt, | 659 int lineCnt) { |
| 657 GrDrawTarget::AutoReleaseGeometry* arg, | |
| 658 SkRect* devBounds) { | |
| 659 GrDrawState* drawState = target->drawState(); | |
| 660 | |
| 661 const SkMatrix& viewM = drawState->getViewMatrix(); | 660 const SkMatrix& viewM = drawState->getViewMatrix(); |
| 662 | 661 |
| 663 int vertCnt = kLineSegNumVertices * lineCnt; | 662 int vertCnt = kLineSegNumVertices * lineCnt; |
| 664 | 663 |
| 665 GrDefaultGeoProcFactory::SetAttribs(drawState, GrDefaultGeoProcFactory::kPos ition_GPType | | 664 GrDefaultGeoProcFactory::SetAttribs(drawState, GrDefaultGeoProcFactory::kPos ition_GPType | |
| 666 GrDefaultGeoProcFactory::kCov erage_GPType); | 665 GrDefaultGeoProcFactory::kCov erage_GPType); |
| 667 | 666 |
| 668 if (!arg->set(target, vertCnt, 0)) { | 667 if (!arg->set(target, vertCnt, drawState->getVertexStride(), 0)) { |
| 669 return false; | 668 return false; |
| 670 } | 669 } |
| 671 | 670 |
| 672 LineVertex* verts = reinterpret_cast<LineVertex*>(arg->vertices()); | 671 LineVertex* verts = reinterpret_cast<LineVertex*>(arg->vertices()); |
| 673 | 672 |
| 674 const SkMatrix* toSrc = NULL; | 673 const SkMatrix* toSrc = NULL; |
| 675 SkMatrix ivm; | 674 SkMatrix ivm; |
| 676 | 675 |
| 677 if (viewM.hasPerspective()) { | 676 if (viewM.hasPerspective()) { |
| 678 if (viewM.invert(&ivm)) { | 677 if (viewM.invert(&ivm)) { |
| 679 toSrc = &ivm; | 678 toSrc = &ivm; |
| 680 } | 679 } |
| 681 } | 680 } |
| 682 devBounds->set(lines.begin(), lines.count()); | 681 devBounds->set(lines.begin(), lines.count()); |
| 683 for (int i = 0; i < lineCnt; ++i) { | 682 for (int i = 0; i < lineCnt; ++i) { |
| 684 add_line(&lines[2*i], toSrc, drawState->getCoverage(), &verts); | 683 add_line(&lines[2*i], toSrc, drawState->getCoverage(), &verts); |
| 685 } | 684 } |
| 686 // All the verts computed by add_line are within sqrt(1^2 + 0.5^2) of the en d points. | 685 // All the verts computed by add_line are within sqrt(1^2 + 0.5^2) of the en d points. |
| 687 static const SkScalar kSqrtOfOneAndAQuarter = 1.118f; | 686 static const SkScalar kSqrtOfOneAndAQuarter = 1.118f; |
| 688 // Add a little extra to account for vector normalization precision. | 687 // Add a little extra to account for vector normalization precision. |
| 689 static const SkScalar kOutset = kSqrtOfOneAndAQuarter + SK_Scalar1 / 20; | 688 static const SkScalar kOutset = kSqrtOfOneAndAQuarter + SK_Scalar1 / 20; |
| 690 devBounds->outset(kOutset, kOutset); | 689 devBounds->outset(kOutset, kOutset); |
| 691 | 690 |
| 692 return true; | 691 return true; |
| 693 } | 692 } |
| 694 | 693 |
| 695 bool GrAAHairLinePathRenderer::createBezierGeom( | 694 bool |
|
bsalomon
2014/11/14 21:51:02
does this need to be on its own line?
| |
| 696 const SkPath& path, | 695 GrAAHairLinePathRenderer::createBezierGeom(GrDrawTarget* target, |
| 697 GrDrawTarget* target, | 696 GrDrawState* drawState, |
| 698 const PtArray& quads, | 697 GrDrawTarget::AutoReleaseGeometry* ar g, |
| 699 int quadCnt, | 698 SkRect* devBounds, |
| 700 const PtArray& conics, | 699 const SkPath& path, |
| 701 int conicCnt, | 700 const PtArray& quads, |
| 702 const IntArray& qSubdivs, | 701 int quadCnt, |
| 703 const FloatArray& cWeights, | 702 const PtArray& conics, |
| 704 GrDrawTarget::AutoReleaseGeometry* arg , | 703 int conicCnt, |
| 705 SkRect* devBounds) { | 704 const IntArray& qSubdivs, |
| 706 GrDrawState* drawState = target->drawState(); | 705 const FloatArray& cWeights) { |
| 707 | |
| 708 const SkMatrix& viewM = drawState->getViewMatrix(); | 706 const SkMatrix& viewM = drawState->getViewMatrix(); |
| 709 | 707 |
| 710 int vertCnt = kQuadNumVertices * quadCnt + kQuadNumVertices * conicCnt; | 708 int vertCnt = kQuadNumVertices * quadCnt + kQuadNumVertices * conicCnt; |
| 711 | 709 |
| 712 int vAttribCnt = SK_ARRAY_COUNT(gHairlineBezierAttribs); | 710 int vAttribCnt = SK_ARRAY_COUNT(gHairlineBezierAttribs); |
| 713 target->drawState()->setVertexAttribs<gHairlineBezierAttribs>(vAttribCnt, si zeof(BezierVertex)); | 711 drawState->setVertexAttribs<gHairlineBezierAttribs>(vAttribCnt, sizeof(Bezie rVertex)); |
| 714 | 712 |
| 715 if (!arg->set(target, vertCnt, 0)) { | 713 if (!arg->set(target, vertCnt, drawState->getVertexStride(), 0)) { |
| 716 return false; | 714 return false; |
| 717 } | 715 } |
| 718 | 716 |
| 719 BezierVertex* verts = reinterpret_cast<BezierVertex*>(arg->vertices()); | 717 BezierVertex* verts = reinterpret_cast<BezierVertex*>(arg->vertices()); |
| 720 | 718 |
| 721 const SkMatrix* toDevice = NULL; | 719 const SkMatrix* toDevice = NULL; |
| 722 const SkMatrix* toSrc = NULL; | 720 const SkMatrix* toSrc = NULL; |
| 723 SkMatrix ivm; | 721 SkMatrix ivm; |
| 724 | 722 |
| 725 if (viewM.hasPerspective()) { | 723 if (viewM.hasPerspective()) { |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 750 add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts, devBounds); | 748 add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts, devBounds); |
| 751 } | 749 } |
| 752 | 750 |
| 753 // Start Conics | 751 // Start Conics |
| 754 for (int i = 0; i < conicCnt; ++i) { | 752 for (int i = 0; i < conicCnt; ++i) { |
| 755 add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &verts, devBounds ); | 753 add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &verts, devBounds ); |
| 756 } | 754 } |
| 757 return true; | 755 return true; |
| 758 } | 756 } |
| 759 | 757 |
| 760 bool GrAAHairLinePathRenderer::canDrawPath(const SkPath& path, | 758 bool GrAAHairLinePathRenderer::canDrawPath(const GrDrawTarget* target, |
| 759 const GrDrawState* drawState, | |
| 760 const SkPath& path, | |
| 761 const SkStrokeRec& stroke, | 761 const SkStrokeRec& stroke, |
| 762 const GrDrawTarget* target, | |
| 763 bool antiAlias) const { | 762 bool antiAlias) const { |
| 764 if (!antiAlias) { | 763 if (!antiAlias) { |
| 765 return false; | 764 return false; |
| 766 } | 765 } |
| 767 | 766 |
| 768 if (!IsStrokeHairlineOrEquivalent(stroke, | 767 if (!IsStrokeHairlineOrEquivalent(stroke, |
| 769 target->getDrawState().getViewMatrix(), | 768 drawState->getViewMatrix(), |
| 770 NULL)) { | 769 NULL)) { |
| 771 return false; | 770 return false; |
| 772 } | 771 } |
| 773 | 772 |
| 774 if (SkPath::kLine_SegmentMask == path.getSegmentMasks() || | 773 if (SkPath::kLine_SegmentMask == path.getSegmentMasks() || |
| 775 target->caps()->shaderDerivativeSupport()) { | 774 target->caps()->shaderDerivativeSupport()) { |
| 776 return true; | 775 return true; |
| 777 } | 776 } |
| 778 return false; | 777 return false; |
| 779 } | 778 } |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 808 actualBounds.growToInclude(pos.fX, pos.fY); | 807 actualBounds.growToInclude(pos.fX, pos.fY); |
| 809 } | 808 } |
| 810 } | 809 } |
| 811 if (!first) { | 810 if (!first) { |
| 812 return tolDevBounds.contains(actualBounds); | 811 return tolDevBounds.contains(actualBounds); |
| 813 } | 812 } |
| 814 | 813 |
| 815 return true; | 814 return true; |
| 816 } | 815 } |
| 817 | 816 |
| 818 bool GrAAHairLinePathRenderer::onDrawPath(const SkPath& path, | 817 bool GrAAHairLinePathRenderer::onDrawPath(GrDrawTarget* target, |
| 818 GrDrawState* drawState, | |
| 819 const SkPath& path, | |
| 819 const SkStrokeRec& stroke, | 820 const SkStrokeRec& stroke, |
| 820 GrDrawTarget* target, | |
| 821 bool antiAlias) { | 821 bool antiAlias) { |
| 822 GrDrawState* drawState = target->drawState(); | |
| 823 | |
| 824 SkScalar hairlineCoverage; | 822 SkScalar hairlineCoverage; |
| 825 if (IsStrokeHairlineOrEquivalent(stroke, | 823 if (IsStrokeHairlineOrEquivalent(stroke, drawState->getViewMatrix(), |
| 826 target->getDrawState().getViewMatrix(), | |
| 827 &hairlineCoverage)) { | 824 &hairlineCoverage)) { |
| 828 uint8_t newCoverage = SkScalarRoundToInt(hairlineCoverage * | 825 uint8_t newCoverage = SkScalarRoundToInt(hairlineCoverage * drawState->g etCoverage()); |
| 829 target->getDrawState().getCover age()); | 826 drawState->setCoverage(newCoverage); |
| 830 target->drawState()->setCoverage(newCoverage); | |
| 831 } | 827 } |
| 832 | 828 |
| 833 SkIRect devClipBounds; | 829 SkIRect devClipBounds; |
| 834 target->getClip()->getConservativeBounds(drawState->getRenderTarget(), &devC lipBounds); | 830 target->getClip()->getConservativeBounds(drawState->getRenderTarget(), &devC lipBounds); |
| 835 | 831 |
| 836 int lineCnt; | 832 int lineCnt; |
| 837 int quadCnt; | 833 int quadCnt; |
| 838 int conicCnt; | 834 int conicCnt; |
| 839 PREALLOC_PTARRAY(128) lines; | 835 PREALLOC_PTARRAY(128) lines; |
| 840 PREALLOC_PTARRAY(128) quads; | 836 PREALLOC_PTARRAY(128) quads; |
| 841 PREALLOC_PTARRAY(128) conics; | 837 PREALLOC_PTARRAY(128) conics; |
| 842 IntArray qSubdivs; | 838 IntArray qSubdivs; |
| 843 FloatArray cWeights; | 839 FloatArray cWeights; |
| 844 quadCnt = generate_lines_and_quads(path, drawState->getViewMatrix(), devClip Bounds, | 840 quadCnt = generate_lines_and_quads(path, drawState->getViewMatrix(), devClip Bounds, |
| 845 &lines, &quads, &conics, &qSubdivs, &cWei ghts); | 841 &lines, &quads, &conics, &qSubdivs, &cWei ghts); |
| 846 lineCnt = lines.count() / 2; | 842 lineCnt = lines.count() / 2; |
| 847 conicCnt = conics.count() / 3; | 843 conicCnt = conics.count() / 3; |
| 848 | 844 |
| 849 // do lines first | 845 // do lines first |
| 850 if (lineCnt) { | 846 if (lineCnt) { |
| 851 GrDrawTarget::AutoReleaseGeometry arg; | 847 GrDrawTarget::AutoReleaseGeometry arg; |
| 852 SkRect devBounds; | 848 SkRect devBounds; |
| 853 | 849 |
| 854 if (!this->createLineGeom(path, | 850 if (!this->createLineGeom(target, |
| 855 target, | 851 drawState, |
| 852 &arg, | |
| 853 &devBounds, | |
| 854 path, | |
| 856 lines, | 855 lines, |
| 857 lineCnt, | 856 lineCnt)) { |
| 858 &arg, | |
| 859 &devBounds)) { | |
| 860 return false; | 857 return false; |
| 861 } | 858 } |
| 862 | 859 |
| 863 GrDrawTarget::AutoStateRestore asr; | |
| 864 | |
| 865 // createLineGeom transforms the geometry to device space when the matri x does not have | 860 // createLineGeom transforms the geometry to device space when the matri x does not have |
| 866 // perspective. | 861 // perspective. |
| 867 if (target->getDrawState().getViewMatrix().hasPerspective()) { | 862 GrDrawState::AutoViewMatrixRestore avmr; |
| 868 asr.set(target, GrDrawTarget::kPreserve_ASRInit); | 863 if (!drawState->getViewMatrix().hasPerspective() && !avmr.setIdentity(dr awState)) { |
| 869 } else if (!asr.setIdentity(target, GrDrawTarget::kPreserve_ASRInit)) { | |
| 870 return false; | 864 return false; |
| 871 } | 865 } |
| 872 GrDrawState* drawState = target->drawState(); | |
| 873 | 866 |
| 874 // Check devBounds | 867 // Check devBounds |
| 875 SkASSERT(check_bounds<LineVertex>(drawState, devBounds, arg.vertices(), | 868 SkASSERT(check_bounds<LineVertex>(drawState, devBounds, arg.vertices(), |
| 876 kLineSegNumVertices * lineCnt)); | 869 kLineSegNumVertices * lineCnt)); |
| 877 | 870 |
| 878 { | 871 { |
| 879 GrDrawState::AutoRestoreEffects are(drawState); | 872 GrDrawState::AutoRestoreEffects are(drawState); |
| 880 drawState->setGeometryProcessor(GrDefaultGeoProcFactory::Create(fals e))->unref(); | 873 drawState->setGeometryProcessor(GrDefaultGeoProcFactory::Create(fals e))->unref(); |
| 881 target->setIndexSourceToBuffer(fLinesIndexBuffer); | 874 target->setIndexSourceToBuffer(fLinesIndexBuffer); |
| 882 int lines = 0; | 875 int lines = 0; |
| 883 while (lines < lineCnt) { | 876 while (lines < lineCnt) { |
| 884 int n = SkTMin(lineCnt - lines, kLineSegsNumInIdxBuffer); | 877 int n = SkTMin(lineCnt - lines, kLineSegsNumInIdxBuffer); |
| 885 target->drawIndexed(kTriangles_GrPrimitiveType, | 878 target->drawIndexed(drawState, |
| 879 kTriangles_GrPrimitiveType, | |
| 886 kLineSegNumVertices*lines, // startV | 880 kLineSegNumVertices*lines, // startV |
| 887 0, // startI | 881 0, // startI |
| 888 kLineSegNumVertices*n, // vCount | 882 kLineSegNumVertices*n, // vCount |
| 889 kIdxsPerLineSeg*n, // iCount | 883 kIdxsPerLineSeg*n, // iCount |
| 890 &devBounds); | 884 &devBounds); |
| 891 lines += n; | 885 lines += n; |
| 892 } | 886 } |
| 893 } | 887 } |
| 894 } | 888 } |
| 895 | 889 |
| 896 // then quadratics/conics | 890 // then quadratics/conics |
| 897 if (quadCnt || conicCnt) { | 891 if (quadCnt || conicCnt) { |
| 898 GrDrawTarget::AutoReleaseGeometry arg; | 892 GrDrawTarget::AutoReleaseGeometry arg; |
| 899 SkRect devBounds; | 893 SkRect devBounds; |
| 900 | 894 |
| 901 if (!this->createBezierGeom(path, | 895 if (!this->createBezierGeom(target, |
| 902 target, | 896 drawState, |
| 897 &arg, | |
| 898 &devBounds, | |
| 899 path, | |
| 903 quads, | 900 quads, |
| 904 quadCnt, | 901 quadCnt, |
| 905 conics, | 902 conics, |
| 906 conicCnt, | 903 conicCnt, |
| 907 qSubdivs, | 904 qSubdivs, |
| 908 cWeights, | 905 cWeights)) { |
| 909 &arg, | |
| 910 &devBounds)) { | |
| 911 return false; | 906 return false; |
| 912 } | 907 } |
| 913 | 908 |
| 914 GrDrawTarget::AutoStateRestore asr; | |
| 915 | |
| 916 // createGeom transforms the geometry to device space when the matrix do es not have | 909 // createGeom transforms the geometry to device space when the matrix do es not have |
| 917 // perspective. | 910 // perspective. |
| 918 if (target->getDrawState().getViewMatrix().hasPerspective()) { | 911 GrDrawState::AutoViewMatrixRestore avmr; |
| 919 asr.set(target, GrDrawTarget::kPreserve_ASRInit); | 912 if (!drawState->getViewMatrix().hasPerspective() && !avmr.setIdentity(dr awState)) { |
| 920 } else if (!asr.setIdentity(target, GrDrawTarget::kPreserve_ASRInit)) { | |
| 921 return false; | 913 return false; |
| 922 } | 914 } |
| 923 GrDrawState* drawState = target->drawState(); | 915 |
| 924 | 916 |
| 925 // Check devBounds | 917 // Check devBounds |
| 926 SkASSERT(check_bounds<BezierVertex>(drawState, devBounds, arg.vertices() , | 918 SkASSERT(check_bounds<BezierVertex>(drawState, devBounds, arg.vertices() , |
| 927 kQuadNumVertices * quadCnt + kQuadNu mVertices * conicCnt)); | 919 kQuadNumVertices * quadCnt + kQuadNu mVertices * conicCnt)); |
| 928 | 920 |
| 929 if (quadCnt > 0) { | 921 if (quadCnt > 0) { |
| 930 GrGeometryProcessor* hairQuadProcessor = | 922 GrGeometryProcessor* hairQuadProcessor = |
| 931 GrQuadEffect::Create(kHairlineAA_GrProcessorEdgeType, *targe t->caps()); | 923 GrQuadEffect::Create(kHairlineAA_GrProcessorEdgeType, *targe t->caps()); |
| 932 SkASSERT(hairQuadProcessor); | 924 SkASSERT(hairQuadProcessor); |
| 933 GrDrawState::AutoRestoreEffects are(drawState); | 925 GrDrawState::AutoRestoreEffects are(drawState); |
| 934 target->setIndexSourceToBuffer(fQuadsIndexBuffer); | 926 target->setIndexSourceToBuffer(fQuadsIndexBuffer); |
| 935 drawState->setGeometryProcessor(hairQuadProcessor)->unref(); | 927 drawState->setGeometryProcessor(hairQuadProcessor)->unref(); |
| 936 int quads = 0; | 928 int quads = 0; |
| 937 while (quads < quadCnt) { | 929 while (quads < quadCnt) { |
| 938 int n = SkTMin(quadCnt - quads, kQuadsNumInIdxBuffer); | 930 int n = SkTMin(quadCnt - quads, kQuadsNumInIdxBuffer); |
| 939 target->drawIndexed(kTriangles_GrPrimitiveType, | 931 target->drawIndexed(drawState, |
| 932 kTriangles_GrPrimitiveType, | |
| 940 kQuadNumVertices*quads, // sta rtV | 933 kQuadNumVertices*quads, // sta rtV |
| 941 0, // sta rtI | 934 0, // sta rtI |
| 942 kQuadNumVertices*n, // vCo unt | 935 kQuadNumVertices*n, // vCo unt |
| 943 kIdxsPerQuad*n, // iCo unt | 936 kIdxsPerQuad*n, // iCo unt |
| 944 &devBounds); | 937 &devBounds); |
| 945 quads += n; | 938 quads += n; |
| 946 } | 939 } |
| 947 } | 940 } |
| 948 | 941 |
| 949 if (conicCnt > 0) { | 942 if (conicCnt > 0) { |
| 950 GrDrawState::AutoRestoreEffects are(drawState); | 943 GrDrawState::AutoRestoreEffects are(drawState); |
| 951 GrGeometryProcessor* hairConicProcessor = GrConicEffect::Create( | 944 GrGeometryProcessor* hairConicProcessor = GrConicEffect::Create( |
| 952 kHairlineAA_GrProcessorEdgeType, *target->caps()); | 945 kHairlineAA_GrProcessorEdgeType, *target->caps()); |
| 953 SkASSERT(hairConicProcessor); | 946 SkASSERT(hairConicProcessor); |
| 954 drawState->setGeometryProcessor(hairConicProcessor)->unref(); | 947 drawState->setGeometryProcessor(hairConicProcessor)->unref(); |
| 955 int conics = 0; | 948 int conics = 0; |
| 956 while (conics < conicCnt) { | 949 while (conics < conicCnt) { |
| 957 int n = SkTMin(conicCnt - conics, kQuadsNumInIdxBuffer); | 950 int n = SkTMin(conicCnt - conics, kQuadsNumInIdxBuffer); |
| 958 target->drawIndexed(kTriangles_GrPrimitiveType, | 951 target->drawIndexed(drawState, |
| 952 kTriangles_GrPrimitiveType, | |
| 959 kQuadNumVertices*(quadCnt + conics), // sta rtV | 953 kQuadNumVertices*(quadCnt + conics), // sta rtV |
| 960 0, // sta rtI | 954 0, // sta rtI |
| 961 kQuadNumVertices*n, // vCo unt | 955 kQuadNumVertices*n, // vCo unt |
| 962 kIdxsPerQuad*n, // iCo unt | 956 kIdxsPerQuad*n, // iCo unt |
| 963 &devBounds); | 957 &devBounds); |
| 964 conics += n; | 958 conics += n; |
| 965 } | 959 } |
| 966 } | 960 } |
| 967 } | 961 } |
| 968 | 962 |
| 969 target->resetIndexSource(); | 963 target->resetIndexSource(); |
| 970 | 964 |
| 971 return true; | 965 return true; |
| 972 } | 966 } |
| OLD | NEW |