Add support for benchmarking SkVM.

This will allow perf to give us insights on SkVM code generation
performance, so that any regressions are automatically noticed and
trends over time can be observed.

This CL includes three separate benchmarks:
- skvm: Compile SkSL to SkVM with no optimization pass
- skvm_opt: Compile to SkVM and perform optimization/DCE
- skvm_jit: Compile, optimize/DCE, and generate native assembly via JIT

Change-Id: I54ad20d7adb9c5e66d33a2fb761303a269341ff2
Bug: skia:13259
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/533957
Auto-Submit: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
This commit is contained in:
John Stiles 2022-04-25 20:16:16 -04:00 committed by SkCQ
parent edef5d0837
commit cea63f4fdf

View File

@ -13,6 +13,9 @@
#include "src/gpu/ganesh/mock/GrMockCaps.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLDSLParser.h"
#include "src/sksl/codegen/SkSLVMCodeGenerator.h"
#include <regex>
class SkSLCompilerStartupBench : public Benchmark {
protected:
@ -38,17 +41,23 @@ enum class Output {
kNone,
kGLSL,
kMetal,
kSPIRV
kSPIRV,
kSkVM, // raw SkVM bytecode
kSkVMOpt, // optimized SkVM bytecode
kSkVMJIT, // optimized native assembly code
};
class SkSLCompileBench : public Benchmark {
public:
static const char* output_string(Output output) {
switch (output) {
case Output::kNone: return "";
case Output::kGLSL: return "glsl_";
case Output::kMetal: return "metal_";
case Output::kSPIRV: return "spirv_";
case Output::kNone: return "";
case Output::kGLSL: return "glsl_";
case Output::kMetal: return "metal_";
case Output::kSPIRV: return "spirv_";
case Output::kSkVM: return "skvm_";
case Output::kSkVMOpt: return "skvm_opt_";
case Output::kSkVMJIT: return "skvm_jit_";
}
SkUNREACHABLE;
}
@ -62,9 +71,12 @@ public:
, fOutput(output) {
fSettings.fOptimize = optimize;
fSettings.fDSLMangling = false;
fSettings.fEnforceES2Restrictions = false;
// The test programs we compile don't follow Vulkan rules and thus produce invalid
// SPIR-V. This is harmless, so long as we don't try to validate them.
fSettings.fValidateSPIRV = false;
this->fixUpSource();
}
protected:
@ -76,11 +88,38 @@ protected:
return backend == kNonRendering_Backend;
}
bool usesRuntimeShader() const {
return fOutput >= Output::kSkVM;
}
void fixUpSource() {
auto fixup = [this](const char* input, const char* replacement) {
fSrc = std::regex_replace(fSrc, std::regex(input), replacement);
};
// Runtime shaders which have slightly different conventions than fragment shaders.
// Perform a handful of fixups to compensate. These are hand-tuned for our current set of
// test shaders and will probably need to be updated if we add more.
if (this->usesRuntimeShader()) {
fixup(R"(void main\(\))", "half4 main(float2 xy)");
fixup(R"(sk_FragColor =)", "return");
fixup(R"(sk_FragCoord)", "_FragCoord");
fixup(R"(out half4 sk_FragColor;)", "");
fixup(R"(uniform sampler2D )", "uniform shader ");
fixup(R"((flat |noperspective |)in )", "uniform ");
fixup(R"(sample\(([A-Za-z0-9_]+), ([A-Za-z0-9_]+)\))", "$01.eval($02)");
fSrc = "uniform float4 _FragCoord;\n" + fSrc;
}
}
void onDraw(int loops, SkCanvas* canvas) override {
for (int i = 0; i < loops; i++) {
const SkSL::ProgramKind kind = this->usesRuntimeShader()
? SkSL::ProgramKind::kRuntimeShader
: SkSL::ProgramKind::kFragment;
std::unique_ptr<SkSL::Program> program = SkSL::DSLParser(&fCompiler,
fSettings,
SkSL::ProgramKind::kFragment,
kind,
fSrc).program();
if (fCompiler.errorCount()) {
SK_ABORT("shader compilation failed: %s\n", fCompiler.errorText().c_str());
@ -88,13 +127,29 @@ protected:
std::string result;
switch (fOutput) {
case Output::kNone: break;
case Output::kGLSL: SkAssertResult(fCompiler.toGLSL(*program, &result)); break;
case Output::kMetal: SkAssertResult(fCompiler.toMetal(*program, &result)); break;
case Output::kSPIRV: SkAssertResult(fCompiler.toSPIRV(*program, &result)); break;
case Output::kGLSL: SkAssertResult(fCompiler.toGLSL(*program, &result)); break;
case Output::kMetal: SkAssertResult(fCompiler.toMetal(*program, &result)); break;
case Output::kSPIRV: SkAssertResult(fCompiler.toSPIRV(*program, &result)); break;
case Output::kSkVM:
case Output::kSkVMOpt:
case Output::kSkVMJIT: SkAssertResult(CompileToSkVM(*program, fOutput)); break;
}
}
}
static bool CompileToSkVM(const SkSL::Program& program, Output mode) {
const bool optimize = (mode >= Output::kSkVMOpt);
const bool allowJIT = (mode >= Output::kSkVMJIT);
skvm::Builder builder{skvm::Features{}};
if (!SkSL::testingOnly_ProgramToSkVMShader(program, &builder, /*debugTrace=*/nullptr)) {
return false;
}
if (optimize) {
builder.done("SkSLBench", allowJIT);
}
return true;
}
private:
std::string fName;
std::string fSrc;
@ -108,13 +163,16 @@ private:
///////////////////////////////////////////////////////////////////////////////
#define COMPILER_BENCH(name, text) \
static constexpr char name ## _SRC[] = text; \
DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/false, Output::kNone);) \
DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kNone);) \
DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kGLSL);) \
DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kMetal);) \
DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kSPIRV);)
#define COMPILER_BENCH(name, text) \
static constexpr char name ## _SRC[] = text; \
DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/false, Output::kNone);) \
DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kNone);) \
DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kGLSL);) \
DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kMetal);) \
DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kSPIRV);) \
DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kSkVM);) \
DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kSkVMOpt);) \
DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kSkVMJIT);)
// This fragment shader is from the third tile on the top row of GM_gradients_2pt_conical_outside.
// To get an ES2 compatible shader, nonconstantArrayIndexSupport in GrShaderCaps is forced off.