/* * 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 "include/core/SkAlphaType.h" #include "include/core/SkBitmap.h" #include "include/core/SkBlendMode.h" #include "include/core/SkCanvas.h" #include "include/core/SkColor.h" #include "include/core/SkColorSpace.h" #include "include/core/SkColorType.h" #include "include/core/SkImage.h" #include "include/core/SkImageInfo.h" #include "include/core/SkMatrix.h" #include "include/core/SkRect.h" #include "include/core/SkRefCnt.h" #include "include/core/SkSamplingOptions.h" #include "include/core/SkSurface.h" #include "include/core/SkSurfaceProps.h" #include "include/core/SkTypes.h" #include "include/gpu/GpuTypes.h" #include "include/gpu/ganesh/GrBackendSurface.h" #include "include/gpu/ganesh/GrContextOptions.h" #include "include/gpu/ganesh/GrDirectContext.h" #include "include/gpu/ganesh/GrRecordingContext.h" #include "include/gpu/ganesh/GrTypes.h" #include "include/gpu/ganesh/SkImageGanesh.h" #include "include/gpu/ganesh/SkSurfaceGanesh.h" #include "include/gpu/ganesh/mock/GrMockTypes.h" #include "include/private/SkColorData.h" #include "include/private/gpu/ganesh/GrTextureGenerator.h" #include "include/private/gpu/ganesh/GrTypesPriv.h" #include "src/gpu/SkBackingFit.h" #include "src/gpu/Swizzle.h" #include "src/gpu/ganesh/Device.h" #include "src/gpu/ganesh/GrBackendTextureImageGenerator.h" #include "src/gpu/ganesh/GrCaps.h" #include "src/gpu/ganesh/GrColorSpaceXform.h" #include "src/gpu/ganesh/GrDirectContextPriv.h" #include "src/gpu/ganesh/GrDrawingManager.h" #include "src/gpu/ganesh/GrProxyProvider.h" #include "src/gpu/ganesh/GrRecordingContextPriv.h" #include "src/gpu/ganesh/GrSamplerState.h" #include "src/gpu/ganesh/GrSemaphore.h" #include "src/gpu/ganesh/GrSurfaceProxy.h" #include "src/gpu/ganesh/GrSurfaceProxyPriv.h" #include "src/gpu/ganesh/GrSurfaceProxyView.h" #include "src/gpu/ganesh/GrTexture.h" #include "src/gpu/ganesh/GrTextureProxy.h" #include "src/gpu/ganesh/SkGr.h" #include "src/gpu/ganesh/SurfaceDrawContext.h" #include "src/gpu/ganesh/ops/OpsTask.h" #include "src/gpu/ganesh/surface/SkSurface_Ganesh.h" #include "tests/CtsEnforcement.h" #include "tests/Test.h" #include "tools/gpu/BackendSurfaceFactory.h" #include "tools/gpu/BackendTextureImageFactory.h" #include "tools/gpu/ManagedBackendTexture.h" #include "tools/gpu/ProxyUtils.h" #include #include #include class GrRenderTask; #if defined(SK_DIRECT3D) #include "include/gpu/ganesh/d3d/GrD3DTypes.h" #endif #if defined(SK_METAL) #include "include/gpu/ganesh/mtl/GrMtlBackendSurface.h" #endif #if defined(SK_GL) #include "include/gpu/ganesh/gl/GrGLBackendSurface.h" #include "include/gpu/ganesh/gl/GrGLTypes.h" #endif #if defined(SK_VULKAN) #include "include/gpu/ganesh/vk/GrVkBackendSurface.h" #include "include/gpu/ganesh/vk/GrVkTypes.h" #endif static constexpr int kSize = 8; // Test that the correct mip map states are on the GrTextures when wrapping GrBackendTextures in // SkImages and SkSurfaces DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrWrappedMipMappedTest, reporter, ctxInfo, CtsEnforcement::kApiLevel_T) { using namespace skgpu; auto dContext = ctxInfo.directContext(); if (!dContext->priv().caps()->mipmapSupport()) { return; } Protected isProtected = Protected(dContext->priv().caps()->supportsProtectedContent()); for (auto mipmapped : { Mipmapped::kNo, Mipmapped::kYes }) { for (auto renderable : {GrRenderable::kNo, GrRenderable::kYes}) { // createBackendTexture currently doesn't support uploading data to mip maps // so we don't send any. However, we pretend there is data for the checks below which is // fine since we are never actually using these textures for any work on the gpu. auto mbet = sk_gpu_test::ManagedBackendTexture::MakeWithData(dContext, kSize, kSize, kRGBA_8888_SkColorType, SkColors::kTransparent, mipmapped, renderable, isProtected); if (!mbet) { ERRORF(reporter, "Could not make texture."); return; } sk_sp proxy; sk_sp image; if (renderable == GrRenderable::kYes) { sk_sp surface = SkSurfaces::WrapBackendTexture( dContext, mbet->texture(), kTopLeft_GrSurfaceOrigin, 0, kRGBA_8888_SkColorType, /*color space*/ nullptr, /*surface props*/ nullptr, sk_gpu_test::ManagedBackendTexture::ReleaseProc, mbet->releaseContext()); auto device = ((SkSurface_Ganesh*)surface.get())->getDevice(); proxy = device->readSurfaceView().asTextureProxyRef(); } else { image = SkImages::BorrowTextureFrom(dContext, mbet->texture(), kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, kPremul_SkAlphaType, /* color space */ nullptr, sk_gpu_test::ManagedBackendTexture::ReleaseProc, mbet->releaseContext()); REPORTER_ASSERT(reporter, (mipmapped == Mipmapped::kYes) == image->hasMipmaps()); proxy = sk_ref_sp(sk_gpu_test::GetTextureImageProxy(image.get(), dContext)); } REPORTER_ASSERT(reporter, proxy); if (!proxy) { continue; } REPORTER_ASSERT(reporter, proxy->isInstantiated()); GrTexture* texture = proxy->peekTexture(); REPORTER_ASSERT(reporter, texture); if (!texture) { continue; } if (mipmapped == Mipmapped::kYes) { REPORTER_ASSERT(reporter, Mipmapped::kYes == texture->mipmapped()); if (GrRenderable::kYes == renderable) { REPORTER_ASSERT(reporter, texture->mipmapsAreDirty()); } else { REPORTER_ASSERT(reporter, !texture->mipmapsAreDirty()); } } else { REPORTER_ASSERT(reporter, Mipmapped::kNo == texture->mipmapped()); } } } } // Test that we correctly copy or don't copy GrBackendTextures in the GrBackendTextureImageGenerator // based on if we will use mips in the draw and the mip status of the GrBackendTexture. DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrBackendTextureImageMipMappedTest, reporter, ctxInfo, CtsEnforcement::kApiLevel_T) { using namespace skgpu; auto dContext = ctxInfo.directContext(); if (!dContext->priv().caps()->mipmapSupport()) { return; } Protected isProtected = Protected(dContext->priv().caps()->supportsProtectedContent()); for (auto betMipmapped : { Mipmapped::kNo, Mipmapped::kYes }) { for (auto requestMipmapped : { Mipmapped::kNo, Mipmapped::kYes }) { auto ii = SkImageInfo::Make({kSize, kSize}, kRGBA_8888_SkColorType, kPremul_SkAlphaType); sk_sp image = sk_gpu_test::MakeBackendTextureImage( dContext, ii, SkColors::kTransparent, betMipmapped, Renderable::kNo, GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin, isProtected); REPORTER_ASSERT(reporter, (betMipmapped == Mipmapped::kYes) == image->hasMipmaps()); GrTextureProxy* proxy = sk_gpu_test::GetTextureImageProxy(image.get(), dContext); REPORTER_ASSERT(reporter, proxy); if (!proxy) { return; } REPORTER_ASSERT(reporter, proxy->isInstantiated()); sk_sp texture = sk_ref_sp(proxy->peekTexture()); REPORTER_ASSERT(reporter, texture); if (!texture) { return; } std::unique_ptr textureGen = GrBackendTextureImageGenerator::Make( texture, kTopLeft_GrSurfaceOrigin, nullptr, kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr); REPORTER_ASSERT(reporter, textureGen); if (!textureGen) { return; } SkImageInfo imageInfo = SkImageInfo::Make(kSize, kSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType); GrSurfaceProxyView genView = textureGen->generateTexture( dContext, imageInfo, requestMipmapped, GrImageTexGenPolicy::kDraw); GrSurfaceProxy* genProxy = genView.proxy(); REPORTER_ASSERT(reporter, genProxy); if (!genProxy) { return; } if (genProxy->isLazy()) { genProxy->priv().doLazyInstantiation(dContext->priv().resourceProvider()); } else if (!genProxy->isInstantiated()) { genProxy->instantiate(dContext->priv().resourceProvider()); } REPORTER_ASSERT(reporter, genProxy->isInstantiated()); if (!genProxy->isInstantiated()) { return; } GrTexture* genTexture = genProxy->peekTexture(); REPORTER_ASSERT(reporter, genTexture); if (!genTexture) { return; } GrBackendTexture backendTex = texture->getBackendTexture(); GrBackendTexture genBackendTex = genTexture->getBackendTexture(); if (GrBackendApi::kOpenGL == genBackendTex.backend()) { #ifdef SK_GL GrGLTextureInfo genTexInfo; GrGLTextureInfo origTexInfo; if (GrBackendTextures::GetGLTextureInfo(genBackendTex, &genTexInfo) && GrBackendTextures::GetGLTextureInfo(backendTex, &origTexInfo)) { if (requestMipmapped == Mipmapped::kYes && betMipmapped == Mipmapped::kNo) { // We did a copy so the texture IDs should be different REPORTER_ASSERT(reporter, origTexInfo.fID != genTexInfo.fID); } else { REPORTER_ASSERT(reporter, origTexInfo.fID == genTexInfo.fID); } } else { ERRORF(reporter, "Failed to get GrGLTextureInfo"); } #endif #ifdef SK_VULKAN } else if (GrBackendApi::kVulkan == genBackendTex.backend()) { GrVkImageInfo genImageInfo; GrVkImageInfo origImageInfo; if (GrBackendTextures::GetVkImageInfo(genBackendTex, &genImageInfo) && GrBackendTextures::GetVkImageInfo(backendTex, &origImageInfo)) { if (requestMipmapped == Mipmapped::kYes && betMipmapped == Mipmapped::kNo) { // We did a copy so the texture IDs should be different REPORTER_ASSERT(reporter, origImageInfo.fImage != genImageInfo.fImage); } else { REPORTER_ASSERT(reporter, origImageInfo.fImage == genImageInfo.fImage); } } else { ERRORF(reporter, "Failed to get GrVkImageInfo"); } #endif #ifdef SK_METAL } else if (GrBackendApi::kMetal == genBackendTex.backend()) { GrMtlTextureInfo genImageInfo; GrMtlTextureInfo origImageInfo; if (GrBackendTextures::GetMtlTextureInfo(genBackendTex, &genImageInfo) && GrBackendTextures::GetMtlTextureInfo(backendTex, &origImageInfo)) { if (requestMipmapped == Mipmapped::kYes && betMipmapped == Mipmapped::kNo) { // We did a copy so the texture IDs should be different REPORTER_ASSERT(reporter, origImageInfo.fTexture != genImageInfo.fTexture); } else { REPORTER_ASSERT(reporter, origImageInfo.fTexture == genImageInfo.fTexture); } } else { ERRORF(reporter, "Failed to get GrMtlTextureInfo"); } #endif #ifdef SK_DIRECT3D } else if (GrBackendApi::kDirect3D == genBackendTex.backend()) { GrD3DTextureResourceInfo genImageInfo; GrD3DTextureResourceInfo origImageInfo; if (genBackendTex.getD3DTextureResourceInfo(&genImageInfo) && backendTex.getD3DTextureResourceInfo(&origImageInfo)) { if (requestMipmapped == Mipmapped::kYes && betMipmapped == Mipmapped::kNo) { // We did a copy so the texture resources should be different REPORTER_ASSERT(reporter, origImageInfo.fResource != genImageInfo.fResource); } else { REPORTER_ASSERT(reporter, origImageInfo.fResource == genImageInfo.fResource); } } else { ERRORF(reporter, "Failed to get GrMtlTextureInfo"); } #endif } else { REPORTER_ASSERT(reporter, false); } } } } // Test that when we call makeImageSnapshot on an SkSurface we retains the same mip status as the // resource we took the snapshot of. DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrImageSnapshotMipMappedTest, reporter, ctxInfo, CtsEnforcement::kApiLevel_T) { auto dContext = ctxInfo.directContext(); if (!dContext->priv().caps()->mipmapSupport()) { return; } GrProtected isProtected = GrProtected(dContext->priv().caps()->supportsProtectedContent()); auto resourceProvider = dContext->priv().resourceProvider(); for (auto willUseMips : {false, true}) { for (auto isWrapped : {false, true}) { skgpu::Mipmapped mipmapped = willUseMips ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo; sk_sp surface; SkImageInfo info = SkImageInfo::Make(kSize, kSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType); if (isWrapped) { surface = sk_gpu_test::MakeBackendTextureSurface(dContext, info, kTopLeft_GrSurfaceOrigin, /* sample count */ 1, mipmapped, isProtected); } else { surface = SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kYes, info, /* sample count */ 1, kTopLeft_GrSurfaceOrigin, nullptr, willUseMips); } REPORTER_ASSERT(reporter, surface); auto device = ((SkSurface_Ganesh*)surface.get())->getDevice(); GrTextureProxy* texProxy = device->readSurfaceView().asTextureProxy(); REPORTER_ASSERT(reporter, mipmapped == texProxy->mipmapped()); texProxy->instantiate(resourceProvider); GrTexture* texture = texProxy->peekTexture(); REPORTER_ASSERT(reporter, mipmapped == texture->mipmapped()); sk_sp image = surface->makeImageSnapshot(); REPORTER_ASSERT(reporter, willUseMips == image->hasMipmaps()); REPORTER_ASSERT(reporter, image); texProxy = sk_gpu_test::GetTextureImageProxy(image.get(), dContext); REPORTER_ASSERT(reporter, mipmapped == texProxy->mipmapped()); texProxy->instantiate(resourceProvider); texture = texProxy->peekTexture(); REPORTER_ASSERT(reporter, mipmapped == texture->mipmapped()); } } } // Test that we don't create a mip mapped texture if the size is 1x1 even if the filter mode is set // to use mips. This test passes by not crashing or hitting asserts in code. DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(Gr1x1TextureMipMappedTest, reporter, ctxInfo, CtsEnforcement::kApiLevel_T) { auto dContext = ctxInfo.directContext(); if (!dContext->priv().caps()->mipmapSupport()) { return; } // Make surface to draw into SkImageInfo info = SkImageInfo::MakeN32(16, 16, kPremul_SkAlphaType); sk_sp surface = SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kNo, info); // Make 1x1 raster bitmap SkBitmap bmp; bmp.allocN32Pixels(1, 1); SkPMColor* pixel = reinterpret_cast(bmp.getPixels()); *pixel = 0; sk_sp bmpImage = bmp.asImage(); // Make sure we scale so we don't optimize out the use of mips. surface->getCanvas()->scale(0.5f, 0.5f); // This should upload the image to a non mipped GrTextureProxy. surface->getCanvas()->drawImage(bmpImage, 0, 0); dContext->flushAndSubmit(surface.get(), GrSyncCpu::kNo); // Now set the filter quality to high so we use mip maps. We should find the non mipped texture // in the cache for the SkImage. Since the texture is 1x1 we should just use that texture // instead of trying to do a copy to a mipped texture. surface->getCanvas()->drawImage(bmpImage, 0, 0, SkSamplingOptions({1.0f/3, 1.0f/3})); dContext->flushAndSubmit(surface.get(), GrSyncCpu::kNo); } // Create a new render target and draw 'mipmapView' into it using the provided 'filter'. static std::unique_ptr draw_mipmap_into_new_render_target( GrRecordingContext* rContext, GrColorType colorType, SkAlphaType alphaType, GrSurfaceProxyView mipmapView, GrSamplerState::MipmapMode mm) { auto proxyProvider = rContext->priv().proxyProvider(); sk_sp renderTarget = proxyProvider->createProxy(mipmapView.proxy()->backendFormat(), {1, 1}, GrRenderable::kYes, 1, skgpu::Mipmapped::kNo, SkBackingFit::kApprox, skgpu::Budgeted::kYes, GrProtected::kNo, /*label=*/"DrawMipMapViewTest"); auto sdc = skgpu::ganesh::SurfaceDrawContext::Make(rContext, colorType, std::move(renderTarget), nullptr, kTopLeft_GrSurfaceOrigin, SkSurfaceProps()); sdc->drawTexture(nullptr, std::move(mipmapView), alphaType, GrSamplerState::Filter::kLinear, mm, SkBlendMode::kSrcOver, {1, 1, 1, 1}, SkRect::MakeWH(4, 4), SkRect::MakeWH(1, 1), GrQuadAAFlags::kAll, SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(), nullptr); return sdc; } // Test that two opsTasks using the same mipmaps both depend on the same GrTextureResolveRenderTask. DEF_GANESH_TEST(GrManyDependentsMipMappedTest, reporter, /* options */, CtsEnforcement::kApiLevel_T) { using Enable = GrContextOptions::Enable; using MipmapMode = GrSamplerState::MipmapMode; for (auto enableSortingAndReduction : {Enable::kYes, Enable::kNo}) { GrMockOptions mockOptions; mockOptions.fMipmapSupport = true; GrContextOptions ctxOptions; ctxOptions.fReduceOpsTaskSplitting = enableSortingAndReduction; sk_sp dContext = GrDirectContext::MakeMock(&mockOptions, ctxOptions); GrDrawingManager* drawingManager = dContext->priv().drawingManager(); if (!dContext) { ERRORF(reporter, "could not create mock dContext with fReduceOpsTaskSplitting %s.", (Enable::kYes == enableSortingAndReduction) ? "enabled" : "disabled"); continue; } SkASSERT(dContext->priv().caps()->mipmapSupport()); GrBackendFormat format = dContext->defaultBackendFormat( kRGBA_8888_SkColorType, GrRenderable::kYes); GrColorType colorType = GrColorType::kRGBA_8888; SkAlphaType alphaType = kPremul_SkAlphaType; GrProxyProvider* proxyProvider = dContext->priv().proxyProvider(); // Create a mipmapped render target. sk_sp mipmapProxy = proxyProvider->createProxy(format, {4, 4}, GrRenderable::kYes, 1, skgpu::Mipmapped::kYes, SkBackingFit::kExact, skgpu::Budgeted::kYes, GrProtected::kNo, /*label=*/"ManyDependentsMipMappedTest"); // Mark the mipmaps clean to ensure things still work properly when they won't be marked // dirty again until GrRenderTask::makeClosed(). mipmapProxy->markMipmapsClean(); auto mipmapSDC = skgpu::ganesh::SurfaceDrawContext::Make(dContext.get(), colorType, mipmapProxy, nullptr, kTopLeft_GrSurfaceOrigin, SkSurfaceProps()); mipmapSDC->clear(SkPMColor4f{.1f, .2f, .3f, .4f}); REPORTER_ASSERT(reporter, drawingManager->getLastRenderTask(mipmapProxy.get())); // mipmapProxy's last render task should now just be the opsTask containing the clear. REPORTER_ASSERT(reporter, mipmapSDC->testingOnly_PeekLastOpsTask() == drawingManager->getLastRenderTask(mipmapProxy.get())); // Mipmaps don't get marked dirty until makeClosed(). REPORTER_ASSERT(reporter, !mipmapProxy->mipmapsAreDirty()); skgpu::Swizzle swizzle = dContext->priv().caps()->getReadSwizzle(format, colorType); GrSurfaceProxyView mipmapView(mipmapProxy, kTopLeft_GrSurfaceOrigin, swizzle); // Draw the dirty mipmap texture into a render target. auto sdc1 = draw_mipmap_into_new_render_target(dContext.get(), colorType, alphaType, mipmapView, MipmapMode::kLinear); auto sdc1Task = sk_ref_sp(sdc1->testingOnly_PeekLastOpsTask()); // Mipmaps should have gotten marked dirty during makeClosed, then marked clean again as // soon as a GrTextureResolveRenderTask was inserted. The way we know they were resolved is // if mipmapProxy->getLastRenderTask() has switched from the opsTask that drew to it, to the // task that resolved its mips. GrRenderTask* initialMipmapRegenTask = drawingManager->getLastRenderTask(mipmapProxy.get()); REPORTER_ASSERT(reporter, initialMipmapRegenTask); REPORTER_ASSERT(reporter, initialMipmapRegenTask != mipmapSDC->testingOnly_PeekLastOpsTask()); REPORTER_ASSERT(reporter, !mipmapProxy->mipmapsAreDirty()); // Draw the now-clean mipmap texture into a second target. auto sdc2 = draw_mipmap_into_new_render_target(dContext.get(), colorType, alphaType, mipmapView, MipmapMode::kLinear); auto sdc2Task = sk_ref_sp(sdc2->testingOnly_PeekLastOpsTask()); // Make sure the mipmap texture still has the same regen task. REPORTER_ASSERT(reporter, drawingManager->getLastRenderTask(mipmapProxy.get()) == initialMipmapRegenTask); SkASSERT(!mipmapProxy->mipmapsAreDirty()); // Reset everything so we can go again, this time with the first draw not mipmapped. dContext->flushAndSubmit(); // Mip regen tasks don't get added as dependencies until makeClosed(). REPORTER_ASSERT(reporter, sdc1Task->dependsOn(initialMipmapRegenTask)); REPORTER_ASSERT(reporter, sdc2Task->dependsOn(initialMipmapRegenTask)); // Render something to dirty the mips. mipmapSDC->clear(SkPMColor4f{.1f, .2f, .3f, .4f}); auto mipmapRTCTask = sk_ref_sp(mipmapSDC->testingOnly_PeekLastOpsTask()); REPORTER_ASSERT(reporter, mipmapRTCTask); // mipmapProxy's last render task should now just be the opsTask containing the clear. REPORTER_ASSERT(reporter, mipmapRTCTask.get() == drawingManager->getLastRenderTask(mipmapProxy.get())); // Mipmaps don't get marked dirty until makeClosed(). REPORTER_ASSERT(reporter, !mipmapProxy->mipmapsAreDirty()); // Draw the dirty mipmap texture into a render target, but don't do mipmap filtering. sdc1 = draw_mipmap_into_new_render_target(dContext.get(), colorType, alphaType, mipmapView, MipmapMode::kNone); // Mipmaps should have gotten marked dirty during makeClosed() when adding the dependency. // Since the last draw did not use mips, they will not have been regenerated and should // therefore still be dirty. REPORTER_ASSERT(reporter, mipmapProxy->mipmapsAreDirty()); // Since mips weren't regenerated, the last render task shouldn't have changed. REPORTER_ASSERT(reporter, mipmapRTCTask.get() == drawingManager->getLastRenderTask(mipmapProxy.get())); // Draw the stil-dirty mipmap texture into a second target with mipmap filtering. sdc2 = draw_mipmap_into_new_render_target(dContext.get(), colorType, alphaType, std::move(mipmapView), MipmapMode::kLinear); sdc2Task = sk_ref_sp(sdc2->testingOnly_PeekLastOpsTask()); // Make sure the mipmap texture now has a new last render task that regenerates the mips, // and that the mipmaps are now clean. auto mipRegenTask2 = drawingManager->getLastRenderTask(mipmapProxy.get()); REPORTER_ASSERT(reporter, mipRegenTask2); REPORTER_ASSERT(reporter, mipmapRTCTask.get() != mipRegenTask2); SkASSERT(!mipmapProxy->mipmapsAreDirty()); // Mip regen tasks don't get added as dependencies until makeClosed(). dContext->flushAndSubmit(); REPORTER_ASSERT(reporter, sdc2Task->dependsOn(mipRegenTask2)); } }