| /* |
| * 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/SkCanvas.h" |
| #include "include/core/SkCustomMesh.h" |
| #include "tests/Test.h" |
| |
| using Attribute = SkCustomMeshSpecification::Attribute; |
| using Varying = SkCustomMeshSpecification::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<const Attribute> attributes, |
| size_t stride, |
| SkSpan<const Varying> 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* r, |
| SkSpan<const Attribute> attributes, |
| size_t stride, |
| SkSpan<const Varying> varyings, |
| const SkString& vs, |
| const SkString& fs) { |
| auto [spec, error] = SkCustomMeshSpecification::Make(attributes, stride, varyings, vs, fs); |
| SkString description; |
| if (!spec) { |
| return true; |
| } |
| ERRORF(r, |
| "Expected to fail but succeeded:\n%s", |
| make_description(attributes, stride, varyings, vs, fs).c_str()); |
| return false; |
| } |
| |
| static bool check_for_success(skiatest::Reporter* r, |
| SkSpan<const Attribute> attributes, |
| size_t stride, |
| SkSpan<const Varying> varyings, |
| const SkString& vs, |
| const SkString& fs) { |
| auto [spec, error] = SkCustomMeshSpecification::Make(attributes, stride, varyings, vs, fs); |
| if (spec) { |
| REPORTER_ASSERT(r, error.isEmpty()); |
| return true; |
| } |
| ERRORF(r, |
| "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 |
| {"float2 main(Attributes attrs, out Varyings v) { return float2(10); }"}; |
| // There are multiple valid VS signatures. |
| static const SkString kValidFSes[] { |
| SkString{"void main(Varyings varyings) {}"}, |
| SkString{"float2 main(Varyings varyings) { return float2(10); }"}, |
| SkString{"void main(Varyings varyings, out half4 color) { color = half4(.2); }"}, |
| SkString{R"( |
| float2 main(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"}}, |
| }; |
| |
| static void test_good(skiatest::Reporter* r) { |
| for (const auto& validFS : kValidFSes) { |
| if (!check_for_success(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| validFS)) { |
| return; |
| } |
| } |
| } |
| |
| static void test_bad_sig(skiatest::Reporter* r) { |
| static constexpr const char* kVSBody = "{ return float2(10); }"; |
| |
| static constexpr const char* kInvalidVSSigs[] { |
| "float3 main(Attributes attrs, out Varyings v)", // bad return |
| "float2 main(inout Attributes attrs, out Varyings v)", // inout Attributes |
| "float2 main(Attributes attrs, inout Varyings v)", // inout Varyings |
| "float2 main(Attributes attrs)", // no Varyings |
| "float2 main(out Varyings)", // no Attributes |
| "float2 main(out Varyings, in Attributes)", // wrong param order |
| "float2 main(Attributes attrs, out Varyings v, float2)" // extra arg |
| }; |
| |
| static constexpr const char* kNoColorFSBody = "{ return float2(10); }"; |
| |
| static constexpr const char* kInvalidNoColorFSSigs[] { |
| "half2 main(Varyings v)", // bad return |
| "float2 main(in Attributes v)", // wrong param type |
| "float2 main(out Varyings attrs)", // out Varyings |
| "float2 main()", // no args |
| "float2 main(Attributes attrs, float)" // extra arg |
| }; |
| |
| static constexpr const char* kColorFSBody = "{ color = half4(.2); return float2(10); }"; |
| |
| static constexpr const char* kInvalidColorFSSigs[] { |
| "half2 main(Varyings v, out half4 color)", // bad return |
| "float2 main(in Attributes v, out half4 color)", // wrong first param type |
| "float2 main(in Varyings v, out half3 color)", // wrong second param type |
| "float2 main(out Varyings attrs, out half4 color)", // out Varyings |
| "float2 main(Varyings attrs, half4 color)", // in color |
| "float2 main(Attributes attrs, 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(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| invalidVS, |
| validFS)) { |
| return; |
| } |
| } |
| } |
| |
| for (const char* noColorFSSig : kInvalidNoColorFSSigs) { |
| SkString invalidFS; |
| invalidFS.appendf("%s %s", noColorFSSig, kNoColorFSBody); |
| if (!check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| invalidFS)) { |
| return; |
| } |
| } |
| |
| for (const char* colorFSSig : kInvalidColorFSSigs) { |
| SkString invalidFS; |
| invalidFS.appendf("%s %s", colorFSSig, kColorFSBody); |
| if (!check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| invalidFS)) { |
| return; |
| } |
| } |
| } |
| |
| // We allow the optional out color from the FS to either be float4 or half4 |
| static void test_float4_color(skiatest::Reporter* r) { |
| static const SkString kFloat4FS { |
| R"( |
| float2 main(Varyings varyings, out float4 color) { |
| color = float4(.2); return float2(10); |
| } |
| )" |
| }; |
| check_for_success(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| kFloat4FS); |
| } |
| |
| // We don't allow value or child uniforms in custom meshes currently. |
| static void test_bad_globals(skiatest::Reporter* r) { |
| static constexpr const char* kBadGlobals[] { |
| "uniform float3 uni;" |
| "uniform shader myshader;" |
| }; |
| for (const auto& global : kBadGlobals) { |
| SkString badVS = kValidVS; |
| badVS.prepend(global); |
| if (!check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| badVS, |
| kValidFSes[0])) { |
| return; |
| } |
| } |
| for (const auto& global : kBadGlobals) { |
| SkString badFS = kValidFSes[0]; |
| badFS.prepend(global); |
| if (!check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| badFS)) { |
| return; |
| } |
| } |
| } |
| |
| static void test_no_main(skiatest::Reporter* r) { |
| static const SkString kHelper{"float2 swiz(float2 x) { return z.yx; }"}; |
| |
| // Empty VS |
| if (!check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| SkString{}, |
| kValidFSes[0])) { |
| return; |
| } |
| |
| // VS with helper function but no main |
| if (!check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kHelper, |
| kValidFSes[0])) { |
| return; |
| } |
| |
| // Empty FS |
| if (!check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| SkString{})) { |
| return; |
| } |
| |
| // VS with helper function but no main |
| if (!check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| kHelper)) { |
| return; |
| } |
| } |
| |
| static void test_zero_attrs(skiatest::Reporter* r) { |
| // We require at least one attribute |
| check_for_failure(r, |
| SkSpan<Attribute>(), |
| kValidStride, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| kValidFSes[0]); |
| } |
| |
| static void test_zero_varyings(skiatest::Reporter* r) { |
| // Varyings are not required. |
| check_for_success(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkSpan<Varying>(), |
| kValidVS, |
| kValidFSes[0]); |
| } |
| |
| static void test_bad_strides(skiatest::Reporter* r) { |
| // Zero stride |
| if (!check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| 0, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| kValidFSes[0])) { |
| return; |
| } |
| |
| // Unaligned |
| if (!check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride + 1, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| kValidFSes[0])) { |
| return; |
| } |
| |
| // Too large |
| if (!check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| 1 << 20, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| kValidFSes[0])) { |
| return; |
| } |
| } |
| |
| static void test_bad_offsets(skiatest::Reporter* r) { |
| { // offset isn't aligned |
| static const Attribute kAttributes[] { |
| {Attribute::Type::kFloat4, 1, SkString{"var"}}, |
| }; |
| if (!check_for_failure(r, |
| SkMakeSpan(kAttributes, SK_ARRAY_COUNT(kAttributes)), |
| 32, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(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(r, |
| SkMakeSpan(kAttributes, SK_ARRAY_COUNT(kAttributes)), |
| 20, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| kValidFSes[0])) { |
| return; |
| } |
| } |
| { // straddles stride boundary with attempt to overflow |
| static const Attribute kAttributes[] { |
| {Attribute::Type::kFloat, std::numeric_limits<size_t>::max() - 3, SkString{"var"}}, |
| }; |
| if (!check_for_failure(r, |
| SkMakeSpan(kAttributes, SK_ARRAY_COUNT(kAttributes)), |
| 4, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| kValidFSes[0])) { |
| return; |
| } |
| } |
| } |
| |
| static void test_too_many_attrs(skiatest::Reporter* r) { |
| static constexpr size_t kN = 500; |
| std::vector<Attribute> 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(r, |
| SkMakeSpan(attrs), |
| 4*4, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| kValidFSes[0]); |
| } |
| |
| static void test_too_many_varyings(skiatest::Reporter* r) { |
| static constexpr size_t kN = 500; |
| std::vector<Varying> 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(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(varyings), |
| kValidVS, |
| kValidFSes[0]); |
| } |
| |
| static void test_duplicate_attribute_names(skiatest::Reporter* r) { |
| static const Attribute kAttributes[] { |
| {Attribute::Type::kFloat4, 0, SkString{"var"}}, |
| {Attribute::Type::kFloat2, 16, SkString{"var"}} |
| }; |
| check_for_failure(r, |
| SkMakeSpan(kAttributes, SK_ARRAY_COUNT(kAttributes)), |
| 24, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| kValidFSes[0]); |
| } |
| |
| static void test_duplicate_varying_names(skiatest::Reporter* r) { |
| static const Varying kVaryings[] { |
| {Varying::Type::kFloat4, SkString{"var"}}, |
| {Varying::Type::kFloat3, SkString{"var"}} |
| }; |
| check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kVaryings, SK_ARRAY_COUNT(kVaryings)), |
| kValidVS, |
| kValidFSes[0]); |
| } |
| |
| static constexpr const char* kSneakyName = "name; float3 sneaky"; |
| |
| static void test_sneaky_attribute_name(skiatest::Reporter* r) { |
| static const Attribute kAttributes[] { |
| {Attribute::Type::kFloat4, 0, SkString{kSneakyName}}, |
| }; |
| check_for_failure(r, |
| SkMakeSpan(kAttributes, SK_ARRAY_COUNT(kAttributes)), |
| 16, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| kValidFSes[0]); |
| } |
| |
| static void test_sneaky_varying_name(skiatest::Reporter* r) { |
| static const Varying kVaryings[] { |
| {Varying::Type::kFloat4, SkString{kSneakyName}}, |
| }; |
| check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kVaryings, SK_ARRAY_COUNT(kVaryings)), |
| kValidVS, |
| kValidFSes[0]); |
| } |
| |
| static void test_empty_attribute_name(skiatest::Reporter* r) { |
| static const Attribute kAttributes[] { |
| {Attribute::Type::kFloat4, 0, SkString{}}, |
| }; |
| check_for_failure(r, |
| SkMakeSpan(kAttributes, SK_ARRAY_COUNT(kAttributes)), |
| 16, |
| SkMakeSpan(kValidVaryings, SK_ARRAY_COUNT(kValidVaryings)), |
| kValidVS, |
| kValidFSes[0]); |
| } |
| |
| static void test_empty_varying_name(skiatest::Reporter* r) { |
| static const Varying kVaryings[] { |
| {Varying::Type::kFloat4, SkString{}}, |
| }; |
| check_for_failure(r, |
| SkMakeSpan(kValidAttrs, SK_ARRAY_COUNT(kValidAttrs)), |
| kValidStride, |
| SkMakeSpan(kVaryings, SK_ARRAY_COUNT(kVaryings)), |
| kValidVS, |
| kValidFSes[0]); |
| } |
| |
| DEF_TEST(CustomMeshSpec, reporter) { |
| struct X {}; |
| test_good(reporter); |
| test_bad_sig(reporter); |
| test_float4_color(reporter); |
| test_bad_globals(reporter); |
| test_no_main(reporter); |
| test_zero_attrs(reporter); |
| test_zero_varyings(reporter); |
| test_bad_strides(reporter); |
| test_bad_offsets(reporter); |
| test_too_many_attrs(reporter); |
| test_too_many_varyings(reporter); |
| // skbug.com/12712 |
| if ((false)) { |
| test_duplicate_attribute_names(reporter); |
| test_duplicate_varying_names(reporter); |
| } |
| test_sneaky_attribute_name(reporter); |
| test_sneaky_varying_name(reporter); |
| test_empty_attribute_name(reporter); |
| test_empty_varying_name(reporter); |
| } |