Add layout(marker) to SkSL

Allows for uniforms to be automatically populated with
marked canvas matrices:

SkSL:
  layout (marker=localToWorld) uniform float4x4 localToWorldMatrix;

C++:
  canvas->concat(...);
  canvas->markCTM("localToWorld");
  canvas->concat(...);
  canvas->drawFoo(...);

Any runtime effects created with that SkSL will have their
localToWorldMatrix uniform filled in with the CTM, ignoring
any transformation that happened before/above the markCTM
call. The marker needs to be a sequence of alphanumeric or
underscore characters, and match the string used in markCTM.

The marker can also be of the form "normals(<string>)", in
which case the uniform will be filled in with the transpose
of the inverse of the upper-left 3x3 portion of the CTM
identified by <string>. This is helpful for transforming
normal vectors, as is often done in lighting.

Change-Id: I7d1ca4dc3f8fabbe91b9bd2c8632013f26d2321a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/285376
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
This commit is contained in:
Brian Osman 2020-04-15 14:18:13 -04:00 committed by Skia Commit-Bot
parent 3cf3a8c64f
commit f59a961dc9
8 changed files with 102 additions and 28 deletions

View File

@ -56,7 +56,9 @@ public:
}; };
enum Flags { enum Flags {
kArray_Flag = 0x1, kArray_Flag = 0x1,
kMarker_Flag = 0x2,
kMarkerNormals_Flag = 0x4,
}; };
SkString fName; SkString fName;
@ -65,12 +67,15 @@ public:
Type fType; Type fType;
int fCount; int fCount;
uint32_t fFlags; uint32_t fFlags;
uint32_t fMarker;
#if SK_SUPPORT_GPU #if SK_SUPPORT_GPU
GrSLType fGPUType; GrSLType fGPUType;
#endif #endif
bool isArray() const { return SkToBool(fFlags & kArray_Flag); } bool isArray() const { return SkToBool(fFlags & kArray_Flag); }
bool hasMarker() const { return SkToBool(fFlags & kMarker_Flag); }
bool hasNormalsMarker() const { return SkToBool(fFlags & kMarkerNormals_Flag); }
size_t sizeInBytes() const; size_t sizeInBytes() const;
}; };

View File

@ -377,8 +377,8 @@ public:
in fragmentProcessor color_map; in fragmentProcessor color_map;
in fragmentProcessor normal_map; in fragmentProcessor normal_map;
uniform float4x4 localToWorld; layout (marker=local_to_world) uniform float4x4 localToWorld;
uniform float4x4 localToWorldAdjInv; layout (marker=normals(local_to_world)) uniform float4x4 localToWorldAdjInv;
uniform float3 lightPos; uniform float3 lightPos;
float3 convert_normal_sample(half4 c) { float3 convert_normal_sample(half4 c) {
@ -413,27 +413,11 @@ public:
return; return;
} }
auto adj_inv = [](const SkM44& m) {
// Normals need to be transformed by the inverse-transpose of the upper-left 3x3 portion
// (scale + rotate) of the local to world matrix. (If the local to world only has
// uniform scale, we can use its upper-left 3x3 directly, but we don't know if that's
// the case here, so go the extra mile.)
SkM44 rot_scale(m.rc(0, 0), m.rc(0, 1), m.rc(0, 2), 0,
m.rc(1, 0), m.rc(1, 1), m.rc(1, 2), 0,
m.rc(2, 0), m.rc(2, 1), m.rc(2, 2), 0,
0, 0, 0, 1);
SkM44 inv(SkM44::kUninitialized_Constructor);
SkAssertResult(rot_scale.invert(&inv));
return inv.transpose();
};
struct Uniforms { struct Uniforms {
SkM44 fLocalToWorld; SkM44 fLocalToWorld; // Automatically populated, via layout(marker)
SkM44 fLocalToWorldAdjInv; SkM44 fLocalToWorldAdjInv; // Ditto
SkV3 fLightPos; SkV3 fLightPos;
} uni; } uni;
uni.fLocalToWorld = this->localToWorld(canvas);
uni.fLocalToWorldAdjInv = adj_inv(uni.fLocalToWorld);
uni.fLightPos = fLight.computeWorldPos(fSphere); uni.fLightPos = fLight.computeWorldPos(fSphere);
sk_sp<SkData> data = SkData::MakeWithCopy(&uni, sizeof(uni)); sk_sp<SkData> data = SkData::MakeWithCopy(&uni, sizeof(uni));

View File

@ -10,6 +10,7 @@
#include "include/effects/SkRuntimeEffect.h" #include "include/effects/SkRuntimeEffect.h"
#include "include/private/SkChecksum.h" #include "include/private/SkChecksum.h"
#include "include/private/SkMutex.h" #include "include/private/SkMutex.h"
#include "src/core/SkMatrixProvider.h"
#include "src/core/SkRasterPipeline.h" #include "src/core/SkRasterPipeline.h"
#include "src/core/SkReadBuffer.h" #include "src/core/SkReadBuffer.h"
#include "src/core/SkUtils.h" #include "src/core/SkUtils.h"
@ -52,6 +53,25 @@ private:
SkSL::Compiler* SharedCompiler::gCompiler = nullptr; SkSL::Compiler* SharedCompiler::gCompiler = nullptr;
} }
// Accepts either "[a-zA-Z0-9_]+" or "normals([a-zA-Z0-9_]+)"
static bool parse_marker(const SkSL::StringFragment& marker, uint32_t* id, uint32_t* flags) {
SkString s = marker;
if (s.startsWith("normals(") && s.endsWith(')')) {
*flags |= SkRuntimeEffect::Variable::kMarkerNormals_Flag;
s.set(marker.fChars + 8, marker.fLength - 9);
}
if (s.isEmpty()) {
return false;
}
for (size_t i = 0; i < s.size(); ++i) {
if (!std::isalnum(s[i]) && s[i] != '_') {
return false;
}
}
*id = SkOpts::hash_fn(s.c_str(), s.size(), 0);
return true;
}
SkRuntimeEffect::EffectResult SkRuntimeEffect::Make(SkString sksl) { SkRuntimeEffect::EffectResult SkRuntimeEffect::Make(SkString sksl) {
SkSL::SharedCompiler compiler; SkSL::SharedCompiler compiler;
auto program = compiler->convertProgram(SkSL::Program::kPipelineStage_Kind, auto program = compiler->convertProgram(SkSL::Program::kPipelineStage_Kind,
@ -202,6 +222,18 @@ SkRuntimeEffect::EffectResult SkRuntimeEffect::Make(SkString sksl) {
break; break;
} }
const SkSL::StringFragment& marker(var.fModifiers.fLayout.fMarker);
if (marker.fLength) {
// Rules that should be enforced by the IR generator:
SkASSERT(v.fQualifier == Variable::Qualifier::kUniform);
SkASSERT(v.fType == Variable::Type::kFloat4x4);
v.fFlags |= Variable::kMarker_Flag;
if (!parse_marker(marker, &v.fMarker, &v.fFlags)) {
RETURN_FAILURE("Invalid 'marker' string: '%.*s'",
(int)marker.fLength, marker.fChars);
}
}
if (v.fType != Variable::Type::kBool) { if (v.fType != Variable::Type::kBool) {
offset = SkAlign4(offset); offset = SkAlign4(offset);
} }
@ -738,7 +770,35 @@ public:
if (!this->totalLocalMatrix(args.fPreLocalMatrix)->invert(&matrix)) { if (!this->totalLocalMatrix(args.fPreLocalMatrix)->invert(&matrix)) {
return nullptr; return nullptr;
} }
auto fp = GrSkSLFP::Make(args.fContext, fEffect, "runtime_shader", fInputs); // If any of our uniforms are late-bound (eg, layout(marker)), we need to clone the blob
sk_sp<SkData> inputs = fInputs;
for (const auto& v : fEffect->inputs()) {
if (v.hasMarker()) {
if (inputs == fInputs) {
inputs = SkData::MakeWithCopy(fInputs->data(), fInputs->size());
}
SkASSERT(v.fType == SkRuntimeEffect::Variable::Type::kFloat4x4);
SkM44* localToMarker = SkTAddOffset<SkM44>(inputs->writable_data(), v.fOffset);
if (!args.fMatrixProvider.getLocalToMarker(v.fMarker, localToMarker)) {
// We couldn't provide a matrix that was requested by the SkSL
SkDebugf("Failed to get marked matrix %u\n", v.fMarker);
return nullptr;
}
if (v.hasNormalsMarker()) {
// Normals need to be transformed by the inverse-transpose of the upper-left
// 3x3 portion (scale + rotate) of the matrix.
localToMarker->setRow(3, {0, 0, 0, 1});
localToMarker->setCol(3, {0, 0, 0, 1});
if (!localToMarker->invert(localToMarker)) {
return nullptr;
}
*localToMarker = localToMarker->transpose();
}
}
}
auto fp = GrSkSLFP::Make(args.fContext, fEffect, "runtime_shader", std::move(inputs));
for (const auto& child : fChildren) { for (const auto& child : fChildren) {
auto childFP = child ? as_SB(child)->asFragmentProcessor(args) : nullptr; auto childFP = child ? as_SB(child)->asFragmentProcessor(args) : nullptr;
if (!childFP) { if (!childFP) {

View File

@ -288,6 +288,17 @@ std::unique_ptr<VarDeclarations> IRGenerator::convertVarDeclarations(const ASTNo
if (modifiers.fLayout.fKey && (modifiers.fFlags & Modifiers::kUniform_Flag)) { if (modifiers.fLayout.fKey && (modifiers.fFlags & Modifiers::kUniform_Flag)) {
fErrors.error(decls.fOffset, "'key' is not permitted on 'uniform' variables"); fErrors.error(decls.fOffset, "'key' is not permitted on 'uniform' variables");
} }
if (modifiers.fLayout.fMarker.fLength) {
if (fKind != Program::kPipelineStage_Kind) {
fErrors.error(decls.fOffset, "'marker' is only permitted in runtime effects");
}
if (!(modifiers.fFlags & Modifiers::kUniform_Flag)) {
fErrors.error(decls.fOffset, "'marker' is only permitted on 'uniform' variables");
}
if (*baseType != *fContext.fFloat4x4_Type) {
fErrors.error(decls.fOffset, "'marker' is only permitted on float4x4 variables");
}
}
if (modifiers.fFlags & Modifiers::kVarying_Flag) { if (modifiers.fFlags & Modifiers::kVarying_Flag) {
if (fKind != Program::kPipelineStage_Kind) { if (fKind != Program::kPipelineStage_Kind) {
fErrors.error(decls.fOffset, "'varying' is only permitted in runtime effects"); fErrors.error(decls.fOffset, "'varying' is only permitted in runtime effects");

View File

@ -85,6 +85,7 @@ void Parser::InitLayoutMap() {
TOKEN(TRIANGLES_ADJACENCY, "triangles_adjacency"); TOKEN(TRIANGLES_ADJACENCY, "triangles_adjacency");
TOKEN(MAX_VERTICES, "max_vertices"); TOKEN(MAX_VERTICES, "max_vertices");
TOKEN(INVOCATIONS, "invocations"); TOKEN(INVOCATIONS, "invocations");
TOKEN(MARKER, "marker");
TOKEN(WHEN, "when"); TOKEN(WHEN, "when");
TOKEN(KEY, "key"); TOKEN(KEY, "key");
TOKEN(TRACKED, "tracked"); TOKEN(TRACKED, "tracked");
@ -771,14 +772,15 @@ Layout Parser::layout() {
Layout::Primitive primitive = Layout::kUnspecified_Primitive; Layout::Primitive primitive = Layout::kUnspecified_Primitive;
int maxVertices = -1; int maxVertices = -1;
int invocations = -1; int invocations = -1;
StringFragment marker;
StringFragment when; StringFragment when;
Layout::Key key = Layout::kNo_Key; Layout::Key key = Layout::kNo_Key;
Layout::CType ctype = Layout::CType::kDefault; Layout::CType ctype = Layout::CType::kDefault;
if (this->checkNext(Token::Kind::TK_LAYOUT)) { if (this->checkNext(Token::Kind::TK_LAYOUT)) {
if (!this->expect(Token::Kind::TK_LPAREN, "'('")) { if (!this->expect(Token::Kind::TK_LPAREN, "'('")) {
return Layout(flags, location, offset, binding, index, set, builtin, return Layout(flags, location, offset, binding, index, set, builtin,
inputAttachmentIndex, format, primitive, maxVertices, invocations, when, inputAttachmentIndex, format, primitive, maxVertices, invocations, marker,
key, ctype); when, key, ctype);
} }
for (;;) { for (;;) {
Token t = this->nextToken(); Token t = this->nextToken();
@ -894,6 +896,9 @@ Layout Parser::layout() {
case LayoutToken::INVOCATIONS: case LayoutToken::INVOCATIONS:
invocations = this->layoutInt(); invocations = this->layoutInt();
break; break;
case LayoutToken::MARKER:
marker = this->layoutCode();
break;
case LayoutToken::WHEN: case LayoutToken::WHEN:
when = this->layoutCode(); when = this->layoutCode();
break; break;
@ -921,7 +926,7 @@ Layout Parser::layout() {
} }
} }
return Layout(flags, location, offset, binding, index, set, builtin, inputAttachmentIndex, return Layout(flags, location, offset, binding, index, set, builtin, inputAttachmentIndex,
format, primitive, maxVertices, invocations, when, key, ctype); format, primitive, maxVertices, invocations, marker, when, key, ctype);
} }
/* layout? (UNIFORM | CONST | IN | OUT | INOUT | LOWP | MEDIUMP | HIGHP | FLAT | NOPERSPECTIVE | /* layout? (UNIFORM | CONST | IN | OUT | INOUT | LOWP | MEDIUMP | HIGHP | FLAT | NOPERSPECTIVE |

View File

@ -68,6 +68,7 @@ public:
TRIANGLES_ADJACENCY, TRIANGLES_ADJACENCY,
MAX_VERTICES, MAX_VERTICES,
INVOCATIONS, INVOCATIONS,
MARKER,
WHEN, WHEN,
KEY, KEY,
TRACKED, TRACKED,

View File

@ -1823,7 +1823,7 @@ SpvId SPIRVCodeGenerator::writeVariableReference(const VariableReference& ref, O
SkASSERT(fProgram.fSettings.fRTHeightOffset >= 0); SkASSERT(fProgram.fSettings.fRTHeightOffset >= 0);
fields.emplace_back(Modifiers(Layout(0, -1, fProgram.fSettings.fRTHeightOffset, -1, fields.emplace_back(Modifiers(Layout(0, -1, fProgram.fSettings.fRTHeightOffset, -1,
-1, -1, -1, -1, Layout::Format::kUnspecified, -1, -1, -1, -1, Layout::Format::kUnspecified,
Layout::kUnspecified_Primitive, -1, -1, "", Layout::kUnspecified_Primitive, -1, -1, "", "",
Layout::kNo_Key, Layout::CType::kDefault), 0), Layout::kNo_Key, Layout::CType::kDefault), 0),
SKSL_RTHEIGHT_NAME, fContext.fFloat_Type.get()); SKSL_RTHEIGHT_NAME, fContext.fFloat_Type.get());
StringFragment name("sksl_synthetic_uniforms"); StringFragment name("sksl_synthetic_uniforms");
@ -1834,7 +1834,7 @@ SpvId SPIRVCodeGenerator::writeVariableReference(const VariableReference& ref, O
SkASSERT(binding != -1 && set != -1); SkASSERT(binding != -1 && set != -1);
Layout layout(0, -1, -1, binding, -1, set, -1, -1, Layout::Format::kUnspecified, Layout layout(0, -1, -1, binding, -1, set, -1, -1, Layout::Format::kUnspecified,
Layout::kUnspecified_Primitive, -1, -1, "", Layout::kNo_Key, Layout::kUnspecified_Primitive, -1, -1, "", "", Layout::kNo_Key,
Layout::CType::kDefault); Layout::CType::kDefault);
Variable* intfVar = (Variable*) fSynthetics.takeOwnership(std::unique_ptr<Symbol>( Variable* intfVar = (Variable*) fSynthetics.takeOwnership(std::unique_ptr<Symbol>(
new Variable(-1, new Variable(-1,

View File

@ -189,7 +189,7 @@ struct Layout {
Layout(int flags, int location, int offset, int binding, int index, int set, int builtin, Layout(int flags, int location, int offset, int binding, int index, int set, int builtin,
int inputAttachmentIndex, Format format, Primitive primitive, int maxVertices, int inputAttachmentIndex, Format format, Primitive primitive, int maxVertices,
int invocations, StringFragment when, Key key, CType ctype) int invocations, StringFragment marker, StringFragment when, Key key, CType ctype)
: fFlags(flags) : fFlags(flags)
, fLocation(location) , fLocation(location)
, fOffset(offset) , fOffset(offset)
@ -202,6 +202,7 @@ struct Layout {
, fPrimitive(primitive) , fPrimitive(primitive)
, fMaxVertices(maxVertices) , fMaxVertices(maxVertices)
, fInvocations(invocations) , fInvocations(invocations)
, fMarker(marker)
, fWhen(when) , fWhen(when)
, fKey(key) , fKey(key)
, fCType(ctype) {} , fCType(ctype) {}
@ -377,6 +378,10 @@ struct Layout {
result += separator + "invocations = " + to_string(fInvocations); result += separator + "invocations = " + to_string(fInvocations);
separator = ", "; separator = ", ";
} }
if (fMarker.fLength) {
result += separator + "marker = " + fMarker;
separator = ", ";
}
if (fWhen.fLength) { if (fWhen.fLength) {
result += separator + "when = " + fWhen; result += separator + "when = " + fWhen;
separator = ", "; separator = ", ";
@ -403,6 +408,7 @@ struct Layout {
fPrimitive == other.fPrimitive && fPrimitive == other.fPrimitive &&
fMaxVertices == other.fMaxVertices && fMaxVertices == other.fMaxVertices &&
fInvocations == other.fInvocations && fInvocations == other.fInvocations &&
fMarker == other.fMarker &&
fWhen == other.fWhen && fWhen == other.fWhen &&
fKey == other.fKey && fKey == other.fKey &&
fCType == other.fCType; fCType == other.fCType;
@ -428,6 +434,8 @@ struct Layout {
Primitive fPrimitive; Primitive fPrimitive;
int fMaxVertices; int fMaxVertices;
int fInvocations; int fInvocations;
// marker refers to matrices tagged on the SkCanvas with markCTM
StringFragment fMarker;
StringFragment fWhen; StringFragment fWhen;
Key fKey; Key fKey;
CType fCType; CType fCType;