| /* |
| * Copyright 2018 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/SkBitmap.h" |
| #include "include/core/SkColor.h" |
| #include "include/core/SkColorSpace.h" |
| #include "include/core/SkFont.h" |
| #include "include/core/SkImageInfo.h" |
| #include "include/core/SkMatrix.h" |
| #include "include/core/SkPaint.h" |
| #include "include/core/SkSize.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/GrDirectContext.h" |
| #include "include/gpu/ganesh/GrTypes.h" |
| #include "include/private/gpu/ganesh/GrTypesPriv.h" |
| #include "src/gpu/AtlasTypes.h" |
| #include "src/gpu/SkBackingFit.h" |
| #include "src/gpu/ganesh/GrCaps.h" |
| #include "src/gpu/ganesh/GrDeferredUpload.h" |
| #include "src/gpu/ganesh/GrDirectContextPriv.h" |
| #include "src/gpu/ganesh/GrDrawOpAtlas.h" |
| #include "src/gpu/ganesh/GrDstProxyView.h" |
| #include "src/gpu/ganesh/GrOnFlushResourceProvider.h" |
| #include "src/gpu/ganesh/GrOpFlushState.h" |
| #include "src/gpu/ganesh/GrSurfaceProxy.h" |
| #include "src/gpu/ganesh/GrSurfaceProxyView.h" |
| #include "src/gpu/ganesh/GrXferProcessor.h" |
| #include "src/gpu/ganesh/SurfaceDrawContext.h" |
| #include "src/gpu/ganesh/ops/AtlasTextOp.h" |
| #include "src/gpu/ganesh/ops/GrOp.h" |
| #include "src/gpu/ganesh/text/GrAtlasManager.h" |
| #include "tests/CtsEnforcement.h" |
| #include "tests/Test.h" |
| #include "tools/fonts/FontToolUtils.h" |
| #include "tools/gpu/ganesh/AtlasTextOpTools.h" |
| #include "tools/gpu/ganesh/GrAtlasTools.h" |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <memory> |
| |
| class GrResourceProvider; |
| struct GrContextOptions; |
| |
| using namespace skgpu::ganesh; |
| using MaskFormat = skgpu::MaskFormat; |
| |
| |
| namespace { |
| const int kNumPlots = 2; |
| const int kPlotSize = 32; |
| const int kAtlasSize = kNumPlots * kPlotSize; |
| |
| class AssertOnEvict : public skgpu::PlotEvictionCallback { |
| public: |
| void evict(skgpu::PlotLocator) override { |
| SkASSERT(0); // The unit test shouldn't exercise this code path |
| } |
| }; |
| |
| void check(skiatest::Reporter* r, GrDrawOpAtlas* atlas, |
| uint32_t expectedActive, int expectedAlloced) { |
| REPORTER_ASSERT(r, atlas->numActivePages() == expectedActive); |
| REPORTER_ASSERT(r, GrDrawOpAtlasTools::NumAllocated(atlas) == expectedAlloced); |
| REPORTER_ASSERT(r, atlas->maxPages() == skgpu::PlotLocator::kMaxMultitexturePages); |
| } |
| |
| bool fill_plot(GrDrawOpAtlas* atlas, |
| GrResourceProvider* resourceProvider, |
| GrDeferredUploadTarget* target, |
| skgpu::AtlasLocator* atlasLocator, |
| int alpha) { |
| SkImageInfo ii = SkImageInfo::MakeA8(kPlotSize, kPlotSize); |
| |
| SkBitmap data; |
| data.allocPixels(ii); |
| data.eraseARGB(alpha, 0, 0, 0); |
| |
| GrDrawOpAtlas::ErrorCode code; |
| code = atlas->addToAtlas(resourceProvider, target, kPlotSize, kPlotSize, |
| data.getAddr(0, 0), atlasLocator); |
| return GrDrawOpAtlas::ErrorCode::kSucceeded == code; |
| } |
| } // anonymous namespace |
| |
| // Can't be in anonymous namespace because it needs friend access to TokenTracker |
| class TestingUploadTarget : public GrDeferredUploadTarget { |
| public: |
| TestingUploadTarget() { } |
| |
| const skgpu::TokenTracker* tokenTracker() final { return &fTokenTracker; } |
| skgpu::TokenTracker* writeableTokenTracker() { return &fTokenTracker; } |
| |
| skgpu::AtlasToken addInlineUpload(GrDeferredTextureUploadFn&&) final { |
| SkASSERT(0); // this test shouldn't invoke this code path |
| return fTokenTracker.nextDrawToken(); |
| } |
| |
| skgpu::AtlasToken addASAPUpload(GrDeferredTextureUploadFn&& upload) final { |
| return fTokenTracker.nextFlushToken(); |
| } |
| |
| void issueDrawToken() { fTokenTracker.issueDrawToken(); } |
| void issueFlushToken() { fTokenTracker.issueFlushToken(); } |
| |
| private: |
| skgpu::TokenTracker fTokenTracker; |
| |
| using INHERITED = GrDeferredUploadTarget; |
| }; |
| |
| // This is a basic DrawOpAtlas test. It simply verifies that multitexture atlases correctly |
| // add and remove pages. Note that this is simulating flush-time behavior. |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(BasicDrawOpAtlas, |
| reporter, |
| ctxInfo, |
| CtsEnforcement::kApiLevel_T) { |
| auto context = ctxInfo.directContext(); |
| auto proxyProvider = context->priv().proxyProvider(); |
| auto resourceProvider = context->priv().resourceProvider(); |
| auto drawingManager = context->priv().drawingManager(); |
| const GrCaps* caps = context->priv().caps(); |
| |
| GrOnFlushResourceProvider onFlushResourceProvider(drawingManager); |
| TestingUploadTarget uploadTarget; |
| |
| GrColorType atlasColorType = GrColorType::kAlpha_8; |
| GrBackendFormat format = caps->getDefaultBackendFormat(atlasColorType, |
| GrRenderable::kNo); |
| |
| AssertOnEvict evictor; |
| skgpu::AtlasGenerationCounter counter; |
| |
| std::unique_ptr<GrDrawOpAtlas> atlas = GrDrawOpAtlas::Make( |
| proxyProvider, |
| format, |
| GrColorTypeToSkColorType(atlasColorType), |
| GrColorTypeBytesPerPixel(atlasColorType), |
| kAtlasSize, kAtlasSize, |
| kAtlasSize/kNumPlots, kAtlasSize/kNumPlots, |
| &counter, |
| GrDrawOpAtlas::AllowMultitexturing::kYes, |
| &evictor, |
| /*label=*/"BasicDrawOpAtlasTest"); |
| check(reporter, atlas.get(), 0, 0); |
| |
| // Fill up the first level |
| skgpu::AtlasLocator atlasLocators[kNumPlots * kNumPlots]; |
| for (int i = 0; i < kNumPlots * kNumPlots; ++i) { |
| bool result = fill_plot( |
| atlas.get(), resourceProvider, &uploadTarget, &atlasLocators[i], i * 32); |
| REPORTER_ASSERT(reporter, result); |
| check(reporter, atlas.get(), 1, 1); |
| } |
| |
| atlas->instantiate(&onFlushResourceProvider); |
| check(reporter, atlas.get(), 1, 1); |
| |
| // Force allocation of a second level |
| skgpu::AtlasLocator atlasLocator; |
| bool result = fill_plot(atlas.get(), resourceProvider, &uploadTarget, &atlasLocator, 4 * 32); |
| REPORTER_ASSERT(reporter, result); |
| check(reporter, atlas.get(), 2, 2); |
| |
| // Simulate a lot of draws using only the first plot. The last texture should be compacted. |
| for (int i = 0; i < 512; ++i) { |
| atlas->setLastUseToken(atlasLocators[0], uploadTarget.tokenTracker()->nextDrawToken()); |
| uploadTarget.issueDrawToken(); |
| uploadTarget.issueFlushToken(); |
| atlas->compact(uploadTarget.tokenTracker()->nextFlushToken()); |
| } |
| |
| check(reporter, atlas.get(), 1, 1); |
| } |
| |
| // This test verifies that the AtlasTextOp::onPrepare method correctly handles a failure |
| // when allocating an atlas page. |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(GrAtlasTextOpPreparation, |
| reporter, |
| ctxInfo, |
| CtsEnforcement::kApiLevel_T) { |
| auto dContext = ctxInfo.directContext(); |
| |
| auto gpu = dContext->priv().getGpu(); |
| auto resourceProvider = dContext->priv().resourceProvider(); |
| |
| auto sdc = skgpu::ganesh::SurfaceDrawContext::Make(dContext, |
| GrColorType::kRGBA_8888, |
| nullptr, |
| SkBackingFit::kApprox, |
| {32, 32}, |
| SkSurfaceProps(), |
| /*label=*/"AtlasTextOpPreparation"); |
| |
| SkPaint paint; |
| paint.setColor(SK_ColorRED); |
| |
| SkFont font = ToolUtils::DefaultFont(); |
| font.setEdging(SkFont::Edging::kAlias); |
| |
| const char* text = "a"; |
| |
| GrOp::Owner op = |
| AtlasTextOpTools::CreateOp(sdc.get(), paint, font, SkMatrix::I(), text, 16, 16); |
| if (!op) { |
| return; |
| } |
| |
| auto atlasTextOp = (AtlasTextOp*)op.get(); |
| atlasTextOp->finalize(*dContext->priv().caps(), nullptr, GrClampType::kAuto); |
| |
| TestingUploadTarget uploadTarget; |
| |
| GrOpFlushState flushState(gpu, resourceProvider, uploadTarget.writeableTokenTracker()); |
| |
| GrSurfaceProxyView surfaceView = sdc->writeSurfaceView(); |
| GrOpFlushState::OpArgs opArgs(op.get(), |
| surfaceView, |
| false /*usesMSAASurface*/, |
| nullptr, |
| GrDstProxyView(), |
| GrXferBarrierFlags::kNone, |
| GrLoadOp::kLoad); |
| |
| // Modify the atlas manager so it can't allocate any pages. This will force a failure |
| // in the preparation of the text op |
| auto atlasManager = dContext->priv().getAtlasManager(); |
| unsigned int numProxies; |
| atlasManager->getViews(MaskFormat::kA8, &numProxies); |
| GrAtlasManagerTools::SetMaxPages(atlasManager, 0); |
| |
| flushState.setOpArgs(&opArgs); |
| op->prepare(&flushState); |
| flushState.setOpArgs(nullptr); |
| } |
| |
| namespace { |
| void test_atlas_config(skiatest::Reporter* reporter, int maxTextureSize, size_t maxBytes, |
| MaskFormat maskFormat, SkISize expectedDimensions, |
| SkISize expectedPlotDimensions) { |
| GrDrawOpAtlasConfig config(maxTextureSize, maxBytes); |
| REPORTER_ASSERT(reporter, config.atlasDimensions(maskFormat) == expectedDimensions); |
| REPORTER_ASSERT(reporter, config.plotDimensions(maskFormat) == expectedPlotDimensions); |
| } |
| } // anonymous namespace |
| |
| DEF_GANESH_TEST(GrDrawOpAtlasConfig_Basic, reporter, options, CtsEnforcement::kApiLevel_T) { |
| // 1/4 MB |
| test_atlas_config(reporter, 65536, 256 * 1024, MaskFormat::kARGB, |
| { 256, 256 }, { 256, 256 }); |
| test_atlas_config(reporter, 65536, 256 * 1024, MaskFormat::kA8, |
| { 512, 512 }, { 256, 256 }); |
| // 1/2 MB |
| test_atlas_config(reporter, 65536, 512 * 1024, MaskFormat::kARGB, |
| { 512, 256 }, { 256, 256 }); |
| test_atlas_config(reporter, 65536, 512 * 1024, MaskFormat::kA8, |
| { 1024, 512 }, { 256, 256 }); |
| // 1 MB |
| test_atlas_config(reporter, 65536, 1024 * 1024, MaskFormat::kARGB, |
| { 512, 512 }, { 256, 256 }); |
| test_atlas_config(reporter, 65536, 1024 * 1024, MaskFormat::kA8, |
| { 1024, 1024 }, { 256, 256 }); |
| // 2 MB |
| test_atlas_config(reporter, 65536, 2 * 1024 * 1024, MaskFormat::kARGB, |
| { 1024, 512 }, { 256, 256 }); |
| test_atlas_config(reporter, 65536, 2 * 1024 * 1024, MaskFormat::kA8, |
| { 2048, 1024 }, { 512, 256 }); |
| // 4 MB |
| test_atlas_config(reporter, 65536, 4 * 1024 * 1024, MaskFormat::kARGB, |
| { 1024, 1024 }, { 256, 256 }); |
| test_atlas_config(reporter, 65536, 4 * 1024 * 1024, MaskFormat::kA8, |
| { 2048, 2048 }, { 512, 512 }); |
| // 8 MB |
| test_atlas_config(reporter, 65536, 8 * 1024 * 1024, MaskFormat::kARGB, |
| { 2048, 1024 }, { 256, 256 }); |
| test_atlas_config(reporter, 65536, 8 * 1024 * 1024, MaskFormat::kA8, |
| { 2048, 2048 }, { 512, 512 }); |
| // 16 MB (should be same as 8 MB) |
| test_atlas_config(reporter, 65536, 16 * 1024 * 1024, MaskFormat::kARGB, |
| { 2048, 1024 }, { 256, 256 }); |
| test_atlas_config(reporter, 65536, 16 * 1024 * 1024, MaskFormat::kA8, |
| { 2048, 2048 }, { 512, 512 }); |
| |
| // 4MB, restricted texture size |
| test_atlas_config(reporter, 1024, 8 * 1024 * 1024, MaskFormat::kARGB, |
| { 1024, 1024 }, { 256, 256 }); |
| test_atlas_config(reporter, 1024, 8 * 1024 * 1024, MaskFormat::kA8, |
| { 1024, 1024 }, { 256, 256 }); |
| |
| // 3 MB (should be same as 2 MB) |
| test_atlas_config(reporter, 65536, 3 * 1024 * 1024, MaskFormat::kARGB, |
| { 1024, 512 }, { 256, 256 }); |
| test_atlas_config(reporter, 65536, 3 * 1024 * 1024, MaskFormat::kA8, |
| { 2048, 1024 }, { 512, 256 }); |
| |
| // minimum size |
| test_atlas_config(reporter, 65536, 0, MaskFormat::kARGB, |
| { 256, 256 }, { 256, 256 }); |
| test_atlas_config(reporter, 65536, 0, MaskFormat::kA8, |
| { 512, 512 }, { 256, 256 }); |
| } |