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 "src/gpu/ganesh/GrSurfaceProxy.h"
9
10 #include "include/core/SkAlphaType.h"
11 #include "include/core/SkColorSpace.h"
12 #include "include/core/SkPoint.h"
13 #include "include/gpu/GpuTypes.h"
14 #include "include/gpu/ganesh/GrRecordingContext.h"
15 #ifdef SKIA_DFX_FOR_RECORD_VKIMAGE
16 #include "include/gpu/vk/GrVulkanTrackerInterface.h"
17 #endif
18 #include "src/gpu/SkBackingFit.h"
19 #include "src/gpu/Swizzle.h"
20 #include "src/gpu/ganesh/GrCaps.h"
21 #include "src/gpu/ganesh/GrGpuResourcePriv.h"
22 #include "src/gpu/ganesh/GrImageInfo.h"
23 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
24 #include "src/gpu/ganesh/GrRenderTargetProxy.h"
25 #include "src/gpu/ganesh/GrRenderTask.h"
26 #include "src/gpu/ganesh/GrResourceProvider.h"
27 #include "src/gpu/ganesh/GrSurface.h"
28 #include "src/gpu/ganesh/GrSurfaceProxyPriv.h"
29 #include "src/gpu/ganesh/GrSurfaceProxyView.h"
30 #include "src/gpu/ganesh/GrTexture.h"
31 #include "src/gpu/ganesh/GrTextureProxy.h"
32 #include "src/gpu/ganesh/SurfaceContext.h"
33 #include "src/gpu/ganesh/SurfaceFillContext.h"
34 #ifdef SKIA_OHOS
35 #include "src/gpu/ganesh/GrPerfMonitorReporter.h"
36 #endif
37
38 #include <memory>
39
40 #ifdef SK_DEBUG
41 #include "include/gpu/ganesh/GrDirectContext.h"
42 #include "src/gpu/ganesh/GrDirectContextPriv.h"
43
is_valid_lazy(const SkISize & dimensions,SkBackingFit fit)44 static bool is_valid_lazy(const SkISize& dimensions, SkBackingFit fit) {
45 // A "fully" lazy proxy's width and height are not known until instantiation time.
46 // So fully lazy proxies are created with width and height < 0. Regular lazy proxies must be
47 // created with positive widths and heights. The width and height are set to 0 only after a
48 // failed instantiation. The former must be "approximate" fit while the latter can be either.
49 return ((dimensions.fWidth < 0 && dimensions.fHeight < 0 && SkBackingFit::kApprox == fit) ||
50 (dimensions.fWidth > 0 && dimensions.fHeight > 0));
51 }
52
is_valid_non_lazy(SkISize dimensions)53 static bool is_valid_non_lazy(SkISize dimensions) {
54 return dimensions.fWidth > 0 && dimensions.fHeight > 0;
55 }
56 #endif
57
58 // emulator mock
59 #ifdef SKIA_DFX_FOR_RECORD_VKIMAGE
60 #ifndef SK_VULKAN
61 namespace ParallelDebug {
IsVkImageDfxEnabled()62 bool IsVkImageDfxEnabled() { return false; }
RecordNodeId(uint64_t nodeId)63 void RecordNodeId(uint64_t nodeId) {}
GetNodeId()64 uint64_t GetNodeId() { return 0; }
65 }
66 #endif
67 #endif
68
LazyCallbackResult(sk_sp<GrSurface> surf,bool releaseCallback,LazyInstantiationKeyMode mode)69 GrSurfaceProxy::LazyCallbackResult::LazyCallbackResult(sk_sp<GrSurface> surf,
70 bool releaseCallback,
71 LazyInstantiationKeyMode mode)
72 : fSurface(std::move(surf)), fKeyMode(mode), fReleaseCallback(releaseCallback) {}
LazyCallbackResult(sk_sp<GrTexture> tex)73 GrSurfaceProxy::LazyCallbackResult::LazyCallbackResult(sk_sp<GrTexture> tex)
74 : LazyCallbackResult(sk_sp<GrSurface>(std::move(tex))) {}
75
76 // Deferred version
GrSurfaceProxy(const GrBackendFormat & format,SkISize dimensions,SkBackingFit fit,skgpu::Budgeted budgeted,GrProtected isProtected,GrInternalSurfaceFlags surfaceFlags,UseAllocator useAllocator,std::string_view label)77 GrSurfaceProxy::GrSurfaceProxy(const GrBackendFormat& format,
78 SkISize dimensions,
79 SkBackingFit fit,
80 skgpu::Budgeted budgeted,
81 GrProtected isProtected,
82 GrInternalSurfaceFlags surfaceFlags,
83 UseAllocator useAllocator,
84 std::string_view label)
85 : fSurfaceFlags(surfaceFlags)
86 , fFormat(format)
87 , fDimensions(dimensions)
88 , fFit(fit)
89 , fBudgeted(budgeted)
90 , fUseAllocator(useAllocator)
91 #ifdef SKIA_DFX_FOR_RECORD_VKIMAGE
92 , fNodeId(ParallelDebug::GetNodeId())
93 #endif
94 , fIsProtected(isProtected)
95 , fLabel(label) {
96 SkASSERT(fFormat.isValid());
97 SkASSERT(is_valid_non_lazy(dimensions));
98 }
99
100 // Lazy-callback version
GrSurfaceProxy(LazyInstantiateCallback && callback,const GrBackendFormat & format,SkISize dimensions,SkBackingFit fit,skgpu::Budgeted budgeted,GrProtected isProtected,GrInternalSurfaceFlags surfaceFlags,UseAllocator useAllocator,std::string_view label)101 GrSurfaceProxy::GrSurfaceProxy(LazyInstantiateCallback&& callback,
102 const GrBackendFormat& format,
103 SkISize dimensions,
104 SkBackingFit fit,
105 skgpu::Budgeted budgeted,
106 GrProtected isProtected,
107 GrInternalSurfaceFlags surfaceFlags,
108 UseAllocator useAllocator,
109 std::string_view label)
110 : fSurfaceFlags(surfaceFlags)
111 , fFormat(format)
112 , fDimensions(dimensions)
113 , fFit(fit)
114 , fBudgeted(budgeted)
115 , fUseAllocator(useAllocator)
116 , fLazyInstantiateCallback(std::move(callback))
117 #ifdef SKIA_DFX_FOR_RECORD_VKIMAGE
118 , fNodeId(ParallelDebug::GetNodeId())
119 #endif
120 , fIsProtected(isProtected)
121 , fLabel(label) {
122 SkASSERT(fFormat.isValid());
123 SkASSERT(fLazyInstantiateCallback);
124 SkASSERT(is_valid_lazy(dimensions, fit));
125 }
126
127 // Wrapped version
GrSurfaceProxy(sk_sp<GrSurface> surface,SkBackingFit fit,UseAllocator useAllocator)128 GrSurfaceProxy::GrSurfaceProxy(sk_sp<GrSurface> surface,
129 SkBackingFit fit,
130 UseAllocator useAllocator)
131 : fTarget(std::move(surface))
132 , fSurfaceFlags(fTarget->flags())
133 , fFormat(fTarget->backendFormat())
134 , fDimensions(fTarget->dimensions())
135 , fFit(fit)
136 , fBudgeted(fTarget->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted
137 ? skgpu::Budgeted::kYes
138 : skgpu::Budgeted::kNo)
139 , fUseAllocator(useAllocator)
140 , fUniqueID(fTarget->uniqueID()) // Note: converting from unique resource ID to a proxy ID!
141 #ifdef SKIA_DFX_FOR_RECORD_VKIMAGE
142 , fNodeId(ParallelDebug::GetNodeId())
143 #endif
144 , fIsProtected(fTarget->isProtected() ? GrProtected::kYes : GrProtected::kNo)
145 , fLabel(fTarget->getLabel()) {
146 SkASSERT(fFormat.isValid());
147 }
148
~GrSurfaceProxy()149 GrSurfaceProxy::~GrSurfaceProxy() {
150 }
151
152 #ifdef SKIA_DFX_FOR_RECORD_VKIMAGE
153 struct NodeIdHelper {
NodeIdHelperNodeIdHelper154 explicit inline NodeIdHelper(uint64_t nodeId): initNodeId_(ParallelDebug::GetNodeId())
155 {
156 ParallelDebug::RecordNodeId(nodeId);
157 }
~NodeIdHelperNodeIdHelper158 inline ~NodeIdHelper()
159 {
160 ParallelDebug::RecordNodeId(initNodeId_);
161 }
162 uint64_t initNodeId_;
163 };
164 #endif
165
createSurfaceImpl(GrResourceProvider * resourceProvider,int sampleCnt,GrRenderable renderable,skgpu::Mipmapped mipmapped) const166 sk_sp<GrSurface> GrSurfaceProxy::createSurfaceImpl(GrResourceProvider* resourceProvider,
167 int sampleCnt,
168 GrRenderable renderable,
169 skgpu::Mipmapped mipmapped) const {
170 #ifdef SKIA_DFX_FOR_RECORD_VKIMAGE
171 NodeIdHelper helper(fNodeId);
172 #endif
173 SkASSERT(mipmapped == skgpu::Mipmapped::kNo || fFit == SkBackingFit::kExact);
174 SkASSERT(!this->isLazy());
175 SkASSERT(!fTarget);
176 #ifdef SKIA_OHOS
177 int64_t currentTime = GrPerfMonitorReporter::getCurrentTime();
178 #endif
179 sk_sp<GrSurface> surface;
180 if (SkBackingFit::kApprox == fFit) {
181 surface = resourceProvider->createApproxTexture(fDimensions,
182 fFormat,
183 fFormat.textureType(),
184 renderable,
185 sampleCnt,
186 fIsProtected,
187 fLabel);
188 } else {
189 surface = resourceProvider->createTexture(fDimensions,
190 fFormat,
191 fFormat.textureType(),
192 renderable,
193 sampleCnt,
194 mipmapped,
195 fBudgeted,
196 fIsProtected,
197 fLabel);
198 }
199 if (!surface) {
200 return nullptr;
201 }
202 #ifdef SKIA_DFX_FOR_RECORD_VKIMAGE
203 if (ParallelDebug::IsVkImageDfxEnabled()) {
204 surface->updateNodeId(fNodeId);
205 }
206 #endif
207
208 if (fGrProxyTag.isGrTagValid()) {
209 surface->setResourceTag(fGrProxyTag);
210 #ifdef SKIA_OHOS
211 int64_t allocTime = GrPerfMonitorReporter::getCurrentTime() - currentTime;
212 GrPerfMonitorReporter::GetInstance().recordTextureNode(fGrProxyTag.fName, allocTime);
213 GrPerfMonitorReporter::GetInstance().recordTexturePerfEvent(fGrProxyTag.fName,
214 fGrProxyTag.fPid, static_cast<int32_t>(resourceProvider->getMaxResourceBytes()),
215 static_cast<int32_t>(resourceProvider->getBudgetedResourceBytes()), allocTime);
216 #endif
217 }
218
219 return surface;
220 }
221
canSkipResourceAllocator() const222 bool GrSurfaceProxy::canSkipResourceAllocator() const {
223 if (fUseAllocator == UseAllocator::kNo) {
224 // Usually an atlas or onFlush proxy
225 return true;
226 }
227
228 auto peek = this->peekSurface();
229 if (!peek) {
230 return false;
231 }
232 // If this resource is already allocated and not recyclable then the resource allocator does
233 // not need to do anything with it.
234 return !peek->resourcePriv().getScratchKey().isValid();
235 }
236
assign(sk_sp<GrSurface> surface)237 void GrSurfaceProxy::assign(sk_sp<GrSurface> surface) {
238 SkASSERT(!fTarget && surface);
239
240 SkDEBUGCODE(this->validateSurface(surface.get());)
241
242 fTarget = std::move(surface);
243
244 #ifdef SK_DEBUG
245 if (this->asRenderTargetProxy()) {
246 SkASSERT(fTarget->asRenderTarget());
247 }
248
249 // In order to give DDL users some flexibility in the destination of there DDLs,
250 // a DDL's target proxy can be more conservative (and thus require less memory)
251 // than the actual GrSurface used to fulfill it.
252 if (!this->isDDLTarget() && kInvalidGpuMemorySize != this->getRawGpuMemorySize_debugOnly()) {
253 // TODO(11373): Can this check be exact?
254 SkASSERT(fTarget->gpuMemorySize() <= this->getRawGpuMemorySize_debugOnly());
255 }
256 #endif
257 }
258
instantiateImpl(GrResourceProvider * resourceProvider,int sampleCnt,GrRenderable renderable,skgpu::Mipmapped mipmapped,const skgpu::UniqueKey * uniqueKey)259 bool GrSurfaceProxy::instantiateImpl(GrResourceProvider* resourceProvider,
260 int sampleCnt,
261 GrRenderable renderable,
262 skgpu::Mipmapped mipmapped,
263 const skgpu::UniqueKey* uniqueKey) {
264 SkASSERT(!this->isLazy());
265 if (fTarget) {
266 if (uniqueKey && uniqueKey->isValid()) {
267 SkASSERT(fTarget->getUniqueKey().isValid() && fTarget->getUniqueKey() == *uniqueKey);
268 }
269 return true;
270 }
271
272 sk_sp<GrSurface> surface = this->createSurfaceImpl(resourceProvider, sampleCnt, renderable,
273 mipmapped);
274 if (!surface) {
275 return false;
276 }
277
278 // If there was an invalidation message pending for this key, we might have just processed it,
279 // causing the key (stored on this proxy) to become invalid.
280 if (uniqueKey && uniqueKey->isValid()) {
281 resourceProvider->assignUniqueKeyToResource(*uniqueKey, surface.get());
282 }
283
284 this->assign(std::move(surface));
285
286 return true;
287 }
288
deinstantiate()289 void GrSurfaceProxy::deinstantiate() {
290 SkASSERT(this->isInstantiated());
291 fTarget = nullptr;
292 }
293
computeScratchKey(const GrCaps & caps,skgpu::ScratchKey * key) const294 void GrSurfaceProxy::computeScratchKey(const GrCaps& caps, skgpu::ScratchKey* key) const {
295 SkASSERT(!this->isFullyLazy());
296 GrRenderable renderable = GrRenderable::kNo;
297 int sampleCount = 1;
298 if (const auto* rtp = this->asRenderTargetProxy()) {
299 renderable = GrRenderable::kYes;
300 sampleCount = rtp->numSamples();
301 }
302
303 const GrTextureProxy* tp = this->asTextureProxy();
304 skgpu::Mipmapped mipmapped = skgpu::Mipmapped::kNo;
305 if (tp) {
306 mipmapped = tp->mipmapped();
307 }
308
309 GrTexture::ComputeScratchKey(caps, this->backendFormat(), this->backingStoreDimensions(),
310 renderable, sampleCount, mipmapped, fIsProtected, key);
311 }
312
backingStoreDimensions() const313 SkISize GrSurfaceProxy::backingStoreDimensions() const {
314 SkASSERT(!this->isFullyLazy());
315 if (fTarget) {
316 return fTarget->dimensions();
317 }
318
319 if (SkBackingFit::kExact == fFit) {
320 return fDimensions;
321 }
322 return skgpu::GetApproxSize(fDimensions);
323 }
324
isFunctionallyExact() const325 bool GrSurfaceProxy::isFunctionallyExact() const {
326 SkASSERT(!this->isFullyLazy());
327 return fFit == SkBackingFit::kExact ||
328 fDimensions == skgpu::GetApproxSize(fDimensions);
329 }
330
isFormatCompressed(const GrCaps * caps) const331 bool GrSurfaceProxy::isFormatCompressed(const GrCaps* caps) const {
332 return caps->isFormatCompressed(this->backendFormat());
333 }
334
335 #ifdef SK_DEBUG
validate(GrContext_Base * context) const336 void GrSurfaceProxy::validate(GrContext_Base* context) const {
337 if (fTarget) {
338 SkASSERT(fTarget->getContext()->priv().matches(context));
339 }
340 }
341 #endif
342
Copy(GrRecordingContext * rContext,sk_sp<GrSurfaceProxy> src,GrSurfaceOrigin origin,skgpu::Mipmapped mipmapped,SkIRect srcRect,SkBackingFit fit,skgpu::Budgeted budgeted,std::string_view label,RectsMustMatch rectsMustMatch,sk_sp<GrRenderTask> * outTask)343 sk_sp<GrSurfaceProxy> GrSurfaceProxy::Copy(GrRecordingContext* rContext,
344 sk_sp<GrSurfaceProxy> src,
345 GrSurfaceOrigin origin,
346 skgpu::Mipmapped mipmapped,
347 SkIRect srcRect,
348 SkBackingFit fit,
349 skgpu::Budgeted budgeted,
350 std::string_view label,
351 RectsMustMatch rectsMustMatch,
352 sk_sp<GrRenderTask>* outTask) {
353 SkASSERT(!src->isFullyLazy());
354 int width;
355 int height;
356
357 SkIPoint dstPoint;
358 if (rectsMustMatch == RectsMustMatch::kYes) {
359 width = src->width();
360 height = src->height();
361 dstPoint = {srcRect.fLeft, srcRect.fTop};
362 } else {
363 width = srcRect.width();
364 height = srcRect.height();
365 dstPoint = {0, 0};
366 }
367
368 if (!srcRect.intersect(SkIRect::MakeSize(src->dimensions()))) {
369 return {};
370 }
371 auto format = src->backendFormat().makeTexture2D();
372 SkASSERT(format.isValid());
373
374 if (src->backendFormat().textureType() != GrTextureType::kExternal) {
375 GrImageInfo info(GrColorType::kUnknown, kUnknown_SkAlphaType, nullptr, {width, height});
376 auto dstContext = rContext->priv().makeSC(info,
377 format,
378 label,
379 fit,
380 origin,
381 GrRenderable::kNo,
382 1,
383 mipmapped,
384 src->isProtected(),
385 budgeted);
386 sk_sp<GrRenderTask> copyTask;
387 if (dstContext && (copyTask = dstContext->copy(src, srcRect, dstPoint))) {
388 if (outTask) {
389 *outTask = std::move(copyTask);
390 }
391 return dstContext->asSurfaceProxyRef();
392 }
393 }
394 if (src->asTextureProxy()) {
395 auto dstContext = rContext->priv().makeSFC(kUnknown_SkAlphaType,
396 nullptr,
397 {width, height},
398 fit,
399 format,
400 1,
401 mipmapped,
402 src->isProtected(),
403 skgpu::Swizzle::RGBA(),
404 skgpu::Swizzle::RGBA(),
405 origin,
406 budgeted,
407 label);
408 GrSurfaceProxyView view(std::move(src), origin, skgpu::Swizzle::RGBA());
409 if (dstContext && dstContext->blitTexture(std::move(view), srcRect, dstPoint)) {
410 if (outTask) {
411 *outTask = dstContext->refRenderTask();
412 }
413 return dstContext->asSurfaceProxyRef();
414 }
415 }
416 // Can't use backend copies or draws.
417 return nullptr;
418 }
419
Copy(GrRecordingContext * context,sk_sp<GrSurfaceProxy> src,GrSurfaceOrigin origin,skgpu::Mipmapped mipmapped,SkBackingFit fit,skgpu::Budgeted budgeted,std::string_view label,sk_sp<GrRenderTask> * outTask)420 sk_sp<GrSurfaceProxy> GrSurfaceProxy::Copy(GrRecordingContext* context,
421 sk_sp<GrSurfaceProxy> src,
422 GrSurfaceOrigin origin,
423 skgpu::Mipmapped mipmapped,
424 SkBackingFit fit,
425 skgpu::Budgeted budgeted,
426 std::string_view label,
427 sk_sp<GrRenderTask>* outTask) {
428 SkASSERT(!src->isFullyLazy());
429 auto rect = SkIRect::MakeSize(src->dimensions());
430 return Copy(context,
431 std::move(src),
432 origin,
433 mipmapped,
434 rect,
435 fit,
436 budgeted,
437 label,
438 RectsMustMatch::kNo,
439 outTask);
440 }
441
442 #if defined(GPU_TEST_UTILS)
testingOnly_getBackingRefCnt() const443 int32_t GrSurfaceProxy::testingOnly_getBackingRefCnt() const {
444 if (fTarget) {
445 return fTarget->testingOnly_getRefCnt();
446 }
447
448 return -1; // no backing GrSurface
449 }
450
testingOnly_getFlags() const451 GrInternalSurfaceFlags GrSurfaceProxy::testingOnly_getFlags() const {
452 return fSurfaceFlags;
453 }
454
dump() const455 SkString GrSurfaceProxy::dump() const {
456 SkString tmp;
457
458 tmp.appendf("proxyID: %u - surfaceID: %u",
459 this->uniqueID().asUInt(),
460 this->peekSurface() ? this->peekSurface()->uniqueID().asUInt()
461 : -1);
462 return tmp;
463 }
464
465 #endif
466
exactify()467 void GrSurfaceProxyPriv::exactify() {
468 SkASSERT(!fProxy->isFullyLazy());
469 if (this->isExact()) {
470 return;
471 }
472
473 // The kApprox case. Setting the proxy's width & height to the backing-store width & height
474 // could have side-effects going forward, since we're obliterating the area of interest
475 // information. This is only used by SkSpecialImage when it's determined that sampling will
476 // not access beyond the safe known region (the current value of fProxy->fDimensions). If
477 // the proxy is instantiated, update the proxy's dimensions to match. Otherwise update them
478 // to the backing-store dimensions.
479 SkASSERT(SkBackingFit::kApprox == fProxy->fFit);
480 fProxy->fDimensions = fProxy->fTarget ? fProxy->fTarget->dimensions()
481 : fProxy->backingStoreDimensions();
482 fProxy->fFit = SkBackingFit::kExact;
483 }
484
doLazyInstantiation(GrResourceProvider * resourceProvider)485 bool GrSurfaceProxyPriv::doLazyInstantiation(GrResourceProvider* resourceProvider) {
486 SkASSERT(fProxy->isLazy());
487
488 sk_sp<GrSurface> surface;
489 if (const auto& uniqueKey = fProxy->getUniqueKey(); uniqueKey.isValid()) {
490 // First try to reattach to a cached version if the proxy is uniquely keyed
491 surface = resourceProvider->findByUniqueKey<GrSurface>(uniqueKey);
492 }
493
494 bool syncKey = true;
495 bool releaseCallback = false;
496 if (!surface) {
497 #ifdef SKIA_DFX_FOR_RECORD_VKIMAGE
498 NodeIdHelper helper(fNodeId);
499 #endif
500 auto result = fProxy->fLazyInstantiateCallback(resourceProvider, fProxy->callbackDesc());
501 surface = std::move(result.fSurface);
502 syncKey = result.fKeyMode == GrSurfaceProxy::LazyInstantiationKeyMode::kSynced;
503 releaseCallback = surface && result.fReleaseCallback;
504 }
505 if (!surface) {
506 fProxy->fDimensions.setEmpty();
507 return false;
508 }
509 #ifdef SKIA_DFX_FOR_RECORD_VKIMAGE
510 if (ParallelDebug::IsVkImageDfxEnabled()) {
511 surface->updateNodeId(fNodeId);
512 }
513 #endif
514
515 if (fProxy->isFullyLazy()) {
516 // This was a fully lazy proxy. We need to fill in the width & height. For partially
517 // lazy proxies we must preserve the original width & height since that indicates
518 // the content area.
519 fProxy->fDimensions = surface->dimensions();
520 }
521
522 SkASSERT(fProxy->width() <= surface->width());
523 SkASSERT(fProxy->height() <= surface->height());
524
525 if (GrTextureProxy* texProxy = fProxy->asTextureProxy()) {
526 texProxy->setTargetKeySync(syncKey);
527 if (syncKey) {
528 const skgpu::UniqueKey& key = texProxy->getUniqueKey();
529 if (key.isValid()) {
530 if (!surface->asTexture()->getUniqueKey().isValid()) {
531 // If 'surface' is newly created, attach the unique key
532 resourceProvider->assignUniqueKeyToResource(key, surface.get());
533 } else {
534 // otherwise we had better have reattached to a cached version
535 SkASSERT(surface->asTexture()->getUniqueKey() == key);
536 }
537 } else {
538 SkASSERT(!surface->getUniqueKey().isValid());
539 }
540 }
541 }
542
543 this->assign(std::move(surface));
544 if (releaseCallback) {
545 fProxy->fLazyInstantiateCallback = nullptr;
546 }
547
548 return true;
549 }
550
551 #ifdef SK_DEBUG
validateSurface(const GrSurface * surface)552 void GrSurfaceProxy::validateSurface(const GrSurface* surface) {
553 SkASSERTF(surface->backendFormat() == fFormat, "%s != %s",
554 surface->backendFormat().toStr().c_str(), fFormat.toStr().c_str());
555
556 this->onValidateSurface(surface);
557 }
558 #endif
559