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