GLSL editing in Viewer

When in GL backend, adds a "Shaders" section to the debug menu.
"Load" scrapes all of the vertex and fragment shaders being used,
then displays them. They can be edited, and "Save" pushes the
results.

Note: It is trivial to trigger an assert by saving a shader that
doesn't compile. I'd like to make the program builder more robust
in a follow-up CL, to fall back to the "real" SkSL, not draw, or
something along those lines.

Change-Id: I841fe2ee76a3c2eae58b64ef587fcbe25b95cc7e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/206905
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index b6d6609..9cfd338 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -149,7 +149,7 @@
 
     const GrGLContext* glContextForTesting() const override { return &this->glContext(); }
 
-    void resetShaderCacheForTesting() const override { fProgramCache->abandon(); }
+    void resetShaderCacheForTesting() const override { fProgramCache->reset(); }
 
     void testingOnly_flushGpuAndSync() override;
 #endif
@@ -314,6 +314,7 @@
         ~ProgramCache();
 
         void abandon();
+        void reset();
         GrGLProgram* refProgram(GrGLGpu*, GrRenderTarget*, GrSurfaceOrigin,
                                 const GrPrimitiveProcessor&,
                                 const GrTextureProxy* const primProcProxies[],
diff --git a/src/gpu/gl/GrGLGpuProgramCache.cpp b/src/gpu/gl/GrGLGpuProgramCache.cpp
index 2cb5726..4a8f150 100644
--- a/src/gpu/gl/GrGLGpuProgramCache.cpp
+++ b/src/gpu/gl/GrGLGpuProgramCache.cpp
@@ -56,15 +56,20 @@
 }
 
 void GrGLGpu::ProgramCache::abandon() {
+    fMap.foreach([](std::unique_ptr<Entry>* e) {
+        (*e)->fProgram->abandon();
+    });
+
+    this->reset();
+}
+
+void GrGLGpu::ProgramCache::reset() {
 #ifdef PROGRAM_CACHE_STATS
     fTotalRequests = 0;
     fCacheMisses = 0;
     fHashMisses = 0;
 #endif
 
-    fMap.foreach([](std::unique_ptr<Entry>* e) {
-        (*e)->fProgram->abandon();
-    });
     fMap.reset();
 }
 
diff --git a/third_party/imgui/BUILD.gn b/third_party/imgui/BUILD.gn
index 8dd3a43..94ce3bf 100644
--- a/third_party/imgui/BUILD.gn
+++ b/third_party/imgui/BUILD.gn
@@ -16,5 +16,6 @@
     "../externals/imgui/imgui_demo.cpp",
     "../externals/imgui/imgui_draw.cpp",
     "../externals/imgui/imgui_widgets.cpp",
+    "../externals/imgui/misc/cpp/imgui_stdlib.cpp",
   ]
 }
diff --git a/tools/gpu/MemoryCache.h b/tools/gpu/MemoryCache.h
index 119ea8d..1e61994 100644
--- a/tools/gpu/MemoryCache.h
+++ b/tools/gpu/MemoryCache.h
@@ -27,6 +27,10 @@
     MemoryCache() = default;
     MemoryCache(const MemoryCache&) = delete;
     MemoryCache& operator=(const MemoryCache&) = delete;
+    void reset() {
+        fCacheMissCnt = 0;
+        fMap.clear();
+    }
 
     sk_sp<SkData> load(const SkData& key) override;
     void store(const SkData& key, const SkData& data) override;
@@ -35,6 +39,13 @@
 
     void writeShadersToDisk(const char* path, GrBackendApi backend);
 
+    template <typename Fn>
+    void foreach(Fn&& fn) {
+        for (auto it = fMap.begin(); it != fMap.end(); ++it) {
+            fn(it->first.fKey, it->second.fData, it->second.fHitCount);
+        }
+    }
+
 private:
     struct Key {
         Key() = default;
diff --git a/tools/viewer/Viewer.cpp b/tools/viewer/Viewer.cpp
index 8c5c7d1..3e20c0f 100644
--- a/tools/viewer/Viewer.cpp
+++ b/tools/viewer/Viewer.cpp
@@ -13,6 +13,8 @@
 #include "GMSlide.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
+#include "GrGpu.h"
+#include "GrPersistentCacheUtils.h"
 #include "ImageSlide.h"
 #include "ParticlesSlide.h"
 #include "Resources.h"
@@ -20,10 +22,12 @@
 #include "SampleSlide.h"
 #include "SkCanvas.h"
 #include "SkColorSpacePriv.h"
+#include "SkData.h"
 #include "SkGraphics.h"
 #include "SkImagePriv.h"
 #include "SkJSONWriter.h"
 #include "SkMakeUnique.h"
+#include "SkMD5.h"
 #include "SkOSFile.h"
 #include "SkOSPath.h"
 #include "SkPaintFilterCanvas.h"
@@ -42,6 +46,7 @@
 #include <map>
 
 #include "imgui.h"
+#include "misc/cpp/imgui_stdlib.h"  // For ImGui support of std::string
 
 #if defined(SK_ENABLE_SKOTTIE)
     #include "SkottieSlide.h"
@@ -274,6 +279,8 @@
     DisplayParams displayParams;
     displayParams.fMSAASampleCount = FLAGS_msaa;
     SetCtxOptionsFromCommonFlags(&displayParams.fGrContextOptions);
+    displayParams.fGrContextOptions.fPersistentCache = &fPersistentCache;
+    displayParams.fGrContextOptions.fDisallowGLSLBinaryCaching = true;
     fWindow->setRequestedDisplayParams(displayParams);
 
     // Configure timers
@@ -1482,6 +1489,8 @@
         ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
         DisplayParams params = fWindow->getRequestedDisplayParams();
         bool paramsChanged = false;
+        const GrContext* ctx = fWindow->getGrContext();
+
         if (ImGui::Begin("Tools", &fShowImGuiDebugWindow,
                          ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
             if (ImGui::CollapsingHeader("Backend")) {
@@ -1507,7 +1516,6 @@
                     });
                 }
 
-                const GrContext* ctx = fWindow->getGrContext();
                 bool* wire = &params.fGrContextOptions.fWireframeMode;
                 if (ctx && ImGui::Checkbox("Wireframe Mode", wire)) {
                     paramsChanged = true;
@@ -1940,6 +1948,85 @@
                     fAnimTimer.setSpeed(speed);
                 }
             }
+
+            if (Window::kNativeGL_BackendType == fBackendType &&
+                ImGui::CollapsingHeader("Shaders")) {
+                // To re-load shaders from the currently active programs, we flush all caches on one
+                // frame, then set a flag to poll the cache on the next frame.
+                static bool gLoadPending = false;
+                if (gLoadPending) {
+                    auto collectShaders = [this](sk_sp<const SkData> key, sk_sp<SkData> data,
+                                                 int hitCount) {
+                        CachedGLSL& entry(fCachedGLSL.push_back());
+                        entry.fKey = key;
+                        SkMD5 hash;
+                        hash.write(key->bytes(), key->size());
+                        SkMD5::Digest digest = hash.finish();
+                        for (int i = 0; i < 16; ++i) {
+                            entry.fKeyString.appendf("%02x", digest.data[i]);
+                        }
+
+                        GrPersistentCacheUtils::UnpackCachedGLSL(data.get(), &entry.fInputs,
+                                                                 entry.fShader);
+                    };
+                    fCachedGLSL.reset();
+                    fPersistentCache.foreach(collectShaders);
+                    gLoadPending = false;
+                }
+
+                // Defer actually doing the load/save logic so that we can trigger a save when we
+                // start or finish hovering on a tree node in the list below:
+                bool doLoad = ImGui::Button("Load"); ImGui::SameLine();
+                bool doSave = ImGui::Button("Save");
+
+                ImGui::BeginChild("##ScrollingRegion");
+                for (auto& entry : fCachedGLSL) {
+                    bool inTreeNode = ImGui::TreeNode(entry.fKeyString.c_str());
+                    bool hovered = ImGui::IsItemHovered();
+                    if (hovered != entry.fHovered) {
+                        // Force a save to patch the highlight shader in/out
+                        entry.fHovered = hovered;
+                        doSave = true;
+                    }
+                    if (inTreeNode) {
+                        // Full width, and a reasonable amount of space for each shader.
+                        ImVec2 boxSize(-1.0f, ImGui::GetTextLineHeight() * 20.0f);
+                        ImGui::InputTextMultiline("##VP", &entry.fShader[kVertex_GrShaderType],
+                                                  boxSize);
+                        ImGui::InputTextMultiline("##FP", &entry.fShader[kFragment_GrShaderType],
+                                                  boxSize);
+                        ImGui::TreePop();
+                    }
+                }
+                ImGui::EndChild();
+
+                if (doLoad) {
+                    fPersistentCache.reset();
+                    fWindow->getGrContext()->priv().getGpu()->resetShaderCacheForTesting();
+                    gLoadPending = true;
+                }
+                if (doSave) {
+                    // The hovered item (if any) gets a special shader to make it identifiable
+                    SkSL::String highlight = ctx->priv().caps()->shaderCaps()->versionDeclString();
+                    highlight.append("out vec4 sk_FragColor;\n"
+                                     "void main() { sk_FragColor = vec4(1, 0, 1, 0.5); }");
+
+                    fPersistentCache.reset();
+                    fWindow->getGrContext()->priv().getGpu()->resetShaderCacheForTesting();
+                    for (auto& entry : fCachedGLSL) {
+                        SkSL::String backup = entry.fShader[kFragment_GrShaderType];
+                        if (entry.fHovered) {
+                            entry.fShader[kFragment_GrShaderType] = highlight;
+                        }
+
+                        auto data = GrPersistentCacheUtils::PackCachedGLSL(entry.fInputs,
+                                                                           entry.fShader);
+                        fPersistentCache.store(*entry.fKey, *data);
+
+                        entry.fShader[kFragment_GrShaderType] = backup;
+                    }
+                }
+            }
         }
         if (paramsChanged) {
             fDeferredActions.push_back([=]() {
diff --git a/tools/viewer/Viewer.h b/tools/viewer/Viewer.h
index 92c5696..061d5ac 100644
--- a/tools/viewer/Viewer.h
+++ b/tools/viewer/Viewer.h
@@ -10,9 +10,12 @@
 
 #include "AnimTimer.h"
 #include "ImGuiLayer.h"
+#include "MemoryCache.h"
 #include "SkExecutor.h"
 #include "SkFont.h"
 #include "SkScan.h"
+#include "ir/SkSLProgram.h"
+#include "SkSLString.h"
 #include "Slide.h"
 #include "StatsLayer.h"
 #include "TouchGesture.h"
@@ -22,6 +25,7 @@
 #include "sk_app/Window.h"
 
 class SkCanvas;
+class SkData;
 
 class Viewer : public sk_app::Application, sk_app::Window::Layer {
 public:
@@ -183,7 +187,19 @@
     SkFont fFont;
     SkFontFields fFontOverrides;
     bool fPixelGeometryOverrides = false;
-};
 
+    struct CachedGLSL {
+        bool                fHovered = false;
+
+        sk_sp<const SkData> fKey;
+        SkString            fKeyString;
+
+        SkSL::Program::Inputs fInputs;
+        SkSL::String fShader[kGrShaderTypeCount];
+    };
+
+    sk_gpu_test::MemoryCache fPersistentCache;
+    SkTArray<CachedGLSL>     fCachedGLSL;
+};
 
 #endif