blob: 19161993a28f7e78b56cd976e2594ada5f36e203 [file] [log] [blame]
/*
* 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 (0) {
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);
}