Shader serialization experiment (with Mali Offline Compiler analysis)

1) Adds a --writeShaders option to fm. When that's included, the GPU
backend is run with a persistent cache (and no binary caching).
Then we dump all of the fragment shaders (GLSL for now) that were
created to the passed-in directory, along with a JSON digest listing
the number of times each one was referenced in the cache.

2) Adds a python script that invokes the Mali Offline Compiler on a
directory generated from #1, scraping the output of each compile for
cycle counts and writing the collated information to stdout.

Change-Id: Ie4b58ddac4f62e936707c6fec44f4fe605c213fa
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/206162
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
diff --git a/tools/gpu/MemoryCache.cpp b/tools/gpu/MemoryCache.cpp
index 7fc3e1b..771d1a1 100644
--- a/tools/gpu/MemoryCache.cpp
+++ b/tools/gpu/MemoryCache.cpp
@@ -5,8 +5,12 @@
  * found in the LICENSE file.
  */
 
+#include "GrPersistentCacheUtils.h"
 #include "MemoryCache.h"
 #include "SkBase64.h"
+#include "SkJSONWriter.h"
+#include "SkMD5.h"
+#include "SkTHash.h"
 
 // Change this to 1 to log cache hits/misses/stores using SkDebugf.
 #define LOG_MEMORY_CACHE 0
@@ -40,9 +44,10 @@
     }
     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());
+                 data_to_str(*result->second.fData).c_str());
     }
-    return result->second;
+    result->second.fHitCount++;
+    return result->second.fData;
 }
 
 void MemoryCache::store(const SkData& key, const SkData& data) {
@@ -50,7 +55,52 @@
         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());
+    fMap[Key(key)] = Value(data);
+}
+
+void MemoryCache::writeShadersToDisk(const char* path, GrBackendApi api) {
+    if (GrBackendApi::kOpenGL != api) {
+        // TODO: Add SPIRV support, too.
+        return;
+    }
+
+    // Default extensions detected by the Mali Offline Compiler
+    const char* extensions[kGrShaderTypeCount] = { "vert", "geom", "frag" };
+
+    // For now, we only dump fragment shaders. They are the biggest factor in performance, and
+    // the shaders for other stages tend to be heavily reused.
+    SkString jsonPath = SkStringPrintf("%s/%s.json", path, extensions[kFragment_GrShaderType]);
+    SkFILEWStream jsonFile(jsonPath.c_str());
+    SkJSONWriter writer(&jsonFile, SkJSONWriter::Mode::kPretty);
+    writer.beginArray();
+
+    for (auto it = fMap.begin(); it != fMap.end(); ++it) {
+        SkMD5 hash;
+        hash.write(it->first.fKey->bytes(), it->first.fKey->size());
+        SkMD5::Digest digest = hash.finish();
+        SkString md5;
+        for (int i = 0; i < 16; ++i) {
+            md5.appendf("%02x", digest.data[i]);
+        }
+
+        // Write [ hash, hitCount ] to JSON digest
+        writer.beginArray(nullptr, false);
+        writer.appendString(md5.c_str());
+        writer.appendS32(it->second.fHitCount);
+        writer.endArray();
+
+        SkReader32 reader(it->second.fData->data(), it->second.fData->size());
+        SkSL::Program::Inputs inputsIgnored;
+        SkSL::String glsl[kGrShaderTypeCount];
+        GrPersistentCacheUtils::UnpackCachedGLSL(reader, &inputsIgnored, glsl);
+
+        SkString filename = SkStringPrintf("%s/%s.%s", path, md5.c_str(),
+                                           extensions[kFragment_GrShaderType]);
+        SkFILEWStream file(filename.c_str());
+        file.write(glsl[kFragment_GrShaderType].c_str(), glsl[kFragment_GrShaderType].size());
+    }
+
+    writer.endArray();
 }
 
 }  // namespace sk_gpu_test