blob: 41631551eaede1db17f964c5c4760c2311361231 [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/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 <algorithm>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <limits>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
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<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* reporter,
SkSpan<const Attribute> attributes,
size_t stride,
SkSpan<const Varying> 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<const Attribute> attributes,
size_t stride,
SkSpan<const Varying> varyings,
const SkString& vs,
const SkString& fs,
sk_sp<SkMeshSpecification>* 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<SkMeshSpecification> 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<SkRuntimeEffect::ChildPtr> children,
const char* expectedError = nullptr) {
SkString fsWithChild{prefix};
fsWithChild.append(kValidFSes[0]);
sk_sp<SkMeshSpecification> meshSpec;
if (!check_for_success(reporter,
kValidAttrs,
kValidStride,
kValidVaryings,
kValidVS,
fsWithChild,
&meshSpec)) {
return;
}
constexpr float kVertexCount = 4;
sk_sp<SkMesh::VertexBuffer> 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<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|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<SkMeshSpecification> spec;
if (!check_for_success(reporter, attrs, kValidStride, varys, vs, fs, &spec)) {
return;
}
SkString desc = make_description(attrs, kValidStride, varys, vs, fs);
SkSpan<const Uniform> 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<int>(actual.type),
static_cast<int>(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<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 = 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<Attribute>(),
kValidStride,
kValidVaryings,
kValidVS,
kValidFSes[0]);
}
DEF_TEST(MeshSpec_ZeroVaryings, reporter) {
// Varyings are not required.
check_for_success(reporter,
kValidAttrs,
kValidStride,
SkSpan<Varying>(),
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<size_t>::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<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(reporter,
attrs,
4*4,
kValidVaryings,
kValidVS,
kValidFSes[0]);
}
DEF_TEST(MeshSpec_TooManyVaryings, reporter) {
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(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("<none>");
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);
}