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>
This commit is contained in:
Brian Osman 2019-04-08 16:40:36 -04:00 committed by Skia Commit-Bot
parent d9b8eed848
commit 5aa11fb67a
4 changed files with 122 additions and 4 deletions

View File

@ -8,7 +8,9 @@
#include "GrContextOptions.h"
#include "GrContextPriv.h"
#include "GrGpu.h"
#include "GrPersistentCacheUtils.h"
#include "HashAndEncode.h"
#include "MemoryCache.h"
#include "SkCodec.h"
#include "SkColorSpace.h"
#include "SkColorSpacePriv.h"
@ -62,6 +64,8 @@ static DEFINE_bool(PDFA, false, "Create PDF/A with --backend pdf?");
static DEFINE_bool (cpuDetect, true, "Detect CPU features for runtime optimizations?");
static DEFINE_string2(writePath, w, "", "Write .pngs to this directory if set.");
static DEFINE_string(writeShaders, "", "Write GLSL shaders to this directory if set.");
static DEFINE_string(key, "", "Metadata passed through to .png encoder and .json output.");
static DEFINE_string(parameters, "", "Metadata passed through to .png encoder and .json output.");
@ -366,6 +370,12 @@ int main(int argc, char** argv) {
GrContextOptions baseOptions;
SetCtxOptionsFromCommonFlags(&baseOptions);
sk_gpu_test::MemoryCache memoryCache;
if (!FLAGS_writeShaders.isEmpty()) {
baseOptions.fPersistentCache = &memoryCache;
baseOptions.fDisallowGLSLBinaryCaching = true;
}
SkTHashMap<SkString, skiagm::GMFactory> gm_factories;
for (skiagm::GMFactory factory : skiagm::GMRegistry::Range()) {
std::unique_ptr<skiagm::GM> gm{factory(nullptr)};
@ -573,5 +583,13 @@ int main(int argc, char** argv) {
(int)std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count());
}
if (!FLAGS_writeShaders.isEmpty()) {
sk_mkdir(FLAGS_writeShaders[0]);
GrBackendApi api =
GrContextFactory::ContextTypeBackend((GrContextFactory::ContextType)backend);
memoryCache.writeShadersToDisk(FLAGS_writeShaders[0], api);
}
return 0;
}

View File

@ -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 @@ sk_sp<SkData> MemoryCache::load(const SkData& key) {
}
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 @@ void MemoryCache::store(const SkData& key, const SkData& data) {
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

View File

@ -33,6 +33,8 @@ public:
int numCacheMisses() const { return fCacheMissCnt; }
void resetNumCacheMisses() { fCacheMissCnt = 0; }
void writeShadersToDisk(const char* path, GrBackendApi backend);
private:
struct Key {
Key() = default;
@ -46,6 +48,18 @@ private:
sk_sp<const SkData> fKey;
};
struct Value {
Value() = default;
Value(const SkData& data)
: fData(SkData::MakeWithCopy(data.data(), data.size()))
, fHitCount(1) {}
Value(const Value& that) = default;
Value& operator=(const Value&) = default;
sk_sp<SkData> fData;
int fHitCount;
};
struct Hash {
using argument_type = Key;
using result_type = uint32_t;
@ -55,7 +69,7 @@ private:
};
int fCacheMissCnt = 0;
std::unordered_map<Key, sk_sp<SkData>, Hash> fMap;
std::unordered_map<Key, Value, Hash> fMap;
};
} // namespace sk_gpu_test

36
tools/malisc/malisc.py Normal file
View File

@ -0,0 +1,36 @@
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import json
import os
import subprocess
import sys
if len(sys.argv) != 3:
print sys.argv[0], ' <compiler> <folder>'
sys.exit(1)
compiler = sys.argv[1]
folder = sys.argv[2]
FRAG_JSON = os.path.join(folder, 'frag.json')
with open(FRAG_JSON) as f:
frags = json.load(f)
if not frags:
print 'No JSON data'
sys.exit(1)
for fragAndCount in frags:
source = os.path.join(folder, fragAndCount[0] + '.frag')
try:
output = subprocess.check_output([compiler, source])
except subprocess.CalledProcessError:
continue
for line in output.splitlines():
if line.startswith('Instructions Emitted'):
inst = line.split(':')[1].split()
print '{0} {1} {2} {3} {4}'.format(
fragAndCount[0], fragAndCount[1], inst[0], inst[1], inst[2])