/* * Copyright 2021 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/core/SkBlendMode.h" #include "include/core/SkBlender.h" #include "include/core/SkColor.h" #include "include/core/SkColorFilter.h" #include "include/core/SkData.h" #include "include/core/SkMesh.h" #include "include/core/SkRect.h" #include "include/core/SkRefCnt.h" #include "include/core/SkShader.h" #include "include/core/SkSpan.h" #include "include/core/SkString.h" #include "include/core/SkTypes.h" #include "include/effects/SkRuntimeEffect.h" #include "src/base/SkZip.h" #include "src/core/SkMeshPriv.h" #include "tests/Test.h" #include #include #include #include #include #include #include #include #include #include using Attribute = SkMeshSpecification::Attribute; using Varying = SkMeshSpecification::Varying; static const char* attr_type_str(const Attribute::Type type) { switch (type) { case Attribute::Type::kFloat: return "float"; case Attribute::Type::kFloat2: return "float2"; case Attribute::Type::kFloat3: return "float3"; case Attribute::Type::kFloat4: return "float4"; case Attribute::Type::kUByte4_unorm: return "ubyte4_unorm"; } SkUNREACHABLE; } static const char* var_type_str(const Varying::Type type) { switch (type) { case Varying::Type::kFloat: return "float"; case Varying::Type::kFloat2: return "float2"; case Varying::Type::kFloat3: return "float3"; case Varying::Type::kFloat4: return "float4"; case Varying::Type::kHalf: return "half"; case Varying::Type::kHalf2: return "half2"; case Varying::Type::kHalf3: return "half3"; case Varying::Type::kHalf4: return "half4"; } SkUNREACHABLE; } static SkString make_description(SkSpan attributes, size_t stride, SkSpan varyings, const SkString& vs, const SkString& fs) { static constexpr size_t kMax = 10; SkString result; result.appendf("Attributes (count=%zu, stride=%zu):\n", attributes.size(), stride); for (size_t i = 0; i < std::min(kMax, attributes.size()); ++i) { const auto& a = attributes[i]; result.appendf(" {%-10s, %3zu, \"%s\"}\n", attr_type_str(a.type), a.offset, a.name.c_str()); } if (kMax < attributes.size()) { result.append(" ...\n"); } result.appendf("Varyings (count=%zu):\n", varyings.size()); for (size_t i = 0; i < std::min(kMax, varyings.size()); ++i) { const auto& v = varyings[i]; result.appendf(" {%5s, \"%s\"}\n", var_type_str(v.type), v.name.c_str()); } if (kMax < varyings.size()) { result.append(" ...\n"); } result.appendf("\n--VS--\n%s\n------\n", vs.c_str()); result.appendf("\n--FS--\n%s\n------\n", fs.c_str()); return result; } static bool check_for_failure(skiatest::Reporter* reporter, SkSpan attributes, size_t stride, SkSpan varyings, const SkString& vs, const SkString& fs, const char* expectedErrorSubstring = nullptr) { auto [spec, error] = SkMeshSpecification::Make(attributes, stride, varyings, vs, fs); if (spec) { ERRORF(reporter, "Expected to fail but succeeded:\n%s", make_description(attributes, stride, varyings, vs, fs).c_str()); return false; } if (expectedErrorSubstring && !error.contains(expectedErrorSubstring)) { ERRORF(reporter, " Expected: %s\n" "Actual error: %s\n", expectedErrorSubstring, error.c_str()); return false; } return true; } static bool check_for_success(skiatest::Reporter* reporter, SkSpan attributes, size_t stride, SkSpan varyings, const SkString& vs, const SkString& fs, sk_sp* spec = nullptr) { auto [s, error] = SkMeshSpecification::Make(attributes, stride, varyings, vs, fs); if (s) { REPORTER_ASSERT(reporter, error.isEmpty()); if (spec) { *spec = std::move(s); } return true; } ERRORF(reporter, "Expected to succeed but failed:\n%sError:\n%s", make_description(attributes, stride, varyings, vs, fs).c_str(), error.c_str()); return false; } // Simple valid strings to make specifications static const SkString kValidVS {R"( Varyings main(const Attributes attrs) { Varyings v; return v; })"}; // There are multiple valid VS signatures. static const SkString kValidFSes[]{ SkString{"float2 main(const Varyings varyings) { return float2(10); }"}, SkString{R"( float2 main(const Varyings varyings, out half4 color) { color = half4(.2); return float2(10); } )"}, }; // Simple valid attributes, stride, and varyings to make specifications static const Attribute kValidAttrs[] = { {Attribute::Type::kFloat4, 0, SkString{"pos"}}, }; static constexpr size_t kValidStride = 4*4; static const Varying kValidVaryings[] = { {Varying::Type::kFloat2, SkString{"uv"}}, }; DEF_TEST(MeshSpec_Valid, reporter) { for (const auto& validFS : kValidFSes) { if (!check_for_success(reporter, kValidAttrs, kValidStride, kValidVaryings, kValidVS, validFS)) { return; } } } DEF_TEST(MeshSpec_InvalidSignature, reporter) { static constexpr const char* kVSBody = "{ return float2(10); }"; static constexpr const char* kInvalidVSSigs[] { "float3 main(const Attributes attrs)", // bad return "Varyings main(Attributes attrs)", // non-const Attributes "Varyings main(out Attributes attrs)", // out Varyings "Varyings main()", // no Attributes "Varyings main(const Varyings v, float2)" // extra arg }; static constexpr const char* kNoColorFSBody = "{ return float2(10); }"; static constexpr const char* kInvalidNoColorFSSigs[] { "half2 main(const Varyings v)", // bad return "float2 main(const Attributes v)", // wrong param type "float2 main(inout Varyings attrs)", // inout Varyings "float2 main(Varyings v)", // non-const Varyings "float2 main()", // no args "float2 main(const Varyings, float)" // extra arg }; static constexpr const char* kColorFSBody = "{ color = half4(.2); return float2(10); }"; static constexpr const char* kInvalidColorFSSigs[] { "half2 main(const Varyings v, out half4 color)", // bad return "float2 main(const Attributes v, out half4 color)", // wrong first param type "float2 main(const Varyings v, out half3 color)", // wrong second param type "float2 main(out Varyings v, out half4 color)", // out Varyings "float2 main(const Varyings v, half4 color)", // in color "float2 main(const Varyings v, out half4 color, float)" // extra arg }; for (const char* vsSig : kInvalidVSSigs) { SkString invalidVS; invalidVS.appendf("%s %s", vsSig, kVSBody); for (const auto& validFS : kValidFSes) { if (!check_for_failure(reporter, kValidAttrs, kValidStride, kValidVaryings, invalidVS, validFS)) { return; } } } for (const char* noColorFSSig : kInvalidNoColorFSSigs) { SkString invalidFS; invalidFS.appendf("%s %s", noColorFSSig, kNoColorFSBody); if (!check_for_failure(reporter, kValidAttrs, kValidStride, kValidVaryings, kValidVS, invalidFS)) { return; } } for (const char* colorFSSig : kInvalidColorFSSigs) { SkString invalidFS; invalidFS.appendf("%s %s", colorFSSig, kColorFSBody); if (!check_for_failure(reporter, kValidAttrs, kValidStride, kValidVaryings, kValidVS, invalidFS)) { return; } } } // We allow the optional out color from the FS to either be float4 or half4 DEF_TEST(MeshSpec_Float4Color, reporter) { static const SkString kFloat4FS { R"( float2 main(const Varyings varyings, out float4 color) { color = float4(.2); return float2(10); } )" }; check_for_success(reporter, kValidAttrs, kValidStride, kValidVaryings, kValidVS, kFloat4FS); } DEF_TEST(MeshSpec_DisallowsChildEffectInVertex, reporter) { static constexpr const char* kChildEffects[] { "uniform shader myshader;", "uniform colorFilter mycolorfilter;", "uniform blender myblender;" }; for (const auto& global : kChildEffects) { SkString vsWithChild{global}; vsWithChild.append(kValidVS); SkString fsWithChild{global}; fsWithChild.append(kValidFSes[0]); if (!check_for_failure(reporter, kValidAttrs, kValidStride, kValidVaryings, vsWithChild, kValidFSes[0], "effects are not permitted in mesh vertex shaders")) { return; } if (!check_for_failure(reporter, kValidAttrs, kValidStride, kValidVaryings, vsWithChild, fsWithChild, "effects are not permitted in mesh vertex shaders")) { return; } } } DEF_TEST(MeshSpec_AllowsChildEffectInFragment, reporter) { static constexpr const char* kChildEffects[] { "uniform shader myshader;", "uniform colorFilter mycolorfilter; uniform shader myshader;", "uniform shader myshader; uniform blender myblender; uniform colorFilter mycolorfilter;" }; for (const auto& global : kChildEffects) { SkString fsWithChild{global}; fsWithChild.append(kValidFSes[0]); if (!check_for_success(reporter, kValidAttrs, kValidStride, kValidVaryings, kValidVS, fsWithChild)) { return; } } } DEF_TEST(MeshSpec_FindChild, reporter) { SkString fsWithChild{"uniform shader myshader;" "uniform blender myblender;" "uniform colorFilter mycolorfilter;"}; fsWithChild.append(kValidFSes[0]); sk_sp meshSpec; if (!check_for_success(reporter, kValidAttrs, kValidStride, kValidVaryings, kValidVS, fsWithChild, &meshSpec)) { return; } REPORTER_ASSERT(reporter, meshSpec->findChild("myshader")->index == 0); REPORTER_ASSERT(reporter, meshSpec->findChild("myblender")->index == 1); REPORTER_ASSERT(reporter, meshSpec->findChild("mycolorfilter")->index == 2); REPORTER_ASSERT(reporter, !meshSpec->findChild("missing")); } DEF_TEST(Mesh_ChildEffectsMatchSpec, reporter) { auto test = [&](const char* prefix, SkSpan children, const char* expectedError = nullptr) { SkString fsWithChild{prefix}; fsWithChild.append(kValidFSes[0]); sk_sp meshSpec; if (!check_for_success(reporter, kValidAttrs, kValidStride, kValidVaryings, kValidVS, fsWithChild, &meshSpec)) { return; } constexpr float kVertexCount = 4; sk_sp vertexBuffer = SkMeshes::MakeVertexBuffer(nullptr, kValidStride * kVertexCount); SkMesh::Result result = SkMesh::Make(meshSpec, SkMesh::Mode::kTriangleStrip, vertexBuffer, kVertexCount, /*vertexOffset=*/0, /*uniforms=*/nullptr, children, SkRect::MakeEmpty()); if (expectedError) { REPORTER_ASSERT(reporter, !result.mesh.isValid()); REPORTER_ASSERT(reporter, result.error.contains(expectedError), "Expected: '%s'\n" " Actual: '%s'\n", expectedError, result.error.c_str()); } else { REPORTER_ASSERT(reporter, result.mesh.isValid()); REPORTER_ASSERT(reporter, result.error.isEmpty(), "Expected: no errors\n" " Actual: '%s'\n", result.error.c_str()); } }; SkRuntimeEffect::ChildPtr childShader[] = {SkShaders::Color(SK_ColorBLACK)}; SkRuntimeEffect::ChildPtr childFilter[] = {SkColorFilters::LinearToSRGBGamma()}; SkRuntimeEffect::ChildPtr childBlender[] = {SkBlender::Mode(SkBlendMode::kSrcOver)}; SkRuntimeEffect::ChildPtr childNull[1] = {}; // These are expected to report a count mismatch. test("uniform shader myshader;", {}, "The mesh specification declares 1 child effects, but the mesh supplies 0."); test("", childShader, "The mesh specification declares 0 child effects, but the mesh supplies 1."); // These are expected to report a type mismatch. test("uniform shader myshader;", childFilter, "Child effect 'myshader' was specified as a shader, but passed as a color filter."); test("uniform shader myshader;", childBlender, "Child effect 'myshader' was specified as a shader, but passed as a blender."); test("uniform colorFilter myfilter;", childShader, "Child effect 'myfilter' was specified as a color filter, but passed as a shader."); test("uniform colorFilter myfilter;", childBlender, "Child effect 'myfilter' was specified as a color filter, but passed as a blender."); test("uniform blender myblender;", childShader, "Child effect 'myblender' was specified as a blender, but passed as a shader."); test("uniform blender myblender;", childFilter, "Child effect 'myblender' was specified as a blender, but passed as a color filter."); // Null children are supported. test("uniform shader myshader;", childNull); test("uniform shader myfilter;", childNull); test("uniform shader myblender;", childNull); // Properly-typed child effects are supported. test("uniform shader myshader;", childShader); test("uniform colorFilter myfilter;", childFilter); test("uniform blender myblender;", childBlender); } DEF_TEST(MeshSpec_ValidUniforms, reporter) { 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; constexpr Flags kHalfP = Uniform::kHalfPrecision_Flag; auto make_uni = [](Type type, std::string_view name, size_t offset, uint32_t flags, int count = 0) { if (count) { return Uniform{name, offset, type, count, flags | Uniform::kArray_Flag}; } else { SkASSERT(!(flags & Uniform::kArray_Flag)); return Uniform{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 vsUniformDecls; const std::vector fsUniformDecls; const std::vector 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|kHalfP, 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|kHalfP, 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|kHalfP, 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 = SkSpan(kValidAttrs); auto varys = SkSpan(kValidVaryings); sk_sp spec; if (!check_for_success(reporter, attrs, kValidStride, varys, vs, fs, &spec)) { return; } SkString desc = make_description(attrs, kValidStride, varys, vs, fs); SkSpan uniforms = spec->uniforms(); if (uniforms.size() != c.expectations.size()) { ERRORF(reporter, "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)) { std::string name = std::string(actual.name); if (name != expected.name) { ERRORF(reporter, "Actual uniform name (%s) does not match expected name (%.*s)", name.c_str(), (int)expected.name.size(), expected.name.data()); return; } if (actual.type != expected.type) { ERRORF(reporter, "Uniform %s: Actual type (%d) does not match expected type (%d)", name.c_str(), static_cast(actual.type), static_cast(expected.type)); return; } if (actual.count != expected.count) { ERRORF(reporter, "Uniform %s: Actual count (%d) does not match expected count (%d)", name.c_str(), actual.count, expected.count); return; } if (actual.flags != expected.flags) { ERRORF(reporter, "Uniform %s: Actual flags (0x%04x) do not match expected flags (0x%04x)", name.c_str(), actual.flags, expected.flags); return; } if (actual.offset != expected.offset) { ERRORF(reporter, "Uniform %s: Actual offset (%zu) does not match expected offset (%zu)", name.c_str(), actual.offset, expected.offset); return; } } } } DEF_TEST(MeshSpec_InvalidUniforms, reporter) { // 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 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 = SkSpan(kValidAttrs); auto varys = SkSpan(kValidVaryings); if (!check_for_failure(reporter, attrs, kValidStride, varys, vs, fs)) { return; } } } } DEF_TEST(MeshSpec_MissingMain, reporter) { static const SkString kHelper{"float2 swiz(float2 x) { return z.yx; }"}; // Empty VS if (!check_for_failure(reporter, kValidAttrs, kValidStride, kValidVaryings, SkString{}, kValidFSes[0])) { return; } // VS with helper function but no main if (!check_for_failure(reporter, kValidAttrs, kValidStride, kValidVaryings, kHelper, kValidFSes[0])) { return; } // Empty FS if (!check_for_failure(reporter, kValidAttrs, kValidStride, kValidVaryings, kValidVS, SkString{})) { return; } // VS with helper function but no main if (!check_for_failure(reporter, kValidAttrs, kValidStride, kValidVaryings, kValidVS, kHelper)) { return; } } DEF_TEST(MeshSpec_ZeroAttributes, reporter) { // We require at least one attribute check_for_failure(reporter, SkSpan(), kValidStride, kValidVaryings, kValidVS, kValidFSes[0]); } DEF_TEST(MeshSpec_ZeroVaryings, reporter) { // Varyings are not required. check_for_success(reporter, kValidAttrs, kValidStride, SkSpan(), kValidVS, kValidFSes[0]); } DEF_TEST(MeshSpec_InvalidStride, reporter) { // Zero stride if (!check_for_failure(reporter, kValidAttrs, 0, kValidVaryings, kValidVS, kValidFSes[0])) { return; } // Unaligned if (!check_for_failure(reporter, kValidAttrs, kValidStride + 1, kValidVaryings, kValidVS, kValidFSes[0])) { return; } // Too large if (!check_for_failure(reporter, kValidAttrs, 1 << 20, kValidVaryings, kValidVS, kValidFSes[0])) { return; } } DEF_TEST(MeshSpec_InvalidOffset, reporter) { { // offset isn't aligned static const Attribute kAttributes[] { {Attribute::Type::kFloat4, 1, SkString{"var"}}, }; if (!check_for_failure(reporter, kAttributes, 32, kValidVaryings, kValidVS, kValidFSes[0])) { return; } } { // straddles stride boundary static const Attribute kAttributes[] { {Attribute::Type::kFloat4, 0, SkString{"var"}}, {Attribute::Type::kFloat2, 16, SkString{"var"}}, }; if (!check_for_failure(reporter, kAttributes, 20, kValidVaryings, kValidVS, kValidFSes[0])) { return; } } { // straddles stride boundary with attempt to overflow static const Attribute kAttributes[] { {Attribute::Type::kFloat, std::numeric_limits::max() - 3, SkString{"var"}}, }; if (!check_for_failure(reporter, kAttributes, 4, kValidVaryings, kValidVS, kValidFSes[0])) { return; } } } DEF_TEST(MeshSpec_TooManyAttributes, reporter) { static constexpr size_t kN = 500; std::vector attrs; attrs.reserve(kN); for (size_t i = 0; i < kN; ++i) { attrs.push_back({Attribute::Type::kFloat4, 0, SkStringPrintf("attr%zu", i)}); } check_for_failure(reporter, attrs, 4*4, kValidVaryings, kValidVS, kValidFSes[0]); } DEF_TEST(MeshSpec_TooManyVaryings, reporter) { static constexpr size_t kN = 500; std::vector varyings; varyings.reserve(kN); for (size_t i = 0; i < kN; ++i) { varyings.push_back({Varying::Type::kFloat4, SkStringPrintf("varying%zu", i)}); } check_for_failure(reporter, kValidAttrs, kValidStride, SkSpan(varyings), kValidVS, kValidFSes[0]); } DEF_TEST(MeshSpec_DuplicateAttributeNames, reporter) { static const Attribute kAttributes[] { {Attribute::Type::kFloat4, 0, SkString{"var"}}, {Attribute::Type::kFloat2, 16, SkString{"var"}} }; check_for_failure(reporter, kAttributes, 24, kValidVaryings, kValidVS, kValidFSes[0]); } DEF_TEST(MeshSpec_DuplicateVaryingNames, reporter) { static const Varying kVaryings[] { {Varying::Type::kFloat4, SkString{"var"}}, {Varying::Type::kFloat3, SkString{"var"}} }; check_for_failure(reporter, kValidAttrs, kValidStride, kVaryings, kValidVS, kValidFSes[0]); } static constexpr const char* kSneakyName = "name; float3 sneaky"; DEF_TEST(MeshSpec_SneakyExtraAttribute, reporter) { static const Attribute kAttributes[] { {Attribute::Type::kFloat4, 0, SkString{kSneakyName}}, }; check_for_failure(reporter, kAttributes, 16, kValidVaryings, kValidVS, kValidFSes[0]); } DEF_TEST(MeshSpec_SneakyExtraVarying, reporter) { static const Varying kVaryings[] { {Varying::Type::kFloat4, SkString{kSneakyName}}, }; check_for_failure(reporter, kValidAttrs, kValidStride, kVaryings, kValidVS, kValidFSes[0]); } DEF_TEST(MeshSpec_AllowsFloat2PositionVarying, reporter) { // Position varying can be explicit if it is float2 static const Varying kVaryings[] { {Varying::Type::kFloat2, SkString{"position"}}, }; check_for_success(reporter, kValidAttrs, kValidStride, kVaryings, kValidVS, kValidFSes[0]); } DEF_TEST(MeshSpec_InvalidPositionType, reporter) { // Position varying can be explicit but it must be float2 static const Varying kVaryings[] { {Varying::Type::kFloat4, SkString{"position"}}, }; check_for_failure(reporter, kValidAttrs, kValidStride, kVaryings, kValidVS, kValidFSes[0]); } DEF_TEST(MeshSpec_EmptyAttributeName, reporter) { static const Attribute kAttributes[] { {Attribute::Type::kFloat4, 0, SkString{}}, }; check_for_failure(reporter, kAttributes, 16, kValidVaryings, kValidVS, kValidFSes[0]); } DEF_TEST(MeshSpec_EmptyVaryingName, reporter) { static const Varying kVaryings[] { {Varying::Type::kFloat4, SkString{}}, }; check_for_failure(reporter, kValidAttrs, kValidStride, kVaryings, kValidVS, kValidFSes[0]); } DEF_TEST(MeshSpecVaryingPassthrough, reporter) { static const Attribute kAttributes[]{ {Attribute::Type::kFloat2, 0, SkString{"position"}}, {Attribute::Type::kFloat2, 8, SkString{"uv"} }, {Attribute::Type::kUByte4_unorm, 16, SkString{"color"} }, }; static const Varying kVaryings[]{ {Varying::Type::kFloat2, SkString{"position"}}, {Varying::Type::kFloat2, SkString{"uv"} }, {Varying::Type::kHalf4, SkString{"color"} }, }; static constexpr char kVS[] = R"( Varyings main(const Attributes a) { Varyings v; v.uv = a.uv; v.position = a.position; v.color = a.color; return v; } )"; auto check = [&] (const char* fs, const char* passthroughAttr) { auto [spec, error] = SkMeshSpecification::Make(kAttributes, /*vertexStride=*/24, kVaryings, SkString(kVS), SkString(fs)); if (!spec) { ERRORF(reporter, "%s\n%s", fs, error.c_str()); return; } int idx = SkMeshSpecificationPriv::PassthroughLocalCoordsVaryingIndex(*spec); const SkString& actualAttr = idx >= 0 ? spec->attributes()[idx].name : SkString(""); if (!passthroughAttr) { if (idx >= 0) { ERRORF(reporter, "Expected no passthrough coords attribute, found %s.\n%s", actualAttr.c_str(), fs); } } else if (!actualAttr.equals(passthroughAttr)) { ERRORF(reporter, "Expected %s as passthrough coords attribute, found %s.\n%s", passthroughAttr, actualAttr.c_str(), fs); } }; // Simple check(R"(float2 main(const Varyings v) { return v.uv; })", "uv"); // Simple, using position check(R"(float2 main(const Varyings v) { return v.position; })", "position"); // Simple, with output color check(R"(float2 main(const Varyings v, out half4 color) { color = v.color; return v.uv; })", "uv"); // Three returns, all the same. check(R"(uniform int selector; float2 main(const Varyings v, out half4 color) { if (selector == 0) { color = half4(1, 0, 0, 1); return v.position; } if (selector == 1) { color = half4(1, 1, 0, 1); return v.position; } color = half4(1, 0, 1, 1); return v.position; })", "position"); // Three returns, one not like the others check(R"(uniform int selector; float2 main(const Varyings v, out half4 color) { if (selector == 0) { color = color.bgra; return v.position; } if (selector == 1) { color = half4(1); return v.uv; } color = color; return v.position; })", nullptr); // Swizzles aren't handled (yet?). check(R"(float2 main(const Varyings v) { return v.uv.yx; })", nullptr); // Return from non-main fools us? check(R"(noinline half4 get_color(const Varyings v) { return v.color; } float2 main(const Varyings v, out half4 color) { color = get_color(v); return v.position; })", "position"); } DEF_TEST(MeshSpecUnusedVaryings, reporter) { static const Attribute kAttributes[]{ {Attribute::Type::kFloat2, 0, SkString{"position"}}, {Attribute::Type::kFloat2, 8, SkString{"uv"} }, {Attribute::Type::kUByte4_unorm, 16, SkString{"color"} }, }; static const Varying kVaryings[]{ {Varying::Type::kFloat2, SkString{"position"}}, {Varying::Type::kFloat2, SkString{"uv"} }, {Varying::Type::kHalf4, SkString{"color"} }, }; static constexpr char kVS[] = R"( Varyings main(const Attributes a) { Varyings v; v.uv = a.uv; v.position = a.position; v.color = a.color; return v; } )"; auto check = [&](const char* fs, bool positionDead, bool uvDead, bool colorDead) { static_assert(std::size(kVaryings) == 3); auto [spec, error] = SkMeshSpecification::Make(kAttributes, /*vertexStride=*/24, kVaryings, SkString(kVS), SkString(fs)); if (!spec) { ERRORF(reporter, "%s\n%s", fs, error.c_str()); return; } bool positionActuallyDead = SkMeshSpecificationPriv::VaryingIsDead(*spec, 0); bool uvActuallyDead = SkMeshSpecificationPriv::VaryingIsDead(*spec, 1); bool colorActuallyDead = SkMeshSpecificationPriv::VaryingIsDead(*spec, 2); auto str = [](bool dead) { return dead ? "dead" : "not dead"; }; if (positionActuallyDead != positionDead) { ERRORF(reporter, "Expected position to be detected %s but it is detected %s.\n%s", str(positionDead), str(positionActuallyDead), fs); } if (uvActuallyDead != uvDead) { ERRORF(reporter, "Expected uv to be detected %s but it is detected %s.\n%s", str(uvDead), str(uvActuallyDead), fs); } if (colorActuallyDead != colorDead) { ERRORF(reporter, "Expected color to be detected %s but it is detected %s.\n%s", str(colorDead), str(colorActuallyDead), fs); } }; // Simple check(R"(float2 main(const Varyings v) { return v.uv; })", true, true, true); // Simple, using position check(R"(float2 main(const Varyings v) { return v.position; })", true, true, true); // Two returns that are both passthrough of the same varying check(R"(float2 main(const Varyings v, out half4 color) { if (v.color.r > 0.5) { color = v.color; return v.uv; } else { color = 2*color; return v.uv; } })", true, true, false); // Two returns that are both passthrough of the different varyings and unused other varying check(R"(float2 main(const Varyings v, out half4 color) { if (v.position.x > 10) { color = half4(0); return v.uv; } else { color = half4(1); return v.position; } })", false, false, true); // Passthrough but we also use the varying elsewhere check(R"(float2 main(const Varyings v, out half4 color) { color = half4(v.uv.x, 0, 0, 1); return v.uv; })", true, false, true); // Use two varyings is a return statement check(R"(float2 main(const Varyings v) { return v.uv + v.position; })", false, false, true); // Slightly more complicated varying use. check(R"(noinline vec2 get_pos(const Varyings v) { return v.position; } noinline half4 identity(half4 c) { return c; } float2 main(const Varyings v, out half4 color) { color = identity(v.color); return v.uv + get_pos(v); })", false, false, false); // Go through assignment to another Varyings. check(R"(float2 main(const Varyings v) { Varyings otherVaryings; otherVaryings = v; return otherVaryings.uv; })", true, false, true); // We're not very smart. We just look for any use of the field in any Varyings value and don't // do any data flow analysis. check(R"(float2 main(const Varyings v) { Varyings otherVaryings; otherVaryings.uv = half2(5); otherVaryings.position = half2(10); return otherVaryings.position; })", false, false, true); }