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 {
kArray_Flag = 0x1,
kArray_Flag = 0x1,
kMarker_Flag = 0x2,
kMarkerNormals_Flag = 0x4,
};
SkString fName;
@ -65,12 +67,15 @@ public:
Type fType;
int fCount;
uint32_t fFlags;
uint32_t fMarker;
#if SK_SUPPORT_GPU
GrSLType fGPUType;
#endif
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;
};

View File

@ -377,8 +377,8 @@ public:
in fragmentProcessor color_map;
in fragmentProcessor normal_map;
uniform float4x4 localToWorld;
uniform float4x4 localToWorldAdjInv;
layout (marker=local_to_world) uniform float4x4 localToWorld;
layout (marker=normals(local_to_world)) uniform float4x4 localToWorldAdjInv;
uniform float3 lightPos;
float3 convert_normal_sample(half4 c) {
@ -413,27 +413,11 @@ public:
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 {
SkM44 fLocalToWorld;
SkM44 fLocalToWorldAdjInv;
SkM44 fLocalToWorld; // Automatically populated, via layout(marker)
SkM44 fLocalToWorldAdjInv; // Ditto
SkV3 fLightPos;
} uni;
uni.fLocalToWorld = this->localToWorld(canvas);
uni.fLocalToWorldAdjInv = adj_inv(uni.fLocalToWorld);
uni.fLightPos = fLight.computeWorldPos(fSphere);
sk_sp<SkData> data = SkData::MakeWithCopy(&uni, sizeof(uni));

View File

@ -10,6 +10,7 @@
#include "include/effects/SkRuntimeEffect.h"
#include "include/private/SkChecksum.h"
#include "include/private/SkMutex.h"
#include "src/core/SkMatrixProvider.h"
#include "src/core/SkRasterPipeline.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkUtils.h"
@ -52,6 +53,25 @@ private:
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) {
SkSL::SharedCompiler compiler;
auto program = compiler->convertProgram(SkSL::Program::kPipelineStage_Kind,
@ -202,6 +222,18 @@ SkRuntimeEffect::EffectResult SkRuntimeEffect::Make(SkString sksl) {
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) {
offset = SkAlign4(offset);
}
@ -738,7 +770,35 @@ public:
if (!this->totalLocalMatrix(args.fPreLocalMatrix)->invert(&matrix)) {
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) {
auto childFP = child ? as_SB(child)->asFragmentProcessor(args) : nullptr;
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)) {
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 (fKind != Program::kPipelineStage_Kind) {
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(MAX_VERTICES, "max_vertices");
TOKEN(INVOCATIONS, "invocations");
TOKEN(MARKER, "marker");
TOKEN(WHEN, "when");
TOKEN(KEY, "key");
TOKEN(TRACKED, "tracked");
@ -771,14 +772,15 @@ Layout Parser::layout() {
Layout::Primitive primitive = Layout::kUnspecified_Primitive;
int maxVertices = -1;
int invocations = -1;
StringFragment marker;
StringFragment when;
Layout::Key key = Layout::kNo_Key;
Layout::CType ctype = Layout::CType::kDefault;
if (this->checkNext(Token::Kind::TK_LAYOUT)) {
if (!this->expect(Token::Kind::TK_LPAREN, "'('")) {
return Layout(flags, location, offset, binding, index, set, builtin,
inputAttachmentIndex, format, primitive, maxVertices, invocations, when,
key, ctype);
inputAttachmentIndex, format, primitive, maxVertices, invocations, marker,
when, key, ctype);
}
for (;;) {
Token t = this->nextToken();
@ -894,6 +896,9 @@ Layout Parser::layout() {
case LayoutToken::INVOCATIONS:
invocations = this->layoutInt();
break;
case LayoutToken::MARKER:
marker = this->layoutCode();
break;
case LayoutToken::WHEN:
when = this->layoutCode();
break;
@ -921,7 +926,7 @@ Layout Parser::layout() {
}
}
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 |

View File

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

View File

@ -1823,7 +1823,7 @@ SpvId SPIRVCodeGenerator::writeVariableReference(const VariableReference& ref, O
SkASSERT(fProgram.fSettings.fRTHeightOffset >= 0);
fields.emplace_back(Modifiers(Layout(0, -1, fProgram.fSettings.fRTHeightOffset, -1,
-1, -1, -1, -1, Layout::Format::kUnspecified,
Layout::kUnspecified_Primitive, -1, -1, "",
Layout::kUnspecified_Primitive, -1, -1, "", "",
Layout::kNo_Key, Layout::CType::kDefault), 0),
SKSL_RTHEIGHT_NAME, fContext.fFloat_Type.get());
StringFragment name("sksl_synthetic_uniforms");
@ -1834,7 +1834,7 @@ SpvId SPIRVCodeGenerator::writeVariableReference(const VariableReference& ref, O
SkASSERT(binding != -1 && set != -1);
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);
Variable* intfVar = (Variable*) fSynthetics.takeOwnership(std::unique_ptr<Symbol>(
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,
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)
, fLocation(location)
, fOffset(offset)
@ -202,6 +202,7 @@ struct Layout {
, fPrimitive(primitive)
, fMaxVertices(maxVertices)
, fInvocations(invocations)
, fMarker(marker)
, fWhen(when)
, fKey(key)
, fCType(ctype) {}
@ -377,6 +378,10 @@ struct Layout {
result += separator + "invocations = " + to_string(fInvocations);
separator = ", ";
}
if (fMarker.fLength) {
result += separator + "marker = " + fMarker;
separator = ", ";
}
if (fWhen.fLength) {
result += separator + "when = " + fWhen;
separator = ", ";
@ -403,6 +408,7 @@ struct Layout {
fPrimitive == other.fPrimitive &&
fMaxVertices == other.fMaxVertices &&
fInvocations == other.fInvocations &&
fMarker == other.fMarker &&
fWhen == other.fWhen &&
fKey == other.fKey &&
fCType == other.fCType;
@ -428,6 +434,8 @@ struct Layout {
Primitive fPrimitive;
int fMaxVertices;
int fInvocations;
// marker refers to matrices tagged on the SkCanvas with markCTM
StringFragment fMarker;
StringFragment fWhen;
Key fKey;
CType fCType;