/* * 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/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/SkSurface.h" #include "include/effects/SkGradientShader.h" #include "include/gpu/GrDirectContext.h" #include "src/base/SkRandom.h" #include "src/core/SkCanvasPriv.h" #include "src/core/SkMeshPriv.h" #include "tools/timer/TimeUtils.h" #include namespace skiagm { class MeshGM : public skiagm::GM { public: MeshGM() {} protected: using Attribute = SkMeshSpecification::Attribute; using Varying = SkMeshSpecification::Varying; SkISize onISize() 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"( float2 main(const in Varyings varyings, out float4 color) { color = 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) override { auto dc = GrAsDirectContext(canvas->recordingContext()); this->ensureBuffers(); if (!dc || dc->abandoned()) { return DrawResult::kOk; } fColorVB = SkMesh::CopyVertexBuffer(dc, fColorVB); fColorIndexedVB = SkMesh::CopyVertexBuffer(dc, fColorIndexedVB); fIB[1] = SkMesh::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 onShortName() override { return SkString("custommesh"); } DrawResult onDraw(SkCanvas* canvas, SkString*) override { int i = 0; for (const sk_sp& 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, kRect); } else { result = SkMesh::Make(fSpecWithNoColor, SkMesh::Mode::kTriangleStrip, fNoColorVB, /*vertexCount=*/4, kNoColorOffset, /*uniforms=*/nullptr, 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, kRect); } else { result = SkMesh::MakeIndexed(fSpecWithNoColor, SkMesh::Mode::kTriangles, fNoColorIndexedVB, /*vertexCount=*/6, /*vertexOffset=*/0, std::move(ib), /*indexCount=*/6, kIndexOffset, /*uniforms=*/nullptr, 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 = SkMesh::MakeVertexBuffer(/*GrDirectContext*=*/nullptr, 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(data->writable_data(), kNoColorOffset), kNoColorQuad, sizeof(kNoColorQuad)); fNoColorVB = SkMesh::MakeVertexBuffer(/*GrDirectContext*=*/nullptr, data->data(), data->size()); } if (!fColorIndexedVB) { // This buffer also has an offset. auto data = SkData::MakeUninitialized(sizeof(kColorIndexedQuad) + kColorIndexedOffset); std::memcpy(SkTAddOffset(data->writable_data(), kColorIndexedOffset), kColorIndexedQuad, sizeof(kColorIndexedQuad)); fColorIndexedVB = SkMesh::MakeVertexBuffer(/*GrDirectContext*=*/nullptr, data->data(), data->size()); } if (!fNoColorIndexedVB) { fNoColorIndexedVB = SkMesh::MakeVertexBuffer(/*GrDirectContext*=*/nullptr, kNoColorIndexedQuad, sizeof(kNoColorIndexedQuad)); } if (!fIB[0]) { // The index buffer has an offset. auto data = SkData::MakeUninitialized(sizeof(kIndices) + kIndexOffset); std::memcpy(SkTAddOffset(data->writable_data(), kIndexOffset), kIndices, sizeof(kIndices)); fIB[0] = SkMesh::MakeIndexBuffer(/*GrDirectContext*=*/nullptr, 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 fShader; sk_sp fSpecWithColor; sk_sp fSpecWithNoColor; // On GPU the first IB is a CPU buffer and the second is a GPU buffer. sk_sp fIB[2]; sk_sp fColorVB; sk_sp fNoColorVB; sk_sp fColorIndexedVB; sk_sp fNoColorIndexedVB; }; DEF_GM(return new MeshGM;) class MeshColorSpaceGM : public skiagm::GM { public: MeshColorSpaceGM() {} protected: using Attribute = SkMeshSpecification::Attribute; using Varying = SkMeshSpecification::Varying; SkISize onISize() 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 = SkMesh::MakeVertexBuffer(nullptr, kQuad, sizeof(kQuad)); } SkString onShortName() 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 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, 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(spin) + 2*static_cast(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 fVB; sk_sp fSpecs[4]; sk_sp fShader; }; 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 onISize() 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 = SkMesh::MakeVertexBuffer(nullptr, kQuad, sizeof(kQuad)); } SkString onShortName() 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(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(unis->writable_data(), offset) = m.rc(row, col); } } u = fSpec->findUniform("color"); SkASSERT(u); std::memcpy(SkTAddOffset(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), 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 fVB; sk_sp fSpec; sk_sp fShader; }; DEF_GM(return new MeshUniformsGM()) class MeshUpdateGM : public skiagm::GM { public: MeshUpdateGM() = default; protected: using Attribute = SkMeshSpecification::Attribute; using Varying = SkMeshSpecification::Varying; SkISize onISize() 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 onShortName() 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 = SkMesh::MakeVertexBuffer(ctx, /*data=*/nullptr, kVBRects*6*sizeof(Vertex)); 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, nullptr, 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 = SkMesh::MakeVertexBuffer( ctx, /*data=*/nullptr, kNumIBUpdates*4*sizeof(Vertex)); 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 = SkMesh::MakeIndexBuffer(ctx, /*data=*/nullptr, kIBRects*6*sizeof(uint16_t)); 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, 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 fSpec; sk_sp fShader; }; DEF_GM(return new MeshUpdateGM()) class MeshZeroInitGM : public skiagm::GM { public: MeshZeroInitGM() = default; protected: using Attribute = SkMeshSpecification::Attribute; using Varying = SkMeshSpecification::Varying; SkISize onISize() 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 onShortName() 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 = SkMesh::MakeVertexBuffer(ctx, nullptr, spec->stride()*std::size(kTri)); 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 = SkMesh::MakeIndexBuffer(ctx, nullptr, sizeof(uint16_t)*4); 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, 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(true); } } } return DrawResult::kOk; } private: sk_sp 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 onISize() 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 = SkMesh::MakeVertexBuffer(nullptr, kQuad, sizeof(kQuad)); fIB = SkMesh::MakeIndexBuffer(nullptr, 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 onShortName() 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 ? SkMesh::CopyVertexBuffer(dc, fVB) : fVB; auto ib = gpu ? SkMesh::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), kRect); break; case 1: r = SkMesh::Make(fSpec, SkMesh::Mode::kTriangleStrip, fVB, 4, 1*sizeof(Vertex), std::move(uniforms), 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), 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), 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 fVB; sk_sp fIB; sk_sp fSpec; sk_sp fShader; }; DEF_GM(return new PictureMesh()) } // namespace skiagm