|  | /* | 
|  | * 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/gpu/ganesh/GrTextureGenerator.h" | 
|  | #include "include/private/gpu/ganesh/GrTypesPriv.h" | 
|  | #include "src/core/SkColorData.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 <initializer_list> | 
|  | #include <memory> | 
|  | #include <utility> | 
|  |  | 
|  | 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<GrTextureProxy> proxy; | 
|  | sk_sp<SkImage> image; | 
|  | if (renderable == GrRenderable::kYes) { | 
|  | sk_sp<SkSurface> 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<SkImage> 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<GrTexture> texture = sk_ref_sp(proxy->peekTexture()); | 
|  | REPORTER_ASSERT(reporter, texture); | 
|  | if (!texture) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<GrTextureGenerator> 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<SkSurface> 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<SkImage> 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<SkSurface> surface = SkSurfaces::RenderTarget(dContext, skgpu::Budgeted::kNo, info); | 
|  |  | 
|  | // Make 1x1 raster bitmap | 
|  | SkBitmap bmp; | 
|  | bmp.allocN32Pixels(1, 1); | 
|  | SkPMColor* pixel = reinterpret_cast<SkPMColor*>(bmp.getPixels()); | 
|  | *pixel = 0; | 
|  |  | 
|  | sk_sp<SkImage> 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<skgpu::ganesh::SurfaceDrawContext> draw_mipmap_into_new_render_target( | 
|  | GrRecordingContext* rContext, | 
|  | GrColorType colorType, | 
|  | SkAlphaType alphaType, | 
|  | GrSurfaceProxyView mipmapView, | 
|  | GrSamplerState::MipmapMode mm) { | 
|  | auto proxyProvider = rContext->priv().proxyProvider(); | 
|  | sk_sp<GrSurfaceProxy> 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<GrDirectContext> 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<GrTextureProxy> 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)); | 
|  | } | 
|  | } |