| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright 2014 Google Inc. | 2 * Copyright 2014 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 "SkLayerInfo.h" | 8 #include "SkLayerInfo.h" |
| 9 #include "SkRecordDraw.h" | 9 #include "SkRecordDraw.h" |
| 10 #include "SkPatchUtils.h" | 10 #include "SkPatchUtils.h" |
| 11 | 11 |
| 12 void SkRecordDraw(const SkRecord& record, | 12 void SkRecordDraw(const SkRecord& record, |
| 13 SkCanvas* canvas, | 13 SkCanvas* canvas, |
| 14 SkPicture const* const drawablePicts[], int drawableCount, | 14 SkPicture const* const drawablePicts[], |
| 15 SkCanvasDrawable* const drawables[], |
| 16 int drawableCount, |
| 15 const SkBBoxHierarchy* bbh, | 17 const SkBBoxHierarchy* bbh, |
| 16 SkDrawPictureCallback* callback) { | 18 SkDrawPictureCallback* callback) { |
| 17 SkAutoCanvasRestore saveRestore(canvas, true /*save now, restore at exit*/); | 19 SkAutoCanvasRestore saveRestore(canvas, true /*save now, restore at exit*/); |
| 18 | 20 |
| 19 if (bbh) { | 21 if (bbh) { |
| 20 // Draw only ops that affect pixels in the canvas's current clip. | 22 // Draw only ops that affect pixels in the canvas's current clip. |
| 21 // The SkRecord and BBH were recorded in identity space. This canvas | 23 // The SkRecord and BBH were recorded in identity space. This canvas |
| 22 // is not necessarily in that same space. getClipBounds() returns us | 24 // is not necessarily in that same space. getClipBounds() returns us |
| 23 // this canvas' clip bounds transformed back into identity space, which | 25 // this canvas' clip bounds transformed back into identity space, which |
| 24 // lets us query the BBH. | 26 // lets us query the BBH. |
| 25 SkRect query; | 27 SkRect query; |
| 26 if (!canvas->getClipBounds(&query)) { | 28 if (!canvas->getClipBounds(&query)) { |
| 27 // We want to make sure our query rectangle is never totally empty. | 29 // We want to make sure our query rectangle is never totally empty. |
| 28 // Clear ignores the clip, so it must draw even if the clip is logic
ally empty. | 30 // Clear ignores the clip, so it must draw even if the clip is logic
ally empty. |
| 29 query = SkRect::MakeWH(SK_ScalarNearlyZero, SK_ScalarNearlyZero); | 31 query = SkRect::MakeWH(SK_ScalarNearlyZero, SK_ScalarNearlyZero); |
| 30 } | 32 } |
| 31 | 33 |
| 32 SkTDArray<unsigned> ops; | 34 SkTDArray<unsigned> ops; |
| 33 bbh->search(query, &ops); | 35 bbh->search(query, &ops); |
| 34 | 36 |
| 35 SkRecords::Draw draw(canvas, drawablePicts, drawableCount); | 37 SkRecords::Draw draw(canvas, drawablePicts, drawables, drawableCount); |
| 36 for (int i = 0; i < ops.count(); i++) { | 38 for (int i = 0; i < ops.count(); i++) { |
| 37 if (callback && callback->abortDrawing()) { | 39 if (callback && callback->abortDrawing()) { |
| 38 return; | 40 return; |
| 39 } | 41 } |
| 40 // This visit call uses the SkRecords::Draw::operator() to call | 42 // This visit call uses the SkRecords::Draw::operator() to call |
| 41 // methods on the |canvas|, wrapped by methods defined with the | 43 // methods on the |canvas|, wrapped by methods defined with the |
| 42 // DRAW() macro. | 44 // DRAW() macro. |
| 43 record.visit<void>(ops[i], draw); | 45 record.visit<void>(ops[i], draw); |
| 44 } | 46 } |
| 45 } else { | 47 } else { |
| 46 // Draw all ops. | 48 // Draw all ops. |
| 47 SkRecords::Draw draw(canvas, drawablePicts, drawableCount); | 49 SkRecords::Draw draw(canvas, drawablePicts, drawables, drawableCount); |
| 48 for (unsigned i = 0; i < record.count(); i++) { | 50 for (unsigned i = 0; i < record.count(); i++) { |
| 49 if (callback && callback->abortDrawing()) { | 51 if (callback && callback->abortDrawing()) { |
| 50 return; | 52 return; |
| 51 } | 53 } |
| 52 // This visit call uses the SkRecords::Draw::operator() to call | 54 // This visit call uses the SkRecords::Draw::operator() to call |
| 53 // methods on the |canvas|, wrapped by methods defined with the | 55 // methods on the |canvas|, wrapped by methods defined with the |
| 54 // DRAW() macro. | 56 // DRAW() macro. |
| 55 record.visit<void>(i, draw); | 57 record.visit<void>(i, draw); |
| 56 } | 58 } |
| 57 } | 59 } |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 126 DRAW(DrawTextBlob, drawTextBlob(r.blob, r.x, r.y, r.paint)); | 128 DRAW(DrawTextBlob, drawTextBlob(r.blob, r.x, r.y, r.paint)); |
| 127 DRAW(DrawTextOnPath, drawTextOnPath(r.text, r.byteLength, r.path, r.matrix, r.pa
int)); | 129 DRAW(DrawTextOnPath, drawTextOnPath(r.text, r.byteLength, r.path, r.matrix, r.pa
int)); |
| 128 DRAW(DrawVertices, drawVertices(r.vmode, r.vertexCount, r.vertices, r.texs, r.co
lors, | 130 DRAW(DrawVertices, drawVertices(r.vmode, r.vertexCount, r.vertices, r.texs, r.co
lors, |
| 129 r.xmode.get(), r.indices, r.indexCount, r.paint)
); | 131 r.xmode.get(), r.indices, r.indexCount, r.paint)
); |
| 130 DRAW(DrawData, drawData(r.data, r.length)); | 132 DRAW(DrawData, drawData(r.data, r.length)); |
| 131 #undef DRAW | 133 #undef DRAW |
| 132 | 134 |
| 133 template <> void Draw::draw(const DrawDrawable& r) { | 135 template <> void Draw::draw(const DrawDrawable& r) { |
| 134 SkASSERT(r.index >= 0); | 136 SkASSERT(r.index >= 0); |
| 135 SkASSERT(r.index < fDrawableCount); | 137 SkASSERT(r.index < fDrawableCount); |
| 136 fCanvas->drawPicture(fDrawablePicts[r.index]); | 138 if (fDrawables) { |
| 139 SkASSERT(NULL == fDrawablePicts); |
| 140 fCanvas->EXPERIMENTAL_drawDrawable(fDrawables[r.index]); |
| 141 } else { |
| 142 fCanvas->drawPicture(fDrawablePicts[r.index]); |
| 143 } |
| 137 } | 144 } |
| 138 | 145 |
| 139 // This is an SkRecord visitor that fills an SkBBoxHierarchy. | 146 // This is an SkRecord visitor that fills an SkBBoxHierarchy. |
| 140 // | 147 // |
| 141 // The interesting part here is how to calculate bounds for ops which don't | 148 // The interesting part here is how to calculate bounds for ops which don't |
| 142 // have intrinsic bounds. What is the bounds of a Save or a Translate? | 149 // have intrinsic bounds. What is the bounds of a Save or a Translate? |
| 143 // | 150 // |
| 144 // We answer this by thinking about a particular definition of bounds: if I | 151 // We answer this by thinking about a particular definition of bounds: if I |
| 145 // don't execute this op, pixels in this rectangle might draw incorrectly. So | 152 // don't execute this op, pixels in this rectangle might draw incorrectly. So |
| 146 // the bounds of a Save, a Translate, a Restore, etc. are the union of the | 153 // the bounds of a Save, a Translate, a Restore, etc. are the union of the |
| 147 // bounds of Draw* ops that they might have an effect on. For any given | 154 // bounds of Draw* ops that they might have an effect on. For any given |
| 148 // Save/Restore block, the bounds of the Save, the Restore, and any other | 155 // Save/Restore block, the bounds of the Save, the Restore, and any other |
| 149 // non-drawing ("control") ops inside are exactly the union of the bounds of | 156 // non-drawing ("control") ops inside are exactly the union of the bounds of |
| 150 // the drawing ops inside that block. | 157 // the drawing ops inside that block. |
| 151 // | 158 // |
| 152 // To implement this, we keep a stack of active Save blocks. As we consume ops | 159 // To implement this, we keep a stack of active Save blocks. As we consume ops |
| 153 // inside the Save/Restore block, drawing ops are unioned with the bounds of | 160 // inside the Save/Restore block, drawing ops are unioned with the bounds of |
| 154 // the block, and control ops are stashed away for later. When we finish the | 161 // the block, and control ops are stashed away for later. When we finish the |
| 155 // block with a Restore, our bounds are complete, and we go back and fill them | 162 // block with a Restore, our bounds are complete, and we go back and fill them |
| 156 // in for all the control ops we stashed away. | 163 // in for all the control ops we stashed away. |
| 157 class FillBounds : SkNoncopyable { | 164 class FillBounds : SkNoncopyable { |
| 158 public: | 165 public: |
| 159 FillBounds(const SkRect& cullRect, const SkRecord& record) | 166 FillBounds(const SkRect& cullRect, const SkRecord& record) |
| 160 : fNumRecords(record.count()) | 167 : fNumRecords(record.count()) |
| 161 , fCullRect(cullRect) | 168 , fCullRect(cullRect) |
| 162 , fBounds(record.count()) { | 169 , fBounds(record.count()) |
| 170 { |
| 163 // Calculate bounds for all ops. This won't go quite in order, so we'll
need | 171 // Calculate bounds for all ops. This won't go quite in order, so we'll
need |
| 164 // to store the bounds separately then feed them in to the BBH later in
order. | 172 // to store the bounds separately then feed them in to the BBH later in
order. |
| 165 fCTM = &SkMatrix::I(); | 173 fCTM = &SkMatrix::I(); |
| 166 fCurrentClipBounds = fCullRect; | 174 fCurrentClipBounds = fCullRect; |
| 167 } | 175 } |
| 168 | 176 |
| 169 void setCurrentOp(unsigned currentOp) { fCurrentOp = currentOp; } | 177 void setCurrentOp(unsigned currentOp) { fCurrentOp = currentOp; } |
| 170 | 178 |
| 171 void cleanUp(SkBBoxHierarchy* bbh) { | 179 void cleanUp(SkBBoxHierarchy* bbh) { |
| 172 // If we have any lingering unpaired Saves, simulate restores to make | 180 // If we have any lingering unpaired Saves, simulate restores to make |
| (...skipping 411 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 584 Bounds fCurrentClipBounds; | 592 Bounds fCurrentClipBounds; |
| 585 | 593 |
| 586 // Used to track the bounds of Save/Restore blocks and the control ops insid
e them. | 594 // Used to track the bounds of Save/Restore blocks and the control ops insid
e them. |
| 587 SkTDArray<SaveBounds> fSaveStack; | 595 SkTDArray<SaveBounds> fSaveStack; |
| 588 SkTDArray<unsigned> fControlIndices; | 596 SkTDArray<unsigned> fControlIndices; |
| 589 }; | 597 }; |
| 590 | 598 |
| 591 // SkRecord visitor to gather saveLayer/restore information. | 599 // SkRecord visitor to gather saveLayer/restore information. |
| 592 class CollectLayers : SkNoncopyable { | 600 class CollectLayers : SkNoncopyable { |
| 593 public: | 601 public: |
| 594 CollectLayers(const SkRect& cullRect, const SkRecord& record, SkLayerInfo* a
ccelData) | 602 CollectLayers(const SkRect& cullRect, const SkRecord& record, |
| 603 const SkPicture::SnapshotArray* pictList, SkLayerInfo* accelDa
ta) |
| 595 : fSaveLayersInStack(0) | 604 : fSaveLayersInStack(0) |
| 596 , fAccelData(accelData) | 605 , fAccelData(accelData) |
| 597 , fFillBounds(cullRect, record) { | 606 , fPictList(pictList) |
| 598 } | 607 , fFillBounds(cullRect, record) |
| 608 {} |
| 599 | 609 |
| 600 void setCurrentOp(unsigned currentOp) { fFillBounds.setCurrentOp(currentOp);
} | 610 void setCurrentOp(unsigned currentOp) { fFillBounds.setCurrentOp(currentOp);
} |
| 601 | 611 |
| 602 void cleanUp(SkBBoxHierarchy* bbh) { | 612 void cleanUp(SkBBoxHierarchy* bbh) { |
| 603 // fFillBounds must perform its cleanUp first so that all the bounding | 613 // fFillBounds must perform its cleanUp first so that all the bounding |
| 604 // boxes associated with unbalanced restores are updated (prior to | 614 // boxes associated with unbalanced restores are updated (prior to |
| 605 // fetching their bound in popSaveLayerInfo). | 615 // fetching their bound in popSaveLayerInfo). |
| 606 fFillBounds.cleanUp(bbh); | 616 fFillBounds.cleanUp(bbh); |
| 607 | 617 |
| 608 while (!fSaveLayerStack.isEmpty()) { | 618 while (!fSaveLayerStack.isEmpty()) { |
| (...skipping 22 matching lines...) Expand all Loading... |
| 631 const SkPaint* fPaint; | 641 const SkPaint* fPaint; |
| 632 }; | 642 }; |
| 633 | 643 |
| 634 template <typename T> void trackSaveLayers(const T& op) { | 644 template <typename T> void trackSaveLayers(const T& op) { |
| 635 /* most ops aren't involved in saveLayers */ | 645 /* most ops aren't involved in saveLayers */ |
| 636 } | 646 } |
| 637 void trackSaveLayers(const Save& s) { this->pushSaveLayerInfo(false, NULL);
} | 647 void trackSaveLayers(const Save& s) { this->pushSaveLayerInfo(false, NULL);
} |
| 638 void trackSaveLayers(const SaveLayer& sl) { this->pushSaveLayerInfo(true, sl
.paint); } | 648 void trackSaveLayers(const SaveLayer& sl) { this->pushSaveLayerInfo(true, sl
.paint); } |
| 639 void trackSaveLayers(const Restore& r) { this->popSaveLayerInfo(); } | 649 void trackSaveLayers(const Restore& r) { this->popSaveLayerInfo(); } |
| 640 | 650 |
| 641 void trackSaveLayers(const DrawPicture& dp) { | 651 void trackSaveLayersForPicture(const SkPicture* picture, const SkPaint* pain
t) { |
| 642 // For sub-pictures, we wrap their layer information within the parent | 652 // For sub-pictures, we wrap their layer information within the parent |
| 643 // picture's rendering hierarchy | 653 // picture's rendering hierarchy |
| 644 SkPicture::AccelData::Key key = SkLayerInfo::ComputeKey(); | 654 SkPicture::AccelData::Key key = SkLayerInfo::ComputeKey(); |
| 645 | 655 |
| 646 const SkLayerInfo* childData = | 656 const SkLayerInfo* childData = |
| 647 static_cast<const SkLayerInfo*>(dp.picture->EXPERIMENTAL_getAccelDat
a(key)); | 657 static_cast<const SkLayerInfo*>(picture->EXPERIMENTAL_getAccelData(k
ey)); |
| 648 if (!childData) { | 658 if (!childData) { |
| 649 // If the child layer hasn't been generated with saveLayer data we | 659 // If the child layer hasn't been generated with saveLayer data we |
| 650 // assume the worst (i.e., that it does contain layers which nest | 660 // assume the worst (i.e., that it does contain layers which nest |
| 651 // inside existing layers). Layers within sub-pictures that don't | 661 // inside existing layers). Layers within sub-pictures that don't |
| 652 // have saveLayer data cannot be hoisted. | 662 // have saveLayer data cannot be hoisted. |
| 653 // TODO: could the analysis data be use to fine tune this? | 663 // TODO: could the analysis data be use to fine tune this? |
| 654 this->updateStackForSaveLayer(); | 664 this->updateStackForSaveLayer(); |
| 655 return; | 665 return; |
| 656 } | 666 } |
| 657 | 667 |
| 658 for (int i = 0; i < childData->numBlocks(); ++i) { | 668 for (int i = 0; i < childData->numBlocks(); ++i) { |
| 659 const SkLayerInfo::BlockInfo& src = childData->block(i); | 669 const SkLayerInfo::BlockInfo& src = childData->block(i); |
| 660 | 670 |
| 661 FillBounds::Bounds newBound = fFillBounds.adjustAndMap(src.fBounds,
dp.paint); | 671 FillBounds::Bounds newBound = fFillBounds.adjustAndMap(src.fBounds,
paint); |
| 662 if (newBound.isEmpty()) { | 672 if (newBound.isEmpty()) { |
| 663 continue; | 673 continue; |
| 664 } | 674 } |
| 665 | 675 |
| 666 this->updateStackForSaveLayer(); | 676 this->updateStackForSaveLayer(); |
| 667 | 677 |
| 668 SkLayerInfo::BlockInfo& dst = fAccelData->addBlock(); | 678 SkLayerInfo::BlockInfo& dst = fAccelData->addBlock(); |
| 669 | 679 |
| 670 // If src.fPicture is NULL the layer is in dp.picture; otherwise | 680 // If src.fPicture is NULL the layer is in dp.picture; otherwise |
| 671 // it belongs to a sub-picture. | 681 // it belongs to a sub-picture. |
| 672 dst.fPicture = src.fPicture ? src.fPicture : static_cast<const SkPic
ture*>(dp.picture); | 682 dst.fPicture = src.fPicture ? src.fPicture : picture; |
| 673 dst.fPicture->ref(); | 683 dst.fPicture->ref(); |
| 674 dst.fBounds = newBound; | 684 dst.fBounds = newBound; |
| 675 dst.fLocalMat = src.fLocalMat; | 685 dst.fLocalMat = src.fLocalMat; |
| 676 dst.fPreMat = src.fPreMat; | 686 dst.fPreMat = src.fPreMat; |
| 677 dst.fPreMat.postConcat(fFillBounds.ctm()); | 687 dst.fPreMat.postConcat(fFillBounds.ctm()); |
| 678 if (src.fPaint) { | 688 if (src.fPaint) { |
| 679 dst.fPaint = SkNEW_ARGS(SkPaint, (*src.fPaint)); | 689 dst.fPaint = SkNEW_ARGS(SkPaint, (*src.fPaint)); |
| 680 } | 690 } |
| 681 dst.fSaveLayerOpID = src.fSaveLayerOpID; | 691 dst.fSaveLayerOpID = src.fSaveLayerOpID; |
| 682 dst.fRestoreOpID = src.fRestoreOpID; | 692 dst.fRestoreOpID = src.fRestoreOpID; |
| 683 dst.fHasNestedLayers = src.fHasNestedLayers; | 693 dst.fHasNestedLayers = src.fHasNestedLayers; |
| 684 dst.fIsNested = fSaveLayersInStack > 0 || src.fIsNested; | 694 dst.fIsNested = fSaveLayersInStack > 0 || src.fIsNested; |
| 685 } | 695 } |
| 686 } | 696 } |
| 687 | 697 |
| 698 void trackSaveLayers(const DrawPicture& dp) { |
| 699 this->trackSaveLayersForPicture(dp.picture, dp.paint); |
| 700 } |
| 701 |
| 702 void trackSaveLayers(const DrawDrawable& dp) { |
| 703 SkASSERT(fPictList); |
| 704 SkASSERT(dp.index >= 0 && dp.index < fPictList->count()); |
| 705 const SkPaint* paint = NULL; // drawables don't get a side-car paint |
| 706 this->trackSaveLayersForPicture(fPictList->begin()[dp.index], paint); |
| 707 } |
| 708 |
| 688 // Inform all the saveLayers already on the stack that they now have a | 709 // Inform all the saveLayers already on the stack that they now have a |
| 689 // nested saveLayer inside them | 710 // nested saveLayer inside them |
| 690 void updateStackForSaveLayer() { | 711 void updateStackForSaveLayer() { |
| 691 for (int index = fSaveLayerStack.count() - 1; index >= 0; --index) { | 712 for (int index = fSaveLayerStack.count() - 1; index >= 0; --index) { |
| 692 if (fSaveLayerStack[index].fHasNestedSaveLayer) { | 713 if (fSaveLayerStack[index].fHasNestedSaveLayer) { |
| 693 break; | 714 break; |
| 694 } | 715 } |
| 695 fSaveLayerStack[index].fHasNestedSaveLayer = true; | 716 fSaveLayerStack[index].fHasNestedSaveLayer = true; |
| 696 if (fSaveLayerStack[index].fIsSaveLayer) { | 717 if (fSaveLayerStack[index].fIsSaveLayer) { |
| 697 break; | 718 break; |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 736 block.fSaveLayerOpID = sli.fStartIndex; | 757 block.fSaveLayerOpID = sli.fStartIndex; |
| 737 block.fRestoreOpID = fFillBounds.currentOp(); | 758 block.fRestoreOpID = fFillBounds.currentOp(); |
| 738 block.fHasNestedLayers = sli.fHasNestedSaveLayer; | 759 block.fHasNestedLayers = sli.fHasNestedSaveLayer; |
| 739 block.fIsNested = fSaveLayersInStack > 0; | 760 block.fIsNested = fSaveLayersInStack > 0; |
| 740 } | 761 } |
| 741 | 762 |
| 742 // Used to collect saveLayer information for layer hoisting | 763 // Used to collect saveLayer information for layer hoisting |
| 743 int fSaveLayersInStack; | 764 int fSaveLayersInStack; |
| 744 SkTDArray<SaveLayerInfo> fSaveLayerStack; | 765 SkTDArray<SaveLayerInfo> fSaveLayerStack; |
| 745 SkLayerInfo* fAccelData; | 766 SkLayerInfo* fAccelData; |
| 767 const SkPicture::SnapshotArray* fPictList; |
| 746 | 768 |
| 747 SkRecords::FillBounds fFillBounds; | 769 SkRecords::FillBounds fFillBounds; |
| 748 }; | 770 }; |
| 749 | 771 |
| 750 } // namespace SkRecords | 772 } // namespace SkRecords |
| 751 | 773 |
| 752 void SkRecordFillBounds(const SkRect& cullRect, const SkRecord& record, SkBBoxHi
erarchy* bbh) { | 774 void SkRecordFillBounds(const SkRect& cullRect, const SkRecord& record, SkBBoxHi
erarchy* bbh) { |
| 753 SkRecords::FillBounds visitor(cullRect, record); | 775 SkRecords::FillBounds visitor(cullRect, record); |
| 754 | 776 |
| 755 for (unsigned curOp = 0; curOp < record.count(); curOp++) { | 777 for (unsigned curOp = 0; curOp < record.count(); curOp++) { |
| 756 visitor.setCurrentOp(curOp); | 778 visitor.setCurrentOp(curOp); |
| 757 record.visit<void>(curOp, visitor); | 779 record.visit<void>(curOp, visitor); |
| 758 } | 780 } |
| 759 | 781 |
| 760 visitor.cleanUp(bbh); | 782 visitor.cleanUp(bbh); |
| 761 } | 783 } |
| 762 | 784 |
| 763 void SkRecordComputeLayers(const SkRect& cullRect, const SkRecord& record, | 785 void SkRecordComputeLayers(const SkRect& cullRect, const SkRecord& record, |
| 764 SkBBoxHierarchy* bbh, SkLayerInfo* data) { | 786 const SkPicture::SnapshotArray* pictList, SkBBoxHiera
rchy* bbh, |
| 765 SkRecords::CollectLayers visitor(cullRect, record, data); | 787 SkLayerInfo* data) { |
| 788 SkRecords::CollectLayers visitor(cullRect, record, pictList, data); |
| 766 | 789 |
| 767 for (unsigned curOp = 0; curOp < record.count(); curOp++) { | 790 for (unsigned curOp = 0; curOp < record.count(); curOp++) { |
| 768 visitor.setCurrentOp(curOp); | 791 visitor.setCurrentOp(curOp); |
| 769 record.visit<void>(curOp, visitor); | 792 record.visit<void>(curOp, visitor); |
| 770 } | 793 } |
| 771 | 794 |
| 772 visitor.cleanUp(bbh); | 795 visitor.cleanUp(bbh); |
| 773 } | 796 } |
| 774 | 797 |
| OLD | NEW |