From 00a5eb8c12536f7843ccb137f94df88583813128 Mon Sep 17 00:00:00 2001 From: Brian Salomon Date: Wed, 11 Jul 2018 15:32:05 -0400 Subject: [PATCH] Add gltestpersistentcache config that tests GrContextOption's cache. Uses a new GPU sink that runs each test twice, once to populate the cache and then again with a new GrContext but a warmed cache. It verifies that the two generated images are the same. Change-Id: Iaba195a69751f14ea946afe7174228a813b83a63 Reviewed-on: https://skia-review.googlesource.com/140567 Commit-Queue: Brian Salomon Reviewed-by: Brian Osman --- BUILD.gn | 2 + dm/DM.cpp | 7 ++ dm/DMSrcSink.cpp | 54 +++++++++++- dm/DMSrcSink.h | 20 +++++ tools/flags/SkCommonFlagsConfig.cpp | 122 +++++++++++++++------------- tools/flags/SkCommonFlagsConfig.h | 4 +- tools/gpu/MemoryCache.cpp | 56 +++++++++++++ tools/gpu/MemoryCache.h | 62 ++++++++++++++ 8 files changed, 265 insertions(+), 62 deletions(-) create mode 100644 tools/gpu/MemoryCache.cpp create mode 100644 tools/gpu/MemoryCache.h diff --git a/BUILD.gn b/BUILD.gn index 11b1943a47..b7a5c04691 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1239,6 +1239,8 @@ if (skia_enable_tools) { sources = [ "tools/gpu/GrContextFactory.cpp", "tools/gpu/GrTest.cpp", + "tools/gpu/MemoryCache.cpp", + "tools/gpu/MemoryCache.h", "tools/gpu/ProxyUtils.cpp", "tools/gpu/TestContext.cpp", "tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp", diff --git a/dm/DM.cpp b/dm/DM.cpp index b8714d3fd9..5b3642ac84 100644 --- a/dm/DM.cpp +++ b/dm/DM.cpp @@ -862,11 +862,18 @@ static Sink* create_sink(const GrContextOptions& grCtxOptions, const SkCommandLi return nullptr; } if (gpuConfig->getTestThreading()) { + SkASSERT(!gpuConfig->getTestPersistentCache()); return new GPUThreadTestingSink( contextType, contextOverrides, gpuConfig->getSurfType(), gpuConfig->getSamples(), gpuConfig->getUseDIText(), gpuConfig->getColorType(), gpuConfig->getAlphaType(), sk_ref_sp(gpuConfig->getColorSpace()), FLAGS_gpu_threading, grCtxOptions); + } else if (gpuConfig->getTestPersistentCache()) { + return new GPUPersistentCacheTestingSink( + contextType, contextOverrides, gpuConfig->getSurfType(), + gpuConfig->getSamples(), gpuConfig->getUseDIText(), + gpuConfig->getColorType(), gpuConfig->getAlphaType(), + sk_ref_sp(gpuConfig->getColorSpace()), FLAGS_gpu_threading, grCtxOptions); } else { return new GPUSink(contextType, contextOverrides, gpuConfig->getSurfType(), gpuConfig->getSamples(), gpuConfig->getUseDIText(), diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp index fb67c52cdc..25b87b376e 100644 --- a/dm/DMSrcSink.cpp +++ b/dm/DMSrcSink.cpp @@ -11,6 +11,10 @@ #include "../src/jumper/SkJumper.h" #include "DDLPromiseImageHelper.h" #include "DDLTileHelper.h" +#include "GrBackendSurface.h" +#include "GrContextPriv.h" +#include "GrGpu.h" +#include "MemoryCache.h" #include "Resources.h" #include "SkAndroidCodec.h" #include "SkAutoMalloc.h" @@ -75,10 +79,6 @@ #include "../third_party/skcms/skcms.h" -#include "GrBackendSurface.h" -#include "GrContextPriv.h" -#include "GrGpu.h" - DEFINE_bool(multiPage, false, "For document-type backends, render the source" " into multiple pages"); DEFINE_bool(RAW_threading, true, "Allow RAW decodes to run on multiple threads?"); @@ -1486,7 +1486,12 @@ Error GPUSink::onDraw(const Src& src, SkBitmap* dst, SkWStream*, SkString* log, const GrContextOptions& baseOptions) const { GrContextOptions grOptions = baseOptions; + // We don't expect the src to mess with the persistent cache or the executor. + SkDEBUGCODE(auto cache = grOptions.fPersistentCache); + SkDEBUGCODE(auto exec = grOptions.fExecutor); src.modifyGrContextOptions(&grOptions); + SkASSERT(cache == grOptions.fPersistentCache); + SkASSERT(exec == grOptions.fExecutor); GrContextFactory factory(grOptions); const SkISize size = src.size(); @@ -1617,6 +1622,47 @@ Error GPUThreadTestingSink::draw(const Src& src, SkBitmap* dst, SkWStream* wStre /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +GPUPersistentCacheTestingSink::GPUPersistentCacheTestingSink( + GrContextFactory::ContextType ct, + GrContextFactory::ContextOverrides overrides, + SkCommandLineConfigGpu::SurfType surfType, + int samples, + bool diText, + SkColorType colorType, + SkAlphaType alphaType, + sk_sp colorSpace, + bool threaded, + const GrContextOptions& grCtxOptions) + : INHERITED(ct, overrides, surfType, samples, diText, colorType, alphaType, + std::move(colorSpace), threaded, grCtxOptions) {} + +Error GPUPersistentCacheTestingSink::draw(const Src& src, SkBitmap* dst, SkWStream* wStream, + SkString* log) const { + // Draw twice, once with a cold cache, and again with a warm cache. Verify that we get the same + // result. + sk_gpu_test::MemoryCache memoryCache; + GrContextOptions contextOptions = this->baseContextOptions(); + contextOptions.fPersistentCache = &memoryCache; + + Error err = this->onDraw(src, dst, wStream, log, contextOptions); + if (!err.isEmpty() || !dst) { + return err; + } + + SkBitmap reference; + SkString refLog; + SkDynamicMemoryWStream refStream; + memoryCache.resetNumCacheMisses(); + Error refErr = this->onDraw(src, &reference, &refStream, &refLog, contextOptions); + if (!refErr.isEmpty()) { + return refErr; + } + SkASSERT(memoryCache.numCacheMisses()); + + return compare_bitmaps(reference, *dst); +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ static Error draw_skdocument(const Src& src, SkDocument* doc, SkWStream* dst) { if (src.size().isEmpty()) { return "Source has empty dimensions"; diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h index 9c31c0ab22..86c2279439 100644 --- a/dm/DMSrcSink.h +++ b/dm/DMSrcSink.h @@ -386,6 +386,26 @@ private: typedef GPUSink INHERITED; }; +class GPUPersistentCacheTestingSink : public GPUSink { +public: + GPUPersistentCacheTestingSink(sk_gpu_test::GrContextFactory::ContextType, + sk_gpu_test::GrContextFactory::ContextOverrides, + SkCommandLineConfigGpu::SurfType surfType, int samples, + bool diText, SkColorType colorType, SkAlphaType alphaType, + sk_sp colorSpace, bool threaded, + const GrContextOptions& grCtxOptions); + + Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override; + + const char* fileExtension() const override { + // Suppress writing out results from this config - we just want to do our matching test + return nullptr; + } + +private: + typedef GPUSink INHERITED; +}; + class PDFSink : public Sink { public: PDFSink(bool pdfa, SkScalar rasterDpi) : fPDFA(pdfa), fRasterDpi(rasterDpi) {} diff --git a/tools/flags/SkCommonFlagsConfig.cpp b/tools/flags/SkCommonFlagsConfig.cpp index aff7a3f960..7aca8f5be1 100644 --- a/tools/flags/SkCommonFlagsConfig.cpp +++ b/tools/flags/SkCommonFlagsConfig.cpp @@ -29,6 +29,7 @@ static const char defaultConfigs[] = #undef DEFAULT_GPU_CONFIG +// clang-format off static const struct { const char* predefinedConfig; const char* backend; @@ -67,6 +68,7 @@ static const struct { { "gldft", "gpu", "api=gl,dit=true" }, { "glesdft", "gpu", "api=gles,dit=true" }, { "gltestthreading", "gpu", "api=gl,testThreading=true" }, + { "gltestpersistentcache", "gpu", "api=gl,testPersistentCache=true" }, { "debuggl", "gpu", "api=debuggl" }, { "nullgl", "gpu", "api=nullgl" }, { "angle_d3d11_es2", "gpu", "api=angle_d3d11_es2" }, @@ -103,6 +105,7 @@ static const struct { ,{ "mtlmsaa8", "gpu", "api=metal,samples=8" } #endif }; +// clang-format on static const char configHelp[] = "Options: 565 8888 srgb f16 nonrendering null pdf pdfa skp pipe svg xps"; @@ -118,63 +121,65 @@ static const char* config_help_fn() { } static const char configExtendedHelp[] = - "Extended form: 'backend(option=value,...)'\n\n" - "Possible backends and options:\n" - "\n" - "gpu[api=string,color=string,dit=bool,nvpr=bool,inst=bool,samples=int]\n" - "\tapi\ttype: string\trequired\n" - "\t Select graphics API to use with gpu backend.\n" - "\t Options:\n" - "\t\tgl \t\t\tUse OpenGL.\n" - "\t\tgles \t\t\tUse OpenGL ES.\n" - "\t\tdebuggl \t\tUse debug OpenGL.\n" - "\t\tnullgl \t\t\tUse null OpenGL.\n" - "\t\tangle_d3d9_es2\t\tUse OpenGL ES2 on the ANGLE Direct3D9 backend.\n" - "\t\tangle_d3d11_es2\t\tUse OpenGL ES2 on the ANGLE Direct3D11 backend.\n" - "\t\tangle_d3d11_es3\t\tUse OpenGL ES3 on the ANGLE Direct3D11 backend.\n" - "\t\tangle_gl_es2\t\tUse OpenGL ES2 on the ANGLE OpenGL backend.\n" - "\t\tangle_gl_es3\t\tUse OpenGL ES3 on the ANGLE OpenGL backend.\n" - "\t\tcommandbuffer\t\tUse command buffer.\n" - "\t\tmock\t\t\tUse mock context.\n" + "Extended form: 'backend(option=value,...)'\n\n" + "Possible backends and options:\n" + "\n" + "gpu[api=string,color=string,dit=bool,nvpr=bool,inst=bool,samples=int]\n" + "\tapi\ttype: string\trequired\n" + "\t Select graphics API to use with gpu backend.\n" + "\t Options:\n" + "\t\tgl \t\t\tUse OpenGL.\n" + "\t\tgles \t\t\tUse OpenGL ES.\n" + "\t\tdebuggl \t\tUse debug OpenGL.\n" + "\t\tnullgl \t\t\tUse null OpenGL.\n" + "\t\tangle_d3d9_es2\t\tUse OpenGL ES2 on the ANGLE Direct3D9 backend.\n" + "\t\tangle_d3d11_es2\t\tUse OpenGL ES2 on the ANGLE Direct3D11 backend.\n" + "\t\tangle_d3d11_es3\t\tUse OpenGL ES3 on the ANGLE Direct3D11 backend.\n" + "\t\tangle_gl_es2\t\tUse OpenGL ES2 on the ANGLE OpenGL backend.\n" + "\t\tangle_gl_es3\t\tUse OpenGL ES3 on the ANGLE OpenGL backend.\n" + "\t\tcommandbuffer\t\tUse command buffer.\n" + "\t\tmock\t\t\tUse mock context.\n" #ifdef SK_VULKAN - "\t\tvulkan\t\t\tUse Vulkan.\n" + "\t\tvulkan\t\t\tUse Vulkan.\n" #endif #ifdef SK_METAL - "\t\tmetal\t\t\tUse Metal.\n" + "\t\tmetal\t\t\tUse Metal.\n" #endif - "\tcolor\ttype: string\tdefault: 8888.\n" - "\t Select framebuffer color format.\n" - "\t Options:\n" - "\t\t8888\t\t\tLinear 8888.\n" - "\t\t888x\t\t\tLinear 888x.\n" - "\t\t4444\t\t\tLinear 4444.\n" - "\t\t565\t\t\tLinear 565.\n" - "\t\t1010102\t\t\tLinear 1010102.\n" - "\t\tsrgb\t\t\tsRGB 8888.\n" - "\t\tesrgb\t\t\tsRGB 16-bit floating point.\n" - "\t\tnarrow\t\t\tNarrow gamut 8888.\n" - "\t\tenarrow\t\t\tNarrow gamut 16-bit floating point.\n" - "\t\tf16\t\t\tLinearly blended 16-bit floating point.\n" - "\tdit\ttype: bool\tdefault: false.\n" - "\t Use device independent text.\n" - "\tnvpr\ttype: bool\tdefault: false.\n" - "\t Use NV_path_rendering OpenGL and OpenGL ES extension.\n" - "\tsamples\ttype: int\tdefault: 0.\n" - "\t Use multisampling with N samples.\n" - "\tstencils\ttype: bool\tdefault: true.\n" - "\t Allow the use of stencil buffers.\n" - "\ttestThreading\ttype: bool\tdefault: false.\n" - "\t Run with and without worker threads, check that results match.\n" - "\tsurf\ttype: string\tdefault: default.\n" - "\t Controls the type of backing store for SkSurfaces.\n" - "\t Options:\n" - "\t\tdefault\t\t\tA renderable texture created in Skia's resource cache.\n" - "\t\tbetex\t\t\tA wrapped backend texture.\n" - "\t\tbert\t\t\tA wrapped backend render target\n" - "\n" - "Predefined configs:\n\n" - // Help text for pre-defined configs is auto-generated from gPredefinedConfigs - ; + "\tcolor\ttype: string\tdefault: 8888.\n" + "\t Select framebuffer color format.\n" + "\t Options:\n" + "\t\t8888\t\t\tLinear 8888.\n" + "\t\t888x\t\t\tLinear 888x.\n" + "\t\t4444\t\t\tLinear 4444.\n" + "\t\t565\t\t\tLinear 565.\n" + "\t\t1010102\t\t\tLinear 1010102.\n" + "\t\tsrgb\t\t\tsRGB 8888.\n" + "\t\tesrgb\t\t\tsRGB 16-bit floating point.\n" + "\t\tnarrow\t\t\tNarrow gamut 8888.\n" + "\t\tenarrow\t\t\tNarrow gamut 16-bit floating point.\n" + "\t\tf16\t\t\tLinearly blended 16-bit floating point.\n" + "\tdit\ttype: bool\tdefault: false.\n" + "\t Use device independent text.\n" + "\tnvpr\ttype: bool\tdefault: false.\n" + "\t Use NV_path_rendering OpenGL and OpenGL ES extension.\n" + "\tsamples\ttype: int\tdefault: 0.\n" + "\t Use multisampling with N samples.\n" + "\tstencils\ttype: bool\tdefault: true.\n" + "\t Allow the use of stencil buffers.\n" + "\ttestThreading\ttype: bool\tdefault: false.\n" + "\t Run with and without worker threads, check that results match.\n" + "\ttestPersistentCache\ttype: bool\tdefault: false.\n" + "\t Run using a pre-warmed GrContextOption::fPersistentCache.\n" + "\tsurf\ttype: string\tdefault: default.\n" + "\t Controls the type of backing store for SkSurfaces.\n" + "\t Options:\n" + "\t\tdefault\t\t\tA renderable texture created in Skia's resource cache.\n" + "\t\tbetex\t\t\tA wrapped backend texture.\n" + "\t\tbert\t\t\tA wrapped backend render target\n" + "\n" + "Predefined configs:\n\n" + // Help text for pre-defined configs is auto-generated from gPredefinedConfigs + ; static const char* config_extended_help_fn() { static SkString helpString; @@ -423,7 +428,7 @@ SkCommandLineConfigGpu::SkCommandLineConfigGpu( const SkString& tag, const SkTArray& viaParts, ContextType contextType, bool useNVPR, bool useDIText, int samples, SkColorType colorType, SkAlphaType alphaType, sk_sp colorSpace, bool useStencilBuffers, bool testThreading, - SurfType surfType) + bool testPersistentCache, SurfType surfType) : SkCommandLineConfig(tag, SkString("gpu"), viaParts) , fContextType(contextType) , fContextOverrides(ContextOverrides::kNone) @@ -433,6 +438,7 @@ SkCommandLineConfigGpu::SkCommandLineConfigGpu( , fAlphaType(alphaType) , fColorSpace(std::move(colorSpace)) , fTestThreading(testThreading) + , fTestPersistentCache(testPersistentCache) , fSurfType(surfType) { if (useNVPR) { fContextOverrides |= ContextOverrides::kRequireNVPRSupport; @@ -459,6 +465,7 @@ SkCommandLineConfigGpu* parse_command_line_config_gpu(const SkString& tag, sk_sp colorSpace = nullptr; bool useStencils = true; bool testThreading = false; + bool testPersistentCache = false; SkCommandLineConfigGpu::SurfType surfType = SkCommandLineConfigGpu::SurfType::kDefault; bool parseSucceeded = false; @@ -475,16 +482,17 @@ SkCommandLineConfigGpu* parse_command_line_config_gpu(const SkString& tag, extendedOptions.get_option_gpu_color("color", &colorType, &alphaType, &colorSpace) && extendedOptions.get_option_bool("stencils", &useStencils) && extendedOptions.get_option_bool("testThreading", &testThreading) && - extendedOptions.get_option_bool("testThreading", &testThreading) && + extendedOptions.get_option_bool("testPersistentCache", &testPersistentCache) && extendedOptions.get_option_gpu_surf_type("surf", &surfType); - if (!validOptions) { + // testing threading and the persistent cache are mutually exclusive. + if (!validOptions || (testThreading && testPersistentCache)) { return nullptr; } return new SkCommandLineConfigGpu(tag, vias, contextType, useNVPR, useDIText, samples, colorType, alphaType, colorSpace, useStencils, testThreading, - surfType); + testPersistentCache, surfType); } SkCommandLineConfigSvg::SkCommandLineConfigSvg(const SkString& tag, diff --git a/tools/flags/SkCommonFlagsConfig.h b/tools/flags/SkCommonFlagsConfig.h index 7c097ea888..fa11cc622d 100644 --- a/tools/flags/SkCommonFlagsConfig.h +++ b/tools/flags/SkCommonFlagsConfig.h @@ -57,7 +57,7 @@ public: ContextType contextType, bool useNVPR, bool useDIText, int samples, SkColorType colorType, SkAlphaType alphaType, sk_sp colorSpace, bool useStencilBuffers, - bool testThreading, SurfType); + bool testThreading, bool testPersistentCache, SurfType); const SkCommandLineConfigGpu* asConfigGpu() const override { return this; } ContextType getContextType() const { return fContextType; } @@ -73,6 +73,7 @@ public: SkAlphaType getAlphaType() const { return fAlphaType; } SkColorSpace* getColorSpace() const { return fColorSpace.get(); } bool getTestThreading() const { return fTestThreading; } + bool getTestPersistentCache() const { return fTestPersistentCache; } SurfType getSurfType() const { return fSurfType; } private: @@ -84,6 +85,7 @@ private: SkAlphaType fAlphaType; sk_sp fColorSpace; bool fTestThreading; + bool fTestPersistentCache; SurfType fSurfType; }; diff --git a/tools/gpu/MemoryCache.cpp b/tools/gpu/MemoryCache.cpp new file mode 100644 index 0000000000..7fc3e1b5d0 --- /dev/null +++ b/tools/gpu/MemoryCache.cpp @@ -0,0 +1,56 @@ +/* + * 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 "MemoryCache.h" +#include "SkBase64.h" + +// Change this to 1 to log cache hits/misses/stores using SkDebugf. +#define LOG_MEMORY_CACHE 0 + +static SkString data_to_str(const SkData& data) { + size_t encodeLength = SkBase64::Encode(data.data(), data.size(), nullptr); + SkString str; + str.resize(encodeLength); + SkBase64::Encode(data.data(), data.size(), str.writable_str()); + static constexpr size_t kMaxLength = 60; + static constexpr char kTail[] = "..."; + static const size_t kTailLen = strlen(kTail); + bool overlength = encodeLength > kMaxLength; + if (overlength) { + str = SkString(str.c_str(), kMaxLength - kTailLen); + str.append(kTail); + } + return str; +} + +namespace sk_gpu_test { + +sk_sp MemoryCache::load(const SkData& key) { + auto result = fMap.find(key); + if (result == fMap.end()) { + if (LOG_MEMORY_CACHE) { + SkDebugf("Load Key: %s\n\tNot Found.\n\n", data_to_str(key).c_str()); + } + ++fCacheMissCnt; + return nullptr; + } + if (LOG_MEMORY_CACHE) { + SkDebugf("Load Key: %s\n\tFound Data: %s\n\n", data_to_str(key).c_str(), + data_to_str(*result->second).c_str()); + } + return result->second; +} + +void MemoryCache::store(const SkData& key, const SkData& data) { + if (LOG_MEMORY_CACHE) { + SkDebugf("Store Key: %s\n\tData: %s\n\n", data_to_str(key).c_str(), + data_to_str(data).c_str()); + } + fMap[Key(key)] = SkData::MakeWithCopy(data.data(), data.size()); +} + +} // namespace sk_gpu_test diff --git a/tools/gpu/MemoryCache.h b/tools/gpu/MemoryCache.h new file mode 100644 index 0000000000..568f755ac5 --- /dev/null +++ b/tools/gpu/MemoryCache.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef MemoryCache_DEFINED +#define MemoryCache_DEFINED + +#include +#include "GrContextOptions.h" +#include "SkChecksum.h" +#include "SkData.h" + +namespace sk_gpu_test { + +/** + * This class can be used to maintain an in memory record of all programs cached by GrContext. + * It can be shared by multiple GrContexts so long as those GrContexts are created with the same + * options and will have the same GrCaps (e.g. same backend, same GL context creation parameters, + * ...). + */ +class MemoryCache : public GrContextOptions::PersistentCache { +public: + MemoryCache() = default; + MemoryCache(const MemoryCache&) = delete; + MemoryCache& operator=(const MemoryCache&) = delete; + + sk_sp load(const SkData& key) override; + void store(const SkData& key, const SkData& data) override; + int numCacheMisses() const { return fCacheMissCnt; } + void resetNumCacheMisses() { fCacheMissCnt = 0; } + +private: + struct Key { + Key() = default; + Key(const SkData& key) : fKey(SkData::MakeWithCopy(key.data(), key.size())) {} + Key(const Key& that) = default; + Key& operator=(const Key&) = default; + bool operator==(const Key& that) const { + return that.fKey->size() == fKey->size() && + !memcmp(fKey->data(), that.fKey->data(), that.fKey->size()); + } + sk_sp fKey; + }; + + struct Hash { + using argument_type = Key; + using result_type = uint32_t; + uint32_t operator()(const Key& key) const { + return key.fKey ? SkOpts::hash_fn(key.fKey->data(), key.fKey->size(), 0) : 0; + } + }; + + int fCacheMissCnt = 0; + std::unordered_map, Hash> fMap; +}; + +} // namespace sk_gpu_test + +#endif