[graphite] Add PrecompileContext::precompile option

This connects all the innards of the Android-style precompilation system.

Bug: b/390186667
Change-Id: I3e37a0f1cf0fbe1cab8d6cc25984631314aa498e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/946057
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index bc04209..67ccb70 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -2302,9 +2302,9 @@
 
     SkASSERT(globalCache->numGraphicsPipelines() == 0);
 
-#if 0
+#if 1
     for (sk_sp<SkData>& d : androidStyleKeys) {
-        bool result = precompileContext->androidSpecificPrecompile(d);
+        bool result = precompileContext->precompile(d);
         SkAssertResult(result);
     }
 #else
diff --git a/include/gpu/graphite/PrecompileContext.h b/include/gpu/graphite/PrecompileContext.h
index 2ba475c..acff0ee 100644
--- a/include/gpu/graphite/PrecompileContext.h
+++ b/include/gpu/graphite/PrecompileContext.h
@@ -14,6 +14,8 @@
 #include <chrono>
 #include <memory>
 
+class SkData;
+
 namespace skgpu::graphite {
 
 class SharedContext;
@@ -32,6 +34,15 @@
      */
     void purgePipelinesNotUsedInMs(std::chrono::milliseconds msNotUsed);
 
+    /**
+     * Precompile one specific Pipeline that has been previously serialized. Serialized pipeline
+     * keys can be acquired via the ContextOptions::PipelineCallback.
+     *
+     * @param serializedPipelineKey   serialized Pipeline key.
+     * @return                        true if a Pipeline was created from the key; false otherwise
+     */
+    bool precompile(sk_sp<SkData> serializedPipelineKey);
+
     // Provides access to functions that aren't part of the public API.
     PrecompileContextPriv priv();
     const PrecompileContextPriv priv() const;  // NOLINT(readability-const-return-type)
diff --git a/relnotes/serializedkeyprecompilation.md b/relnotes/serializedkeyprecompilation.md
new file mode 100644
index 0000000..9627484
--- /dev/null
+++ b/relnotes/serializedkeyprecompilation.md
@@ -0,0 +1 @@
+The `PrecompileContext` now allows clients to precompile previously serialized Pipelines via the `PrecompileContext::precompile` entry point. Serialized keys can be obtained by implementing a `ContextOptions::PipelineCallback` handler. 
diff --git a/src/gpu/graphite/BUILD.bazel b/src/gpu/graphite/BUILD.bazel
index a590482..007c395 100644
--- a/src/gpu/graphite/BUILD.bazel
+++ b/src/gpu/graphite/BUILD.bazel
@@ -160,6 +160,8 @@
     "PrecompileInternal.h",
     "PublicPrecompile.cpp",
     "AndroidSpecificPrecompile.h",
+    "SerializationUtils.cpp",
+    "SerializationUtils.h",
 ]
 
 split_srcs_and_hdrs(
diff --git a/src/gpu/graphite/GlobalCache.cpp b/src/gpu/graphite/GlobalCache.cpp
index 63115b4..6e05ec4 100644
--- a/src/gpu/graphite/GlobalCache.cpp
+++ b/src/gpu/graphite/GlobalCache.cpp
@@ -12,7 +12,14 @@
 #include "src/gpu/graphite/ComputePipeline.h"
 #include "src/gpu/graphite/ContextUtils.h"
 #include "src/gpu/graphite/GraphicsPipeline.h"
+#include "src/gpu/graphite/GraphicsPipelineDesc.h"
+#include "src/gpu/graphite/RenderPassDesc.h"
 #include "src/gpu/graphite/Resource.h"
+#include "src/gpu/graphite/SharedContext.h"
+
+#if defined(SK_ENABLE_PRECOMPILE)
+#include "src/gpu/graphite/precompile/SerializationUtils.h"
+#endif
 
 namespace {
 
@@ -52,12 +59,39 @@
 }
 
 void GlobalCache::setPipelineCallback(PipelineCallback callback, PipelineCallbackContext context) {
-    SkAutoSpinlock lock{ fSpinLock };
+    SkAutoSpinlock lock{fSpinLock};
 
     fPipelineCallback = callback;
     fPipelineCallbackContext = context;
 }
 
+void GlobalCache::invokePipelineCallback(SharedContext* sharedContext,
+                                         const GraphicsPipelineDesc& pipelineDesc,
+                                         const RenderPassDesc& renderPassDesc) {
+#if defined(SK_ENABLE_PRECOMPILE)
+    PipelineCallback tmpCB = nullptr;
+    PipelineCallbackContext tmpContext = nullptr;
+
+    {
+        // We want to get a consistent callback/context pair but not invoke the callback
+        // w/in our lock.
+        SkAutoSpinlock lock{fSpinLock};
+
+        tmpCB = fPipelineCallback;
+        tmpContext = fPipelineCallbackContext;
+    }
+
+    if (tmpCB) {
+        sk_sp<SkData> data = PipelineDescToData(sharedContext->shaderCodeDictionary(),
+                                                pipelineDesc,
+                                                renderPassDesc);
+        if (data) {
+            tmpCB(tmpContext, std::move(data));
+        }
+    }
+#endif
+}
+
 void GlobalCache::deleteResources() {
     SkAutoSpinlock lock{ fSpinLock };
 
diff --git a/src/gpu/graphite/GlobalCache.h b/src/gpu/graphite/GlobalCache.h
index a4aa3353..e1a97b2 100644
--- a/src/gpu/graphite/GlobalCache.h
+++ b/src/gpu/graphite/GlobalCache.h
@@ -91,6 +91,9 @@
     using PipelineCallback = void (*)(PipelineCallbackContext context, sk_sp<SkData> pipelineData);
     void setPipelineCallback(PipelineCallback, PipelineCallbackContext) SK_EXCLUDES(fSpinLock);
 
+    void invokePipelineCallback(SharedContext*,
+                                const GraphicsPipelineDesc&,
+                                const RenderPassDesc&);
 private:
     struct KeyHash {
         uint32_t operator()(const UniqueKey& key) const { return key.hash(); }
diff --git a/src/gpu/graphite/PrecompileContext.cpp b/src/gpu/graphite/PrecompileContext.cpp
index 6b5d1c1..1e2d840 100644
--- a/src/gpu/graphite/PrecompileContext.cpp
+++ b/src/gpu/graphite/PrecompileContext.cpp
@@ -7,9 +7,17 @@
 
 #include "include/gpu/graphite/PrecompileContext.h"
 
+#include "src/gpu/graphite/GraphicsPipelineDesc.h"
+#include "src/gpu/graphite/Log.h"
+#include "src/gpu/graphite/RenderPassDesc.h"
 #include "src/gpu/graphite/ResourceProvider.h"
+#include "src/gpu/graphite/RuntimeEffectDictionary.h"
 #include "src/gpu/graphite/SharedContext.h"
 
+#if defined(SK_ENABLE_PRECOMPILE)
+#include "src/gpu/graphite/precompile/SerializationUtils.h"
+#endif
+
 namespace skgpu::graphite {
 
 #define ASSERT_SINGLE_OWNER SKGPU_ASSERT_SINGLE_OWNER(&fSingleOwner)
@@ -37,4 +45,35 @@
 }
 
 
+bool PrecompileContext::precompile(sk_sp<SkData> serializedPipelineKey) {
+#if defined(SK_ENABLE_PRECOMPILE)
+    auto rtEffectDict = std::make_unique<RuntimeEffectDictionary>();
+
+    GraphicsPipelineDesc pipelineDesc;
+    RenderPassDesc renderPassDesc;
+
+    if (!DataToPipelineDesc(fSharedContext->caps(),
+                            fSharedContext->shaderCodeDictionary(),
+                            serializedPipelineKey.get(),
+                            &pipelineDesc,
+                            &renderPassDesc)) {
+        return false;
+    }
+
+    sk_sp<GraphicsPipeline> pipeline = fResourceProvider->findOrCreateGraphicsPipeline(
+            rtEffectDict.get(),
+            pipelineDesc,
+            renderPassDesc,
+            PipelineCreationFlags::kForPrecompilation);
+    if (!pipeline) {
+        SKGPU_LOG_W("Failed to create GraphicsPipeline in precompile!");
+        return false;
+    }
+
+    return true;
+#else
+    return false;
+#endif
+}
+
 } // namespace skgpu::graphite
diff --git a/src/gpu/graphite/ResourceProvider.cpp b/src/gpu/graphite/ResourceProvider.cpp
index 627fee7..a194262 100644
--- a/src/gpu/graphite/ResourceProvider.cpp
+++ b/src/gpu/graphite/ResourceProvider.cpp
@@ -94,6 +94,7 @@
                                                 pipelineCreationFlags,
                                                 compilationID);
         if (pipeline) {
+            globalCache->invokePipelineCallback(fSharedContext, pipelineDesc, renderPassDesc);
             // TODO: Should we store a null pipeline if we failed to create one so that subsequent
             // usage immediately sees that the pipeline cannot be created, vs. retrying every time?
             pipeline = globalCache->addGraphicsPipeline(pipelineKey, std::move(pipeline));
diff --git a/src/gpu/graphite/precompile/SerializationUtils.cpp b/src/gpu/graphite/precompile/SerializationUtils.cpp
index a6b1bb6..5be46e6 100644
--- a/src/gpu/graphite/precompile/SerializationUtils.cpp
+++ b/src/gpu/graphite/precompile/SerializationUtils.cpp
@@ -230,8 +230,6 @@
     return true;
 }
 
-} // anonymous namespace
-
 #define SK_BLOB_END_TAG SkSetFourByteTag('e', 'n', 'd', ' ')
 
 bool SerializePipelineDesc(ShaderCodeDictionary* shaderCodeDictionary,
@@ -285,4 +283,39 @@
     return true;
 }
 
+} // anonymous namespace
+
+sk_sp<SkData> PipelineDescToData(ShaderCodeDictionary* shaderCodeDictionary,
+                                 const GraphicsPipelineDesc& pipelineDesc,
+                                 const RenderPassDesc& renderPassDesc) {
+    SkDynamicMemoryWStream stream;
+
+    if (!SerializePipelineDesc(shaderCodeDictionary,
+                               &stream,
+                               pipelineDesc, renderPassDesc)) {
+        return nullptr;
+    }
+
+    return stream.detachAsData();
+}
+
+bool DataToPipelineDesc(const Caps* caps,
+                        ShaderCodeDictionary* shaderCodeDictionary,
+                        const SkData* data,
+                        GraphicsPipelineDesc* pipelineDesc,
+                        RenderPassDesc* renderPassDesc) {
+    if (!data) {
+        return false;
+    }
+    SkMemoryStream stream(data->data(), data->size());
+
+    if (!DeserializePipelineDesc(caps, shaderCodeDictionary, &stream,
+                                 pipelineDesc,
+                                 renderPassDesc)) {
+        return false;
+    }
+
+    return true;
+}
+
 } // namespace skgpu::graphite
diff --git a/src/gpu/graphite/precompile/SerializationUtils.h b/src/gpu/graphite/precompile/SerializationUtils.h
index 2e9ebbb..65ce616 100644
--- a/src/gpu/graphite/precompile/SerializationUtils.h
+++ b/src/gpu/graphite/precompile/SerializationUtils.h
@@ -8,30 +8,28 @@
 #ifndef skgpu_graphite_precompile_SerializationUtils_DEFINED
 #define skgpu_graphite_precompile_SerializationUtils_DEFINED
 
-#include "include/core/SkTypes.h"
+#include "include/core/SkRefCnt.h"
 
-class SkStream;
-class SkWStream;
+class SkData;
 
 namespace skgpu::graphite {
 
 class Caps;
 class GraphicsPipelineDesc;
-class RendererProvider;
 struct RenderPassDesc;
 class ShaderCodeDictionary;
 
 // These are the top-level entry points to serialize Pipeline data for the Android-style
 // Precompilation API
-[[nodiscard]] bool SerializePipelineDesc(ShaderCodeDictionary*,
-                                         SkWStream*,
-                                         const GraphicsPipelineDesc&,
-                                         const RenderPassDesc&);
-[[nodiscard]] bool DeserializePipelineDesc(const Caps*,
-                                           ShaderCodeDictionary*,
-                                           SkStream*,
-                                           GraphicsPipelineDesc*,
-                                           RenderPassDesc*);
+[[nodiscard]] sk_sp<SkData> PipelineDescToData(ShaderCodeDictionary*,
+                                               const GraphicsPipelineDesc&,
+                                               const RenderPassDesc&);
+
+[[nodiscard]] bool DataToPipelineDesc(const Caps*,
+                                      ShaderCodeDictionary*,
+                                      const SkData*,
+                                      GraphicsPipelineDesc* pipelineDesc,
+                                      RenderPassDesc* renderPassDesc);
 
 } // skgpu::graphite