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:
parent
ffeb6f2339
commit
995d16fc91
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user