1 /*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "tools/DDLTileHelper.h"
9
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkDeferredDisplayListRecorder.h"
12 #include "include/core/SkPicture.h"
13 #include "include/core/SkSurface.h"
14 #include "include/core/SkSurfaceCharacterization.h"
15 #include "src/core/SkDeferredDisplayListPriv.h"
16 #include "src/core/SkTaskGroup.h"
17 #include "src/gpu/GrContextPriv.h"
18 #include "src/image/SkImage_Gpu.h"
19 #include "tools/DDLPromiseImageHelper.h"
20
init(int id,sk_sp<SkSurface> dstSurface,const SkIRect & clip)21 void DDLTileHelper::TileData::init(int id, sk_sp<SkSurface> dstSurface, const SkIRect& clip) {
22 fID = id;
23 fDstSurface = dstSurface;
24 fClip = clip;
25
26 SkSurfaceCharacterization tmp;
27 SkAssertResult(fDstSurface->characterize(&tmp));
28 fCharacterization = tmp.createResized(clip.width(), clip.height());
29 SkASSERT(fCharacterization.isValid());
30 }
31
~TileData()32 DDLTileHelper::TileData::~TileData() {}
33
createTileSpecificSKP(SkData * compressedPictureData,const DDLPromiseImageHelper & helper)34 void DDLTileHelper::TileData::createTileSpecificSKP(SkData* compressedPictureData,
35 const DDLPromiseImageHelper& helper) {
36 SkASSERT(!fReconstitutedPicture);
37
38 // This is bending the DDLRecorder contract! The promise images in the SKP should be
39 // created by the same recorder used to create the matching DDL.
40 SkDeferredDisplayListRecorder recorder(fCharacterization);
41
42 fReconstitutedPicture = helper.reinflateSKP(&recorder, compressedPictureData, &fPromiseImages);
43
44 std::unique_ptr<SkDeferredDisplayList> ddl = recorder.detach();
45 if (ddl->priv().numRenderTasks()) {
46 // TODO: remove this once skbug.com/8424 is fixed. If the DDL resulting from the
47 // reinflation of the SKPs contains opsTasks that means some image subset operation
48 // created a draw.
49 fReconstitutedPicture.reset();
50 }
51 }
52
createDDL()53 void DDLTileHelper::TileData::createDDL() {
54 SkASSERT(!fDisplayList);
55
56 SkDeferredDisplayListRecorder recorder(fCharacterization);
57
58 // DDL TODO: the DDLRecorder's GrContext isn't initialized until getCanvas is called.
59 // Maybe set it up in the ctor?
60 SkCanvas* subCanvas = recorder.getCanvas();
61
62 // Because we cheated in createTileSpecificSKP and used the wrong DDLRecorder, the GrContext's
63 // stored in fReconstitutedPicture's promise images are incorrect. Patch them with the correct
64 // one now.
65 for (int i = 0; i < fPromiseImages.count(); ++i) {
66 GrContext* newContext = subCanvas->getGrContext();
67
68 if (fPromiseImages[i]->isTextureBacked()) {
69 SkImage_GpuBase* gpuImage = (SkImage_GpuBase*) fPromiseImages[i].get();
70 gpuImage->resetContext(sk_ref_sp(newContext));
71 }
72 }
73
74 subCanvas->clipRect(SkRect::MakeWH(fClip.width(), fClip.height()));
75 subCanvas->translate(-fClip.fLeft, -fClip.fTop);
76
77 // Note: in this use case we only render a picture to the deferred canvas
78 // but, more generally, clients will use arbitrary draw calls.
79 if (fReconstitutedPicture) {
80 subCanvas->drawPicture(fReconstitutedPicture);
81 }
82
83 fDisplayList = recorder.detach();
84 }
85
draw(GrContext * context)86 void DDLTileHelper::TileData::draw(GrContext* context) {
87 SkASSERT(fDisplayList && !fImage);
88
89 sk_sp<SkSurface> tileSurface = SkSurface::MakeRenderTarget(context, fCharacterization,
90 SkBudgeted::kYes);
91 if (tileSurface) {
92 tileSurface->draw(fDisplayList.get());
93
94 fImage = tileSurface->makeImageSnapshot();
95 }
96 }
97
98 // TODO: We should create a single DDL for the composition step and just add replaying it
99 // as the last GPU task
compose()100 void DDLTileHelper::TileData::compose() {
101 SkASSERT(fDstSurface && fImage);
102
103 SkCanvas* canvas = fDstSurface->getCanvas();
104 canvas->save();
105 canvas->clipRect(SkRect::Make(fClip));
106 canvas->drawImage(fImage, fClip.fLeft, fClip.fTop);
107 canvas->restore();
108 }
109
reset()110 void DDLTileHelper::TileData::reset() {
111 // TODO: when DDLs are re-renderable we don't need to do this
112 fDisplayList = nullptr;
113 }
114
115 ///////////////////////////////////////////////////////////////////////////////////////////////////
116
DDLTileHelper(sk_sp<SkSurface> dstSurface,const SkIRect & viewport,int numDivisions)117 DDLTileHelper::DDLTileHelper(sk_sp<SkSurface> dstSurface,
118 const SkIRect& viewport,
119 int numDivisions)
120 : fNumDivisions(numDivisions) {
121 SkASSERT(fNumDivisions > 0);
122 fTiles = new TileData[this->numTiles()];
123
124 int xTileSize = viewport.width()/fNumDivisions;
125 int yTileSize = viewport.height()/fNumDivisions;
126
127 // Create the destination tiles
128 for (int y = 0, yOff = 0; y < fNumDivisions; ++y, yOff += yTileSize) {
129 int ySize = (y < fNumDivisions-1) ? yTileSize : viewport.height()-yOff;
130
131 for (int x = 0, xOff = 0; x < fNumDivisions; ++x, xOff += xTileSize) {
132 int xSize = (x < fNumDivisions-1) ? xTileSize : viewport.width()-xOff;
133
134 SkIRect clip = SkIRect::MakeXYWH(xOff, yOff, xSize, ySize);
135
136 SkASSERT(viewport.contains(clip));
137
138 fTiles[y*fNumDivisions+x].init(y*fNumDivisions+x, dstSurface, clip);
139 }
140 }
141 }
142
createSKPPerTile(SkData * compressedPictureData,const DDLPromiseImageHelper & helper)143 void DDLTileHelper::createSKPPerTile(SkData* compressedPictureData,
144 const DDLPromiseImageHelper& helper) {
145 for (int i = 0; i < this->numTiles(); ++i) {
146 fTiles[i].createTileSpecificSKP(compressedPictureData, helper);
147 }
148 }
149
createDDLsInParallel()150 void DDLTileHelper::createDDLsInParallel() {
151 #if 1
152 SkTaskGroup().batch(this->numTiles(), [&](int i) { fTiles[i].createDDL(); });
153 SkTaskGroup().wait();
154 #else
155 // Use this code path to debug w/o threads
156 for (int i = 0; i < fTiles.count(); ++i) {
157 fTiles[i].createDDL();
158 }
159 #endif
160 }
161
162 // On the gpu thread:
163 // precompile any programs
164 // replay the DDL into a surface to make the tile image
165 // compose the tile image into the main canvas
do_gpu_stuff(GrContext * context,DDLTileHelper::TileData * tile)166 static void do_gpu_stuff(GrContext* context, DDLTileHelper::TileData* tile) {
167 auto& programData = tile->ddl()->priv().programData();
168
169 // TODO: schedule program compilation as their own tasks
170 for (auto& programDatum : programData) {
171 context->priv().compile(programDatum.desc(), programDatum.info());
172 }
173
174 tile->draw(context);
175
176 // TODO: we should actually have a separate DDL that does
177 // the final composition draw
178 tile->compose();
179 }
180
181 // We expect to have more than one recording thread but just one gpu thread
kickOffThreadedWork(SkTaskGroup * recordingTaskGroup,SkTaskGroup * gpuTaskGroup,GrContext * gpuThreadContext)182 void DDLTileHelper::kickOffThreadedWork(SkTaskGroup* recordingTaskGroup,
183 SkTaskGroup* gpuTaskGroup,
184 GrContext* gpuThreadContext) {
185 SkASSERT(recordingTaskGroup && gpuTaskGroup && gpuThreadContext);
186
187 for (int i = 0; i < this->numTiles(); ++i) {
188 TileData* tile = &fTiles[i];
189
190 // On a recording thread:
191 // generate the tile's DDL
192 // schedule gpu-thread processing of the DDL
193 // Note: a finer grained approach would be add a scheduling task which would evaluate
194 // which DDLs were ready to be rendered based on their prerequisites
195 recordingTaskGroup->add([tile, gpuTaskGroup, gpuThreadContext]() {
196 tile->createDDL();
197
198 gpuTaskGroup->add([gpuThreadContext, tile]() {
199 do_gpu_stuff(gpuThreadContext, tile);
200 });
201 });
202 }
203 }
204
drawAllTilesAndFlush(GrContext * context,bool flush)205 void DDLTileHelper::drawAllTilesAndFlush(GrContext* context, bool flush) {
206 for (int i = 0; i < this->numTiles(); ++i) {
207 fTiles[i].draw(context);
208 }
209 if (flush) {
210 context->flush();
211 }
212 }
213
composeAllTiles()214 void DDLTileHelper::composeAllTiles() {
215 for (int i = 0; i < this->numTiles(); ++i) {
216 fTiles[i].compose();
217 }
218 }
219
resetAllTiles()220 void DDLTileHelper::resetAllTiles() {
221 for (int i = 0; i < this->numTiles(); ++i) {
222 fTiles[i].reset();
223 }
224 }
225