Use varyings to implement MatrixEffects applied to DeviceCoord FPs.

Modify GrGLSLGP's logic to add a varying for a series of matrix sample
usages below a DeviceSpace FP in the FP hierarchy. The varying is based
off the GPs position output variable in the VS.

Adds a new sample usage type for the DeviceSpace FP.

Bug: skia:12198
Change-Id: Ic39f3296c60a073656ad0c72a431a462d5714e92
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/435717
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Brian Salomon 2021-08-02 13:04:04 -04:00 committed by SkCQ
parent 0dd5f62310
commit 9eca2ca0c6
6 changed files with 67 additions and 24 deletions

View File

@ -140,15 +140,21 @@ SkBitmap make_test_bitmap() {
enum EffectType { enum EffectType {
kUniform, kUniform,
kExplicit, kExplicit,
kDevice,
}; };
static std::unique_ptr<GrFragmentProcessor> wrap(std::unique_ptr<GrFragmentProcessor> fp, static std::unique_ptr<GrFragmentProcessor> wrap(std::unique_ptr<GrFragmentProcessor> fp,
EffectType effectType) { EffectType effectType,
int drawX, int drawY) {
switch (effectType) { switch (effectType) {
case kUniform: case kUniform:
return std::make_unique<UniformMatrixEffect>(std::move(fp)); return std::make_unique<UniformMatrixEffect>(std::move(fp));
case kExplicit: case kExplicit:
return std::make_unique<ExplicitCoordEffect>(std::move(fp)); return std::make_unique<ExplicitCoordEffect>(std::move(fp));
case kDevice:
// Subtract out upper-left corner of draw so that device is effectively identity.
fp = GrMatrixEffect::Make(SkMatrix::Translate(-drawX, -drawY), std::move(fp));
return GrFragmentProcessor::DeviceSpace(std::move(fp));
} }
SkUNREACHABLE; SkUNREACHABLE;
} }
@ -157,7 +163,7 @@ static std::unique_ptr<GrFragmentProcessor> wrap(std::unique_ptr<GrFragmentProce
namespace skiagm { namespace skiagm {
DEF_SIMPLE_GPU_GM_CAN_FAIL(fp_sample_chaining, rContext, canvas, errorMsg, 232, 232) { DEF_SIMPLE_GPU_GM_CAN_FAIL(fp_sample_chaining, rContext, canvas, errorMsg, 232, 306) {
auto sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas); auto sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
if (!sdc) { if (!sdc) {
*errorMsg = GM::kErrorMsg_DrawSkippedGpuOnly; *errorMsg = GM::kErrorMsg_DrawSkippedGpuOnly;
@ -182,7 +188,7 @@ DEF_SIMPLE_GPU_GM_CAN_FAIL(fp_sample_chaining, rContext, canvas, errorMsg, 232,
auto fp = GrTextureEffect::Make(std::move(view), bmp.alphaType()); auto fp = GrTextureEffect::Make(std::move(view), bmp.alphaType());
#endif #endif
for (EffectType effectType : effects) { for (EffectType effectType : effects) {
fp = wrap(std::move(fp), effectType); fp = wrap(std::move(fp), effectType, x, y);
} }
GrPaint paint; GrPaint paint;
paint.setColorFragmentProcessor(std::move(fp)); paint.setColorFragmentProcessor(std::move(fp));
@ -206,9 +212,16 @@ DEF_SIMPLE_GPU_GM_CAN_FAIL(fp_sample_chaining, rContext, canvas, errorMsg, 232,
draw({ kExplicit, kExplicit }); // Translate up by 16px draw({ kExplicit, kExplicit }); // Translate up by 16px
nextRow(); nextRow();
// Remember, these are applied inside out: // Third row: Remember, these are applied inside out:
draw({ kUniform, kExplicit }); // Scale Y by 2x and translate up by 8px draw({ kUniform, kExplicit }); // Scale Y by 2x and translate up by 8px
draw({ kExplicit, kUniform }); // Scale Y by 2x and translate up by 16px draw({ kExplicit, kUniform }); // Scale Y by 2x and translate up by 16px
nextRow();
// Fourth row: device space.
draw({ kDevice, kUniform }); // Same as identity (uniform applied *before*
// device so ignored).
draw({ kExplicit, kUniform, kDevice }); // Scale Y by 2x and translate up by 16px
draw({ kDevice, kExplicit, kUniform, kDevice }); // Identity, again.
return DrawResult::kOk; return DrawResult::kOk;
} }

View File

@ -26,6 +26,8 @@ public:
kPassThrough, kPassThrough,
// Child is sampled with a matrix whose value is uniform // Child is sampled with a matrix whose value is uniform
kUniformMatrix, kUniformMatrix,
// Child is sampled with sk_FragCoord.xy
kFragCoord,
// Child is sampled using explicit coordinates // Child is sampled using explicit coordinates
kExplicit, kExplicit,
}; };
@ -52,6 +54,8 @@ public:
return SampleUsage(Kind::kPassThrough, false); return SampleUsage(Kind::kPassThrough, false);
} }
static SampleUsage FragCoord() { return SampleUsage(Kind::kFragCoord, false); }
bool operator==(const SampleUsage& that) const { bool operator==(const SampleUsage& that) const {
return fKind == that.fKind && fHasPerspective == that.fHasPerspective; return fKind == that.fKind && fHasPerspective == that.fHasPerspective;
} }
@ -71,6 +75,7 @@ public:
bool isPassThrough() const { return fKind == Kind::kPassThrough; } bool isPassThrough() const { return fKind == Kind::kPassThrough; }
bool isExplicit() const { return fKind == Kind::kExplicit; } bool isExplicit() const { return fKind == Kind::kExplicit; }
bool isUniformMatrix() const { return fKind == Kind::kUniformMatrix; } bool isUniformMatrix() const { return fKind == Kind::kUniformMatrix; }
bool isFragCoord() const { return fKind == Kind::kFragCoord; }
std::string constructor() const; std::string constructor() const;

View File

@ -652,7 +652,8 @@ std::unique_ptr<GrFragmentProcessor> GrFragmentProcessor::DeviceSpace(
private: private:
DeviceSpace(std::unique_ptr<GrFragmentProcessor> fp) DeviceSpace(std::unique_ptr<GrFragmentProcessor> fp)
: GrFragmentProcessor(kDeviceSpace_ClassID, fp->optimizationFlags()) { : GrFragmentProcessor(kDeviceSpace_ClassID, fp->optimizationFlags()) {
this->registerChild(std::move(fp), SkSL::SampleUsage::Explicit()); // Passing FragCoord here is the reason this is a subclass and not a runtime-FP.
this->registerChild(std::move(fp), SkSL::SampleUsage::FragCoord());
} }
std::unique_ptr<GrFragmentProcessor> clone() const override { std::unique_ptr<GrFragmentProcessor> clone() const override {

View File

@ -70,6 +70,7 @@ SkString GrGLSLFragmentProcessor::invokeChild(int childIndex,
SkASSERT(!childProc->sampleUsage().isUniformMatrix()); SkASSERT(!childProc->sampleUsage().isUniformMatrix());
if (args.fFragBuilder->getProgramBuilder()->fragmentProcessorHasCoordsParam(childProc)) { if (args.fFragBuilder->getProgramBuilder()->fragmentProcessorHasCoordsParam(childProc)) {
SkASSERT(!childProc->sampleUsage().isFragCoord() || skslCoords == "sk_FragCoord.xy");
// The child's function takes a half4 color and a float2 coordinate // The child's function takes a half4 color and a float2 coordinate
invocation.appendf(", %s", skslCoords.empty() ? args.fSampleCoord : skslCoords.c_str()); invocation.appendf(", %s", skslCoords.empty() ? args.fSampleCoord : skslCoords.c_str());
} }

View File

@ -26,6 +26,7 @@ GrGLSLGeometryProcessor::FPCoordsMap GrGLSLGeometryProcessor::emitCode(EmitArgs&
args.fVaryingHandler, args.fVaryingHandler,
args.fUniformHandler, args.fUniformHandler,
gpArgs.fLocalCoordVar, gpArgs.fLocalCoordVar,
gpArgs.fPositionVar,
pipeline); pipeline);
if (args.fGeomProc.willUseTessellationShaders()) { if (args.fGeomProc.willUseTessellationShaders()) {
@ -76,18 +77,22 @@ GrGLSLGeometryProcessor::FPCoordsMap GrGLSLGeometryProcessor::collectTransforms(
GrGLSLVaryingHandler* varyingHandler, GrGLSLVaryingHandler* varyingHandler,
GrGLSLUniformHandler* uniformHandler, GrGLSLUniformHandler* uniformHandler,
const GrShaderVar& localCoordsVar, const GrShaderVar& localCoordsVar,
const GrShaderVar& positionVar,
const GrPipeline& pipeline) { const GrPipeline& pipeline) {
SkASSERT(localCoordsVar.getType() == kFloat2_GrSLType || SkASSERT(localCoordsVar.getType() == kFloat2_GrSLType ||
localCoordsVar.getType() == kFloat3_GrSLType || localCoordsVar.getType() == kFloat3_GrSLType ||
localCoordsVar.getType() == kVoid_GrSLType); localCoordsVar.getType() == kVoid_GrSLType);
SkASSERT(positionVar.getType() == kFloat2_GrSLType ||
positionVar.getType() == kFloat3_GrSLType);
enum class BaseCoord { kNone, kLocal, kPosition };
auto baseLocalCoordFSVar = [&, baseLocalCoord = GrGLSLVarying()]() mutable { auto baseLocalCoordFSVar = [&, baseLocalCoord = GrGLSLVarying()]() mutable {
SkASSERT(GrSLTypeIsFloatType(localCoordsVar.getType())); SkASSERT(GrSLTypeIsFloatType(localCoordsVar.getType()));
if (baseLocalCoord.type() == kVoid_GrSLType) { if (baseLocalCoord.type() == kVoid_GrSLType) {
// Initialize to the GP provided coordinate // Initialize to the GP provided coordinate
SkString baseLocalCoordName = SkStringPrintf("LocalCoord");
baseLocalCoord = GrGLSLVarying(localCoordsVar.getType()); baseLocalCoord = GrGLSLVarying(localCoordsVar.getType());
varyingHandler->addVarying(baseLocalCoordName.c_str(), &baseLocalCoord); varyingHandler->addVarying("LocalCoord", &baseLocalCoord);
vb->codeAppendf("%s = %s;\n", baseLocalCoord.vsOut(), localCoordsVar.getName().c_str()); vb->codeAppendf("%s = %s;\n", baseLocalCoord.vsOut(), localCoordsVar.getName().c_str());
} }
return baseLocalCoord.fsInVar(); return baseLocalCoord.fsInVar();
@ -97,12 +102,13 @@ GrGLSLGeometryProcessor::FPCoordsMap GrGLSLGeometryProcessor::collectTransforms(
// Performs a pre-order traversal of FP hierarchy rooted at fp and identifies FPs that are // Performs a pre-order traversal of FP hierarchy rooted at fp and identifies FPs that are
// sampled with a series of matrices applied to local coords. For each such FP a varying is // sampled with a series of matrices applied to local coords. For each such FP a varying is
// added to the varying handler and added to 'result'. // added to the varying handler and added to 'result'.
auto liftTransforms = [&, traversalIndex = 0](auto& self, auto liftTransforms = [&, traversalIndex = 0](
const GrFragmentProcessor& fp, auto& self,
bool hasPerspective, const GrFragmentProcessor& fp,
const GrFragmentProcessor* lastMatrixFP = nullptr, bool hasPerspective,
int lastMatrixTraversalIndex = -1, const GrFragmentProcessor* lastMatrixFP = nullptr,
bool inExplicitSubtree = false) mutable -> void { int lastMatrixTraversalIndex = -1,
BaseCoord baseCoord = BaseCoord::kLocal) mutable -> void {
++traversalIndex; ++traversalIndex;
switch (fp.sampleUsage().kind()) { switch (fp.sampleUsage().kind()) {
case SkSL::SampleUsage::Kind::kNone: case SkSL::SampleUsage::Kind::kNone:
@ -117,15 +123,27 @@ GrGLSLGeometryProcessor::FPCoordsMap GrGLSLGeometryProcessor::collectTransforms(
lastMatrixFP = &fp; lastMatrixFP = &fp;
lastMatrixTraversalIndex = traversalIndex; lastMatrixTraversalIndex = traversalIndex;
break; break;
case SkSL::SampleUsage::Kind::kFragCoord:
hasPerspective = positionVar.getType() == kFloat3_GrSLType;
lastMatrixFP = nullptr;
lastMatrixTraversalIndex = -1;
baseCoord = BaseCoord::kPosition;
break;
case SkSL::SampleUsage::Kind::kExplicit: case SkSL::SampleUsage::Kind::kExplicit:
inExplicitSubtree = true; baseCoord = BaseCoord::kNone;
break; break;
} }
auto& [varyingFSVar, hasCoordsParam] = result[&fp]; auto& [varyingFSVar, hasCoordsParam] = result[&fp];
hasCoordsParam = fp.usesSampleCoordsDirectly(); hasCoordsParam = fp.usesSampleCoordsDirectly();
if (fp.usesSampleCoordsDirectly() && !inExplicitSubtree) { // We add a varying if we're in a chain of matrices multiplied by local or device coords.
// If the coord is the untransformed local coord we add a varying. We don't if it is
// untransformed device coords since it doesn't save us anything over "sk_FragCoord.xy". Of
// course, if the FP doesn't directly use its coords then we don't add a varying.
if (fp.usesSampleCoordsDirectly() &&
(baseCoord == BaseCoord::kLocal ||
(baseCoord == BaseCoord::kPosition && lastMatrixFP))) {
// Associate the varying with the highest possible node in the FP tree that shares the // Associate the varying with the highest possible node in the FP tree that shares the
// same coordinates so that multiple FPs in a subtree can share. If there are no matrix // same coordinates so that multiple FPs in a subtree can share. If there are no matrix
// sample nodes on the way up the tree then directly use the local coord. // sample nodes on the way up the tree then directly use the local coord.
@ -134,13 +152,13 @@ GrGLSLGeometryProcessor::FPCoordsMap GrGLSLGeometryProcessor::collectTransforms(
} else { } else {
// If there is an already a varying that incorporates all matrices from the root to // If there is an already a varying that incorporates all matrices from the root to
// lastMatrixFP just use it. Otherwise, we add it. // lastMatrixFP just use it. Otherwise, we add it.
auto& [varying, localCoord, varyingIdx] = fTransformVaryingsMap[lastMatrixFP]; auto& [varying, inputCoords, varyingIdx] = fTransformVaryingsMap[lastMatrixFP];
if (varying.type() == kVoid_GrSLType) { if (varying.type() == kVoid_GrSLType) {
varying = GrGLSLVarying(hasPerspective ? kFloat3_GrSLType : kFloat2_GrSLType); varying = GrGLSLVarying(hasPerspective ? kFloat3_GrSLType : kFloat2_GrSLType);
SkString strVaryingName = SkStringPrintf("TransformedCoords_%d", SkString strVaryingName = SkStringPrintf("TransformedCoords_%d",
lastMatrixTraversalIndex); lastMatrixTraversalIndex);
varyingHandler->addVarying(strVaryingName.c_str(), &varying); varyingHandler->addVarying(strVaryingName.c_str(), &varying);
localCoord = localCoordsVar; inputCoords = baseCoord == BaseCoord::kLocal ? localCoordsVar : positionVar;
varyingIdx = lastMatrixTraversalIndex; varyingIdx = lastMatrixTraversalIndex;
} }
SkASSERT(varyingIdx == lastMatrixTraversalIndex); SkASSERT(varyingIdx == lastMatrixTraversalIndex);
@ -157,11 +175,12 @@ GrGLSLGeometryProcessor::FPCoordsMap GrGLSLGeometryProcessor::collectTransforms(
hasPerspective, hasPerspective,
lastMatrixFP, lastMatrixFP,
lastMatrixTraversalIndex, lastMatrixTraversalIndex,
inExplicitSubtree); baseCoord);
// If we have a varying then we never need a param. Otherwise, if one of our // If we have a varying then we never need a param. Otherwise, if one of our
// children takes a non-explicit coord then we'll need our coord. // children takes a non-explicit coord then we'll need our coord.
hasCoordsParam |= varyingFSVar.getType() == kVoid_GrSLType && hasCoordsParam |= varyingFSVar.getType() == kVoid_GrSLType &&
!child->sampleUsage().isExplicit() && !child->sampleUsage().isExplicit() &&
!child->sampleUsage().isFragCoord() &&
result[child].hasCoordsParam; result[child].hasCoordsParam;
} }
} }
@ -199,7 +218,7 @@ void GrGLSLGeometryProcessor::emitTransformCode(GrGLSLVertexBuilder* vb,
// If we hit an ancestor with a varying on our walk up then save off the varying as the // If we hit an ancestor with a varying on our walk up then save off the varying as the
// input to our accumulated transformExpression. Start off assuming we'll reach the root. // input to our accumulated transformExpression. Start off assuming we'll reach the root.
GrShaderVar inputCoords = info.localCoords; GrShaderVar inputCoords = info.inputCoords;
for (const auto* base = fp->parent(); base; base = base->parent()) { for (const auto* base = fp->parent(); base; base = base->parent()) {
if (auto iter = fTransformVaryingsMap.find(base); iter != fTransformVaryingsMap.end()) { if (auto iter = fTransformVaryingsMap.find(base); iter != fTransformVaryingsMap.end()) {
@ -209,13 +228,16 @@ void GrGLSLGeometryProcessor::emitTransformCode(GrGLSLVertexBuilder* vb,
inputCoords = iter->second.varying.vsOutVar(); inputCoords = iter->second.varying.vsOutVar();
break; break;
} else if (base->sampleUsage().isUniformMatrix()) { } else if (base->sampleUsage().isUniformMatrix()) {
// Accumulate any matrices along the path to either the original local coords or // Accumulate any matrices along the path to either the original local/device coords
// a parent varying. Getting here means this FP was sampled with a uniform matrix // or a parent varying. Getting here means this FP was sampled with a uniform matrix
// but all uses of coords below here in the FP hierarchy are beneath additional // but all uses of coords below here in the FP hierarchy are beneath additional
// matrix samples and thus this node wasn't assigned a varying. // matrix samples and thus this node wasn't assigned a varying.
GrShaderVar uniform = uniformHandler->liftUniformToVertexShader( GrShaderVar uniform = uniformHandler->liftUniformToVertexShader(
*base->parent(), SkString(SkSL::SampleUsage::MatrixUniformName())); *base->parent(), SkString(SkSL::SampleUsage::MatrixUniformName()));
transformExpression.appendf(" * %s", uniform.getName().c_str()); transformExpression.appendf(" * %s", uniform.getName().c_str());
} else if (base->sampleUsage().isFragCoord()) {
// Our chain of matrices starts here and is based on the device space position.
break;
} else { } else {
// This intermediate FP is just a pass through and doesn't need to be built // This intermediate FP is just a pass through and doesn't need to be built
// in to the expression, but we must visit its parents in case they add transforms. // in to the expression, but we must visit its parents in case they add transforms.

View File

@ -216,18 +216,19 @@ private:
// //
// This must happen before FP code emission so that the FPs can find the appropriate varying // This must happen before FP code emission so that the FPs can find the appropriate varying
// handles they use in place of explicit coord sampling; it is automatically called after // handles they use in place of explicit coord sampling; it is automatically called after
// onEmitCode() returns using the value stored in GpArgs::fLocalCoordVar. // onEmitCode() returns using the value stored in GpArgs::fLocalCoordVar and
// GpArgs::fPositionVar.
FPCoordsMap collectTransforms(GrGLSLVertexBuilder* vb, FPCoordsMap collectTransforms(GrGLSLVertexBuilder* vb,
GrGLSLVaryingHandler* varyingHandler, GrGLSLVaryingHandler* varyingHandler,
GrGLSLUniformHandler* uniformHandler, GrGLSLUniformHandler* uniformHandler,
const GrShaderVar& localCoordsVar, const GrShaderVar& localCoordsVar,
const GrShaderVar& positionVar,
const GrPipeline& pipeline); const GrPipeline& pipeline);
struct TransformInfo { struct TransformInfo {
// The varying that conveys the coordinates to one or more FPs in the FS. // The varying that conveys the coordinates to one or more FPs in the FS.
GrGLSLVarying varying; GrGLSLVarying varying;
// The coordinate to be transformed. varying is computed from this. // The coordinate to be transformed. varying is computed from this.
GrShaderVar localCoords; GrShaderVar inputCoords;
// Used to sort so that ancestor FP varyings are initialized before descendant FP varyings. // Used to sort so that ancestor FP varyings are initialized before descendant FP varyings.
int traversalOrder; int traversalOrder;
}; };