/* * 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/SkSurface.h" #include "include/effects/SkGradientShader.h" #include "include/gpu/GrDirectContext.h" #include "src/core/SkCanvasPriv.h" #include "src/core/SkMeshPriv.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; } float2 main(in Attributes attributes, out Varyings varyings) { varyings.color = unswizzle_color(attributes.brag); varyings.uv = attributes.xuyv.yw; return attributes.xuyv.xz; } )"; static constexpr char kFS[] = R"( float2 main(in Varyings varyings, out float4 color) { color = varyings.color; return varyings.uv; } )"; auto[spec, error] = SkMeshSpecification::Make(SkMakeSpan(kAttributes, SK_ARRAY_COUNT(kAttributes)), sizeof(ColorVertex), SkMakeSpan(kVaryings, SK_ARRAY_COUNT(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"( float2 main(in Attributes a, out Varyings v) { v.vux2 = 2*a.xuyv.wy; return a.xuyv.xz; } )"; static constexpr char kFS[] = R"( float2 helper(in float2 vux2) { return vux2.yx/2; } float2 main(in Varyings varyings) { return helper(varyings.vux2); } )"; auto[spec, error] = SkMeshSpecification::Make(SkMakeSpan(kAttributes, SK_ARRAY_COUNT(kAttributes)), sizeof(NoColorVertex), SkMakeSpan(kVaryings, SK_ARRAY_COUNT(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(GrDirectContext* context, SkString* string) override { this->ensureBuffers(); if (!context || context->abandoned()) { return DrawResult::kOk; } fColorVB = SkMesh::MakeVertexBuffer(context, CpuVBAsData(fColorVB)); fColorIndexedVB = SkMesh::MakeVertexBuffer(context, CpuVBAsData(fColorIndexedVB)); fIB[1] = SkMesh::MakeIndexBuffer (context, CpuIBAsData(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 mesh; // Rather than pile onto the combinatorics we draw every other test case indexed. if ((i & 1) == 0) { if (colors) { mesh = SkMesh::Make(fSpecWithColor, SkMesh::Mode::kTriangleStrip, fColorVB, /*vertexCount= */ 4, /*vertexOffset=*/ 0, kRect); } else { mesh = SkMesh::Make(fSpecWithNoColor, SkMesh::Mode::kTriangleStrip, fNoColorVB, /*vertexCount=*/4, kNoColorOffset, kRect); } } else { // Alternate between CPU and GPU-backend index buffers. auto ib = (i%4 == 0) ? fIB[0] : fIB[1]; if (colors) { mesh = SkMesh::MakeIndexed(fSpecWithColor, SkMesh::Mode::kTriangles, fColorIndexedVB, /*vertexCount=*/ 6, kColorIndexedOffset, std::move(ib), /*indexCount=*/6, kIndexOffset, kRect); } else { mesh = SkMesh::MakeIndexed(fSpecWithNoColor, SkMesh::Mode::kTriangles, fNoColorIndexedVB, /*vertexCount=*/ 6, /*vertexOffset=*/0, std::move(ib), /*indexCount=*/6, kIndexOffset, kRect); } } SkPaint paint; paint.setColor(SK_ColorGREEN); paint.setShader(shader ? fShader : nullptr); paint.setAlpha(alpha); SkCanvasPriv::DrawMesh(canvas, mesh, blender, paint); canvas->translate(0, 150); ++i; } canvas->restore(); canvas->translate(150, 0); } return DrawResult::kOk; } private: static sk_sp CpuVBAsData(sk_sp buffer) { auto vb = static_cast(buffer.get()); SkASSERT(vb->asData()); return vb->asData(); } static sk_sp CpuIBAsData(sk_sp buffer) { auto ib = static_cast(buffer.get()); SkASSERT(ib->asData()); return ib->asData(); } void ensureBuffers() { if (!fColorVB) { fColorVB = SkMesh::MakeVertexBuffer( /*GrDirectContext*=*/nullptr, SkData::MakeWithoutCopy(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, std::move(data)); } 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, std::move(data)); } if (!fNoColorIndexedVB) { fNoColorIndexedVB = SkMesh::MakeVertexBuffer( /*GrDirectContext*=*/nullptr, SkData::MakeWithoutCopy(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, std::move(data)); } 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"( float2 main(in Attributes attributes, out Varyings varyings) { varyings.color = half4(attributes.color.a*attributes.color.rgb, attributes.color.a); return attributes.pos; } )"; static constexpr char kUnpremulVS[] = R"( float2 main(in Attributes attributes, out Varyings varyings) { varyings.color = attributes.color; return attributes.pos; } )"; static constexpr char kFS[] = R"( void main(in Varyings varyings, out half4 color) { color = varyings.color; } )"; 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( SkMakeSpan(kAttributes, SK_ARRAY_COUNT(kAttributes)), sizeof(Vertex), SkMakeSpan(kVaryings, SK_ARRAY_COUNT(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, SkData::MakeWithoutCopy(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}) { SkMesh mesh = SkMesh::Make(fSpecs[SpecIndex(unpremul, spin)], SkMesh::Mode::kTriangleStrip, fVB, /*vertexCount=*/ 4, /*vertexOffset=*/0, kRect); SkPaint paint; paint.setShader(useShader ? fShader : nullptr); SkBlendMode mode = useShader ? SkBlendMode::kModulate : SkBlendMode::kDst; SkCanvasPriv::DrawMesh(c, 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;) } // namespace skiagm