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;
+}