| /* | |
| * 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/SkGradient.h" | |
| #include "include/gpu/ganesh/GrDirectContext.h" | |
| #include "include/gpu/ganesh/SkMeshGanesh.h" | |
| #include "include/private/SkAssert.h" | |
| #include "src/core/SkCanvasPriv.h" | |
| #include "src/core/SkRandom.h" | |
| #include "tools/DecodeUtils.h" | |
| #include "tools/timer/TimeUtils.h" | |
| #include <array> | |
| 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 SkColor4f kColors[] = {SkColors::kTransparent, SkColors::kWhite}; | |
| fShader = SkShaders::RadialGradient({10, 10}, 3, {{kColors, {}, 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}}; | |
| const SkColor4f colors[] = {SkColors::kWhite, SkColors::kTransparent}; | |
| fShader = SkShaders::LinearGradient(pts, {{colors, {}, 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; | |
| c->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; | |
| std::array<sk_sp<SkMeshSpecification>, 4> fSpecs; | |
| 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); | |
| const SkColor4f colors[] = {SkColors::kWhite, SkColors::kBlack}; | |
| fShader = SkShaders::RadialGradient(kGradCenter, .4f, | |
| {{colors, {}, 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; | |
| static constexpr int kWidth = 270; | |
| static constexpr int kHeight = 490; | |
| static constexpr int kVerticalPadding = 10; | |
| SkISize getISize() override { return {kWidth, kHeight}; } | |
| 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"); } | |
| // The top 4 rows are CPU buffers - the bottom 4 rows are GPU buffers | |
| // Within each set of 4, the top 3 rows are vertex updates while the 4th row is an index update. | |
| 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); | |
| const 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; | |
| } | |
| std::array<SkPoint, 4> t = SkRect::MakeWH(2.f, 2.f).toQuad(); | |
| SkMatrix::RotateDeg(90.f*i, {1.f, 1.f}).mapPoints(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() + kVerticalPadding); | |
| } | |
| // 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) { | |
| auto rect = r.makeOffset(100*i, 0); | |
| const std::array<SkPoint, 4> p = rect.toQuad(); | |
| if (i) { | |
| bounds.join(rect); | |
| } else { | |
| bounds = rect; | |
| } | |
| std::array<SkPoint, 4> t = SkRect::MakeWH(2.f, 2.f).toQuad(); | |
| SkMatrix::RotateDeg(90.f*i, {1.f, 1.f}).mapPoints(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() + kVerticalPadding); | |
| } | |
| 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 auto kTri = std::to_array<SkPoint>({ | |
| SkPoint{10, 10}, | |
| SkPoint{20, 10}, | |
| SkPoint{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))); | |
| const auto bounds = SkRect::BoundsOrEmpty(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: | |
| std::array<sk_sp<SkMeshSpecification>, 2> fSpec; | |
| }; | |
| 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; | |
| std::array<SkColor4f, 6> colors; | |
| 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; | |
| SkGradient::Interpolation interpolation; | |
| interpolation.fColorSpace = SkGradient::Interpolation::ColorSpace::kHSL; | |
| fShader = SkShaders::SweepGradient(kRect.center(), 0, 360, | |
| {{colors, {}, SkTileMode::kRepeat, SkColorSpace::MakeSRGB()}, | |
| interpolation}); | |
| } | |
| 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 |