* 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;
}
static const SkString kValidVS {R"(
Varyings main(const Attributes attrs) {
Varyings v;
return v;
})"};
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);
}
)"},
};
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)",
"Varyings main(Attributes attrs)",
"Varyings main(out Attributes attrs)",
"Varyings main()",
"Varyings main(const Varyings v, float2)"
};
static constexpr const char* kNoColorFSBody = "{ return float2(10); }";
static constexpr const char* kInvalidNoColorFSSigs[] {
"half2 main(const Varyings v)",
"float2 main(const Attributes v)",
"float2 main(inout Varyings attrs)",
"float2 main(Varyings v)",
"float2 main()",
"float2 main(const Varyings, float)"
};
static constexpr const char* kColorFSBody = "{ color = half4(.2); return float2(10); }";
static constexpr const char* kInvalidColorFSSigs[] {
"half2 main(const Varyings v, out half4 color)",
"float2 main(const Attributes v, out half4 color)",
"float2 main(const Varyings v, out half3 color)",
"float2 main(out Varyings v, out half4 color)",
"float2 main(const Varyings v, half4 color)",
"float2 main(const Varyings v, out half4 color, float)"
};
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;
}
}
}
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,
0,
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] = {};
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.");
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.");
test("uniform shader myshader;", childNull);
test("uniform shader myfilter;", childNull);
test("uniform shader myblender;", childNull);
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};
}
};
struct {
const std::vector<const char*> vsUniformDecls;
const std::vector<const char*> fsUniformDecls;
const std::vector<SkMeshSpecification::Uniform> expectations;
} static kTestCases[] {
{
{
"uniform float x;"
},
{},
{
make_uni(Type::kFloat, "x", 0, kVS)
}
},
{
{},
{
"uniform float2 v;"
},
{
make_uni(Type::kFloat2, "v", 0, kFS)
}
},
{
{
"layout(color) uniform float4 color;",
},
{
"layout(color) uniform float4 color;",
},
{
make_uni(Type::kFloat4, "color", 0, kVS|kFS|kColor)
}
},
{
{
"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)
}
},
{
{
"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)
}
},
{
{
"uniform float3x3 m;",
},
{
"uniform int2 i2;",
"uniform float3x3 m;",
},
{
make_uni(Type::kFloat3x3, "m" , 0, kVS|kFS),
make_uni(Type::kInt2 , "i2", 36, kFS )
}
},
{
{
"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)
}
},
{
{
"uniform float x;"
"uniform half4x4 m[4];",
"uniform int2 i2[2];"
"uniform float3 v[8];"
"uniform int3 i3;"
},
{
"uniform float y;"
"uniform float3 v[8];"
"uniform int4 i4[2];"
"uniform half4x4 m[4];",
"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) {
static std::tuple<const char*, const char*> kTestCases[]{
{"uniform float x;", "uniform int x;"},
{"uniform float2x2 m[1];", "uniform float2x2 m;"},
{"uniform int3 i[1];", "uniform int3 i[2];"},
{"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; }"};
if (!check_for_failure(reporter,
kValidAttrs,
kValidStride,
kValidVaryings,
SkString{},
kValidFSes[0])) {
return;
}
if (!check_for_failure(reporter,
kValidAttrs,
kValidStride,
kValidVaryings,
kHelper,
kValidFSes[0])) {
return;
}
if (!check_for_failure(reporter,
kValidAttrs,
kValidStride,
kValidVaryings,
kValidVS,
SkString{})) {
return;
}
if (!check_for_failure(reporter,
kValidAttrs,
kValidStride,
kValidVaryings,
kValidVS,
kHelper)) {
return;
}
}
DEF_TEST(MeshSpec_ZeroAttributes, reporter) {
check_for_failure(reporter,
SkSpan<Attribute>(),
kValidStride,
kValidVaryings,
kValidVS,
kValidFSes[0]);
}
DEF_TEST(MeshSpec_ZeroVaryings, reporter) {
check_for_success(reporter,
kValidAttrs,
kValidStride,
SkSpan<Varying>(),
kValidVS,
kValidFSes[0]);
}
DEF_TEST(MeshSpec_InvalidStride, reporter) {
if (!check_for_failure(reporter,
kValidAttrs,
0,
kValidVaryings,
kValidVS,
kValidFSes[0])) {
return;
}
if (!check_for_failure(reporter,
kValidAttrs,
kValidStride + 1,
kValidVaryings,
kValidVS,
kValidFSes[0])) {
return;
}
if (!check_for_failure(reporter,
kValidAttrs,
1 << 20,
kValidVaryings,
kValidVS,
kValidFSes[0])) {
return;
}
}
DEF_TEST(MeshSpec_InvalidOffset, reporter) {
{
static const Attribute kAttributes[] {
{Attribute::Type::kFloat4, 1, SkString{"var"}},
};
if (!check_for_failure(reporter,
kAttributes,
32,
kValidVaryings,
kValidVS,
kValidFSes[0])) {
return;
}
}
{
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;
}
}
{
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) {
static const Varying kVaryings[] {
{Varying::Type::kFloat2, SkString{"position"}},
};
check_for_success(reporter,
kValidAttrs,
kValidStride,
kVaryings,
kValidVS,
kValidFSes[0]);
}
DEF_TEST(MeshSpec_InvalidPositionType, reporter) {
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,
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);
}
};
check(R"(float2 main(const Varyings v) {
return v.uv;
})",
"uv");
check(R"(float2 main(const Varyings v) {
return v.position;
})",
"position");
check(R"(float2 main(const Varyings v, out half4 color) {
color = v.color;
return v.uv;
})",
"uv");
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");
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);
check(R"(float2 main(const Varyings v) {
return v.uv.yx;
})",
nullptr);
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,
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);
}
};
check(R"(float2 main(const Varyings v) {
return v.uv;
})",
true,
true,
true);
check(R"(float2 main(const Varyings v) {
return v.position;
})",
true,
true,
true);
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);
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);
check(R"(float2 main(const Varyings v, out half4 color) {
color = half4(v.uv.x, 0, 0, 1);
return v.uv;
})",
true,
false,
true);
check(R"(float2 main(const Varyings v) {
return v.uv + v.position;
})",
false,
false,
true);
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);
check(R"(float2 main(const Varyings v) {
Varyings otherVaryings;
otherVaryings = v;
return otherVaryings.uv;
})",
true,
false,
true);
check(R"(float2 main(const Varyings v) {
Varyings otherVaryings;
otherVaryings.uv = half2(5);
otherVaryings.position = half2(10);
return otherVaryings.position;
})",
false,
false,
true);
}