Sketch of drawing meshes in renderer
diff --git a/include/rive/renderer.hpp b/include/rive/renderer.hpp
index 5b35d21..d82402e 100644
--- a/include/rive/renderer.hpp
+++ b/include/rive/renderer.hpp
@@ -70,6 +70,31 @@
                                                int count,
                                                const Mat2D* localMatrix = nullptr);
 
+    class RenderMesh : public RefCnt {
+    public:
+        enum Type {
+            triangles,
+            strip,
+            fan,
+        };
+    };
+
+    /*  Copies the data provided, and returns a reusable RenderMesh object which can
+     *  be drawn in drawMesh(...).
+     *
+     *  vertexCount is the number of vertex points (i.e. pairs of x,y)
+     *  vertices[] and texCoords[] are x,y interleaved
+     *  Note: texCoords[] are normalized (0..1), but can be outside of that range.
+     *        if they are out of range, the shader (in drawMesh) will handle tiling
+     */
+    extern rcp<RenderMesh> makeMesh(RenderMesh::Type,
+                                    int vertexCount,
+                                    const float vertices[],     //  x0,  y0,  x1,  y1, ...
+                                    const float texCoords[],    // tx0, ty0, tx1, ty1, ...
+                                    int indexCount,
+                                    const uint16_t indices[]);
+
+
     class RenderPaint {
     public:
         virtual void style(RenderPaintStyle style) = 0;
@@ -117,8 +142,8 @@
         virtual void transform(const Mat2D& transform) = 0;
         virtual void drawPath(RenderPath* path, RenderPaint* paint) = 0;
         virtual void clipPath(RenderPath* path) = 0;
-        virtual void
-        drawImage(RenderImage* image, BlendMode value, float opacity) = 0;
+        virtual void drawImage(RenderImage*, BlendMode, float opacity) = 0;
+        virtual void drawMesh(const RenderMesh*, const RenderShader*, BlendMode, float opacity) = 0;
 
         // helpers
 
diff --git a/skia/renderer/include/skia_renderer.hpp b/skia/renderer/include/skia_renderer.hpp
index d67333d..f28dfa7 100644
--- a/skia/renderer/include/skia_renderer.hpp
+++ b/skia/renderer/include/skia_renderer.hpp
@@ -61,10 +61,10 @@
         void save() override;
         void restore() override;
         void transform(const Mat2D& transform) override;
-        void drawPath(RenderPath* path, RenderPaint* paint) override;
-        void
-        drawImage(RenderImage* image, BlendMode value, float opacity) override;
         void clipPath(RenderPath* path) override;
+        void drawPath(RenderPath* path, RenderPaint* paint) override;
+        void drawImage(RenderImage*, BlendMode, float opacity) override;
+        void drawMesh(const RenderMesh*, const RenderShader*, BlendMode, float opacity) override;
     };
 } // namespace rive
 #endif
diff --git a/skia/renderer/include/to_skia.hpp b/skia/renderer/include/to_skia.hpp
index 6420106..7756f97 100644
--- a/skia/renderer/include/to_skia.hpp
+++ b/skia/renderer/include/to_skia.hpp
@@ -93,6 +93,16 @@
             }
             return SkBlendMode::kSrcOver;
         }
+        
+        static SkVertices::VertexMode convert(rive::RenderMesh::Type t) {
+            switch (t) {
+                case rive::RenderMesh::triangles: return SkVertices::kTriangles_VertexMode;
+                case rive::RenderMesh::strip:     return SkVertices::kTriangleStrip_VertexMode;
+                case rive::RenderMesh::fan:       return SkVertices::kTriangleFan_VertexMode;
+            }
+            assert(false);
+            return SkVertices::kTriangles_VertexMode;
+        }
     };
 } // namespace rive
 #endif
diff --git a/skia/renderer/src/skia_renderer.cpp b/skia/renderer/src/skia_renderer.cpp
index 96eb5d8..59a100b 100644
--- a/skia/renderer/src/skia_renderer.cpp
+++ b/skia/renderer/src/skia_renderer.cpp
@@ -1,6 +1,7 @@
 #include "skia_renderer.hpp"
 #include "SkGradientShader.h"
 #include "SkPath.h"
+#include "SkVertices.h"
 #include "rive/math/vec2d.hpp"
 #include "rive/shapes/paint/color.hpp"
 #include "to_skia.hpp"
@@ -46,6 +47,13 @@
     sk_sp<SkShader> shader;
 };
 
+class SkiaRenderMesh : public RenderMesh {
+public:
+    SkiaRenderMesh(sk_sp<SkVertices> vt) : vertices(std::move(vt)) {}
+
+    sk_sp<SkVertices> vertices;
+};
+
 void SkiaRenderPath::fillRule(FillRule value) {
     switch (value) {
         case FillRule::evenOdd:
@@ -128,6 +136,20 @@
         skiaImage->skImage(), 0.0f, 0.0f, samplingOptions, &paint);
 }
 
+void SkiaRenderer::drawMesh(const RenderMesh* mesh,
+                            const RenderShader* shader,
+                            BlendMode mode,
+                            float opacity) {
+    auto skmesh = reinterpret_cast<const SkiaRenderMesh*>(mesh);
+    auto skshader = reinterpret_cast<const SkiaRenderShader*>(shader);
+
+    SkPaint paint;
+    paint.setBlendMode(ToSkia::convert(mode));
+    paint.setAlphaf(opacity);
+    paint.setShader(skshader->shader);
+    m_Canvas->drawVertices(skmesh->vertices, SkBlendMode::kModulate, paint);
+}
+
 bool SkiaRenderImage::decode(const uint8_t* bytes, std::size_t size) {
 
     sk_sp<SkData> data = SkData::MakeWithoutCopy(bytes, size);
@@ -194,4 +216,17 @@
                                              0, &lm);
        return rcp<RenderShader>(new SkiaRenderShader(std::move(sh)));
    }
+
+    rcp<RenderMesh> makeMesh(RenderMesh::Type meshType,
+                             int vertexCount, const float vertices[], const float texCoords[],
+                             int indexCount, const uint16_t indices[])
+    {
+        const SkColor* colors = nullptr;
+        auto vt = SkVertices::MakeCopy(ToSkia::convert(meshType), vertexCount,
+                                       (const SkPoint*)vertices,
+                                       (const SkPoint*)texCoords,
+                                       colors,
+                                       indexCount, indices);
+        return rcp<RenderMesh>(new SkiaRenderMesh(std::move(vt)));
+    }
 } // namespace rive