| /* |
| * Copyright 2017 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "SkTypes.h" |
| |
| #if SK_SUPPORT_GPU |
| |
| #include "GrBackendSurface.h" |
| #include "GrGpu.h" |
| #include "SkCanvas.h" |
| #include "SkDeferredDisplayListRecorder.h" |
| #include "SkGpuDevice.h" |
| #include "SkSurface.h" |
| #include "SkSurface_Gpu.h" |
| #include "SkSurfaceCharacterization.h" |
| #include "SkSurfaceProps.h" |
| #include "Test.h" |
| |
| #include "gl/GrGLDefines.h" |
| #ifdef SK_VULKAN |
| #include "vk/GrVkDefines.h" |
| #endif |
| |
| static GrBackendFormat create_backend_format(GrContext* context, SkColorType colorType) { |
| const GrCaps* caps = context->caps(); |
| |
| switch (context->contextPriv().getBackend()) { |
| case kOpenGL_GrBackend: |
| if (kRGBA_8888_SkColorType == colorType) { |
| GrGLenum format = caps->srgbSupport() ? GR_GL_SRGB8_ALPHA8 : GR_GL_RGBA8; |
| return GrBackendFormat::MakeGL(format, GR_GL_TEXTURE_2D); |
| } else if (kRGBA_F16_SkColorType == colorType) { |
| return GrBackendFormat::MakeGL(GR_GL_RGBA16F, GR_GL_TEXTURE_2D); |
| } |
| break; |
| #ifdef SK_VULKAN |
| case kVulkan_GrBackend: |
| if (kRGBA_8888_SkColorType == colorType) { |
| VkFormat format = caps->srgbSupport() ? VK_FORMAT_R8G8B8A8_SRGB |
| : VK_FORMAT_R8G8B8A8_UNORM; |
| return GrBackendFormat::MakeVK(format); |
| } else if (kRGBA_F16_SkColorType == colorType) { |
| return GrBackendFormat::MakeVK(VK_FORMAT_R16G16B16A16_SFLOAT); |
| } |
| break; |
| #endif |
| case kMock_GrBackend: |
| if (kRGBA_8888_SkColorType == colorType) { |
| GrPixelConfig config = caps->srgbSupport() ? kSRGBA_8888_GrPixelConfig |
| : kRGBA_8888_GrPixelConfig; |
| return GrBackendFormat::MakeMock(config); |
| } else if (kRGBA_F16_SkColorType == colorType) { |
| return GrBackendFormat::MakeMock(kRGBA_half_GrPixelConfig); |
| } |
| break; |
| default: |
| return GrBackendFormat(); // return an invalid format |
| } |
| |
| return GrBackendFormat(); // return an invalid format |
| } |
| |
| |
| class SurfaceParameters { |
| public: |
| static const int kNumParams = 9; |
| static const int kSampleCount = 5; |
| static const int kMipMipCount = 8; |
| |
| SurfaceParameters() |
| : fWidth(64) |
| , fHeight(64) |
| , fOrigin(kTopLeft_GrSurfaceOrigin) |
| , fColorType(kRGBA_8888_SkColorType) |
| , fColorSpace(SkColorSpace::MakeSRGB()) |
| , fSampleCount(1) |
| , fSurfaceProps(0x0, kUnknown_SkPixelGeometry) |
| , fShouldCreateMipMaps(true) { |
| } |
| |
| int sampleCount() const { return fSampleCount; } |
| |
| // Modify the SurfaceParameters in just one way |
| void modify(int i) { |
| switch (i) { |
| case 0: |
| fWidth = 63; |
| break; |
| case 1: |
| fHeight = 63; |
| break; |
| case 2: |
| fOrigin = kBottomLeft_GrSurfaceOrigin; |
| break; |
| case 3: |
| fColorType = kRGBA_F16_SkColorType; |
| break; |
| case 4: |
| fColorSpace = SkColorSpace::MakeSRGBLinear(); |
| break; |
| case kSampleCount: |
| fSampleCount = 4; |
| break; |
| case 6: |
| fSurfaceProps = SkSurfaceProps(0x0, kRGB_H_SkPixelGeometry); |
| break; |
| case 7: |
| fSurfaceProps = SkSurfaceProps(SkSurfaceProps::kUseDeviceIndependentFonts_Flag, |
| kUnknown_SkPixelGeometry); |
| break; |
| case 8: |
| fShouldCreateMipMaps = false; |
| break; |
| } |
| } |
| |
| // Create a DDL whose characterization captures the current settings |
| std::unique_ptr<SkDeferredDisplayList> createDDL(GrContext* context) const { |
| sk_sp<SkSurface> s = this->make(context); |
| if (!s) { |
| return nullptr; |
| } |
| |
| int maxResourceCount; |
| size_t maxResourceBytes; |
| context->getResourceCacheLimits(&maxResourceCount, &maxResourceBytes); |
| |
| // Note that Ganesh doesn't make use of the SkImageInfo's alphaType |
| SkImageInfo ii = SkImageInfo::Make(fWidth, fHeight, fColorType, |
| kPremul_SkAlphaType, fColorSpace); |
| |
| GrBackendFormat backendFormat = create_backend_format(context, fColorType); |
| |
| SkSurfaceCharacterization c = context->threadSafeProxy()->createCharacterization( |
| maxResourceBytes, ii, backendFormat, fSampleCount, |
| fOrigin, fSurfaceProps, fShouldCreateMipMaps); |
| SkAssertResult(c.isValid()); |
| |
| SkDeferredDisplayListRecorder r(c); |
| SkCanvas* canvas = r.getCanvas(); |
| if (!canvas) { |
| return nullptr; |
| } |
| |
| canvas->drawRect(SkRect::MakeXYWH(10, 10, 10, 10), SkPaint()); |
| return r.detach(); |
| } |
| |
| // Create the surface with the current set of parameters |
| sk_sp<SkSurface> make(GrContext* context) const { |
| // Note that Ganesh doesn't make use of the SkImageInfo's alphaType |
| SkImageInfo ii = SkImageInfo::Make(fWidth, fHeight, fColorType, |
| kPremul_SkAlphaType, fColorSpace); |
| |
| return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, ii, fSampleCount, |
| fOrigin, &fSurfaceProps, fShouldCreateMipMaps); |
| } |
| |
| // Create a surface w/ the current parameters but make it non-textureable |
| sk_sp<SkSurface> makeNonTextureable(GrContext* context, GrBackendTexture* backend) const { |
| GrGpu* gpu = context->contextPriv().getGpu(); |
| |
| GrPixelConfig config = SkImageInfo2GrPixelConfig(fColorType, nullptr, *context->caps()); |
| SkASSERT(kUnknown_GrPixelConfig != config); |
| |
| *backend = gpu->createTestingOnlyBackendTexture(nullptr, fWidth, fHeight, |
| config, true, GrMipMapped::kNo); |
| |
| if (!backend->isValid() || !gpu->isTestingOnlyBackendTexture(*backend)) { |
| return nullptr; |
| } |
| |
| sk_sp<SkSurface> surface = SkSurface::MakeFromBackendTextureAsRenderTarget( |
| context, *backend, fOrigin, fSampleCount, fColorType, nullptr, nullptr); |
| |
| if (!surface) { |
| gpu->deleteTestingOnlyBackendTexture(backend); |
| return nullptr; |
| } |
| |
| return surface; |
| } |
| |
| void cleanUpBackEnd(GrContext* context, GrBackendTexture* backend) const { |
| GrGpu* gpu = context->contextPriv().getGpu(); |
| |
| gpu->deleteTestingOnlyBackendTexture(backend); |
| } |
| |
| private: |
| int fWidth; |
| int fHeight; |
| GrSurfaceOrigin fOrigin; |
| SkColorType fColorType; |
| sk_sp<SkColorSpace> fColorSpace; |
| int fSampleCount; |
| SkSurfaceProps fSurfaceProps; |
| bool fShouldCreateMipMaps; |
| }; |
| |
| // This tests SkSurfaceCharacterization/SkSurface compatibility |
| DEF_GPUTEST_FOR_ALL_CONTEXTS(DDLSurfaceCharacterizationTest, reporter, ctxInfo) { |
| GrContext* context = ctxInfo.grContext(); |
| |
| // Create a bitmap that we can readback into |
| SkImageInfo imageInfo = SkImageInfo::Make(64, 64, kRGBA_8888_SkColorType, |
| kPremul_SkAlphaType); |
| SkBitmap bitmap; |
| bitmap.allocPixels(imageInfo); |
| |
| std::unique_ptr<SkDeferredDisplayList> ddl; |
| |
| // First, create a DDL using the stock SkSurface parameters |
| { |
| SurfaceParameters params; |
| |
| ddl = params.createDDL(context); |
| SkAssertResult(ddl); |
| |
| // The DDL should draw into an SkSurface created with the same parameters |
| sk_sp<SkSurface> s = params.make(context); |
| if (!s) { |
| return; |
| } |
| |
| REPORTER_ASSERT(reporter, s->draw(ddl.get())); |
| s->readPixels(imageInfo, bitmap.getPixels(), bitmap.rowBytes(), 0, 0); |
| } |
| |
| // Then, alter each parameter in turn and check that the DDL & surface are incompatible |
| for (int i = 0; i < SurfaceParameters::kNumParams; ++i) { |
| SurfaceParameters params; |
| params.modify(i); |
| |
| sk_sp<SkSurface> s = params.make(context); |
| if (!s) { |
| continue; |
| } |
| |
| if (SurfaceParameters::kSampleCount == i) { |
| SkSurface_Gpu* gpuSurf = static_cast<SkSurface_Gpu*>(s.get()); |
| |
| int supportedSampleCount = context->caps()->getRenderTargetSampleCount( |
| params.sampleCount(), |
| gpuSurf->getDevice()->accessRenderTargetContext()->asRenderTargetProxy()->config()); |
| if (1 == supportedSampleCount) { |
| // If changing the sample count won't result in a different |
| // surface characterization, skip this step |
| continue; |
| } |
| } |
| |
| if (SurfaceParameters::kMipMipCount == i && !context->caps()->mipMapSupport()) { |
| continue; |
| } |
| |
| REPORTER_ASSERT(reporter, !s->draw(ddl.get()), |
| "DDLSurfaceCharacterizationTest failed on parameter: %d\n", i); |
| } |
| |
| // Next test the compatibility of resource cache parameters |
| { |
| const SurfaceParameters params; |
| sk_sp<SkSurface> s = params.make(context); |
| |
| int maxResourceCount; |
| size_t maxResourceBytes; |
| context->getResourceCacheLimits(&maxResourceCount, &maxResourceBytes); |
| |
| context->setResourceCacheLimits(maxResourceCount, maxResourceBytes/2); |
| REPORTER_ASSERT(reporter, !s->draw(ddl.get())); |
| |
| // DDL TODO: once proxies/ops can be de-instantiated we can re-enable these tests. |
| // For now, DDLs are drawn once. |
| #if 0 |
| // resource limits >= those at characterization time are accepted |
| context->setResourceCacheLimits(2*maxResourceCount, maxResourceBytes); |
| REPORTER_ASSERT(reporter, s->draw(ddl.get())); |
| s->readPixels(imageInfo, bitmap.getPixels(), bitmap.rowBytes(), 0, 0); |
| |
| context->setResourceCacheLimits(maxResourceCount, 2*maxResourceBytes); |
| REPORTER_ASSERT(reporter, s->draw(ddl.get())); |
| s->readPixels(imageInfo, bitmap.getPixels(), bitmap.rowBytes(), 0, 0); |
| |
| context->setResourceCacheLimits(maxResourceCount, maxResourceBytes); |
| REPORTER_ASSERT(reporter, s->draw(ddl.get())); |
| s->readPixels(imageInfo, bitmap.getPixels(), bitmap.rowBytes(), 0, 0); |
| #endif |
| } |
| |
| // Test that the textureability of the DDL characterization can block a DDL draw |
| { |
| GrBackendTexture backend; |
| const SurfaceParameters params; |
| sk_sp<SkSurface> s = params.makeNonTextureable(context, &backend); |
| if (s) { |
| REPORTER_ASSERT(reporter, !s->draw(ddl.get())); |
| |
| s = nullptr; |
| params.cleanUpBackEnd(context, &backend); |
| } |
| } |
| |
| // Make sure non-GPU-backed surfaces fail characterization |
| { |
| SkImageInfo ii = SkImageInfo::MakeN32(64, 64, kOpaque_SkAlphaType); |
| |
| sk_sp<SkSurface> rasterSurface = SkSurface::MakeRaster(ii); |
| SkSurfaceCharacterization c; |
| REPORTER_ASSERT(reporter, !rasterSurface->characterize(&c)); |
| } |
| |
| // Exercise the createResized method |
| { |
| SurfaceParameters params; |
| |
| sk_sp<SkSurface> s = params.make(context); |
| if (!s) { |
| return; |
| } |
| |
| SkSurfaceCharacterization char0; |
| SkAssertResult(s->characterize(&char0)); |
| |
| // Too small |
| SkSurfaceCharacterization char1 = char0.createResized(-1, -1); |
| REPORTER_ASSERT(reporter, !char1.isValid()); |
| |
| // Too large |
| SkSurfaceCharacterization char2 = char0.createResized(1000000, 32); |
| REPORTER_ASSERT(reporter, !char2.isValid()); |
| |
| // Just right |
| SkSurfaceCharacterization char3 = char0.createResized(32, 32); |
| REPORTER_ASSERT(reporter, char3.isValid()); |
| REPORTER_ASSERT(reporter, 32 == char3.width()); |
| REPORTER_ASSERT(reporter, 32 == char3.height()); |
| } |
| } |
| |
| static constexpr int kSize = 8; |
| |
| struct TextureReleaseChecker { |
| TextureReleaseChecker() : fReleaseCount(0) {} |
| int fReleaseCount; |
| static void Release(void* self) { |
| static_cast<TextureReleaseChecker*>(self)->fReleaseCount++; |
| } |
| }; |
| |
| enum class DDLStage { kMakeImage, kDrawImage, kDetach, kDrawDDL }; |
| |
| // This tests the ability to create and use wrapped textures in a DDL world |
| DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DDLWrapBackendTest, reporter, ctxInfo) { |
| GrContext* context = ctxInfo.grContext(); |
| GrGpu* gpu = context->contextPriv().getGpu(); |
| for (auto lastStage : { DDLStage::kMakeImage, DDLStage::kDrawImage, |
| DDLStage::kDetach, DDLStage::kDrawDDL } ) { |
| for (auto earlyImageReset : { false , true } ) { |
| GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture( |
| nullptr, kSize, kSize, kRGBA_8888_GrPixelConfig, false, GrMipMapped::kNo); |
| if (!backendTex.isValid()) { |
| continue; |
| } |
| |
| SurfaceParameters params; |
| |
| sk_sp<SkSurface> s = params.make(context); |
| if (!s) { |
| gpu->deleteTestingOnlyBackendTexture(&backendTex); |
| continue; |
| } |
| |
| SkSurfaceCharacterization c; |
| SkAssertResult(s->characterize(&c)); |
| |
| std::unique_ptr<SkDeferredDisplayListRecorder> recorder( |
| new SkDeferredDisplayListRecorder(c)); |
| |
| SkCanvas* canvas = recorder->getCanvas(); |
| if (!canvas) { |
| gpu->deleteTestingOnlyBackendTexture(&backendTex); |
| continue; |
| } |
| |
| GrContext* deferredContext = canvas->getGrContext(); |
| if (!deferredContext) { |
| gpu->deleteTestingOnlyBackendTexture(&backendTex); |
| continue; |
| } |
| |
| sk_sp<SkImage> image = SkImage::MakeFromAdoptedTexture(deferredContext, backendTex, |
| kTopLeft_GrSurfaceOrigin, |
| kRGBA_8888_SkColorType, |
| kPremul_SkAlphaType, nullptr); |
| // Adopted Textures are not supported in DDL |
| REPORTER_ASSERT(reporter, !image); |
| |
| TextureReleaseChecker releaseChecker; |
| image = SkImage::MakeFromTexture(deferredContext, backendTex, |
| kTopLeft_GrSurfaceOrigin, |
| kRGBA_8888_SkColorType, |
| kPremul_SkAlphaType, nullptr, |
| TextureReleaseChecker::Release, &releaseChecker); |
| |
| REPORTER_ASSERT(reporter, image); |
| if (!image) { |
| gpu->deleteTestingOnlyBackendTexture(&backendTex); |
| continue; |
| } |
| |
| if (DDLStage::kMakeImage == lastStage) { |
| REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount); |
| image.reset(); |
| REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount); |
| recorder.reset(); |
| REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount); |
| gpu->deleteTestingOnlyBackendTexture(&backendTex); |
| continue; |
| } |
| |
| canvas->drawImage(image.get(), 0, 0); |
| |
| if (earlyImageReset) { |
| REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount); |
| image.reset(); |
| // Ref should still be held by DDL recorder since we did the draw |
| REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount); |
| } |
| |
| if (DDLStage::kDrawImage == lastStage) { |
| REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount); |
| recorder.reset(); |
| if (earlyImageReset) { |
| REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount); |
| } else { |
| REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount); |
| image.reset(); |
| REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount); |
| } |
| gpu->deleteTestingOnlyBackendTexture(&backendTex); |
| continue; |
| } |
| |
| std::unique_ptr<SkDeferredDisplayList> ddl = recorder->detach(); |
| if (DDLStage::kDetach == lastStage) { |
| REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount); |
| recorder.reset(); |
| #ifndef SK_RASTER_RECORDER_IMPLEMENTATION |
| REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount); |
| #endif |
| ddl.reset(); |
| if (earlyImageReset) { |
| REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount); |
| } else { |
| REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount); |
| image.reset(); |
| REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount); |
| } |
| gpu->deleteTestingOnlyBackendTexture(&backendTex); |
| continue; |
| } |
| |
| REPORTER_ASSERT(reporter, s->draw(ddl.get())); |
| |
| REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount); |
| recorder.reset(); |
| #ifndef SK_RASTER_RECORDER_IMPLEMENTATION |
| REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount); |
| #endif |
| ddl.reset(); |
| #ifndef SK_RASTER_RECORDER_IMPLEMENTATION |
| REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount); |
| #endif |
| |
| // Force all draws to flush and sync by calling a read pixels |
| SkImageInfo imageInfo = SkImageInfo::Make(kSize, kSize, kRGBA_8888_SkColorType, |
| kPremul_SkAlphaType); |
| SkBitmap bitmap; |
| bitmap.allocPixels(imageInfo); |
| s->readPixels(imageInfo, bitmap.getPixels(), bitmap.rowBytes(), 0, 0); |
| |
| if (earlyImageReset) { |
| REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount); |
| } else { |
| REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount); |
| image.reset(); |
| REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount); |
| } |
| |
| gpu->deleteTestingOnlyBackendTexture(&backendTex); |
| } |
| } |
| } |
| |
| |
| #endif |