| /* | |
| * 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/SkPoint.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkSize.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/gpu/GrBackendSurface.h" | |
| #include "include/gpu/GrDirectContext.h" | |
| #include "include/private/GrTypesPriv.h" | |
| #include "src/core/SkIPoint16.h" | |
| #include "src/gpu/GrCaps.h" | |
| #include "src/gpu/GrDeferredUpload.h" | |
| #include "src/gpu/GrDirectContextPriv.h" | |
| #include "src/gpu/GrDrawOpAtlas.h" | |
| #include "src/gpu/GrDrawingManager.h" | |
| #include "src/gpu/GrMemoryPool.h" | |
| #include "src/gpu/GrOnFlushResourceProvider.h" | |
| #include "src/gpu/GrOpFlushState.h" | |
| #include "src/gpu/GrTextureProxy.h" | |
| #include "src/gpu/GrXferProcessor.h" | |
| #include "src/gpu/ops/AtlasTextOp.h" | |
| #include "src/gpu/ops/GrDrawOp.h" | |
| #include "src/gpu/ops/GrOp.h" | |
| #include "src/gpu/text/GrAtlasManager.h" | |
| #include "tests/Test.h" | |
| #include "tools/gpu/GrContextFactory.h" | |
| #include <memory> | |
| #include <utility> | |
| class GrResourceProvider; | |
| static const int kNumPlots = 2; | |
| static const int kPlotSize = 32; | |
| static const int kAtlasSize = kNumPlots * kPlotSize; | |
| int GrDrawOpAtlas::numAllocated_TestingOnly() const { | |
| int count = 0; | |
| for (uint32_t i = 0; i < this->maxPages(); ++i) { | |
| if (fViews[i].proxy()->isInstantiated()) { | |
| ++count; | |
| } | |
| } | |
| return count; | |
| } | |
| void GrAtlasManager::setMaxPages_TestingOnly(uint32_t maxPages) { | |
| for (int i = 0; i < kMaskFormatCount; i++) { | |
| if (fAtlases[i]) { | |
| fAtlases[i]->setMaxPages_TestingOnly(maxPages); | |
| } | |
| } | |
| } | |
| void GrDrawOpAtlas::setMaxPages_TestingOnly(uint32_t maxPages) { | |
| SkASSERT(!fNumActivePages); | |
| fMaxPages = maxPages; | |
| } | |
| class AssertOnEvict : public GrDrawOpAtlas::EvictionCallback { | |
| public: | |
| void evict(GrDrawOpAtlas::PlotLocator) override { | |
| SkASSERT(0); // The unit test shouldn't exercise this code path | |
| } | |
| }; | |
| static void check(skiatest::Reporter* r, GrDrawOpAtlas* atlas, | |
| uint32_t expectedActive, uint32_t expectedMax, int expectedAlloced) { | |
| REPORTER_ASSERT(r, expectedActive == atlas->numActivePages()); | |
| REPORTER_ASSERT(r, expectedMax == atlas->maxPages()); | |
| REPORTER_ASSERT(r, expectedAlloced == atlas->numAllocated_TestingOnly()); | |
| } | |
| class TestingUploadTarget : public GrDeferredUploadTarget { | |
| public: | |
| TestingUploadTarget() { } | |
| const GrTokenTracker* tokenTracker() final { return &fTokenTracker; } | |
| GrTokenTracker* writeableTokenTracker() { return &fTokenTracker; } | |
| GrDeferredUploadToken addInlineUpload(GrDeferredTextureUploadFn&&) final { | |
| SkASSERT(0); // this test shouldn't invoke this code path | |
| return fTokenTracker.nextDrawToken(); | |
| } | |
| GrDeferredUploadToken addASAPUpload(GrDeferredTextureUploadFn&& upload) final { | |
| return fTokenTracker.nextTokenToFlush(); | |
| } | |
| void issueDrawToken() { fTokenTracker.issueDrawToken(); } | |
| void flushToken() { fTokenTracker.flushToken(); } | |
| private: | |
| GrTokenTracker fTokenTracker; | |
| using INHERITED = GrDeferredUploadTarget; | |
| }; | |
| static bool fill_plot(GrDrawOpAtlas* atlas, | |
| GrResourceProvider* resourceProvider, | |
| GrDeferredUploadTarget* target, | |
| GrDrawOpAtlas::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; | |
| } | |
| // 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_GPUTEST_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; | |
| GrBackendFormat format = caps->getDefaultBackendFormat(GrColorType::kAlpha_8, | |
| GrRenderable::kNo); | |
| AssertOnEvict evictor; | |
| GrDrawOpAtlas::GenerationCounter counter; | |
| std::unique_ptr<GrDrawOpAtlas> atlas = GrDrawOpAtlas::Make( | |
| proxyProvider, | |
| format, | |
| GrColorType::kAlpha_8, | |
| kAtlasSize, kAtlasSize, | |
| kAtlasSize/kNumPlots, kAtlasSize/kNumPlots, | |
| &counter, | |
| GrDrawOpAtlas::AllowMultitexturing::kYes, | |
| &evictor); | |
| check(reporter, atlas.get(), 0, 4, 0); | |
| // Fill up the first level | |
| GrDrawOpAtlas::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, 4, 1); | |
| } | |
| atlas->instantiate(&onFlushResourceProvider); | |
| check(reporter, atlas.get(), 1, 4, 1); | |
| // Force allocation of a second level | |
| GrDrawOpAtlas::AtlasLocator atlasLocator; | |
| bool result = fill_plot(atlas.get(), resourceProvider, &uploadTarget, &atlasLocator, 4 * 32); | |
| REPORTER_ASSERT(reporter, result); | |
| check(reporter, atlas.get(), 2, 4, 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.flushToken(); | |
| atlas->compact(uploadTarget.tokenTracker()->nextTokenToFlush()); | |
| } | |
| check(reporter, atlas.get(), 1, 4, 1); | |
| } | |
| #if SK_GPU_V1 | |
| #include "src/gpu/v1/SurfaceDrawContext_v1.h" | |
| // This test verifies that the AtlasTextOp::onPrepare method correctly handles a failure | |
| // when allocating an atlas page. | |
| DEF_GPUTEST_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::v1::SurfaceDrawContext::Make(dContext, GrColorType::kRGBA_8888, nullptr, | |
| SkBackingFit::kApprox, {32, 32}, | |
| SkSurfaceProps()); | |
| SkPaint paint; | |
| paint.setColor(SK_ColorRED); | |
| SkFont font; | |
| font.setEdging(SkFont::Edging::kAlias); | |
| const char* text = "a"; | |
| SkMatrixProvider matrixProvider(SkMatrix::I()); | |
| GrOp::Owner op = skgpu::v1::AtlasTextOp::CreateOpTestingOnly(sdc.get(), paint, | |
| font, matrixProvider, | |
| text, 16, 16); | |
| if (!op) { | |
| return; | |
| } | |
| auto atlasTextOp = (skgpu::v1::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(kA8_GrMaskFormat, &numProxies); | |
| atlasManager->setMaxPages_TestingOnly(0); | |
| flushState.setOpArgs(&opArgs); | |
| op->prepare(&flushState); | |
| flushState.setOpArgs(nullptr); | |
| } | |
| #endif // SK_GPU_V1 | |
| void test_atlas_config(skiatest::Reporter* reporter, int maxTextureSize, size_t maxBytes, | |
| GrMaskFormat maskFormat, SkISize expectedDimensions, | |
| SkISize expectedPlotDimensions) { | |
| GrDrawOpAtlasConfig config(maxTextureSize, maxBytes); | |
| REPORTER_ASSERT(reporter, config.atlasDimensions(maskFormat) == expectedDimensions); | |
| REPORTER_ASSERT(reporter, config.plotDimensions(maskFormat) == expectedPlotDimensions); | |
| } | |
| DEF_GPUTEST(GrDrawOpAtlasConfig_Basic, reporter, options, CtsEnforcement::kApiLevel_T) { | |
| // 1/4 MB | |
| test_atlas_config(reporter, 65536, 256 * 1024, kARGB_GrMaskFormat, | |
| { 256, 256 }, { 256, 256 }); | |
| test_atlas_config(reporter, 65536, 256 * 1024, kA8_GrMaskFormat, | |
| { 512, 512 }, { 256, 256 }); | |
| // 1/2 MB | |
| test_atlas_config(reporter, 65536, 512 * 1024, kARGB_GrMaskFormat, | |
| { 512, 256 }, { 256, 256 }); | |
| test_atlas_config(reporter, 65536, 512 * 1024, kA8_GrMaskFormat, | |
| { 1024, 512 }, { 256, 256 }); | |
| // 1 MB | |
| test_atlas_config(reporter, 65536, 1024 * 1024, kARGB_GrMaskFormat, | |
| { 512, 512 }, { 256, 256 }); | |
| test_atlas_config(reporter, 65536, 1024 * 1024, kA8_GrMaskFormat, | |
| { 1024, 1024 }, { 256, 256 }); | |
| // 2 MB | |
| test_atlas_config(reporter, 65536, 2 * 1024 * 1024, kARGB_GrMaskFormat, | |
| { 1024, 512 }, { 256, 256 }); | |
| test_atlas_config(reporter, 65536, 2 * 1024 * 1024, kA8_GrMaskFormat, | |
| { 2048, 1024 }, { 512, 256 }); | |
| // 4 MB | |
| test_atlas_config(reporter, 65536, 4 * 1024 * 1024, kARGB_GrMaskFormat, | |
| { 1024, 1024 }, { 256, 256 }); | |
| test_atlas_config(reporter, 65536, 4 * 1024 * 1024, kA8_GrMaskFormat, | |
| { 2048, 2048 }, { 512, 512 }); | |
| // 8 MB | |
| test_atlas_config(reporter, 65536, 8 * 1024 * 1024, kARGB_GrMaskFormat, | |
| { 2048, 1024 }, { 256, 256 }); | |
| test_atlas_config(reporter, 65536, 8 * 1024 * 1024, kA8_GrMaskFormat, | |
| { 2048, 2048 }, { 512, 512 }); | |
| // 16 MB (should be same as 8 MB) | |
| test_atlas_config(reporter, 65536, 16 * 1024 * 1024, kARGB_GrMaskFormat, | |
| { 2048, 1024 }, { 256, 256 }); | |
| test_atlas_config(reporter, 65536, 16 * 1024 * 1024, kA8_GrMaskFormat, | |
| { 2048, 2048 }, { 512, 512 }); | |
| // 4MB, restricted texture size | |
| test_atlas_config(reporter, 1024, 8 * 1024 * 1024, kARGB_GrMaskFormat, | |
| { 1024, 1024 }, { 256, 256 }); | |
| test_atlas_config(reporter, 1024, 8 * 1024 * 1024, kA8_GrMaskFormat, | |
| { 1024, 1024 }, { 256, 256 }); | |
| // 3 MB (should be same as 2 MB) | |
| test_atlas_config(reporter, 65536, 3 * 1024 * 1024, kARGB_GrMaskFormat, | |
| { 1024, 512 }, { 256, 256 }); | |
| test_atlas_config(reporter, 65536, 3 * 1024 * 1024, kA8_GrMaskFormat, | |
| { 2048, 1024 }, { 512, 256 }); | |
| // minimum size | |
| test_atlas_config(reporter, 65536, 0, kARGB_GrMaskFormat, | |
| { 256, 256 }, { 256, 256 }); | |
| test_atlas_config(reporter, 65536, 0, kA8_GrMaskFormat, | |
| { 512, 512 }, { 256, 256 }); | |
| } |