Add support for pre-compiling cached SkSL shaders
The client can do a test run of their application with
a persistent cache set to SkSL mode. They store the key
and data blobs that are produced.
Ship those blobs with the application. At startup, call
GrContext::precompileShader for each key/data pair. This
compiles the shaders, and stores the GL program ID, plus
a small amount of metadata in our runtime program cache.
Caveats:
* Currently only implemented for the GL backend. Other
backends will require more metadata to do any useful
amount of work. Metal may need a more drastic workflow
change, involving offline compilation of the shaders.
* Currently only implemented for cached SkSL (not GLSL
or program binaries). Supporting other formats again
requires more metadata, and the cached shaders become
increasingly specialized to GPU and driver versions.
* Reusing the cached SkSL on different hardware is not
supported. Many driver workarounds are implemented in
the SkSL -> GLSL transformation, but some are higher
level. Limiting device variance by artificially hiding
extensions may help, but there are no guarantees.
* The 'gltestprecompile' DM config exercises this code
similarly to 'gltestpersistentcache', ensuring that
results are visually identical when precompiling, and
that no cache misses occur after precompiling.
Change-Id: Id314c5d5f5a58fe503a0505a613bd4a540cc3589
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/239438
Reviewed-by: Greg Daniel <egdaniel@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/src/gpu/gl/GrGLGpuProgramCache.cpp b/src/gpu/gl/GrGLGpuProgramCache.cpp
index 279dd03..d9efe08 100644
--- a/src/gpu/gl/GrGLGpuProgramCache.cpp
+++ b/src/gpu/gl/GrGLGpuProgramCache.cpp
@@ -15,9 +15,14 @@
#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
struct GrGLGpu::ProgramCache::Entry {
- Entry(sk_sp<GrGLProgram> program) : fProgram(std::move(program)) {}
+ Entry(sk_sp<GrGLProgram> program)
+ : fProgram(std::move(program)) {}
+
+ Entry(const GrGLPrecompiledProgram& precompiledProgram)
+ : fPrecompiledProgram(precompiledProgram) {}
sk_sp<GrGLProgram> fProgram;
+ GrGLPrecompiledProgram fPrecompiledProgram;
};
GrGLGpu::ProgramCache::ProgramCache(GrGLGpu* gpu)
@@ -28,7 +33,9 @@
void GrGLGpu::ProgramCache::abandon() {
fMap.foreach([](std::unique_ptr<Entry>* e) {
- (*e)->fProgram->abandon();
+ if ((*e)->fProgram) {
+ (*e)->fProgram->abandon();
+ }
});
this->reset();
@@ -56,7 +63,21 @@
desc.setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(origin));
std::unique_ptr<Entry>* entry = fMap.find(desc);
- if (!entry) {
+ if (entry && !(*entry)->fProgram) {
+ // We've pre-compiled the GL program, but don't have the GrGLProgram scaffolding
+ const GrGLPrecompiledProgram* precompiledProgram = &((*entry)->fPrecompiledProgram);
+ SkASSERT(precompiledProgram->fProgramID != 0);
+ GrGLProgram* program = GrGLProgramBuilder::CreateProgram(renderTarget, origin,
+ primProc, primProcProxies,
+ pipeline, &desc, fGpu,
+ precompiledProgram);
+ if (nullptr == program) {
+ // Should we purge the program ID from the cache at this point?
+ SkDEBUGFAIL("Couldn't create program from precompiled program");
+ return nullptr;
+ }
+ (*entry)->fProgram.reset(program);
+ } else if (!entry) {
// We have a cache miss
GrGLProgram* program = GrGLProgramBuilder::CreateProgram(renderTarget, origin,
primProc, primProcProxies,
@@ -69,3 +90,24 @@
return SkRef((*entry)->fProgram.get());
}
+
+bool GrGLGpu::ProgramCache::precompileShader(const SkData& key, const SkData& data) {
+ GrProgramDesc desc;
+ if (!GrProgramDesc::BuildFromData(&desc, key.data(), key.size())) {
+ return false;
+ }
+
+ std::unique_ptr<Entry>* entry = fMap.find(desc);
+ if (entry) {
+ // We've already seen/compiled this shader
+ return true;
+ }
+
+ GrGLPrecompiledProgram precompiledProgram;
+ if (!GrGLProgramBuilder::PrecompileProgram(&precompiledProgram, fGpu, data)) {
+ return false;
+ }
+
+ fMap.insert(desc, std::unique_ptr<Entry>(new Entry(precompiledProgram)));
+ return true;
+}