1 /*
2 * Copyright 2017 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 "InstancedOp.h"
9 #include "InstanceProcessor.h"
10 #include "InstancedRendering.h"
11 #include "GrGpu.h"
12 #include "GrOpFlushState.h"
13 #include "GrRenderTargetOpList.h"
14
15
16 namespace gr_instanced {
17
InstancedOp(uint32_t classID,GrPaint && paint,OpAllocator * alloc)18 InstancedOp::InstancedOp(uint32_t classID, GrPaint&& paint, OpAllocator* alloc)
19 : INHERITED(classID)
20 , fIsTracked(false)
21 , fRequiresBarrierOnOverlap(false)
22 , fAllowsSRGBInputs(paint.getAllowSRGBInputs())
23 , fDisableSRGBOutputConversion(paint.getDisableOutputConversionToSRGB())
24 , fNumDraws(1)
25 , fNumChangesInGeometry(0)
26 , fAllocator(alloc)
27 , fInstancedRendering(nullptr)
28 , fProcessors(std::move(paint)) {
29 fHeadDraw = fTailDraw = alloc->allocateDraw();
30 #ifdef SK_DEBUG
31 fHeadDraw->fGeometry = {-1, 0};
32 #endif
33 fHeadDraw->fNext = nullptr;
34 }
35
~InstancedOp()36 InstancedOp::~InstancedOp() {
37 if (fIsTracked) {
38 fInstancedRendering->removeOp(this);
39 }
40
41 Draw* draw = fHeadDraw;
42 while (draw) {
43 Draw* next = draw->fNext;
44 fAllocator->releaseDraw(draw);
45 draw = next;
46 }
47 }
48
appendRRectParams(const SkRRect & rrect)49 void InstancedOp::appendRRectParams(const SkRRect& rrect) {
50 SkASSERT(!fIsTracked);
51 switch (rrect.getType()) {
52 case SkRRect::kSimple_Type: {
53 const SkVector& radii = rrect.getSimpleRadii();
54 this->appendParamsTexel(radii.x(), radii.y(), rrect.width(), rrect.height());
55 return;
56 }
57 case SkRRect::kNinePatch_Type: {
58 float twoOverW = 2 / rrect.width();
59 float twoOverH = 2 / rrect.height();
60 const SkVector& radiiTL = rrect.radii(SkRRect::kUpperLeft_Corner);
61 const SkVector& radiiBR = rrect.radii(SkRRect::kLowerRight_Corner);
62 this->appendParamsTexel(radiiTL.x() * twoOverW, radiiBR.x() * twoOverW,
63 radiiTL.y() * twoOverH, radiiBR.y() * twoOverH);
64 return;
65 }
66 case SkRRect::kComplex_Type: {
67 /**
68 * The x and y radii of each arc are stored in separate vectors,
69 * in the following order:
70 *
71 * __x1 _ _ _ x3__
72 * y1 | | y2
73 *
74 * | |
75 *
76 * y3 |__ _ _ _ __| y4
77 * x2 x4
78 *
79 */
80 float twoOverW = 2 / rrect.width();
81 float twoOverH = 2 / rrect.height();
82 const SkVector& radiiTL = rrect.radii(SkRRect::kUpperLeft_Corner);
83 const SkVector& radiiTR = rrect.radii(SkRRect::kUpperRight_Corner);
84 const SkVector& radiiBR = rrect.radii(SkRRect::kLowerRight_Corner);
85 const SkVector& radiiBL = rrect.radii(SkRRect::kLowerLeft_Corner);
86 this->appendParamsTexel(radiiTL.x() * twoOverW, radiiBL.x() * twoOverW,
87 radiiTR.x() * twoOverW, radiiBR.x() * twoOverW);
88 this->appendParamsTexel(radiiTL.y() * twoOverH, radiiTR.y() * twoOverH,
89 radiiBL.y() * twoOverH, radiiBR.y() * twoOverH);
90 return;
91 }
92 default: return;
93 }
94 }
95
appendParamsTexel(const SkScalar * vals,int count)96 void InstancedOp::appendParamsTexel(const SkScalar* vals, int count) {
97 SkASSERT(!fIsTracked);
98 SkASSERT(count <= 4 && count >= 0);
99 const float* valsAsFloats = vals; // Ensure SkScalar == float.
100 memcpy(&fParams.push_back(), valsAsFloats, count * sizeof(float));
101 fInfo.fHasParams = true;
102 }
103
appendParamsTexel(SkScalar x,SkScalar y,SkScalar z,SkScalar w)104 void InstancedOp::appendParamsTexel(SkScalar x, SkScalar y, SkScalar z, SkScalar w) {
105 SkASSERT(!fIsTracked);
106 ParamsTexel& texel = fParams.push_back();
107 texel.fX = SkScalarToFloat(x);
108 texel.fY = SkScalarToFloat(y);
109 texel.fZ = SkScalarToFloat(z);
110 texel.fW = SkScalarToFloat(w);
111 fInfo.fHasParams = true;
112 }
113
appendParamsTexel(SkScalar x,SkScalar y,SkScalar z)114 void InstancedOp::appendParamsTexel(SkScalar x, SkScalar y, SkScalar z) {
115 SkASSERT(!fIsTracked);
116 ParamsTexel& texel = fParams.push_back();
117 texel.fX = SkScalarToFloat(x);
118 texel.fY = SkScalarToFloat(y);
119 texel.fZ = SkScalarToFloat(z);
120 fInfo.fHasParams = true;
121 }
122
finalize(const GrCaps & caps,const GrAppliedClip * clip)123 GrDrawOp::RequiresDstTexture InstancedOp::finalize(const GrCaps& caps, const GrAppliedClip* clip) {
124 GrProcessorAnalysisCoverage coverageInput;
125 bool isMixedSamples = false;
126 if (GrAAType::kCoverage == fInfo.aaType() ||
127 (GrAAType::kNone == fInfo.aaType() && !fInfo.isSimpleRects() && fInfo.fCannotDiscard)) {
128 coverageInput = GrProcessorAnalysisCoverage::kSingleChannel;
129 } else {
130 coverageInput = GrProcessorAnalysisCoverage::kNone;
131 isMixedSamples = GrAAType::kMixedSamples == fInfo.aaType();
132 }
133 GrProcessorSet::Analysis analysis =
134 fProcessors.finalize(this->getSingleInstance().fColor, coverageInput, clip,
135 isMixedSamples, caps, &this->getSingleDraw().fInstance.fColor);
136
137 Draw& draw = this->getSingleDraw(); // This will assert if we have > 1 command.
138 SkASSERT(draw.fGeometry.isEmpty());
139 SkASSERT(SkIsPow2(fInfo.fShapeTypes));
140 SkASSERT(!fIsTracked);
141
142 if (kRect_ShapeFlag == fInfo.fShapeTypes) {
143 draw.fGeometry = InstanceProcessor::GetIndexRangeForRect(fInfo.aaType());
144 } else if (kOval_ShapeFlag == fInfo.fShapeTypes) {
145 draw.fGeometry = InstanceProcessor::GetIndexRangeForOval(fInfo.aaType(), this->bounds());
146 } else {
147 draw.fGeometry = InstanceProcessor::GetIndexRangeForRRect(fInfo.aaType());
148 }
149
150 fInfo.fCannotTweakAlphaForCoverage = !analysis.isCompatibleWithCoverageAsAlpha();
151
152 fInfo.fUsesLocalCoords = analysis.usesLocalCoords();
153 fRequiresBarrierOnOverlap = analysis.requiresBarrierBetweenOverlappingDraws();
154 return analysis.requiresDstTexture() ? RequiresDstTexture::kYes : RequiresDstTexture::kNo;
155 }
156
wasRecorded(GrRenderTargetOpList * opList)157 void InstancedOp::wasRecorded(GrRenderTargetOpList* opList) {
158 SkASSERT(!fInstancedRendering);
159 SkASSERT(!fIsTracked);
160
161 fInstancedRendering = opList->instancedRendering();
162
163 this->getSingleInstance().fInfo |= fInstancedRendering->addOpParams(this);
164 fInstancedRendering->addOp(this);
165 fIsTracked = true;
166 }
167
onCombineIfPossible(GrOp * other,const GrCaps &)168 bool InstancedOp::onCombineIfPossible(GrOp* other, const GrCaps&) {
169 InstancedOp* that = static_cast<InstancedOp*>(other);
170 SkASSERT(!that->fInstancedRendering || (fInstancedRendering == that->fInstancedRendering));
171 SkASSERT(fTailDraw);
172 SkASSERT(that->fTailDraw);
173
174 if (!OpInfo::CanCombine(fInfo, that->fInfo) || fProcessors != that->fProcessors) {
175 return false;
176 }
177
178 if (fAllowsSRGBInputs != that->fAllowsSRGBInputs ||
179 fDisableSRGBOutputConversion != that->fDisableSRGBOutputConversion) {
180 return false;
181 }
182 SkASSERT(fRequiresBarrierOnOverlap == that->fRequiresBarrierOnOverlap);
183 if (fRequiresBarrierOnOverlap && this->bounds().intersects(that->bounds())) {
184 return false;
185 }
186 OpInfo combinedInfo = fInfo | that->fInfo;
187 if (!combinedInfo.isSimpleRects()) {
188 // This threshold was chosen with the "shapes_mixed" bench on a MacBook with Intel graphics.
189 // There seems to be a wide range where it doesn't matter if we combine or not. What matters
190 // is that the itty bitty rects combine with other shapes and the giant ones don't.
191 constexpr SkScalar kMaxPixelsToGeneralizeRects = 256 * 256;
192 if (fInfo.isSimpleRects() && fPixelLoad > kMaxPixelsToGeneralizeRects) {
193 return false;
194 }
195 if (that->fInfo.isSimpleRects() && that->fPixelLoad > kMaxPixelsToGeneralizeRects) {
196 return false;
197 }
198 }
199
200 if (!that->fInstancedRendering) {
201 that->fInstancedRendering = fInstancedRendering;
202 that->getSingleInstance().fInfo |= fInstancedRendering->addOpParams(that);
203 }
204
205 this->joinBounds(*that);
206 fInfo = combinedInfo;
207 fPixelLoad += that->fPixelLoad;
208 // Adopt the other op's draws.
209 fNumDraws += that->fNumDraws;
210 fNumChangesInGeometry += that->fNumChangesInGeometry;
211 if (fTailDraw->fGeometry != that->fHeadDraw->fGeometry) {
212 ++fNumChangesInGeometry;
213 }
214 fTailDraw->fNext = that->fHeadDraw;
215 fTailDraw = that->fTailDraw;
216
217 that->fHeadDraw = that->fTailDraw = nullptr;
218
219 return true;
220 }
221
onExecute(GrOpFlushState * state)222 void InstancedOp::onExecute(GrOpFlushState* state) {
223 SkASSERT(fInstancedRendering->isFlushing());
224 SkASSERT(state->gpu() == fInstancedRendering->gpu());
225
226 state->gpu()->handleDirtyContext();
227
228 GrPipeline pipeline;
229 GrPipeline::InitArgs args;
230 args.fAppliedClip = state->drawOpArgs().fAppliedClip;
231 args.fCaps = &state->caps();
232 args.fResourceProvider = state->resourceProvider();
233 args.fProcessors = &fProcessors;
234 args.fFlags = GrAATypeIsHW(fInfo.aaType()) ? GrPipeline::kHWAntialias_Flag : 0;
235 if (fAllowsSRGBInputs) {
236 args.fFlags |= GrPipeline::kAllowSRGBInputs_Flag;
237 }
238 if (fDisableSRGBOutputConversion) {
239 args.fFlags |= GrPipeline::kDisableOutputConversionToSRGB_Flag;
240 }
241 args.fRenderTarget = state->drawOpArgs().fRenderTarget;
242 args.fDstProxy = state->drawOpArgs().fDstProxy;
243 pipeline.init(args);
244
245 if (GrXferBarrierType barrierType = pipeline.xferBarrierType(*state->gpu()->caps())) {
246 state->gpu()->xferBarrier(pipeline.getRenderTarget(), barrierType);
247 }
248 fInstancedRendering->draw(pipeline, fInfo, this);
249 }
250
251 ////////////////////////////////////////////////////////////////////////////////////////////////////
252
OpAllocator(const GrCaps * caps)253 OpAllocator::OpAllocator(const GrCaps* caps)
254 : fDrawPool(1024, 1024)
255 , fCaps(sk_ref_sp(caps)) {
256 }
257
~OpAllocator()258 OpAllocator::~OpAllocator() {}
259
recordRect(const SkRect & rect,const SkMatrix & viewMatrix,GrPaint && paint,GrAA aa,const GrInstancedPipelineInfo & info)260 std::unique_ptr<GrDrawOp> OpAllocator::recordRect(const SkRect& rect,
261 const SkMatrix& viewMatrix,
262 GrPaint&& paint, GrAA aa,
263 const GrInstancedPipelineInfo& info) {
264 return this->recordShape(ShapeType::kRect, rect, viewMatrix, std::move(paint), rect, aa, info);
265 }
266
recordRect(const SkRect & rect,const SkMatrix & viewMatrix,GrPaint && paint,const SkRect & localRect,GrAA aa,const GrInstancedPipelineInfo & info)267 std::unique_ptr<GrDrawOp> OpAllocator::recordRect(const SkRect& rect,
268 const SkMatrix& viewMatrix,
269 GrPaint&& paint, const SkRect& localRect,
270 GrAA aa,
271 const GrInstancedPipelineInfo& info) {
272 return this->recordShape(ShapeType::kRect, rect, viewMatrix, std::move(paint), localRect, aa,
273 info);
274 }
275
recordRect(const SkRect & rect,const SkMatrix & viewMatrix,GrPaint && paint,const SkMatrix & localMatrix,GrAA aa,const GrInstancedPipelineInfo & info)276 std::unique_ptr<GrDrawOp> OpAllocator::recordRect(const SkRect& rect,
277 const SkMatrix& viewMatrix,
278 GrPaint&& paint,
279 const SkMatrix& localMatrix, GrAA aa,
280 const GrInstancedPipelineInfo& info) {
281 if (localMatrix.hasPerspective()) {
282 return nullptr; // Perspective is not yet supported in the local matrix.
283 }
284 if (std::unique_ptr<InstancedOp> op = this->recordShape(ShapeType::kRect, rect, viewMatrix,
285 std::move(paint), rect, aa, info)) {
286 op->getSingleInstance().fInfo |= kLocalMatrix_InfoFlag;
287 op->appendParamsTexel(localMatrix.getScaleX(), localMatrix.getSkewX(),
288 localMatrix.getTranslateX());
289 op->appendParamsTexel(localMatrix.getSkewY(), localMatrix.getScaleY(),
290 localMatrix.getTranslateY());
291 op->fInfo.fHasLocalMatrix = true;
292 return std::move(op);
293 }
294 return nullptr;
295 }
296
recordOval(const SkRect & oval,const SkMatrix & viewMatrix,GrPaint && paint,GrAA aa,const GrInstancedPipelineInfo & info)297 std::unique_ptr<GrDrawOp> OpAllocator::recordOval(const SkRect& oval,
298 const SkMatrix& viewMatrix,
299 GrPaint&& paint, GrAA aa,
300 const GrInstancedPipelineInfo& info) {
301 return this->recordShape(ShapeType::kOval, oval, viewMatrix, std::move(paint), oval, aa, info);
302 }
303
recordRRect(const SkRRect & rrect,const SkMatrix & viewMatrix,GrPaint && paint,GrAA aa,const GrInstancedPipelineInfo & info)304 std::unique_ptr<GrDrawOp> OpAllocator::recordRRect(const SkRRect& rrect,
305 const SkMatrix& viewMatrix,
306 GrPaint&& paint, GrAA aa,
307 const GrInstancedPipelineInfo& info) {
308 if (std::unique_ptr<InstancedOp> op =
309 this->recordShape(GetRRectShapeType(rrect), rrect.rect(), viewMatrix,
310 std::move(paint), rrect.rect(), aa, info)) {
311 op->appendRRectParams(rrect);
312 return std::move(op);
313 }
314 return nullptr;
315 }
316
recordDRRect(const SkRRect & outer,const SkRRect & inner,const SkMatrix & viewMatrix,GrPaint && paint,GrAA aa,const GrInstancedPipelineInfo & info)317 std::unique_ptr<GrDrawOp> OpAllocator::recordDRRect(const SkRRect& outer,
318 const SkRRect& inner,
319 const SkMatrix& viewMatrix,
320 GrPaint&& paint, GrAA aa,
321 const GrInstancedPipelineInfo& info) {
322 if (inner.getType() > SkRRect::kSimple_Type) {
323 return nullptr; // Complex inner round rects are not yet supported.
324 }
325 if (SkRRect::kEmpty_Type == inner.getType()) {
326 return this->recordRRect(outer, viewMatrix, std::move(paint), aa, info);
327 }
328 if (std::unique_ptr<InstancedOp> op =
329 this->recordShape(GetRRectShapeType(outer), outer.rect(), viewMatrix,
330 std::move(paint), outer.rect(), aa, info)) {
331 op->appendRRectParams(outer);
332 ShapeType innerShapeType = GetRRectShapeType(inner);
333 op->fInfo.fInnerShapeTypes |= GetShapeFlag(innerShapeType);
334 op->getSingleInstance().fInfo |= ((int)innerShapeType << kInnerShapeType_InfoBit);
335 op->appendParamsTexel(inner.rect().asScalars(), 4);
336 op->appendRRectParams(inner);
337 return std::move(op);
338 }
339 return nullptr;
340 }
341
recordShape(ShapeType type,const SkRect & bounds,const SkMatrix & viewMatrix,GrPaint && paint,const SkRect & localRect,GrAA aa,const GrInstancedPipelineInfo & info)342 std::unique_ptr<InstancedOp> OpAllocator::recordShape(
343 ShapeType type, const SkRect& bounds, const SkMatrix& viewMatrix, GrPaint&& paint,
344 const SkRect& localRect, GrAA aa, const GrInstancedPipelineInfo& info) {
345
346 if (info.fIsRenderingToFloat && fCaps->avoidInstancedDrawsToFPTargets()) {
347 return nullptr;
348 }
349
350 GrAAType aaType;
351 if (!this->selectAntialiasMode(viewMatrix, aa, info, &aaType)) {
352 return nullptr;
353 }
354
355 GrColor color = paint.getColor();
356 std::unique_ptr<InstancedOp> op = this->makeOp(std::move(paint));
357 op->fInfo.setAAType(aaType);
358 op->fInfo.fShapeTypes = GetShapeFlag(type);
359 op->fInfo.fCannotDiscard = true;
360 Instance& instance = op->getSingleInstance();
361 instance.fInfo = (int)type << kShapeType_InfoBit;
362
363 InstancedOp::HasAABloat aaBloat =
364 (aaType == GrAAType::kCoverage) ? InstancedOp::HasAABloat::kYes
365 : InstancedOp::HasAABloat::kNo;
366 InstancedOp::IsZeroArea zeroArea = bounds.isEmpty() ? InstancedOp::IsZeroArea::kYes
367 : InstancedOp::IsZeroArea::kNo;
368
369 // The instanced shape renderer draws rectangles of [-1, -1, +1, +1], so we find the matrix that
370 // will map this rectangle to the same device coordinates as "viewMatrix * bounds".
371 float sx = 0.5f * bounds.width();
372 float sy = 0.5f * bounds.height();
373 float tx = sx + bounds.fLeft;
374 float ty = sy + bounds.fTop;
375 if (!viewMatrix.hasPerspective()) {
376 float* m = instance.fShapeMatrix2x3;
377 m[0] = viewMatrix.getScaleX() * sx;
378 m[1] = viewMatrix.getSkewX() * sy;
379 m[2] = viewMatrix.getTranslateX() +
380 viewMatrix.getScaleX() * tx + viewMatrix.getSkewX() * ty;
381
382 m[3] = viewMatrix.getSkewY() * sx;
383 m[4] = viewMatrix.getScaleY() * sy;
384 m[5] = viewMatrix.getTranslateY() +
385 viewMatrix.getSkewY() * tx + viewMatrix.getScaleY() * ty;
386
387 // Since 'm' is a 2x3 matrix that maps the rect [-1, +1] into the shape's device-space quad,
388 // it's quite simple to find the bounding rectangle:
389 float devBoundsHalfWidth = fabsf(m[0]) + fabsf(m[1]);
390 float devBoundsHalfHeight = fabsf(m[3]) + fabsf(m[4]);
391 SkRect opBounds;
392 opBounds.fLeft = m[2] - devBoundsHalfWidth;
393 opBounds.fRight = m[2] + devBoundsHalfWidth;
394 opBounds.fTop = m[5] - devBoundsHalfHeight;
395 opBounds.fBottom = m[5] + devBoundsHalfHeight;
396 op->setBounds(opBounds, aaBloat, zeroArea);
397
398 // TODO: Is this worth the CPU overhead?
399 op->fInfo.fNonSquare =
400 fabsf(devBoundsHalfHeight - devBoundsHalfWidth) > 0.5f || // Early out.
401 fabs(m[0] * m[3] + m[1] * m[4]) > 1e-3f || // Skew?
402 fabs(m[0] * m[0] + m[1] * m[1] - m[3] * m[3] - m[4] * m[4]) >
403 1e-2f; // Diff. lengths?
404 } else {
405 SkMatrix shapeMatrix(viewMatrix);
406 shapeMatrix.preTranslate(tx, ty);
407 shapeMatrix.preScale(sx, sy);
408 instance.fInfo |= kPerspective_InfoFlag;
409
410 float* m = instance.fShapeMatrix2x3;
411 m[0] = SkScalarToFloat(shapeMatrix.getScaleX());
412 m[1] = SkScalarToFloat(shapeMatrix.getSkewX());
413 m[2] = SkScalarToFloat(shapeMatrix.getTranslateX());
414 m[3] = SkScalarToFloat(shapeMatrix.getSkewY());
415 m[4] = SkScalarToFloat(shapeMatrix.getScaleY());
416 m[5] = SkScalarToFloat(shapeMatrix.getTranslateY());
417
418 // Send the perspective column as a param.
419 op->appendParamsTexel(shapeMatrix[SkMatrix::kMPersp0], shapeMatrix[SkMatrix::kMPersp1],
420 shapeMatrix[SkMatrix::kMPersp2]);
421 op->fInfo.fHasPerspective = true;
422
423 op->setBounds(bounds, aaBloat, zeroArea);
424 op->fInfo.fNonSquare = true;
425 }
426
427 instance.fColor = color;
428
429 const float* rectAsFloats = localRect.asScalars(); // Ensure SkScalar == float.
430 memcpy(&instance.fLocalRect, rectAsFloats, 4 * sizeof(float));
431
432 op->fPixelLoad = op->bounds().height() * op->bounds().width();
433 return op;
434 }
435
selectAntialiasMode(const SkMatrix & viewMatrix,GrAA aa,const GrInstancedPipelineInfo & info,GrAAType * aaType)436 inline bool OpAllocator::selectAntialiasMode(const SkMatrix& viewMatrix, GrAA aa,
437 const GrInstancedPipelineInfo& info,
438 GrAAType* aaType) {
439 SkASSERT(!info.fIsMixedSampled || info.fIsMultisampled);
440 SkASSERT(GrCaps::InstancedSupport::kNone != fCaps->instancedSupport());
441
442 if (!info.fIsMultisampled || fCaps->multisampleDisableSupport()) {
443 if (GrAA::kNo == aa) {
444 *aaType = GrAAType::kNone;
445 return true;
446 }
447
448 if (info.canUseCoverageAA() && viewMatrix.preservesRightAngles()) {
449 *aaType = GrAAType::kCoverage;
450 return true;
451 }
452 }
453
454 if (info.fIsMultisampled &&
455 fCaps->instancedSupport() >= GrCaps::InstancedSupport::kMultisampled) {
456 if (!info.fIsMixedSampled) {
457 *aaType = GrAAType::kMSAA;
458 return true;
459 }
460 if (fCaps->instancedSupport() >= GrCaps::InstancedSupport::kMixedSampled) {
461 *aaType = GrAAType::kMixedSamples;
462 return true;
463 }
464 }
465
466 return false;
467 }
468
469 }
470