blob: 47f15867c9fddc69118e749b068d914709b0eb79 [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 "gm/gm.h"
#include "include/core/SkBlender.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkData.h"
#include "include/core/SkMesh.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkPoint.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/effects/SkGradientShader.h"
#include "include/gpu/GrDirectContext.h"
#include "include/gpu/ganesh/SkMeshGanesh.h"
#include "include/private/base/SkAssert.h"
#include "src/base/SkRandom.h"
#include "src/core/SkCanvasPriv.h"
#include "tools/DecodeUtils.h"
#include "tools/timer/TimeUtils.h"
using namespace skia_private;
namespace skiagm {
class MeshGM : public skiagm::GM {
public:
MeshGM() {}
protected:
using Attribute = SkMeshSpecification::Attribute;
using Varying = SkMeshSpecification::Varying;
SkISize getISize() override { return {435, 1180}; }
void onOnceBeforeDraw() override {
{
static const Attribute kAttributes[]{
{Attribute::Type::kFloat4, 8, SkString{"xuyv"}},
{Attribute::Type::kUByte4_unorm, 4, SkString{"brag"}},
};
static const Varying kVaryings[]{
{Varying::Type::kHalf4, SkString{"color"}},
{Varying::Type::kFloat2, SkString{"uv"} },
};
static constexpr char kVS[] = R"(
half4 unswizzle_color(half4 color) { return color.garb; }
Varyings main(const in Attributes attributes) {
Varyings varyings;
varyings.color = unswizzle_color(attributes.brag);
varyings.uv = attributes.xuyv.yw;
varyings.position = attributes.xuyv.xz;
return varyings;
}
)";
static constexpr char kFS[] = R"(
uniform colorFilter filter;
float2 main(const in Varyings varyings, out float4 color) {
color = filter.eval(varyings.color);
return varyings.uv;
}
)";
auto [spec, error] = SkMeshSpecification::Make(kAttributes,
sizeof(ColorVertex),
kVaryings,
SkString(kVS),
SkString(kFS));
if (!spec) {
SkDebugf("%s\n", error.c_str());
}
fSpecWithColor = std::move(spec);
}
{
static const Attribute kAttributes[]{
{Attribute::Type::kFloat4, 0, SkString{"xuyv"}},
};
static const Varying kVaryings[]{
{Varying::Type::kFloat2, SkString{"vux2"}},
};
static constexpr char kVS[] = R"(
Varyings main(const in Attributes a) {
Varyings v;
v.vux2 = 2*a.xuyv.wy;
v.position = a.xuyv.xz;
return v;
}
)";
static constexpr char kFS[] = R"(
float2 helper(in float2 vux2) { return vux2.yx/2; }
float2 main(const in Varyings varyings) {
return helper(varyings.vux2);
}
)";
auto [spec, error] = SkMeshSpecification::Make(kAttributes,
sizeof(NoColorVertex),
kVaryings,
SkString(kVS),
SkString(kFS));
if (!spec) {
SkDebugf("%s\n", error.c_str());
}
fSpecWithNoColor = std::move(spec);
}
static constexpr SkColor kColors[] = {SK_ColorTRANSPARENT, SK_ColorWHITE};
fShader = SkGradientShader::MakeRadial({10, 10},
3,
kColors,
nullptr,
2,
SkTileMode::kMirror);
}
DrawResult onGpuSetup(SkCanvas* canvas, SkString* string, GraphiteTestContext*) override {
auto dc = GrAsDirectContext(canvas->recordingContext());
this->ensureBuffers();
if (!dc || dc->abandoned()) {
return DrawResult::kOk;
}
fColorVB = SkMeshes::CopyVertexBuffer(dc, fColorVB);
fColorIndexedVB = SkMeshes::CopyVertexBuffer(dc, fColorIndexedVB);
fIB[1] = SkMeshes::CopyIndexBuffer (dc, fIB[0]);
if (!fColorVB || !fColorIndexedVB || !fIB[1]) {
return DrawResult::kFail;
}
return DrawResult::kOk;
}
void onGpuTeardown() override {
// Destroy the GPU buffers and recreate on CPU
fColorVB = nullptr;
fColorIndexedVB = nullptr;
fIB[1] = nullptr;
this->ensureBuffers();
}
SkString getName() const override { return SkString("custommesh"); }
DrawResult onDraw(SkCanvas* canvas, SkString*) override {
SkRuntimeEffect::ChildPtr nullChild[1] = {};
int i = 0;
for (const sk_sp<SkBlender>& blender : {SkBlender::Mode(SkBlendMode::kDst),
SkBlender::Mode(SkBlendMode::kSrc),
SkBlender::Mode(SkBlendMode::kSaturation)}) {
canvas->save();
for (uint8_t alpha : {0xFF , 0x40})
for (bool colors : {false, true})
for (bool shader : {false, true}) {
SkMesh::Result result;
// Rather than pile onto the combinatorics we draw every other test case indexed.
if ((i & 1) == 0) {
if (colors) {
result = SkMesh::Make(fSpecWithColor,
SkMesh::Mode::kTriangleStrip,
fColorVB,
/*vertexCount=*/4,
/*vertexOffset=*/0,
/*uniforms=*/nullptr,
/*children=*/nullChild,
kRect);
} else {
result = SkMesh::Make(fSpecWithNoColor,
SkMesh::Mode::kTriangleStrip,
fNoColorVB,
/*vertexCount=*/4,
kNoColorOffset,
/*uniforms=*/nullptr,
/*children=*/{},
kRect);
}
} else {
// Alternate between CPU and GPU-backend index buffers.
auto ib = (i % 4 == 0) ? fIB[0] : fIB[1];
if (colors) {
result = SkMesh::MakeIndexed(fSpecWithColor,
SkMesh::Mode::kTriangles,
fColorIndexedVB,
/*vertexCount=*/6,
kColorIndexedOffset,
std::move(ib),
/*indexCount=*/6,
kIndexOffset,
/*uniforms=*/nullptr,
/*children=*/nullChild,
kRect);
} else {
result = SkMesh::MakeIndexed(fSpecWithNoColor,
SkMesh::Mode::kTriangles,
fNoColorIndexedVB,
/*vertexCount=*/6,
/*vertexOffset=*/0,
std::move(ib),
/*indexCount=*/6,
kIndexOffset,
/*uniforms=*/nullptr,
/*children=*/{},
kRect);
}
}
if (!result.mesh.isValid()) {
SkDebugf("Mesh creation failed: %s\n", result.error.c_str());
return DrawResult::kFail;
}
SkPaint paint;
paint.setColor(SK_ColorGREEN);
paint.setShader(shader ? fShader : nullptr);
paint.setAlpha(alpha);
canvas->drawMesh(result.mesh, blender, paint);
canvas->translate(0, 150);
++i;
}
canvas->restore();
canvas->translate(150, 0);
}
return DrawResult::kOk;
}
private:
void ensureBuffers() {
if (!fColorVB) {
fColorVB = SkMeshes::MakeVertexBuffer(kColorQuad, sizeof(kColorQuad));
}
if (!fNoColorVB) {
// Make this one such that the data is offset into the buffer.
auto data = SkData::MakeUninitialized(sizeof(kNoColorQuad) + kNoColorOffset);
std::memcpy(SkTAddOffset<void>(data->writable_data(), kNoColorOffset),
kNoColorQuad,
sizeof(kNoColorQuad));
fNoColorVB = SkMeshes::MakeVertexBuffer(data->data(), data->size());
}
if (!fColorIndexedVB) {
// This buffer also has an offset.
auto data = SkData::MakeUninitialized(sizeof(kColorIndexedQuad) + kColorIndexedOffset);
std::memcpy(SkTAddOffset<void>(data->writable_data(), kColorIndexedOffset),
kColorIndexedQuad,
sizeof(kColorIndexedQuad));
fColorIndexedVB = SkMeshes::MakeVertexBuffer(data->data(), data->size());
}
if (!fNoColorIndexedVB) {
fNoColorIndexedVB =
SkMeshes::MakeVertexBuffer(kNoColorIndexedQuad, sizeof(kNoColorIndexedQuad));
}
if (!fIB[0]) {
// The index buffer has an offset.
auto data = SkData::MakeUninitialized(sizeof(kIndices) + kIndexOffset);
std::memcpy(SkTAddOffset<void>(data->writable_data(), kIndexOffset),
kIndices,
sizeof(kIndices));
fIB[0] = SkMeshes::MakeIndexBuffer(data->data(), data->size());
}
if (!fIB[1]) {
// On CPU we always use the same CPU-backed index buffer.
fIB[1] = fIB[0];
}
}
struct ColorVertex {
uint32_t pad;
uint32_t brag;
float xuyv[4];
};
struct NoColorVertex {
float xuyv[4];
};
static constexpr auto kRect = SkRect::MakeLTRB(20, 20, 120, 120);
static constexpr auto kUV = SkRect::MakeLTRB( 0, 0, 20, 20);
static constexpr ColorVertex kColorQuad[] {
{0, 0x00FFFF00, {kRect.left(), kUV.left(), kRect.top(), kUV.top() }},
{0, 0x00FFFFFF, {kRect.right(), kUV.right(), kRect.top(), kUV.top() }},
{0, 0xFFFF00FF, {kRect.left(), kUV.left(), kRect.bottom(), kUV.bottom()}},
{0, 0xFFFFFF00, {kRect.right(), kUV.right(), kRect.bottom(), kUV.bottom()}},
};
static constexpr NoColorVertex kNoColorQuad[]{
{{kRect.left(), kUV.left(), kRect.top(), kUV.top() }},
{{kRect.right(), kUV.right(), kRect.top(), kUV.top() }},
{{kRect.left(), kUV.left(), kRect.bottom(), kUV.bottom()}},
{{kRect.right(), kUV.right(), kRect.bottom(), kUV.bottom()}},
};
// The indexed quads draw the same as the non-indexed. They just have unused vertices that the
// index buffer skips over draw with triangles instead of a triangle strip.
static constexpr ColorVertex kColorIndexedQuad[] {
{0, 0x00FFFF00, {kRect.left(), kUV.left(), kRect.top(), kUV.top() }},
{0, 0x00000000, { 100.f, 0.f, 100.f, 5.f }}, // unused
{0, 0x00FFFFFF, {kRect.right(), kUV.right(), kRect.top(), kUV.top() }},
{0, 0x00000000, { 200.f, 10.f, 200.f, 10.f }}, // unused
{0, 0xFFFF00FF, {kRect.left(), kUV.left(), kRect.bottom(), kUV.bottom()}},
{0, 0xFFFFFF00, {kRect.right(), kUV.right(), kRect.bottom(), kUV.bottom()}},
};
static constexpr NoColorVertex kNoColorIndexedQuad[]{
{{kRect.left(), kUV.left(), kRect.top(), kUV.top() }},
{{ 100.f, 0.f, 100.f, 5.f }}, // unused
{{kRect.right(), kUV.right(), kRect.top(), kUV.top() }},
{{ 200.f, 10.f, 200.f, 10.f }}, // unused
{{kRect.left(), kUV.left(), kRect.bottom(), kUV.bottom()}},
{{kRect.right(), kUV.right(), kRect.bottom(), kUV.bottom()}},
};
static constexpr uint16_t kIndices[]{0, 2, 4, 2, 5, 4};
// For some buffers we add an offset to ensure we're exercising drawing from mid-buffer.
static constexpr size_t kNoColorOffset = sizeof(NoColorVertex);
static constexpr size_t kColorIndexedOffset = 2*sizeof(ColorVertex);
static constexpr size_t kIndexOffset = 6;
sk_sp<SkShader> fShader;
sk_sp<SkMeshSpecification> fSpecWithColor;
sk_sp<SkMeshSpecification> fSpecWithNoColor;
// On GPU the first IB is a CPU buffer and the second is a GPU buffer.
sk_sp<SkMesh::IndexBuffer> fIB[2];
sk_sp<SkMesh::VertexBuffer> fColorVB;
sk_sp<SkMesh::VertexBuffer> fNoColorVB;
sk_sp<SkMesh::VertexBuffer> fColorIndexedVB;
sk_sp<SkMesh::VertexBuffer> fNoColorIndexedVB;
};
DEF_GM(return new MeshGM;)
class MeshColorSpaceGM : public skiagm::GM {
public:
MeshColorSpaceGM() {}
protected:
using Attribute = SkMeshSpecification::Attribute;
using Varying = SkMeshSpecification::Varying;
SkISize getISize() override { return {468, 258}; }
void onOnceBeforeDraw() override {
static const Attribute kAttributes[]{
{Attribute::Type::kFloat2, 0, SkString{"pos"} },
{Attribute::Type::kFloat4, 8, SkString{"color"}},
};
static const Varying kVaryings[]{
{Varying::Type::kHalf4, SkString{"color"}},
};
static constexpr char kPremulVS[] = R"(
Varyings main(const in Attributes attributes) {
Varyings varyings;
varyings.color = half4(attributes.color.a*attributes.color.rgb,
attributes.color.a);
varyings.position = attributes.pos;
return varyings;
}
)";
static constexpr char kUnpremulVS[] = R"(
Varyings main(const in Attributes attributes) {
Varyings varyings;
varyings.color = attributes.color;
varyings.position = attributes.pos;
return varyings;
}
)";
static constexpr char kFS[] = R"(
float2 main(in const Varyings varyings, out half4 color) {
color = varyings.color;
return varyings.position;
}
)";
for (bool unpremul : {false, true}) {
auto at = unpremul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType;
auto vs = unpremul ? kUnpremulVS : kPremulVS;
for (bool spin : {false, true}) {
auto cs = SkColorSpace::MakeSRGB();
if (spin) {
cs = cs->makeColorSpin();
}
auto [spec, error] = SkMeshSpecification::Make(
kAttributes,
sizeof(Vertex),
kVaryings,
SkString(vs),
SkString(kFS),
std::move(cs),
at);
if (!spec) {
SkDebugf("%s\n", error.c_str());
}
fSpecs[SpecIndex(unpremul, spin)] = std::move(spec);
}
}
SkPoint pts[] = {{kRect.fLeft, 0}, {kRect.centerX(), 0}};
SkColor colors[] = {SK_ColorWHITE, SK_ColorTRANSPARENT};
fShader = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kMirror);
fVB = SkMeshes::MakeVertexBuffer(kQuad, sizeof(kQuad));
}
SkString getName() const override { return SkString("custommesh_cs"); }
DrawResult onDraw(SkCanvas* canvas, SkString* error) override {
// Force an intermediate surface if the canvas is in "legacy" mode
SkCanvas* c = canvas;
sk_sp<SkSurface> surface;
if (!c->imageInfo().colorSpace()) {
SkImageInfo info = canvas->imageInfo().makeColorSpace(SkColorSpace::MakeSRGB());
surface = canvas->makeSurface(info);
if (!surface) {
// This GM won't work on configs that use a recording canvas.
return DrawResult::kSkip;
}
c = surface->getCanvas();
c->clear(SK_ColorWHITE);
}
for (bool useShader : {false, true})
for (bool unpremul : {false, true}) {
c->save();
for (bool spin : {false, true}) {
auto result = SkMesh::Make(fSpecs[SpecIndex(unpremul, spin)],
SkMesh::Mode::kTriangleStrip,
fVB,
/*vertexCount=*/4,
/*vertexOffset=*/0,
/*uniforms=*/nullptr,
/*children=*/{},
kRect);
if (!result.mesh.isValid()) {
SkDebugf("Mesh creation failed: %s\n", result.error.c_str());
return DrawResult::kFail;
}
SkPaint paint;
paint.setShader(useShader ? fShader : nullptr);
SkBlendMode mode = useShader ? SkBlendMode::kModulate : SkBlendMode::kDst;
canvas->drawMesh(result.mesh, SkBlender::Mode(mode), paint);
c->translate(0, kRect.height() + 10);
}
c->restore();
c->translate(kRect.width() + 10, 0);
c->save();
}
if (surface) {
surface->draw(canvas, 0, 0);
}
return DrawResult::kOk;
}
private:
struct Vertex {
SkPoint pos;
SkColor4f color;
};
static int SpecIndex(bool spin, bool unpremul) {
return static_cast<int>(spin) + 2*static_cast<int>(unpremul);
}
static constexpr auto kRect = SkRect::MakeLTRB(20, 20, 120, 120);
static constexpr Vertex kQuad[] {
{{kRect.left() , kRect.top() }, {1, 0, 0, 1}},
{{kRect.right(), kRect.top() }, {0, 1, 0, 0}},
{{kRect.left() , kRect.bottom()}, {1, 1, 0, 0}},
{{kRect.right(), kRect.bottom()}, {0, 0, 1, 1}},
};
sk_sp<SkMesh::VertexBuffer> fVB;
sk_sp<SkMeshSpecification> fSpecs[4];
sk_sp<SkShader> fShader;
};
// helpers for cases when ctx could be nullptr
static sk_sp<SkMesh::VertexBuffer> make_vertex_buffer(GrDirectContext* ctx,
const void* data,
size_t size) {
if (ctx) {
return SkMeshes::MakeVertexBuffer(ctx, data, size);
}
return SkMeshes::MakeVertexBuffer(data, size);
}
static sk_sp<SkMesh::IndexBuffer> make_index_buffer(GrDirectContext* ctx,
const void* data,
size_t size) {
if (ctx) {
return SkMeshes::MakeIndexBuffer(ctx, data, size);
}
return SkMeshes::MakeIndexBuffer(data, size);
}
DEF_GM(return new MeshColorSpaceGM;)
class MeshUniformsGM : public skiagm::GM {
public:
MeshUniformsGM() { this->onAnimate(0); }
protected:
using Attribute = SkMeshSpecification::Attribute;
using Varying = SkMeshSpecification::Varying;
SkISize getISize() override { return {140, 250}; }
void onOnceBeforeDraw() override {
static const Attribute kAttributes[]{
{Attribute::Type::kFloat2, 0, SkString{"pos"} },
{Attribute::Type::kFloat2, 8, SkString{"coords"}},
};
static const Varying kVaryings[]{
{Varying::Type::kFloat2, SkString{"coords"}},
};
// To exercise shared VS/FS uniforms we have a matrix that is applied twice, once in each
// stage.
static constexpr char kVS[] = R"(
uniform float t[2];
uniform half3x3 m;
Varyings main(in const Attributes attributes) {
Varyings varyings;
varyings.coords = (m*float3(attributes.coords + float2(t[0], t[1]), 1)).xy;
varyings.position = attributes.pos;
return varyings;
}
)";
static constexpr char kFS[] = R"(
uniform half3x3 m;
layout(color) uniform half4 color;
float2 main(const Varyings varyings, out half4 c) {
c = color;
return (m*float3(varyings.coords, 1)).xy;
}
)";
auto [spec, error] =
SkMeshSpecification::Make(kAttributes,
sizeof(Vertex),
kVaryings,
SkString(kVS),
SkString(kFS),
SkColorSpace::MakeSRGB(),
kPremul_SkAlphaType);
if (!spec) {
SkDebugf("%s\n", error.c_str());
}
fSpec = std::move(spec);
SkColor colors[] = {SK_ColorWHITE, SK_ColorBLACK};
fShader = SkGradientShader::MakeRadial(kGradCenter,
.4f,
colors,
nullptr,
2,
SkTileMode::kMirror);
fVB = SkMeshes::MakeVertexBuffer(kQuad, sizeof(kQuad));
}
SkString getName() const override { return SkString("custommesh_uniforms"); }
DrawResult onDraw(SkCanvas* canvas, SkString* error) override {
SkMatrix matrices[] {
SkMatrix::MakeAll(-1, 0, 0, // self inverse so no effect.
0, -1, 0,
0, 0, 1),
SkMatrix::RotateDeg(fDegrees/2.f, {0.5f, 0.5f}),
};
for (const auto& m : matrices) {
auto unis = SkData::MakeUninitialized(fSpec->uniformSize());
SkPoint trans = -kCoordTrans;
static_assert(sizeof(SkPoint) == 2*sizeof(float));
const SkMeshSpecification::Uniform* u = fSpec->findUniform("t");
SkASSERT(u);
std::memcpy(SkTAddOffset<void>(unis->writable_data(), u->offset),
(void*)&trans,
2*sizeof(float));
u = fSpec->findUniform("m");
SkASSERT(u);
for (size_t offset = u->offset, col = 0; col < 3; ++col) {
for (size_t row = 0; row < 3; ++row, offset += sizeof(float)) {
*SkTAddOffset<float>(unis->writable_data(), offset) = m.rc(row, col);
}
}
u = fSpec->findUniform("color");
SkASSERT(u);
std::memcpy(SkTAddOffset<void>(unis->writable_data(), u->offset),
fColor.vec(),
4*sizeof(float));
auto result = SkMesh::Make(fSpec,
SkMesh::Mode::kTriangleStrip,
fVB,
/*vertexCount=*/4,
/*vertexOffset=*/0,
/*uniforms=*/std::move(unis),
/*children=*/{},
kRect);
if (!result.mesh.isValid()) {
SkDebugf("Mesh creation failed: %s\n", result.error.c_str());
return DrawResult::kFail;
}
SkPaint paint;
paint.setShader(fShader);
canvas->drawMesh(result.mesh, SkBlender::Mode(SkBlendMode::kModulate), paint);
canvas->translate(0, kRect.height() + 10);
}
return DrawResult::kOk;
}
bool onAnimate(double nanos) override {
fDegrees = TimeUtils::NanosToSeconds(nanos) * 360.f/10.f + 45.f;
// prime number periods, like locusts.
fColor.fR = TimeUtils::SineWave(nanos, 13.f, 0.f, 0.f, 1.f);
fColor.fG = TimeUtils::SineWave(nanos, 23.f, 5.f, 0.f, 1.f);
fColor.fB = TimeUtils::SineWave(nanos, 11.f, 0.f, 0.f, 1.f);
fColor.fA = 1.f;
return true;
}
private:
struct Vertex {
SkPoint pos;
SkPoint tex;
};
static constexpr auto kRect = SkRect::MakeLTRB(20, 20, 120, 120);
// Our logical tex coords are [0..1] but we insert an arbitrary translation that gets undone
// with a uniform.
static constexpr SkPoint kCoordTrans = {75, -37};
static constexpr auto kCoordRect = SkRect::MakeXYWH(kCoordTrans.x(), kCoordTrans.y(), 1, 1);
static constexpr SkPoint kGradCenter = {0.3f, 0.2f};
static constexpr Vertex kQuad[] {
{{kRect.left() , kRect.top() }, {kCoordRect.left() , kCoordRect.top()} },
{{kRect.right(), kRect.top() }, {kCoordRect.right(), kCoordRect.top()} },
{{kRect.left() , kRect.bottom()}, {kCoordRect.left() , kCoordRect.bottom()}},
{{kRect.right(), kRect.bottom()}, {kCoordRect.right(), kCoordRect.bottom()}},
};
float fDegrees;
SkColor4f fColor;
sk_sp<SkMesh::VertexBuffer> fVB;
sk_sp<SkMeshSpecification> fSpec;
sk_sp<SkShader> fShader;
};
DEF_GM(return new MeshUniformsGM())
class MeshUpdateGM : public skiagm::GM {
public:
MeshUpdateGM() = default;
protected:
using Attribute = SkMeshSpecification::Attribute;
using Varying = SkMeshSpecification::Varying;
SkISize getISize() override { return {270, 490}; }
void onOnceBeforeDraw() override {
static const Attribute kAttributes[]{
{Attribute::Type::kFloat2, 0, SkString{"pos"}},
{Attribute::Type::kFloat2, 8, SkString{"coords"}},
};
static const Varying kVaryings[]{
{Varying::Type::kFloat2, SkString{"coords"}},
};
static constexpr char kVS[] = R"(
Varyings main(const in Attributes attributes) {
Varyings varyings;
varyings.coords = attributes.coords;
varyings.position = attributes.pos;
return varyings;
}
)";
static constexpr char kFS[] = R"(
float2 main(const Varyings varyings) { return varyings.coords; }
)";
auto [spec, error] = SkMeshSpecification::Make(kAttributes,
sizeof(Vertex),
kVaryings,
SkString(kVS),
SkString(kFS),
SkColorSpace::MakeSRGB(),
kPremul_SkAlphaType);
if (!spec) {
SkDebugf("%s\n", error.c_str());
}
fSpec = std::move(spec);
uint32_t colors[] = {SK_ColorYELLOW, SK_ColorMAGENTA, SK_ColorCYAN, SK_ColorWHITE};
SkPixmap pixmap(SkImageInfo::Make({2, 2}, kBGRA_8888_SkColorType, kPremul_SkAlphaType),
colors,
/*rowBytes=*/8);
fShader = SkImages::RasterFromPixmapCopy(pixmap)->makeShader(
SkTileMode::kClamp, SkTileMode::kClamp, SkFilterMode::kLinear);
}
SkString getName() const override { return SkString("mesh_updates"); }
DrawResult onDraw(SkCanvas* canvas, SkString* error) override {
canvas->clear(SK_ColorBLACK);
GrRecordingContext* rc = canvas->recordingContext();
GrDirectContext* dc = GrAsDirectContext(rc);
if (rc && !dc) {
// On GPU this relies on using the DC to update the GPU backed vertex/index buffers.
return DrawResult::kSkip;
}
if (dc && dc->abandoned()) {
return DrawResult::kSkip;
}
SkPaint paint;
paint.setShader(fShader);
SkRect r = SkRect::MakeXYWH(10.f, 10.f, 50.f, 50.f);
// We test updating CPU and GPU buffers.
for (bool gpuBuffer : {false, true}) {
auto ctx = gpuBuffer ? dc : nullptr;
// How many rects worth of storage is in the vertex buffer?
static constexpr int kVBRects = 2;
// How many times do we update the vertex buffer? Wraps to start of buffer if
// > kVBRects.
static constexpr int kUpdatesRects = 3;
auto vb = make_vertex_buffer(ctx, /*data=*/nullptr, kVBRects * 6 * sizeof(Vertex));
SkASSERT(vb);
SkRect bounds;
for (int i = 0; i < kUpdatesRects; ++i) {
auto p = r.makeOffset(100.f*i, 0.f);
if (i) {
bounds.join(p);
} else {
bounds = p;
}
SkPoint t[4];
SkRect::MakeWH(2.f, 2.f).toQuad(t);
SkMatrix::RotateDeg(90.f*i, {1.f, 1.f}).mapPoints(t, std::size(t));
Vertex vertices[6];
vertices[0] = {{p.left(), p.top()}, t[0]};
vertices[1] = {{p.left(), p.bottom()}, t[3]};
vertices[2] = {{p.right(), p.top()}, t[1]};
vertices[3] = vertices[2];
vertices[4] = vertices[1];
vertices[5] = {{p.right(), p.bottom()}, t[2]};
size_t offset = 6*(i % kVBRects)*sizeof(Vertex);
SkAssertResult(vb->update(ctx, vertices, offset, 6*sizeof(Vertex)));
// Make there aren't accidentally deferred reads of the client data.
std::memset(vertices, 0, sizeof(vertices));
int rectCount = std::min(i + 1, kVBRects);
auto result = SkMesh::Make(fSpec,
SkMesh::Mode::kTriangles,
vb,
/*vertexCount=*/6 * rectCount,
/*vertexOffset=*/0,
/*uniforms=*/nullptr,
/*children=*/{},
bounds);
if (!result.mesh.isValid()) {
SkDebugf("Mesh creation failed: %s\n", result.error.c_str());
return DrawResult::kFail;
}
canvas->drawMesh(result.mesh, SkBlender::Mode(SkBlendMode::kDst), paint);
canvas->translate(0, r.height() + 10);
}
// Now test updating an IB.
// How many rects worth of storage is in the index buffer?
static constexpr int kIBRects = 2;
// How many times do we update the index buffer? Wraps to start of buffer if > kIBRects.
static constexpr int kNumIBUpdates = 3;
// Make the vertex buffer large enough to hold all the rects and populate.
vb = make_vertex_buffer(ctx, /*data=*/nullptr, kNumIBUpdates * 4 * sizeof(Vertex));
SkASSERT(vb);
for (int i = 0; i < kNumIBUpdates; ++i) {
SkPoint p[4];
auto rect = r.makeOffset(100*i, 0);
rect.toQuad(p);
if (i) {
bounds.join(rect);
} else {
bounds = rect;
}
SkPoint t[4];
SkRect::MakeWH(2.f, 2.f).toQuad(t);
SkMatrix::RotateDeg(90.f*i, {1.f, 1.f}).mapPoints(t, std::size(t));
Vertex vertices[4]{{p[0], t[0]}, {p[1], t[1]}, {p[2], t[2]}, {p[3], t[3]}};
SkAssertResult(
vb->update(ctx, vertices, i*4*sizeof(Vertex), 4*sizeof(Vertex)));
}
auto ib = make_index_buffer(
ctx, /*data=*/nullptr, kIBRects * 6 * sizeof(uint16_t));
SkASSERT(ib);
for (int i = 0; i < kNumIBUpdates; ++i) {
uint16_t indices[6] = {SkToU16(0 + 4*i),
SkToU16(3 + 4*i),
SkToU16(1 + 4*i),
SkToU16(1 + 4*i),
SkToU16(3 + 4*i),
SkToU16(2 + 4*i)};
size_t offset = 6*(i % kIBRects)*sizeof(uint16_t);
SkAssertResult(ib->update(ctx, indices, offset, 6*sizeof(uint16_t)));
std::memset(indices, 0, 6*sizeof(uint16_t));
auto result = SkMesh::MakeIndexed(fSpec,
SkMesh::Mode::kTriangles,
vb,
/*vertexCount=*/4 * kNumIBUpdates,
/*vertexOffset=*/0,
ib,
/*indexCount=*/6,
/*indexOffset=*/offset,
/*uniforms=*/nullptr,
/*children=*/{},
bounds);
if (!result.mesh.isValid()) {
SkDebugf("Mesh creation failed: %s\n", result.error.c_str());
return DrawResult::kFail;
}
canvas->drawMesh(result.mesh, SkBlender::Mode(SkBlendMode::kDst), paint);
}
canvas->translate(0, r.height() + 10);
}
return DrawResult::kOk;
}
private:
struct Vertex {
SkPoint pos;
SkPoint tex;
};
sk_sp<SkMeshSpecification> fSpec;
sk_sp<SkShader> fShader;
};
DEF_GM(return new MeshUpdateGM())
class MeshZeroInitGM : public skiagm::GM {
public:
MeshZeroInitGM() = default;
protected:
using Attribute = SkMeshSpecification::Attribute;
using Varying = SkMeshSpecification::Varying;
SkISize getISize() override { return {90, 30}; }
void onOnceBeforeDraw() override {
static const Attribute kAttributes1[]{
{Attribute::Type::kUByte4_unorm, 0, SkString{"color"}},
{Attribute::Type::kFloat2, 4, SkString{"pos" }},
};
static const Attribute kAttributes2[]{
{Attribute::Type::kFloat2, 0, SkString{"pos" }},
{Attribute::Type::kUByte4_unorm, 8, SkString{"color"}},
};
static const Varying kVaryings[]{{Varying::Type::kHalf4, SkString{"color"}}};
static constexpr char kVS[] = R"(
Varyings main(const in Attributes attributes) {
Varyings varyings;
varyings.color = attributes.color;
varyings.position = attributes.pos;
return varyings;
}
)";
static constexpr char kFS[] = R"(
float2 main(const Varyings varyings, out half4 color) {
color = varyings.color;
return varyings.position;
}
)";
auto result = SkMeshSpecification::Make(kAttributes1,
/*vertexStride==*/12,
kVaryings,
SkString(kVS),
SkString(kFS),
SkColorSpace::MakeSRGB(),
kPremul_SkAlphaType);
if (!result.specification) {
SkDebugf("%s\n", result.error.c_str());
}
fSpec[0] = std::move(result.specification);
result = SkMeshSpecification::Make(kAttributes1,
/*vertexStride=*/12,
kVaryings,
SkString(kVS),
SkString(kFS),
SkColorSpace::MakeSRGB(),
kPremul_SkAlphaType);
if (!result.specification) {
SkDebugf("%s\n", result.error.c_str());
}
fSpec[1] = std::move(result.specification);
}
SkString getName() const override { return SkString("mesh_zero_init"); }
DrawResult onDraw(SkCanvas* canvas, SkString* error) override {
GrRecordingContext* rc = canvas->recordingContext();
GrDirectContext* dc = GrAsDirectContext(rc);
if (rc && !dc) {
// On GPU this relies on using the DC to update the GPU backed vertex/index buffers.
return DrawResult::kSkip;
}
if (dc && dc->abandoned()) {
return DrawResult::kSkip;
}
static constexpr SkPoint kTri[]{{10, 10}, {20, 10}, {10, 20}};
// The zero will come from the uninit part of the buffer.
static constexpr uint16_t kTiIndices[]{1, 2};
// We test updating CPU and GPU buffers.
for (bool gpuBuffer : {false, true}) {
auto ctx = gpuBuffer ? dc : nullptr;
for (int i = 0; i < 2; ++i) {
const auto& spec = fSpec[i];
size_t posOffset = spec->findAttribute("pos")->offset;
auto vb = make_vertex_buffer(ctx, nullptr, spec->stride() * std::size(kTri));
SkASSERT(vb);
for (size_t j = 0; j < std::size(kTri); ++j) {
SkAssertResult(vb->update(ctx,
&kTri[j],
spec->stride()*j + posOffset,
sizeof(kTri[j])));
}
// The first time we make the indices be 0,1,2 using the zero'ed buffer for the
// first. However, because uploads must be 4 byte aligned it's actually 0,0,1,2.
// The second time we upload 1,2 to beginning of the buffer to form 1,2,0.
size_t indexUploadOffset = i == 0 ? 4 : 0;
size_t indexMeshOffset = i == 0 ? 2 : 0;
auto ib = make_index_buffer(ctx, nullptr, sizeof(uint16_t) * 4);
SkASSERT(ib);
SkAssertResult(ib->update(ctx, kTiIndices, indexUploadOffset, sizeof(kTiIndices)));
SkRect bounds;
bounds.setBounds(kTri, std::size(kTri));
auto result = SkMesh::MakeIndexed(spec,
SkMesh::Mode::kTriangles,
std::move(vb),
/*vertexCount=*/std::size(kTri),
/*vertexOffset=*/0,
std::move(ib),
/*indexCount=*/std::size(kTiIndices) + 1,
indexMeshOffset,
/*uniforms=*/nullptr,
/*children=*/{},
bounds);
if (!result.mesh.isValid()) {
SkDebugf("Mesh creation failed: %s\n", result.error.c_str());
return DrawResult::kFail;
}
SkPaint paint;
// The color will be transparent black. Set the blender to kDstOver so when combined
// with the paint's opaque black we get opaque black.
canvas->drawMesh(result.mesh, SkBlender::Mode(SkBlendMode::kDstOver), paint);
canvas->translate(bounds.width() + 10, 0);
if (ctx) {
// Free up the buffers for recycling in the cache. This helps test that
// a recycled buffer gets zero'ed.
result.mesh = {};
SkASSERT(!ib); // NOLINT - bugprone-use-after-move. We're asserting it's moved.
SkASSERT(!vb); // NOLINT
ctx->flushAndSubmit(GrSyncCpu::kYes);
}
}
}
return DrawResult::kOk;
}
private:
sk_sp<SkMeshSpecification> fSpec[2];
};
DEF_GM(return new MeshZeroInitGM())
// We have a special GM for testing SkMesh through SkPicture because all of SkPicture GM testing
// uses the CPU backend and SkMesh only works on GPU.
class PictureMesh : public skiagm::GM {
public:
PictureMesh() = default;
protected:
using Attribute = SkMeshSpecification::Attribute;
using Varying = SkMeshSpecification::Varying;
SkISize getISize() override { return {390, 90}; }
void onOnceBeforeDraw() override {
static const Attribute kAttributes[]{
{Attribute::Type::kFloat2, 0, SkString{"pos"}},
};
static const Varying kVaryings[]{
{Varying::Type::kFloat2, SkString{"coords"}},
};
static constexpr char kVS[] = R"(
Varyings main(in const Attributes attributes) {
Varyings varyings;
varyings.position = attributes.pos;
return varyings;
}
)";
static const SkString kFS = SkStringPrintf(R"(
uniform float2 offset;
float2 main(const Varyings varyings, out float4 color) {
float2 tl = float2(%f, %f);
float2 wh = float2(%f, %f);
float2 c = tl + wh/2;
float r = length(wh)/4;
color.rba = float3(1);
color.g = min(1, length(varyings.position - c + offset) / r);
return varyings.position;
}
)", kRect.x(), kRect.y(), kRect.width(), kRect.height());
auto [spec, error] =
SkMeshSpecification::Make(kAttributes,
sizeof(Vertex),
kVaryings,
SkString(kVS),
kFS,
SkColorSpace::MakeSRGB()->makeColorSpin(),
kPremul_SkAlphaType);
if (!spec) {
SkDebugf("%s\n", error.c_str());
}
fSpec = std::move(spec);
fVB = SkMeshes::MakeVertexBuffer(kQuad, sizeof(kQuad));
fIB = SkMeshes::MakeIndexBuffer(kIndices, sizeof(kIndices));
SkRandom random;
SkColor4f colors[6];
for (size_t i = 0; i < std::size(colors) - 1; ++i) {
colors[i] = {random.nextF(), random.nextF(), random.nextF(), 1.f};
}
colors[std::size(colors) - 1] = colors[0];
SkPaint paint;
SkGradientShader::Interpolation interpolation;
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kHSL;
fShader = SkGradientShader::MakeSweep(kRect.centerX(), kRect.centerY(),
colors,
SkColorSpace::MakeSRGB(),
nullptr,
std::size(colors),
SkTileMode::kRepeat,
0,
360.f,
interpolation,
/*localMatrix=*/nullptr);
}
SkString getName() const override { return SkString("picture_mesh"); }
DrawResult onDraw(SkCanvas* canvas, SkString* error) override {
SkPaint paint;
paint.setShader(fShader);
auto dc = GrAsDirectContext(canvas->recordingContext());
for (bool picture : {false, true}) {
canvas->save();
for (bool gpu : {false, true}) {
auto vb = gpu ? SkMeshes::CopyVertexBuffer(dc, fVB) : fVB;
auto ib = gpu ? SkMeshes::CopyIndexBuffer (dc, fIB) : fIB;
float offset[2] = {8, 8};
for (size_t i = 0; i < 4; ++i) {
auto uniforms = SkData::MakeWithCopy(&offset, sizeof(offset));
SkMesh::Result r;
switch (i) {
case 0:
r = SkMesh::Make(fSpec,
SkMesh::Mode::kTriangles,
fVB,
6,
1 * sizeof(Vertex),
std::move(uniforms),
/*children=*/{},
kRect);
break;
case 1:
r = SkMesh::Make(fSpec,
SkMesh::Mode::kTriangleStrip,
fVB,
4,
1 * sizeof(Vertex),
std::move(uniforms),
/*children=*/{},
kRect);
break;
case 2:
r = SkMesh::MakeIndexed(fSpec,
SkMesh::Mode::kTriangles,
fVB,
std::size(kQuad),
0,
fIB,
6,
2 * (sizeof(uint16_t)),
std::move(uniforms),
/*children=*/{},
kRect);
break;
case 3:
r = SkMesh::MakeIndexed(fSpec,
SkMesh::Mode::kTriangleStrip,
fVB,
std::size(kQuad),
0,
fIB,
6,
2 * sizeof(uint16_t),
std::move(uniforms),
/*children=*/{},
kRect);
break;
}
if (!r.mesh.isValid()) {
*error = r.error;
return DrawResult::kFail;
}
auto draw = [&](SkCanvas* c) {
c->drawMesh(r.mesh, SkBlender::Mode(SkBlendMode::kDifference), paint);
};
if (picture) {
SkPictureRecorder recorder;
draw(recorder.beginRecording(SkRect::Make(this->getISize()),
/*bbhFactory=*/nullptr));
canvas->drawPicture(recorder.finishRecordingAsPicture());
} else {
draw(canvas);
}
offset[i%2] *= -1;
canvas->translate(kRect.width() + 10, 0);
}
}
canvas->restore();
canvas->translate(0, kRect.height() + 10);
}
return DrawResult::kOk;
}
private:
struct Vertex {
SkPoint pos;
};
static constexpr auto kRect = SkRect::MakeWH(40, 40);
static constexpr Vertex kQuad[] {
{1000, 1000}, // skip
{{kRect.left() , kRect.top() }},
{{kRect.right(), kRect.top() }},
{{kRect.left() , kRect.bottom()}},
{{kRect.right(), kRect.bottom()}},
{{kRect.left() , kRect.bottom()}},
{{kRect.right(), kRect.top() }},
};
static constexpr uint16_t kIndices[] = {1000, 2000, 1, 2, 3, 4, 5, 6};
sk_sp<SkMesh::VertexBuffer> fVB;
sk_sp<SkMesh::IndexBuffer> fIB;
sk_sp<SkMeshSpecification> fSpec;
sk_sp<SkShader> fShader;
};
DEF_GM(return new PictureMesh())
class MeshWithShadersGM : public skiagm::GM {
public:
enum class Type {
kMeshWithImage,
kMeshWithPaintColor,
kMeshWithPaintImage,
kMeshWithEffects,
};
MeshWithShadersGM(Type type) : fType(type) {
// Create a grid of evenly spaced points for our mesh
this->onAnimate(0.0);
// Create an index buffer of triangles over our point mesh.
for (int y = 0; y < kMeshSize - 1; ++y) {
for (int x = 0; x < kMeshSize - 1; ++x) {
SkASSERT(((y + 1) * kMeshSize + x + 1) < fVerts.size());
uint16_t TL = y * kMeshSize + x;
uint16_t TR = y * kMeshSize + x + 1;
uint16_t BL = (y + 1) * kMeshSize + x;
uint16_t BR = (y + 1) * kMeshSize + x + 1;
fIndices.push_back(TL);
fIndices.push_back(TR);
fIndices.push_back(BL);
fIndices.push_back(BR);
fIndices.push_back(BL);
fIndices.push_back(TR);
}
}
}
protected:
using Attribute = SkMeshSpecification::Attribute;
using Varying = SkMeshSpecification::Varying;
SkISize getISize() override { return {320, 320}; }
void onOnceBeforeDraw() override {
{
static const Attribute kAttributes[] = {
{Attribute::Type::kFloat2, 0, SkString{"position"}},
{Attribute::Type::kFloat2, 8, SkString{"uv"}},
};
static const Varying kVaryings[] = {
{Varying::Type::kFloat2, SkString{"uv"}},
};
static constexpr char kVS[] = R"(
Varyings main(const in Attributes attributes) {
Varyings varyings;
varyings.uv = attributes.uv;
varyings.position = attributes.position;
return varyings;
}
)";
static constexpr char kFS[] = R"(
uniform shader myShader1;
uniform shader myShader2;
uniform colorFilter myColorFilter;
uniform blender myBlend;
float2 main(const in Varyings varyings, out half4 color) {
half4 color1 = myShader1.eval(varyings.uv);
half4 color2 = myShader2.eval(varyings.uv);
// Apply a inverse color filter to the first image.
color1 = myColorFilter.eval(color1);
// Fade in the second image horizontally, leveraging the UVs.
color2 *= varyings.uv.x / 128.0;
// Combine the two images by using a blender (set to dst-over).
color = myBlend.eval(color1, color2);
return varyings.uv;
}
)";
auto [spec, error] = SkMeshSpecification::Make(kAttributes,
sizeof(Vertex),
kVaryings,
SkString(kVS),
SkString(kFS));
if (!spec) {
SkDebugf("%s\n", error.c_str());
}
fSpec = std::move(spec);
}
switch (fType) {
case Type::kMeshWithImage: {
fShader1 = ToolUtils::GetResourceAsImage("images/mandrill_128.png")
->makeShader(SkSamplingOptions(SkFilterMode::kLinear));
fShader2 = nullptr;
fColorFilter = nullptr;
fBlender = nullptr;
fPaintShader = nullptr;
break;
}
case Type::kMeshWithEffects: {
uint8_t inverseTable[256];
for (int index = 0; index < 256; ++index) {
inverseTable[index] = 255 - index;
}
fShader1 = ToolUtils::GetResourceAsImage("images/mandrill_128.png")
->makeShader(SkSamplingOptions(SkFilterMode::kLinear));
fShader2 = ToolUtils::GetResourceAsImage("images/color_wheel.png")
->makeShader(SkSamplingOptions(SkFilterMode::kLinear));
fColorFilter = SkColorFilters::TableARGB(/*tableA=*/nullptr,
inverseTable,
inverseTable,
inverseTable);
fBlender = SkBlender::Mode(SkBlendMode::kDstOver);
fPaintShader = nullptr;
break;
}
case Type::kMeshWithPaintColor: {
fShader1 = nullptr;
fShader2 = ToolUtils::GetResourceAsImage("images/mandrill_128.png")
->makeShader(SkSamplingOptions(SkFilterMode::kLinear));
fColorFilter = nullptr;
fBlender = SkBlender::Mode(SkBlendMode::kDst);
fPaintShader = SkShaders::Color(SK_ColorGREEN);
break;
}
case Type::kMeshWithPaintImage: {
fShader1 = ToolUtils::GetResourceAsImage("images/color_wheel.png")
->makeShader(SkSamplingOptions(SkFilterMode::kLinear));
fShader2 = nullptr;
fColorFilter = nullptr;
fBlender = nullptr;
fPaintShader = ToolUtils::GetResourceAsImage("images/mandrill_128.png")
->makeShader(SkSamplingOptions(SkFilterMode::kLinear));
break;
}
default:
SkUNREACHABLE;
}
}
DrawResult onGpuSetup(SkCanvas* canvas, SkString* string, GraphiteTestContext*) override {
auto dc = GrAsDirectContext(canvas->recordingContext());
this->ensureBuffers();
if (!dc || dc->abandoned()) {
return DrawResult::kOk;
}
fVB = SkMeshes::CopyVertexBuffer(dc, fVB);
fIB = SkMeshes::CopyIndexBuffer (dc, fIB);
return (!fVB || !fIB) ? DrawResult::kFail
: DrawResult::kOk;
}
void onGpuTeardown() override {
// Destroy the GPU buffers and recreate on CPU
fVB = nullptr;
fIB = nullptr;
this->ensureBuffers();
}
SkString getName() const override {
switch (fType) {
case Type::kMeshWithImage: return SkString("mesh_with_image");
case Type::kMeshWithEffects: return SkString("mesh_with_effects");
case Type::kMeshWithPaintColor: return SkString("mesh_with_paint_color");
case Type::kMeshWithPaintImage: return SkString("mesh_with_paint_image");
default: SkUNREACHABLE;
}
}
bool onAnimate(double nanos) override {
// `periodic` goes from zero to 2Ï€ every four seconds, then wraps around.
double periodic = nanos / 4'000'000'000.;
periodic -= std::floor(periodic);
periodic *= 2 * SK_DoublePI;
double xOff[kMeshSize], yOff[kMeshSize];
for (int index = 0; index < kMeshSize; ++index) {
xOff[index] = std::sin(periodic) * kRippleSize;
yOff[index] = std::sin(periodic + 10.0) * kRippleSize;
periodic += 0.8;
}
fVerts.clear();
for (int y = 0; y < kMeshSize; ++y) {
float yf = (float)y / (kMeshSize - 1); // yf = 0 .. 1
for (int x = 0; x < kMeshSize; ++x) {
float xf = (float)x / (kMeshSize - 1); // xf = 0 .. 1
Vertex* vert = &fVerts.push_back();
vert->pos[0] = kRect.left() + xf * kRect.width() + xOff[y];
vert->pos[1] = kRect.top() + yf * kRect.height() + yOff[x];
vert->uv[0] = kUV.left() + xf * kUV.width();
vert->uv[1] = kUV.top() + yf * kUV.height();
}
}
return true;
}
DrawResult onDraw(SkCanvas* canvas, SkString*) override {
SkRuntimeEffect::ChildPtr child[4] = {fShader1, fShader2, fColorFilter, fBlender};
GrRecordingContext* rc = canvas->recordingContext();
GrDirectContext* dc = GrAsDirectContext(rc);
fVB->update(dc, fVerts.data(), /*offset=*/0, fVerts.size_bytes());
SkMesh::Result result = SkMesh::MakeIndexed(fSpec,
SkMesh::Mode::kTriangles,
fVB,
fVerts.size(),
/*vertexOffset=*/0,
fIB,
fIndices.size(),
/*indexOffset=*/0,
/*uniforms=*/nullptr,
/*children=*/child,
kRect.makeOutset(kRippleSize, kRippleSize));
if (!result.mesh.isValid()) {
SkDebugf("Mesh creation failed: %s\n", result.error.c_str());
return DrawResult::kFail;
}
SkPaint paint;
paint.setShader(fPaintShader);
canvas->drawMesh(result.mesh, SkBlender::Mode(SkBlendMode::kDstOver), paint);
return DrawResult::kOk;
}
private:
void ensureBuffers() {
if (!fVB) {
fVB = SkMeshes::MakeVertexBuffer(fVerts.data(), fVerts.size_bytes());
}
if (!fIB) {
fIB = SkMeshes::MakeIndexBuffer(fIndices.data(), fIndices.size_bytes());
}
}
struct Vertex {
float pos[2];
float uv[2];
};
static constexpr auto kRect = SkRect::MakeLTRB(20, 20, 300, 300);
static constexpr auto kUV = SkRect::MakeLTRB( 0, 0, 128, 128);
static constexpr int kMeshSize = 16;
static constexpr float kRippleSize = 6.0f;
Type fType;
TArray<Vertex> fVerts;
TArray<uint16_t> fIndices;
sk_sp<SkShader> fShader1, fShader2, fPaintShader;
sk_sp<SkColorFilter> fColorFilter;
sk_sp<SkBlender> fBlender;
sk_sp<SkMeshSpecification> fSpec;
sk_sp<SkMesh::VertexBuffer> fVB;
sk_sp<SkMesh::IndexBuffer> fIB;
};
DEF_GM(return new MeshWithShadersGM(MeshWithShadersGM::Type::kMeshWithImage))
DEF_GM(return new MeshWithShadersGM(MeshWithShadersGM::Type::kMeshWithPaintColor))
DEF_GM(return new MeshWithShadersGM(MeshWithShadersGM::Type::kMeshWithPaintImage))
DEF_GM(return new MeshWithShadersGM(MeshWithShadersGM::Type::kMeshWithEffects))
DEF_SIMPLE_GM_CAN_FAIL(custommesh_cs_uniforms, canvas, errorMsg, 200, 900) {
if (!canvas->recordingContext() && !canvas->recorder()) {
*errorMsg = GM::kErrorMsg_DrawSkippedGpuOnly;
return DrawResult::kSkip;
}
// Shared data
static constexpr SkRect kRect = SkRect::MakeLTRB(20, 20, 80, 80);
static constexpr SkPoint kQuad[]{
{kRect.left(), kRect.top()},
{kRect.right(), kRect.top()},
{kRect.left(), kRect.bottom()},
{kRect.right(), kRect.bottom()},
};
sk_sp<SkMesh::VertexBuffer> vb = SkMeshes::MakeVertexBuffer(kQuad, sizeof(kQuad));
sk_sp<SkData> unis = SkData::MakeWithCopy(&SkColors::kRed, sizeof(SkColor4f));
// Surface helper
auto makeSurface = [=](sk_sp<SkColorSpace> cs) {
SkImageInfo ii = SkImageInfo::MakeN32Premul(200, 100, cs);
sk_sp<SkSurface> surface = canvas->makeSurface(ii);
return surface ? surface : SkSurfaces::Raster(ii);
};
// Mesh helper
enum class Managed : bool { kNo, kYes };
auto makeMesh = [&](Managed managed, sk_sp<SkColorSpace> workingCS) {
static const SkMeshSpecification::Attribute kAttributes[]{
{SkMeshSpecification::Attribute::Type::kFloat2, 0, SkString{"pos"}},
};
static constexpr char kVS[] = R"(
Varyings main(in const Attributes attributes) {
Varyings varyings;
varyings.position = attributes.pos;
return varyings;
}
)";
static constexpr char kManagedFS[] = R"(
layout(color) uniform half4 color;
float2 main(const Varyings varyings, out half4 c) {
c = color;
return varyings.position;
}
)";
static constexpr char kRawFS[] = R"(
uniform half4 color;
float2 main(const Varyings varyings, out half4 c) {
c = color;
return varyings.position;
}
)";
auto [spec, error] = SkMeshSpecification::Make(
kAttributes,
sizeof(SkPoint),
/*varyings=*/{},
SkString(kVS),
SkString(managed == Managed::kYes ? kManagedFS : kRawFS),
std::move(workingCS),
kPremul_SkAlphaType);
SkASSERT(spec);
SkMesh::Result result = SkMesh::Make(std::move(spec),
SkMesh::Mode::kTriangleStrip,
vb,
/*vertexCount=*/4,
/*vertexOffset=*/0,
/*uniforms=*/unis,
/*children=*/{},
kRect);
SkASSERT(result.mesh.isValid());
return result.mesh;
};
sk_sp<SkColorSpace> null = nullptr,
srgb = SkColorSpace::MakeSRGB(),
spin = SkColorSpace::MakeSRGB()->makeColorSpin(),
wide = SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2,
SkNamedGamut::kRec2020);
struct Config {
sk_sp<SkColorSpace> fMeshCS;
sk_sp<SkColorSpace> fSurfaceCS;
Managed fManaged;
SkColor fExpectedColor = SK_ColorRED;
};
static const Config kConfigs[] = {
// Uniforms should remain in sRGB mode, then get converted to destination after mesh FS
// Before b/316594914 was fixed, these would get double-converted:
{srgb, null, Managed::kYes},
{srgb, srgb, Managed::kYes},
{srgb, spin, Managed::kYes},
{srgb, wide, Managed::kYes},
// Uniforms should be converted to working space (spun), then converted to destination
{spin, srgb, Managed::kYes},
{spin, spin, Managed::kYes},
{spin, wide, Managed::kYes},
// Non-managed uniforms serve as a control group. The red uniforms are not converted to
// the working space. The mesh FS returns "red" {1, 0, 0, 1}, but that's actually green,
// because the working space of the mesh is `spin`. That output is converted to dest,
// rendering as green. Therefore, we manually change the control color's box to green.
{spin, srgb, Managed::kNo, SK_ColorGREEN},
{spin, wide, Managed::kNo, SK_ColorGREEN},
};
for (const Config& config : kConfigs) {
SkMesh mesh = makeMesh(config.fManaged, config.fMeshCS);
sk_sp<SkSurface> offscreen = makeSurface(config.fSurfaceCS);
SkCanvas* offscreenCanvas = offscreen->getCanvas();
SkPaint paint;
offscreenCanvas->drawMesh(mesh, SkBlender::Mode(SkBlendMode::kDst), paint);
offscreenCanvas->translate(100, 0);
paint.setColor(config.fExpectedColor);
offscreenCanvas->drawRect(kRect, paint);
offscreen->draw(canvas, 0, 0);
canvas->translate(0, 100);
}
return DrawResult::kOk;
}
} // namespace skiagm