SkMeshSpecification supports uniforms.

Currently there is no way to specify the data that is bound to them.
That will come in later changes.

Bug: skia:12720
Change-Id: I3301a825486469396a13a4095b66d9e0b81f183b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/543716
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Brian Salomon 2022-05-31 14:15:46 -04:00 committed by SkCQ
parent 479fa1c9ca
commit 586e6354aa
7 changed files with 486 additions and 90 deletions

View File

@ -16,6 +16,7 @@
#include "include/core/SkRefCnt.h" #include "include/core/SkRefCnt.h"
#include "include/core/SkSpan.h" #include "include/core/SkSpan.h"
#include "include/core/SkString.h" #include "include/core/SkString.h"
#include "include/effects/SkRuntimeEffect.h"
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -44,6 +45,10 @@ namespace SkSL { struct Program; }
* return type is void then the interpolated position from vertex shader return is used as the local * return type is void then the interpolated position from vertex shader return is used as the local
* coordinate. If the color variant is used it will be blended with SkShader (or SkPaint color in * coordinate. If the color variant is used it will be blended with SkShader (or SkPaint color in
* absence of a shader) using the SkBlender provided to the SkCanvas draw call. * absence of a shader) using the SkBlender provided to the SkCanvas draw call.
*
* The vertex and fragment programs may both contain uniforms. Uniforms with the same name are
* assumed to be shared between stages. It is an error to specify uniforms in the vertex and
* fragment program with the same name but different types, dimensionality, or layouts.
*/ */
class SkMeshSpecification : public SkNVRefCnt<SkMeshSpecification> { class SkMeshSpecification : public SkNVRefCnt<SkMeshSpecification> {
public: public:
@ -86,6 +91,8 @@ public:
SkString name; SkString name;
}; };
using Uniform = SkRuntimeEffect::Uniform;
~SkMeshSpecification(); ~SkMeshSpecification();
struct Result { struct Result {
@ -136,6 +143,13 @@ public:
SkSpan<const Attribute> attributes() const { return SkMakeSpan(fAttributes); } SkSpan<const Attribute> attributes() const { return SkMakeSpan(fAttributes); }
size_t uniformSize() const;
SkSpan<const Uniform> uniforms() const { return SkMakeSpan(fUniforms); }
/** Returns pointer to the named uniform variable's description, or nullptr if not found. */
const Uniform* findUniform(const char* name) const;
size_t stride() const { return fStride; } size_t stride() const { return fStride; }
private: private:
@ -158,8 +172,9 @@ private:
SkMeshSpecification(SkSpan<const Attribute>, SkMeshSpecification(SkSpan<const Attribute>,
size_t, size_t,
SkSpan<const Varying>, SkSpan<const Varying>,
std::unique_ptr<SkSL::Program>, std::vector<Uniform> uniforms,
std::unique_ptr<SkSL::Program>, std::unique_ptr<const SkSL::Program>,
std::unique_ptr<const SkSL::Program>,
ColorType, ColorType,
bool hasLocalCoords, bool hasLocalCoords,
sk_sp<SkColorSpace>, sk_sp<SkColorSpace>,
@ -171,16 +186,17 @@ private:
SkMeshSpecification& operator=(const SkMeshSpecification&) = delete; SkMeshSpecification& operator=(const SkMeshSpecification&) = delete;
SkMeshSpecification& operator=(SkMeshSpecification&&) = delete; SkMeshSpecification& operator=(SkMeshSpecification&&) = delete;
const std::vector<Attribute> fAttributes; const std::vector<Attribute> fAttributes;
const std::vector<Varying> fVaryings; const std::vector<Varying> fVaryings;
std::unique_ptr<SkSL::Program> fVS; const std::vector<Uniform> fUniforms;
std::unique_ptr<SkSL::Program> fFS; const std::unique_ptr<const SkSL::Program> fVS;
size_t fStride; const std::unique_ptr<const SkSL::Program> fFS;
uint32_t fHash; const size_t fStride;
ColorType fColorType; uint32_t fHash;
bool fHasLocalCoords; const ColorType fColorType;
sk_sp<SkColorSpace> fColorSpace; const bool fHasLocalCoords;
SkAlphaType fAlphaType; const sk_sp<SkColorSpace> fColorSpace;
const SkAlphaType fAlphaType;
}; };
/** /**

View File

@ -68,13 +68,21 @@ public:
}; };
enum Flags { enum Flags {
// Uniform is an declared as an array. 'count' contains array length. // Uniform is declared as an array. 'count' contains array length.
kArray_Flag = 0x1, kArray_Flag = 0x1,
// Uniform is declared with layout(color). Colors should be supplied as unpremultiplied, // Uniform is declared with layout(color). Colors should be supplied as unpremultiplied,
// extended-range (unclamped) sRGB (ie SkColor4f). The uniform will be automatically // extended-range (unclamped) sRGB (ie SkColor4f). The uniform will be automatically
// transformed to unpremultiplied extended-range working-space colors. // transformed to unpremultiplied extended-range working-space colors.
kColor_Flag = 0x2, kColor_Flag = 0x2,
// When used with SkMeshSpecification, indicates that the uniform is present in the
// vertex shader. Not used with SkRuntimeEffect.
kVertex_Flag = 0x4,
// When used with SkMeshSpecification, indicates that the uniform is present in the
// fragment shader. Not used with SkRuntimeEffect.
kFragment_Flag = 0x8,
}; };
SkString name; SkString name;

View File

@ -15,6 +15,7 @@
#include "include/private/SkSLProgramElement.h" #include "include/private/SkSLProgramElement.h"
#include "include/private/SkSLProgramKind.h" #include "include/private/SkSLProgramKind.h"
#include "src/core/SkMeshPriv.h" #include "src/core/SkMeshPriv.h"
#include "src/core/SkRuntimeEffectPriv.h"
#include "src/core/SkSafeMath.h" #include "src/core/SkSafeMath.h"
#include "src/sksl/SkSLAnalysis.h" #include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLBuiltinTypes.h" #include "src/sksl/SkSLBuiltinTypes.h"
@ -24,6 +25,7 @@
#include "src/sksl/ir/SkSLFunctionDefinition.h" #include "src/sksl/ir/SkSLFunctionDefinition.h"
#include "src/sksl/ir/SkSLProgram.h" #include "src/sksl/ir/SkSLProgram.h"
#include "src/sksl/ir/SkSLType.h" #include "src/sksl/ir/SkSLType.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/sksl/ir/SkSLVariable.h" #include "src/sksl/ir/SkSLVariable.h"
#include <locale> #include <locale>
@ -44,17 +46,64 @@ using VertexBuffer = SkMesh::VertexBuffer;
#define RETURN_SUCCESS return std::make_tuple(true, SkString{}) #define RETURN_SUCCESS return std::make_tuple(true, SkString{})
static bool has_main(const SkSL::Program& p) { using Uniform = SkMeshSpecification::Uniform;
for (const SkSL::ProgramElement* elem : p.elements()) {
static std::vector<Uniform>::iterator find_uniform(std::vector<Uniform>& uniforms,
std::string_view name) {
return std::find_if(uniforms.begin(), uniforms.end(),
[name](const SkMeshSpecification::Uniform& u) {
return u.name.equals(name.data(), name.size());
});
}
static std::tuple<bool, SkString>
gather_uniforms_and_check_for_main(const SkSL::Program& program,
std::vector<Uniform>* uniforms,
SkMeshSpecification::Uniform::Flags stage,
size_t* offset) {
bool foundMain = false;
for (const SkSL::ProgramElement* elem : program.elements()) {
if (elem->is<SkSL::FunctionDefinition>()) { if (elem->is<SkSL::FunctionDefinition>()) {
const SkSL::FunctionDefinition& defn = elem->as<SkSL::FunctionDefinition>(); const SkSL::FunctionDefinition& defn = elem->as<SkSL::FunctionDefinition>();
const SkSL::FunctionDeclaration& decl = defn.declaration(); const SkSL::FunctionDeclaration& decl = defn.declaration();
if (decl.isMain()) { if (decl.isMain()) {
return true; foundMain = true;
}
} else if (elem->is<SkSL::GlobalVarDeclaration>()) {
const SkSL::GlobalVarDeclaration& global = elem->as<SkSL::GlobalVarDeclaration>();
const SkSL::VarDeclaration& varDecl = global.declaration()->as<SkSL::VarDeclaration>();
const SkSL::Variable& var = varDecl.var();
if (var.modifiers().fFlags & SkSL::Modifiers::kUniform_Flag) {
auto iter = find_uniform(*uniforms, var.name());
const auto& context = *program.fContext;
if (iter == uniforms->end()) {
uniforms->push_back(SkRuntimeEffectPriv::VarAsUniform(var, context, offset));
uniforms->back().flags |= stage;
} else {
// Check that the two declarations are equivalent
size_t ignoredOffset = 0;
auto uniform = SkRuntimeEffectPriv::VarAsUniform(var, context, &ignoredOffset);
if (uniform.isArray() != iter->isArray() ||
uniform.type != iter->type ||
uniform.count != iter->count) {
return {false, SkStringPrintf("Uniform %s declared with different types"
" in vertex and fragment shaders.",
iter->name.c_str())};
}
if (uniform.isColor() != iter->isColor()) {
return {false, SkStringPrintf("Uniform %s declared with different color"
" layout in vertex and fragment shaders.",
iter->name.c_str())};
}
(*iter).flags |= stage;
}
} }
} }
} }
return false; if (!foundMain) {
return {false, SkString("No main function found.")};
}
return {true, {}};
} }
using ColorType = SkMeshSpecificationPriv::ColorType; using ColorType = SkMeshSpecificationPriv::ColorType;
@ -278,6 +327,9 @@ SkMeshSpecification::Result SkMeshSpecification::MakeFromSourceWithStructs(
} }
} }
std::vector<Uniform> uniforms;
size_t offset = 0;
SkSL::SharedCompiler compiler; SkSL::SharedCompiler compiler;
SkSL::Program::Settings settings; SkSL::Program::Settings settings;
// TODO(skia:11209): Add SkCapabilities to the API, check against required version. // TODO(skia:11209): Add SkCapabilities to the API, check against required version.
@ -288,9 +340,16 @@ SkMeshSpecification::Result SkMeshSpecification::MakeFromSourceWithStructs(
if (!vsProgram) { if (!vsProgram) {
RETURN_FAILURE("VS: %s", compiler->errorText().c_str()); RETURN_FAILURE("VS: %s", compiler->errorText().c_str());
} }
if (!has_main(*vsProgram)) {
RETURN_FAILURE("Vertex shader must have main function."); if (auto [result, error] = gather_uniforms_and_check_for_main(
*vsProgram,
&uniforms,
SkMeshSpecification::Uniform::Flags::kVertex_Flag,
&offset);
!result) {
return {nullptr, std::move(error)};
} }
if (SkSL::Analysis::CallsColorTransformIntrinsics(*vsProgram)) { if (SkSL::Analysis::CallsColorTransformIntrinsics(*vsProgram)) {
RETURN_FAILURE("Color transform intrinsics are not permitted in custom mesh shaders"); RETURN_FAILURE("Color transform intrinsics are not permitted in custom mesh shaders");
} }
@ -303,9 +362,16 @@ SkMeshSpecification::Result SkMeshSpecification::MakeFromSourceWithStructs(
if (!fsProgram) { if (!fsProgram) {
RETURN_FAILURE("FS: %s", compiler->errorText().c_str()); RETURN_FAILURE("FS: %s", compiler->errorText().c_str());
} }
if (!has_main(*fsProgram)) {
RETURN_FAILURE("Fragment shader must have main function."); if (auto [result, error] = gather_uniforms_and_check_for_main(
*fsProgram,
&uniforms,
SkMeshSpecification::Uniform::Flags::kFragment_Flag,
&offset);
!result) {
return {nullptr, std::move(error)};
} }
if (SkSL::Analysis::CallsColorTransformIntrinsics(*fsProgram)) { if (SkSL::Analysis::CallsColorTransformIntrinsics(*fsProgram)) {
RETURN_FAILURE("Color transform intrinsics are not permitted in custom mesh shaders"); RETURN_FAILURE("Color transform intrinsics are not permitted in custom mesh shaders");
} }
@ -327,6 +393,7 @@ SkMeshSpecification::Result SkMeshSpecification::MakeFromSourceWithStructs(
return {sk_sp<SkMeshSpecification>(new SkMeshSpecification(attributes, return {sk_sp<SkMeshSpecification>(new SkMeshSpecification(attributes,
stride, stride,
varyings, varyings,
std::move(uniforms),
std::move(vsProgram), std::move(vsProgram),
std::move(fsProgram), std::move(fsProgram),
ct, ct,
@ -338,17 +405,19 @@ SkMeshSpecification::Result SkMeshSpecification::MakeFromSourceWithStructs(
SkMeshSpecification::~SkMeshSpecification() = default; SkMeshSpecification::~SkMeshSpecification() = default;
SkMeshSpecification::SkMeshSpecification(SkSpan<const Attribute> attributes, SkMeshSpecification::SkMeshSpecification(SkSpan<const Attribute> attributes,
size_t stride, size_t stride,
SkSpan<const Varying> varyings, SkSpan<const Varying> varyings,
std::unique_ptr<SkSL::Program> vs, std::vector<Uniform> uniforms,
std::unique_ptr<SkSL::Program> fs, std::unique_ptr<const SkSL::Program> vs,
ColorType ct, std::unique_ptr<const SkSL::Program> fs,
bool hasLocalCoords, ColorType ct,
sk_sp<SkColorSpace> cs, bool hasLocalCoords,
SkAlphaType at) sk_sp<SkColorSpace> cs,
SkAlphaType at)
: fAttributes(attributes.begin(), attributes.end()) : fAttributes(attributes.begin(), attributes.end())
, fVaryings(varyings.begin(), varyings.end()) , fVaryings(varyings.begin(), varyings.end())
, fUniforms(std::move(uniforms))
, fVS(std::move(vs)) , fVS(std::move(vs))
, fFS(std::move(fs)) , fFS(std::move(fs))
, fStride(stride) , fStride(stride)
@ -376,6 +445,22 @@ SkMeshSpecification::SkMeshSpecification(SkSpan<const Attribute> attribut
fHash = SkOpts::hash_fn(&atInt, sizeof(atInt), fHash); fHash = SkOpts::hash_fn(&atInt, sizeof(atInt), fHash);
} }
size_t SkMeshSpecification::uniformSize() const {
return fUniforms.empty() ? 0
: SkAlign4(fUniforms.back().offset + fUniforms.back().sizeInBytes());
}
const Uniform* SkMeshSpecification::findUniform(const char* name) const {
SkASSERT(name);
size_t len = strlen(name);
auto iter = std::find_if(fUniforms.begin(), fUniforms.end(), [name, len] (const Uniform& u) {
return u.name.equals(name, len);
});
return iter == fUniforms.end() ? nullptr : &(*iter);
}
//////////////////////////////////////////////////////////////////////////////
SkMesh::SkMesh() = default; SkMesh::SkMesh() = default;
SkMesh::~SkMesh() = default; SkMesh::~SkMesh() = default;

View File

@ -59,30 +59,6 @@
using ChildType = SkRuntimeEffect::ChildType; using ChildType = SkRuntimeEffect::ChildType;
static bool flattenable_is_valid_as_child(const SkFlattenable* f) {
if (!f) { return true; }
switch (f->getFlattenableType()) {
case SkFlattenable::kSkShader_Type:
case SkFlattenable::kSkColorFilter_Type:
case SkFlattenable::kSkBlender_Type:
return true;
default:
return false;
}
}
SkRuntimeEffect::ChildPtr::ChildPtr(sk_sp<SkFlattenable> f) : fChild(std::move(f)) {
SkASSERT(flattenable_is_valid_as_child(fChild.get()));
}
static sk_sp<SkSL::SkVMDebugTrace> make_skvm_debug_trace(SkRuntimeEffect* effect,
const SkIPoint& coord) {
auto debugTrace = sk_make_sp<SkSL::SkVMDebugTrace>();
debugTrace->setSource(effect->source());
debugTrace->setTraceCoord(coord);
return debugTrace;
}
static bool init_uniform_type(const SkSL::Context& ctx, static bool init_uniform_type(const SkSL::Context& ctx,
const SkSL::Type* type, const SkSL::Type* type,
SkRuntimeEffect::Uniform* v) { SkRuntimeEffect::Uniform* v) {
@ -110,6 +86,60 @@ static bool init_uniform_type(const SkSL::Context& ctx,
return false; return false;
} }
SkRuntimeEffect::Uniform SkRuntimeEffectPriv::VarAsUniform(const SkSL::Variable& var,
const SkSL::Context& context,
size_t* offset) {
using Uniform = SkRuntimeEffect::Uniform;
SkASSERT(var.modifiers().fFlags & SkSL::Modifiers::kUniform_Flag);
Uniform uni;
uni.name = SkString(var.name());
uni.flags = 0;
uni.count = 1;
const SkSL::Type* type = &var.type();
if (type->isArray()) {
uni.flags |= Uniform::kArray_Flag;
uni.count = type->columns();
type = &type->componentType();
}
SkAssertResult(init_uniform_type(context, type, &uni));
if (var.modifiers().fLayout.fFlags & SkSL::Layout::Flag::kColor_Flag) {
uni.flags |= Uniform::kColor_Flag;
}
uni.offset = *offset;
*offset += uni.sizeInBytes();
SkASSERT(SkIsAlign4(*offset));
return uni;
}
//////////////////////////////////////////////////////////////////////////////
static bool flattenable_is_valid_as_child(const SkFlattenable* f) {
if (!f) { return true; }
switch (f->getFlattenableType()) {
case SkFlattenable::kSkShader_Type:
case SkFlattenable::kSkColorFilter_Type:
case SkFlattenable::kSkBlender_Type:
return true;
default:
return false;
}
}
SkRuntimeEffect::ChildPtr::ChildPtr(sk_sp<SkFlattenable> f) : fChild(std::move(f)) {
SkASSERT(flattenable_is_valid_as_child(fChild.get()));
}
static sk_sp<SkSL::SkVMDebugTrace> make_skvm_debug_trace(SkRuntimeEffect* effect,
const SkIPoint& coord) {
auto debugTrace = sk_make_sp<SkSL::SkVMDebugTrace>();
debugTrace->setSource(effect->source());
debugTrace->setTraceCoord(coord);
return debugTrace;
}
static ChildType child_type(const SkSL::Type& type) { static ChildType child_type(const SkSL::Type& type) {
switch (type.typeKind()) { switch (type.typeKind()) {
case SkSL::Type::TypeKind::kBlender: return ChildType::kBlender; case SkSL::Type::TypeKind::kBlender: return ChildType::kBlender;
@ -324,28 +354,7 @@ SkRuntimeEffect::Result SkRuntimeEffect::MakeInternal(std::unique_ptr<SkSL::Prog
} }
// 'uniform' variables // 'uniform' variables
else if (var.modifiers().fFlags & SkSL::Modifiers::kUniform_Flag) { else if (var.modifiers().fFlags & SkSL::Modifiers::kUniform_Flag) {
Uniform uni; uniforms.push_back(SkRuntimeEffectPriv::VarAsUniform(var, ctx, &offset));
uni.name = SkString(var.name());
uni.flags = 0;
uni.count = 1;
const SkSL::Type* type = &var.type();
if (type->isArray()) {
uni.flags |= Uniform::kArray_Flag;
uni.count = type->columns();
type = &type->componentType();
}
SkAssertResult(init_uniform_type(ctx, type, &uni));
if (var.modifiers().fLayout.fFlags & SkSL::Layout::Flag::kColor_Flag) {
uni.flags |= Uniform::kColor_Flag;
}
uni.offset = offset;
offset += uni.sizeInBytes();
SkASSERT(SkIsAlign4(offset));
uniforms.push_back(uni);
} }
} }
} }

View File

@ -16,6 +16,11 @@
#ifdef SK_ENABLE_SKSL #ifdef SK_ENABLE_SKSL
namespace SkSL {
class Variable;
class Context;
}
class SkRuntimeEffectPriv { class SkRuntimeEffectPriv {
public: public:
// Helper function when creating an effect for a GrSkSLFP that verifies an effect will // Helper function when creating an effect for a GrSkSLFP that verifies an effect will
@ -33,6 +38,10 @@ public:
static void UsePrivateRTShaderModule(SkRuntimeEffect::Options* options) { static void UsePrivateRTShaderModule(SkRuntimeEffect::Options* options) {
options->usePrivateRTShaderModule = true; options->usePrivateRTShaderModule = true;
} }
static SkRuntimeEffect::Uniform VarAsUniform(const SkSL::Variable&,
const SkSL::Context&,
size_t* offset);
}; };
// These internal APIs for creating runtime effects vary from the public API in two ways: // These internal APIs for creating runtime effects vary from the public API in two ways:

View File

@ -147,10 +147,9 @@ void VarDeclaration::ErrorCheck(const Context& context,
context.fErrors->error(pos, context.fErrors->error(pos,
"variables of type '" + baseType->displayName() + "' must be uniform"); "variables of type '" + baseType->displayName() + "' must be uniform");
} }
if (modifiers.fFlags & Modifiers::kUniform_Flag && if (baseType->isEffectChild() && (context.fConfig->fKind == ProgramKind::kMeshVertex ||
(context.fConfig->fKind == ProgramKind::kMeshVertex || context.fConfig->fKind == ProgramKind::kMeshFragment)) {
context.fConfig->fKind == ProgramKind::kMeshFragment)) { context.fErrors->error(pos, "effects are not permitted in custom mesh shaders");
context.fErrors->error(pos, "uniforms are not permitted in custom mesh shaders");
} }
if (modifiers.fLayout.fFlags & Layout::kColor_Flag) { if (modifiers.fLayout.fFlags & Layout::kColor_Flag) {
if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) { if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {

View File

@ -7,6 +7,7 @@
#include "include/core/SkCanvas.h" #include "include/core/SkCanvas.h"
#include "include/core/SkMesh.h" #include "include/core/SkMesh.h"
#include "src/core/SkZip.h"
#include "tests/Test.h" #include "tests/Test.h"
using Attribute = SkMeshSpecification::Attribute; using Attribute = SkMeshSpecification::Attribute;
@ -84,15 +85,19 @@ static bool check_for_failure(skiatest::Reporter* r,
return false; return false;
} }
static bool check_for_success(skiatest::Reporter* r, static bool check_for_success(skiatest::Reporter* r,
SkSpan<const Attribute> attributes, SkSpan<const Attribute> attributes,
size_t stride, size_t stride,
SkSpan<const Varying> varyings, SkSpan<const Varying> varyings,
const SkString& vs, const SkString& vs,
const SkString& fs) { const SkString& fs,
auto [spec, error] = SkMeshSpecification::Make(attributes, stride, varyings, vs, fs); sk_sp<SkMeshSpecification>* spec = nullptr) {
if (spec) { auto [s, error] = SkMeshSpecification::Make(attributes, stride, varyings, vs, fs);
if (s) {
REPORTER_ASSERT(r, error.isEmpty()); REPORTER_ASSERT(r, error.isEmpty());
if (spec) {
*spec = std::move(s);
}
return true; return true;
} }
ERRORF(r, ERRORF(r,
@ -233,10 +238,9 @@ static void test_float4_color(skiatest::Reporter* r) {
kFloat4FS); kFloat4FS);
} }
// We don't allow value or child uniforms in custom meshes currently. // We don't allow child effects in custom meshes currently.
static void test_bad_globals(skiatest::Reporter* r) { static void test_bad_globals(skiatest::Reporter* r) {
static constexpr const char* kBadGlobals[] { static constexpr const char* kBadGlobals[] {
"uniform float3 uni;"
"uniform shader myshader;" "uniform shader myshader;"
}; };
for (const auto& global : kBadGlobals) { for (const auto& global : kBadGlobals) {
@ -265,6 +269,270 @@ static void test_bad_globals(skiatest::Reporter* r) {
} }
} }
static void test_good_uniforms(skiatest::Reporter* r) {
using Uniform = SkMeshSpecification::Uniform;
using Type = Uniform::Type;
using Flags = Uniform::Flags;
constexpr Flags kVS = Uniform::kVertex_Flag;
constexpr Flags kFS = Uniform::kFragment_Flag;
constexpr Flags kColor = Uniform::kColor_Flag;
auto make_uni = [](Type type, const char* name, size_t offset, uint32_t flags, int count = 0) {
if (count) {
return Uniform{SkString(name), offset, type, count, flags | Uniform::kArray_Flag};
} else {
SkASSERT(!(flags & Uniform::kArray_Flag));
return Uniform{SkString(name), offset, type, 1, flags};
}
};
// Each test case is a set of VS and FS uniform declarations followed and the expected output
// of SkMeshSpecification::uniforms().
struct {
const std::vector<const char*> vsUniformDecls;
const std::vector<const char*> fsUniformDecls;
const std::vector<SkMeshSpecification::Uniform> expectations;
} static kTestCases[] {
// A single VS uniform.
{
{
"uniform float x;"
},
{},
{
make_uni(Type::kFloat, "x", 0, kVS)
}
},
// A single FS uniform.
{
{},
{
"uniform float2 v;"
},
{
make_uni(Type::kFloat2, "v", 0, kFS)
}
},
// A single uniform in both that uses color layout.
{
{
"layout(color) uniform float4 color;",
},
{
"layout(color) uniform float4 color;",
},
{
make_uni(Type::kFloat4, "color", 0, kVS|kFS|kColor)
}
},
// A shared uniform after an unshared vertex uniform
{
{
"layout(color) uniform float4 color;",
" uniform float x[5];",
},
{
"uniform float x[5];",
},
{
make_uni(Type::kFloat4, "color", 0, kVS|kColor, 0),
make_uni(Type::kFloat , "x" , 16, kVS|kFS , 5)
}
},
// A shared uniform before an unshared vertex uniform
{
{
"uniform half x[2];",
"uniform int y;",
},
{
"uniform half x[2];",
},
{
make_uni(Type::kFloat, "x", 0, kVS|kFS, 2),
make_uni(Type::kInt, "y", 8, kVS , 0)
}
},
// A shared uniform after an unshared fragment uniform
{
{
"uniform float3x3 m;",
},
{
"uniform int2 i2;",
"uniform float3x3 m;",
},
{
make_uni(Type::kFloat3x3, "m" , 0, kVS|kFS),
make_uni(Type::kInt2 , "i2", 36, kFS )
}
},
// A shared uniform before an unshared fragment uniform
{
{
"uniform half4x4 m[4];",
},
{
"uniform half4x4 m[4];",
"uniform int3 i3[1];",
},
{
make_uni(Type::kFloat4x4, "m", 0, kVS|kFS, 4),
make_uni(Type::kInt3, "i3", 256, kFS , 1)
}
},
// Complex case with 2 shared uniforms that are declared in the opposite order.
{
{
"uniform float x;"
"uniform half4x4 m[4];", // shared
"uniform int2 i2[2];"
"uniform float3 v[8];" // shared
"uniform int3 i3;"
},
{
"uniform float y;"
"uniform float3 v[8];" // shared
"uniform int4 i4[2];"
"uniform half4x4 m[4];", // shared
"uniform int i;"
},
{
make_uni(Type::kFloat, "x" , 0, kVS , 0),
make_uni(Type::kFloat4x4, "m" , 4, kVS|kFS, 4),
make_uni(Type::kInt2, "i2", 260, kVS , 2),
make_uni(Type::kFloat3, "v" , 276, kVS|kFS, 8),
make_uni(Type::kInt3, "i3", 372, kVS , 0),
make_uni(Type::kFloat, "y" , 384, kFS , 0),
make_uni(Type::kInt4, "i4", 388, kFS , 2),
make_uni(Type::kInt, "i" , 420, kFS , 0),
}
},
};
for (const auto& c : kTestCases) {
SkString vs = kValidVS;
SkString unis;
for (const auto u : c.vsUniformDecls) {
unis.append(u);
}
vs.prepend(unis);
SkString fs = kValidFSes[0];
unis = {};
for (const auto u : c.fsUniformDecls) {
unis.append(u);
}
fs.prepend(unis);
auto attrs = SkMakeSpan(kValidAttrs , SK_ARRAY_COUNT(kValidAttrs) );
auto varys = SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings));
sk_sp<SkMeshSpecification> spec;
if (!check_for_success(r, attrs, kValidStride, varys, vs, fs, &spec)) {
return;
}
SkString desc = make_description(attrs, kValidStride, varys, vs, fs);
auto uniforms = spec->uniforms();
if (uniforms.size() != c.expectations.size()) {
ERRORF(r,
"Expected %zu uniforms but actually %zu:\n%s",
c.expectations.size(),
uniforms.size(),
desc.c_str());
return;
}
for (const auto& [actual, expected] : SkMakeZip(uniforms, c.expectations)) {
if (actual.name != expected.name) {
ERRORF(r,
"Actual uniform name (%s) does not match expected name (%s)",
actual.name.c_str(),
expected.name.c_str());
return;
}
const char* name = actual.name.c_str();
if (actual.type != expected.type) {
ERRORF(r,
"Uniform %s: Actual type (%d) does not match expected type (%d)",
name,
static_cast<int>(actual.type),
static_cast<int>(expected.type));
return;
}
if (actual.count != expected.count) {
ERRORF(r,
"Uniform %s: Actual count (%d) does not match expected count (%d)",
name,
actual.count,
expected.count);
return;
}
if (actual.flags != expected.flags) {
ERRORF(r,
"Uniform %s: Actual flags (0x%04x) do not match expected flags (0x%04x)",
name,
actual.flags,
expected.flags);
return;
}
if (actual.offset != expected.offset) {
ERRORF(r,
"Uniform %s: Actual offset (%zu) does not match expected offset (%zu)",
name,
actual.offset,
expected.offset);
return;
}
}
}
}
static void test_bad_uniforms(skiatest::Reporter* r) {
// We assume general uniform declarations are broadly tested generically in SkSL. Here we are
// concerned with agreement between VS and FS declarations, which is a unique aspect of
// SkMeshSpecification.
// Each test case is a fs and vs uniform declaration with the same name but some other
// difference that should make them incompatible.
static std::tuple<const char*, const char*> kTestCases[]{
// different types
{"uniform float x;", "uniform int x;"},
// array vs non-array
{"uniform float2x2 m[1];", "uniform float2x2 m;"},
// array count mismatch
{"uniform int3 i[1];", "uniform int3 i[2];"},
// layout difference
{"layout(color) uniform float4 color;", "uniform float4 color;"},
};
for (bool reverse : {false, true}) {
for (auto [u1, u2] : kTestCases) {
if (reverse) {
using std::swap;
swap(u1, u2);
}
SkString vs = kValidVS;
vs.prepend(u1);
SkString fs = kValidFSes[0];
fs.prepend(u2);
auto attrs = SkMakeSpan(kValidAttrs , SK_ARRAY_COUNT(kValidAttrs) );
auto varys = SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings));
if (!check_for_failure(r, attrs, kValidStride, varys, vs, fs)) {
return;
}
}
}
}
static void test_no_main(skiatest::Reporter* r) { static void test_no_main(skiatest::Reporter* r) {
static const SkString kHelper{"float2 swiz(float2 x) { return z.yx; }"}; static const SkString kHelper{"float2 swiz(float2 x) { return z.yx; }"};
@ -516,6 +784,8 @@ DEF_TEST(MeshSpec, reporter) {
test_bad_sig(reporter); test_bad_sig(reporter);
test_float4_color(reporter); test_float4_color(reporter);
test_bad_globals(reporter); test_bad_globals(reporter);
test_good_uniforms(reporter);
test_bad_uniforms(reporter);
test_no_main(reporter); test_no_main(reporter);
test_zero_attrs(reporter); test_zero_attrs(reporter);
test_zero_varyings(reporter); test_zero_varyings(reporter);