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 16 namespace gr_instanced { 17 18 InstancedRendering::InstancedRendering(GrGpu* gpu) 19 : fGpu(SkRef(gpu)), 20 fState(State::kRecordingDraws), 21 fDrawPool(1024, 1024) { 22 } 23 24 std::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 31 std::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 40 std::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 61 std::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 68 std::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 81 std::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 106 std::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 201 inline 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 234 InstancedRendering::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 248 InstancedRendering::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 261 void 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 308 void 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 316 void 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 326 void 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 335 bool 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 379 void InstancedRendering::Op::wasRecorded() { 380 SkASSERT(!fIsTracked); 381 fInstancedRendering->fTrackedOps.addToTail(this); 382 fProcessors.makePendingExecution(); 383 fIsTracked = true; 384 } 385 386 bool 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 430 void 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 466 void 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 507 void 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 518 void InstancedRendering::resetGpuResources(ResetType resetType) { 519 fVertexBuffer.reset(); 520 fIndexBuffer.reset(); 521 fParamsBuffer.reset(); 522 this->onResetGpuResources(resetType); 523 } 524 525 } 526