For runtime shaders, deduce isOpaque automatically

The forceOpaque parameter is now ignored. Instead, we do a conservative
analysis of the shader's main function to determine if it always returns
an opaque color. This is good enough to detect simple cases, including
things like:

  return child.eval(p).rgb1

Bug: skia:12643
Bug: skia:12896
Change-Id: I74b331aa12fadb1d0d1bb85f225dc7aa01ba2455
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/503346
Reviewed-by: John Stiles <johnstiles@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
This commit is contained in:
Brian Osman 2022-02-02 14:53:35 -05:00 committed by SkCQ
parent ffeb6f2339
commit 995d16fc91
5 changed files with 146 additions and 32 deletions

View File

@ -198,12 +198,12 @@ public:
sk_sp<SkShader> makeShader(sk_sp<SkData> uniforms,
sk_sp<SkShader> children[],
size_t childCount,
const SkMatrix* localMatrix,
bool isOpaque) const;
const SkMatrix* localMatrix = nullptr,
bool /*isOpaque [DEPRECATED]*/ = false) const;
sk_sp<SkShader> makeShader(sk_sp<SkData> uniforms,
SkSpan<ChildPtr> children,
const SkMatrix* localMatrix,
bool isOpaque) const;
const SkMatrix* localMatrix = nullptr,
bool /*isOpaque [DEPRECATED]*/ = false) const;
sk_sp<SkImage> makeImage(GrRecordingContext*,
sk_sp<SkData> uniforms,
@ -266,6 +266,7 @@ private:
kAllowBlender_Flag = 0x08,
kSamplesOutsideMain_Flag = 0x10,
kUsesColorTransform_Flag = 0x20,
kAlwaysOpaque_Flag = 0x40,
};
SkRuntimeEffect(std::unique_ptr<SkSL::Program> baseProgram,
@ -302,6 +303,7 @@ private:
bool allowBlender() const { return (fFlags & kAllowBlender_Flag); }
bool samplesOutsideMain() const { return (fFlags & kSamplesOutsideMain_Flag); }
bool usesColorTransform() const { return (fFlags & kUsesColorTransform_Flag); }
bool alwaysOpaque() const { return (fFlags & kAlwaysOpaque_Flag); }
const SkFilterColorProgram* getFilterColorProgram();
@ -481,7 +483,8 @@ public:
SkRuntimeShaderBuilder(const SkRuntimeShaderBuilder&) = default;
~SkRuntimeShaderBuilder();
sk_sp<SkShader> makeShader(const SkMatrix* localMatrix, bool isOpaque);
sk_sp<SkShader> makeShader(const SkMatrix* localMatrix = nullptr,
bool /*isOpaque [DEPRECATED]*/ = false);
sk_sp<SkImage> makeImage(GrRecordingContext*,
const SkMatrix* localMatrix,
SkImageInfo resultInfo,

View File

@ -273,6 +273,11 @@ SkRuntimeEffect::Result SkRuntimeEffect::MakeInternal(std::unique_ptr<SkSL::Prog
flags |= kUsesColorTransform_Flag;
}
// Shaders are the only thing that cares about this, but it's inexpensive (and safe) to call.
if (SkSL::Analysis::ReturnsOpaqueColor(*main)) {
flags |= kAlwaysOpaque_Flag;
}
size_t offset = 0;
std::vector<Uniform> uniforms;
std::vector<Child> children;
@ -1099,12 +1104,10 @@ public:
sk_sp<SkSL::SkVMDebugTrace> debugTrace,
sk_sp<SkData> uniforms,
const SkMatrix* localMatrix,
SkSpan<SkRuntimeEffect::ChildPtr> children,
bool isOpaque)
SkSpan<SkRuntimeEffect::ChildPtr> children)
: SkShaderBase(localMatrix)
, fEffect(std::move(effect))
, fDebugTrace(std::move(debugTrace))
, fIsOpaque(isOpaque)
, fUniforms(std::move(uniforms))
, fChildren(children.begin(), children.end()) {}
@ -1112,13 +1115,12 @@ public:
sk_sp<SkRuntimeEffect> unoptimized = fEffect->makeUnoptimizedClone();
sk_sp<SkSL::SkVMDebugTrace> debugTrace = make_skvm_debug_trace(unoptimized.get(), coord);
auto debugShader = sk_make_sp<SkRTShader>(unoptimized, debugTrace, fUniforms,
&this->getLocalMatrix(), SkMakeSpan(fChildren),
fIsOpaque);
&this->getLocalMatrix(), SkMakeSpan(fChildren));
return SkRuntimeEffect::TracedShader{std::move(debugShader), std::move(debugTrace)};
}
bool isOpaque() const override { return fIsOpaque; }
bool isOpaque() const override { return fEffect->alwaysOpaque(); }
#if SK_SUPPORT_GPU
std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs& args) const override {
@ -1142,11 +1144,6 @@ public:
return nullptr;
}
// If the shader was created with isOpaque = true, we *force* that result here.
// CPU does the same thing (in SkShaderBase::program).
if (fIsOpaque) {
fp = GrFragmentProcessor::SwizzleOutput(std::move(fp), GrSwizzle::RGB1());
}
return GrMatrixEffect::Make(matrix, std::move(fp));
}
#endif
@ -1180,9 +1177,6 @@ public:
void flatten(SkWriteBuffer& buffer) const override {
uint32_t flags = 0;
if (fIsOpaque) {
flags |= kIsOpaque_Flag;
}
if (!this->getLocalMatrix().isIdentity()) {
flags |= kHasLocalMatrix_Flag;
}
@ -1202,13 +1196,11 @@ public:
private:
enum Flags {
kIsOpaque_Flag = 1 << 0,
kHasLocalMatrix_Flag = 1 << 1,
};
sk_sp<SkRuntimeEffect> fEffect;
sk_sp<SkSL::SkVMDebugTrace> fDebugTrace;
bool fIsOpaque;
sk_sp<SkData> fUniforms;
std::vector<SkRuntimeEffect::ChildPtr> fChildren;
@ -1220,7 +1212,6 @@ sk_sp<SkFlattenable> SkRTShader::CreateProc(SkReadBuffer& buffer) {
sk_sp<SkData> uniforms = buffer.readByteArrayAsData();
uint32_t flags = buffer.read32();
bool isOpaque = SkToBool(flags & kIsOpaque_Flag);
SkMatrix localM, *localMPtr = nullptr;
if (flags & kHasLocalMatrix_Flag) {
buffer.readMatrix(&localM);
@ -1237,7 +1228,7 @@ sk_sp<SkFlattenable> SkRTShader::CreateProc(SkReadBuffer& buffer) {
return nullptr;
}
return effect->makeShader(std::move(uniforms), SkMakeSpan(children), localMPtr, isOpaque);
return effect->makeShader(std::move(uniforms), SkMakeSpan(children), localMPtr);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -1331,18 +1322,18 @@ sk_sp<SkShader> SkRuntimeEffect::makeShader(sk_sp<SkData> uniforms,
sk_sp<SkShader> childShaders[],
size_t childCount,
const SkMatrix* localMatrix,
bool isOpaque) const {
bool /*isOpaque [DEPRECATED]*/) const {
SkSTArray<4, ChildPtr> children(childCount);
for (size_t i = 0; i < childCount; ++i) {
children.emplace_back(childShaders[i]);
}
return this->makeShader(std::move(uniforms), SkMakeSpan(children), localMatrix, isOpaque);
return this->makeShader(std::move(uniforms), SkMakeSpan(children), localMatrix);
}
sk_sp<SkShader> SkRuntimeEffect::makeShader(sk_sp<SkData> uniforms,
SkSpan<ChildPtr> children,
const SkMatrix* localMatrix,
bool isOpaque) const {
bool /*isOpaque [DEPRECATED]*/) const {
if (!this->allowShader()) {
return nullptr;
}
@ -1356,7 +1347,7 @@ sk_sp<SkShader> SkRuntimeEffect::makeShader(sk_sp<SkData> uniforms,
return nullptr;
}
return sk_make_sp<SkRTShader>(sk_ref_sp(this), /*debugTrace=*/nullptr, std::move(uniforms),
localMatrix, children, isOpaque);
localMatrix, children);
}
sk_sp<SkImage> SkRuntimeEffect::makeImage(GrRecordingContext* rContext,
@ -1564,11 +1555,10 @@ sk_sp<SkImage> SkRuntimeShaderBuilder::makeImage(GrRecordingContext* recordingCo
mipmapped);
}
sk_sp<SkShader> SkRuntimeShaderBuilder::makeShader(const SkMatrix* localMatrix, bool isOpaque) {
return this->effect()->makeShader(this->uniforms(),
SkMakeSpan(this->children(), this->numChildren()),
localMatrix,
isOpaque);
sk_sp<SkShader> SkRuntimeShaderBuilder::makeShader(const SkMatrix* localMatrix,
bool /*isOpaque [DEPRECATED]*/) {
return this->effect()->makeShader(
this->uniforms(), SkMakeSpan(this->children(), this->numChildren()), localMatrix);
}
SkRuntimeBlendBuilder::SkRuntimeBlendBuilder(sk_sp<SkRuntimeEffect> effect)

View File

@ -160,6 +160,31 @@ public:
using INHERITED = ProgramVisitor;
};
class ReturnsNonOpaqueColorVisitor : public ProgramVisitor {
public:
ReturnsNonOpaqueColorVisitor() {}
bool visitStatement(const Statement& s) override {
if (s.is<ReturnStatement>()) {
const Expression* e = s.as<ReturnStatement>().expression().get();
bool knownOpaque = e && e->type().slotCount() == 4 &&
ConstantFolder::GetConstantValueForVariable(*e)
->getConstantValue(/*n=*/3)
.value_or(0) == 1;
return !knownOpaque;
}
return INHERITED::visitStatement(s);
}
bool visitExpression(const Expression& e) override {
// No need to recurse into expressions, these can never contain return statements
return false;
}
using INHERITED = ProgramVisitor;
using INHERITED::visitProgramElement;
};
// Visitor that counts the number of nodes visited
class NodeCountVisitor : public ProgramVisitor {
public:
@ -346,6 +371,11 @@ bool Analysis::CallsColorTransformIntrinsics(const Program& program) {
return false;
}
bool Analysis::ReturnsOpaqueColor(const FunctionDefinition& function) {
ReturnsNonOpaqueColorVisitor visitor;
return !visitor.visitProgramElement(function);
}
bool Analysis::DetectVarDeclarationWithoutScope(const Statement& stmt, ErrorReporter* errors) {
// A variable declaration can create either a lone VarDeclaration or an unscoped Block
// containing multiple VarDeclaration statements. We need to detect either case.

View File

@ -58,6 +58,12 @@ bool CallsSampleOutsideMain(const Program& program);
bool CallsColorTransformIntrinsics(const Program& program);
/**
* Determines if `function` always returns an opaque color (a vec4 where the last component is known
* to be 1). This is conservative, and based on constant expression analysis.
*/
bool ReturnsOpaqueColor(const FunctionDefinition& function);
/**
* Computes the size of the program in a completely flattened state--loops fully unrolled,
* function calls inlined--and rejects programs that exceed an arbitrary upper bound. This is

View File

@ -1048,6 +1048,91 @@ DEF_TEST(SkRuntimeShaderSampleCoords, r) {
"half4 main(float2 xy) { return helper(xy); }", true, true);
}
DEF_TEST(SkRuntimeShaderIsOpaque, r) {
// This test verifies that we detect certain simple patterns in runtime shaders, and can deduce
// (via code in SkSL::Analysis::ReturnsOpaqueColor) that the resulting shader is always opaque.
// That logic is conservative, and the tests below reflect this.
auto test = [&](const char* body, bool expectOpaque) {
auto [effect, err] = SkRuntimeEffect::MakeForShader(SkStringPrintf(R"(
uniform shader cOnes;
uniform shader cZeros;
uniform float4 uOnes;
uniform float4 uZeros;
half4 main(float2 xy) {
%s
})", body));
REPORTER_ASSERT(r, effect);
auto cOnes = SkShaders::Color(SK_ColorWHITE);
auto cZeros = SkShaders::Color(SK_ColorTRANSPARENT);
SkASSERT(cOnes->isOpaque());
SkASSERT(!cZeros->isOpaque());
SkRuntimeShaderBuilder builder(effect);
builder.child("cOnes") = std::move(cOnes);
builder.child("cZeros") = std::move(cZeros);
builder.uniform("uOnes") = SkColors::kWhite;
builder.uniform("uZeros") = SkColors::kTransparent;
auto shader = builder.makeShader();
REPORTER_ASSERT(r, shader->isOpaque() == expectOpaque);
};
// Cases where our optimization is valid, and works:
// Returning opaque literals
test("return half4(1);", true);
test("return half4(0, 1, 0, 1);", true);
test("return half4(0, 0, 0, 1);", true);
// Simple expressions involving uniforms
test("return uZeros.rgb1;", true);
test("return uZeros.bgra.rgb1;", true);
test("return half4(uZeros.rgb, 1);", true);
// Simple expressions involving child.eval
test("return cZeros.eval(xy).rgb1;", true);
test("return cZeros.eval(xy).bgra.rgb1;", true);
test("return half4(cZeros.eval(xy).rgb, 1);", true);
// Multiple returns
test("if (xy.x < 100) { return uZeros.rgb1; } else { return cZeros.eval(xy).rgb1; }", true);
// More expression cases:
test("return (cZeros.eval(xy) * uZeros).rgb1;", true);
test("return half4(1, 1, 1, 0.5 + 0.5);", true);
// Constant variable propagation
test("const half4 kWhite = half4(1); return kWhite;", true);
// Cases where our optimization is not valid, and does not happen:
// Returning non-opaque literals
test("return half4(0);", false);
test("return half4(1, 1, 1, 0);", false);
// Returning non-opaque uniforms or children
test("return uZeros;", false);
test("return cZeros.eval(xy);", false);
// Multiple returns
test("if (xy.x < 100) { return uZeros; } else { return cZeros.eval(xy).rgb1; }", false);
test("if (xy.x < 100) { return uZeros.rgb1; } else { return cZeros.eval(xy); }", false);
// There should (must) not be any false-positive cases. There are false-negatives.
// In these cases, our optimization would be valid, but does not happen:
// More complex expressions that can't be simplified
test("return xy.x < 100 ? uZeros.rgb1 : cZeros.eval(xy).rgb1;", false);
// Finally, there are cases that are conditional on the uniforms and children. These *could*
// determine dynamically if the uniform and/or child being referenced is opaque, and use that
// information. Today, we don't do this, so we pessimistically assume they're transparent:
test("return uOnes;", false);
test("return cOnes.eval(xy);", false);
}
DEF_GPUTEST_FOR_ALL_CONTEXTS(GrSkSLFP_Specialized, r, ctxInfo) {
struct FpAndKey {
std::unique_ptr<GrFragmentProcessor> fp;