Revert "Move externs into Factory"

This reverts commit c60952751935fadef8d94949698a20e5af10f923.
diff --git a/include/rive/artboard.hpp b/include/rive/artboard.hpp
index 3dcf69c..1e483d4 100644
--- a/include/rive/artboard.hpp
+++ b/include/rive/artboard.hpp
@@ -17,7 +17,6 @@
 namespace rive {
     class File;
     class Drawable;
-    class Factory;
     class Node;
     class DrawTarget;
     class ArtboardImporter;
@@ -43,7 +42,6 @@
         unsigned int m_DirtDepth = 0;
         std::unique_ptr<CommandPath> m_BackgroundPath;
         std::unique_ptr<CommandPath> m_ClipPath;
-        Factory* m_Factory = nullptr;
         Drawable* m_FirstDrawable = nullptr;
         bool m_IsInstance = false;
         bool m_FrameOrigin = true;
@@ -53,11 +51,8 @@
         void sortDependencies();
         void sortDrawOrder();
 
-        Artboard* getArtboard() override { return this; }
-
 #ifdef TESTING
     public:
-        Artboard(Factory* factory) : m_Factory(factory) {}
 #endif
         void addObject(Core* object);
         void addAnimation(LinearAnimation* object);
@@ -67,19 +62,15 @@
         void testing_only_enque_message(const Message&);
 
     public:
-        Artboard() {}
         ~Artboard();
         StatusCode initialize();
 
         Core* resolve(uint32_t id) const override;
 
-
         /// Find the id of a component in the artboard the object in the artboard. The artboard
         /// itself has id 0 so we use that as a flag for not found.
         uint32_t idOf(Core* object) const;
 
-        Factory* factory() const { return m_Factory; }
-
         // EXPERIMENTAL -- for internal testing only for now.
         // DO NOT RELY ON THIS as it may change/disappear in the future.
         Core* hitTest(HitInfo*, const Mat2D* = nullptr);
@@ -170,8 +161,6 @@
 
     class ArtboardInstance : public Artboard {
     public:
-        ArtboardInstance() {}
-
         std::unique_ptr<LinearAnimationInstance> animationAt(size_t index);
         std::unique_ptr<LinearAnimationInstance> animationNamed(std::string name);
 
diff --git a/include/rive/assets/file_asset.hpp b/include/rive/assets/file_asset.hpp
index 01ef88e..e2bff1d 100644
--- a/include/rive/assets/file_asset.hpp
+++ b/include/rive/assets/file_asset.hpp
@@ -1,14 +1,12 @@
 #ifndef _RIVE_FILE_ASSET_HPP_
 #define _RIVE_FILE_ASSET_HPP_
 #include "rive/generated/assets/file_asset_base.hpp"
-#include "rive/span.hpp"
 #include <string>
 
 namespace rive {
-    class Factory;
     class FileAsset : public FileAssetBase {
     public:
-        virtual bool decode(Span<const uint8_t>, Factory*) = 0;
+        virtual bool decode(const uint8_t* bytes, std::size_t size) = 0;
         virtual std::string fileExtension() = 0;
         StatusCode import(ImportStack& importStack) override;
 
diff --git a/include/rive/assets/image_asset.hpp b/include/rive/assets/image_asset.hpp
index 9386fcf..76718b9 100644
--- a/include/rive/assets/image_asset.hpp
+++ b/include/rive/assets/image_asset.hpp
@@ -2,24 +2,24 @@
 #define _RIVE_IMAGE_ASSET_HPP_
 
 #include "rive/generated/assets/image_asset_base.hpp"
-#include "rive/renderer.hpp"
 #include <string>
 
 namespace rive {
+    class RenderImage;
     class ImageAsset : public ImageAssetBase {
     private:
-        std::unique_ptr<RenderImage> m_RenderImage;
+        RenderImage* m_RenderImage;
 
     public:
-        ImageAsset() {}
+        ImageAsset();
         ~ImageAsset();
 
 #ifdef TESTING
         std::size_t decodedByteSize = 0;
 #endif
-        bool decode(Span<const uint8_t>, Factory*) override;
+        bool decode(const uint8_t* bytes, std::size_t size) override;
         std::string fileExtension() override;
-        RenderImage* renderImage() const { return m_RenderImage.get(); }
+        RenderImage* renderImage() const { return m_RenderImage; }
     };
 } // namespace rive
 
diff --git a/include/rive/core_context.hpp b/include/rive/core_context.hpp
index 76ed0b2..d8d7a4d 100644
--- a/include/rive/core_context.hpp
+++ b/include/rive/core_context.hpp
@@ -4,7 +4,6 @@
 #include "rive/rive_types.hpp"
 
 namespace rive {
-    class Artboard;
     class Core;
     class CoreContext {
     public:
diff --git a/include/rive/factory.hpp b/include/rive/factory.hpp
deleted file mode 100644
index 1960faf..0000000
--- a/include/rive/factory.hpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2022 Rive
- */
-
-#ifndef _RIVE_FACTORY_HPP_
-#define _RIVE_FACTORY_HPP_
-
-#include "rive/renderer.hpp"
-#include "rive/refcnt.hpp"
-#include "rive/span.hpp"
-#include "rive/math/aabb.hpp"
-#include "rive/math/mat2d.hpp"
-
-#include <cmath>
-#include <stdio.h>
-#include <cstdint>
-
-namespace rive {
-
-    class Factory {
-    public:
-        Factory() {}
-        virtual ~Factory() {}
-
-        virtual rcp<RenderBuffer> makeBufferU16(Span<const uint16_t>) = 0;
-        virtual rcp<RenderBuffer> makeBufferU32(Span<const uint32_t>) = 0;
-        virtual rcp<RenderBuffer> makeBufferF32(Span<const float>) = 0;
-
-        virtual rcp<RenderShader> makeLinearGradient(float sx, float sy,
-                                                     float ex, float ey,
-                                                     const ColorInt colors[],    // [count]
-                                                     const float stops[],        // [count]
-                                                     int count,
-                                                     RenderTileMode,
-                                                     const Mat2D* localMatrix = nullptr) = 0;
-
-        virtual rcp<RenderShader> makeRadialGradient(float cx, float cy, float radius,
-                                                     const ColorInt colors[],    // [count]
-                                                     const float stops[],        // [count]
-                                                     int count,
-                                                     RenderTileMode,
-                                                     const Mat2D* localMatrix = nullptr) = 0;
-
-        // Returns a full-formed RenderPath -- can be treated as immutable
-        virtual std::unique_ptr<RenderPath> makeRenderPath(Span<const Vec2D> points,
-                                                           Span<const uint8_t> verbs,
-                                                           FillRule) = 0;
-
-        virtual std::unique_ptr<RenderPath> makeEmptyRenderPath() = 0;
-
-        virtual std::unique_ptr<RenderPaint> makeRenderPaint() = 0;
-
-        virtual std::unique_ptr<RenderImage> decodeImage(Span<const uint8_t>) = 0;
-    };
-
-} // namespace rive
-#endif
diff --git a/include/rive/file.hpp b/include/rive/file.hpp
index c7b4017..419634e 100644
--- a/include/rive/file.hpp
+++ b/include/rive/file.hpp
@@ -3,7 +3,6 @@
 
 #include "rive/artboard.hpp"
 #include "rive/backboard.hpp"
-#include "rive/factory.hpp"
 #include "rive/file_asset_resolver.hpp"
 #include <vector>
 
@@ -13,7 +12,6 @@
 namespace rive {
     class BinaryReader;
     class RuntimeHeader;
-    class Factory;
 
     ///
     /// Tracks the success/failure result when importing a Rive file.
@@ -46,13 +44,11 @@
         /// Rive components and animations.
         std::vector<std::unique_ptr<Artboard>> m_Artboards;
 
-        Factory* m_Factory;
-
         /// The helper used to resolve assets when they're not provided in-band
         /// with the file.
         FileAssetResolver* m_AssetResolver;
 
-        File(Factory*, FileAssetResolver*);
+        File(FileAssetResolver* assetResolver);
 
     public:
         ~File();
@@ -65,9 +61,8 @@
         /// cannot be found in-band.
         /// @returns a pointer to the file, or null on failure.
         static std::unique_ptr<File> import(Span<const uint8_t> data,
-                                            Factory*,
-                                            ImportResult* result  = nullptr,
-                                            FileAssetResolver* assetResolver = nullptr);
+                                   ImportResult* result  = nullptr,
+                                   FileAssetResolver* assetResolver = nullptr);
 
         /// @returns the file's backboard. All files have exactly one backboard.
         Backboard* backboard() const { return m_Backboard.get(); }
@@ -92,7 +87,7 @@
         Artboard* artboard(size_t index) const;
 
     private:
-        ImportResult read(BinaryReader&, const RuntimeHeader&);
+        ImportResult read(BinaryReader& reader, const RuntimeHeader& header);
     };
 } // namespace rive
-#endif
+#endif
\ No newline at end of file
diff --git a/include/rive/importers/file_asset_importer.hpp b/include/rive/importers/file_asset_importer.hpp
index 170a440..6ad9d0c 100644
--- a/include/rive/importers/file_asset_importer.hpp
+++ b/include/rive/importers/file_asset_importer.hpp
@@ -9,17 +9,14 @@
     class FileAsset;
     class FileAssetContents;
     class FileAssetResolver;
-    class Factory;
-
     class FileAssetImporter : public ImportStackObject {
     private:
         bool m_LoadedContents = false;
         FileAsset* m_FileAsset;
         FileAssetResolver* m_FileAssetResolver;
-        Factory* m_Factory;
 
     public:
-        FileAssetImporter(FileAsset*, FileAssetResolver*, Factory*);
+        FileAssetImporter(FileAsset* fileAsset, FileAssetResolver* assetResolver);
         void loadContents(const FileAssetContents& contents);
         StatusCode resolve() override;
     };
diff --git a/include/rive/relative_local_asset_resolver.hpp b/include/rive/relative_local_asset_resolver.hpp
index 5aa3f4c..63a7edb 100644
--- a/include/rive/relative_local_asset_resolver.hpp
+++ b/include/rive/relative_local_asset_resolver.hpp
@@ -8,19 +8,14 @@
 
 namespace rive {
     class FileAsset;
-    class Factory;
-
     /// An implementation of FileAssetResolver which finds the assets in a local
     /// path relative to the original .riv file looking for them.
     class RelativeLocalAssetResolver : public FileAssetResolver {
     private:
         std::string m_Path;
-        Factory* m_Factory;
 
     public:
-        RelativeLocalAssetResolver(std::string filename, Factory* factory)
-            : m_Factory(factory)
-        {
+        RelativeLocalAssetResolver(std::string filename) {
             std::size_t finalSlash = filename.rfind('/');
 
             if (finalSlash != std::string::npos) {
@@ -37,7 +32,7 @@
             fseek(fp, 0, SEEK_SET);
             uint8_t* bytes = new uint8_t[length];
             if (fread(bytes, 1, length, fp) == length) {
-                asset.decode(Span<const uint8_t>(bytes, length), m_Factory);
+                asset.decode(bytes, length);
             }
             delete[] bytes;
         }
diff --git a/include/rive/renderer.hpp b/include/rive/renderer.hpp
index 5d23576..affe81f 100644
--- a/include/rive/renderer.hpp
+++ b/include/rive/renderer.hpp
@@ -1,7 +1,3 @@
-/*
- * Copyright 2022 Rive
- */
-
 #ifndef _RIVE_RENDERER_HPP_
 #define _RIVE_RENDERER_HPP_
 
@@ -12,10 +8,10 @@
 #include "rive/span.hpp"
 #include "rive/math/aabb.hpp"
 #include "rive/math/mat2d.hpp"
+#include "rive/math/raw_path.hpp"
 #include "rive/shapes/paint/blend_mode.hpp"
 #include "rive/shapes/paint/stroke_cap.hpp"
 #include "rive/shapes/paint/stroke_join.hpp"
-
 #include <cmath>
 #include <stdio.h>
 #include <cstdint>
@@ -36,6 +32,10 @@
         size_t count() const { return m_Count; }
     };
 
+    extern rcp<RenderBuffer> makeBufferU16(Span<const uint16_t>);
+    extern rcp<RenderBuffer> makeBufferU32(Span<const uint32_t>);
+    extern rcp<RenderBuffer> makeBufferF32(Span<const float>);
+
     enum class RenderPaintStyle { stroke, fill };
 
     enum class RenderTileMode {
@@ -55,6 +55,32 @@
      */
     class RenderShader : public RefCnt {};
 
+    extern rcp<RenderShader> makeLinearGradient(float sx,
+                                                float sy,
+                                                float ex,
+                                                float ey,
+                                                const ColorInt colors[], // [count]
+                                                const float stops[],     // [count]
+                                                int count,
+                                                RenderTileMode,
+                                                const Mat2D* localMatrix = nullptr);
+
+    extern rcp<RenderShader> makeRadialGradient(float cx,
+                                                float cy,
+                                                float radius,
+                                                const ColorInt colors[], // [count]
+                                                const float stops[],     // [count]
+                                                int count,
+                                                RenderTileMode,
+                                                const Mat2D* localMatrix = nullptr);
+
+    extern rcp<RenderShader> makeSweepGradient(float cx,
+                                               float cy,
+                                               const ColorInt colors[], // [count]
+                                               const float stops[],     // [count]
+                                               int count,
+                                               const Mat2D* localMatrix = nullptr);
+
     class RenderPaint {
     public:
         virtual void style(RenderPaintStyle style) = 0;
@@ -75,6 +101,7 @@
 
     public:
         virtual ~RenderImage() {}
+        virtual bool decode(Span<const uint8_t>) = 0;
         int width() const { return m_Width; }
         int height() const { return m_Height; }
 
@@ -119,5 +146,13 @@
             transform(computeAlignment(fit, alignment, frame, content));
         }
     };
+
+    // Returns a full-formed RenderPath -- can be treated as immutable
+    extern RenderPath*
+    makeRenderPath(Span<const Vec2D> points, Span<const uint8_t> verbs, FillRule);
+
+    extern RenderPath* makeRenderPath();
+    extern RenderPaint* makeRenderPaint();
+    extern RenderImage* makeRenderImage();
 } // namespace rive
 #endif
diff --git a/include/rive/shapes/clipping_shape.hpp b/include/rive/shapes/clipping_shape.hpp
index 3ed3227..4d01176 100644
--- a/include/rive/shapes/clipping_shape.hpp
+++ b/include/rive/shapes/clipping_shape.hpp
@@ -1,6 +1,5 @@
 #ifndef _RIVE_CLIPPING_SHAPE_HPP_
 #define _RIVE_CLIPPING_SHAPE_HPP_
-#include "rive/renderer.hpp"
 #include "rive/generated/shapes/clipping_shape_base.hpp"
 #include <stdio.h>
 #include <vector>
@@ -13,9 +12,10 @@
     private:
         std::vector<Shape*> m_Shapes;
         Node* m_Source = nullptr;
-        std::unique_ptr<RenderPath> m_RenderPath;
+        RenderPath* m_RenderPath = nullptr;
 
     public:
+        ~ClippingShape();
         Node* source() const { return m_Source; }
         const std::vector<Shape*>& shapes() const { return m_Shapes; }
         StatusCode onAddedClean(CoreContext* context) override;
@@ -23,7 +23,7 @@
         void buildDependencies() override;
         void update(ComponentDirt value) override;
 
-        RenderPath* renderPath() const { return m_RenderPath.get(); }
+        RenderPath* renderPath() const { return m_RenderPath; }
     };
 } // namespace rive
 
diff --git a/include/rive/shapes/metrics_path.hpp b/include/rive/shapes/metrics_path.hpp
index daa694a..c504c3a 100644
--- a/include/rive/shapes/metrics_path.hpp
+++ b/include/rive/shapes/metrics_path.hpp
@@ -80,11 +80,12 @@
 
     class RenderMetricsPath : public MetricsPath {
     private:
-        std::unique_ptr<RenderPath> m_RenderPath;
+        RenderPath* m_RenderPath;
 
     public:
-        RenderMetricsPath(std::unique_ptr<RenderPath>);
-        RenderPath* renderPath() override { return m_RenderPath.get(); }
+        RenderMetricsPath();
+        ~RenderMetricsPath();
+        RenderPath* renderPath() override { return m_RenderPath; }
         void addPath(CommandPath* path, const Mat2D& transform) override;
 
         void fillRule(FillRule value) override;
diff --git a/include/rive/shapes/paint/shape_paint.hpp b/include/rive/shapes/paint/shape_paint.hpp
index 661adb7..ed6b3b7 100644
--- a/include/rive/shapes/paint/shape_paint.hpp
+++ b/include/rive/shapes/paint/shape_paint.hpp
@@ -10,10 +10,11 @@
     class ShapePaintMutator;
     class ShapePaint : public ShapePaintBase {
     protected:
-        std::unique_ptr<RenderPaint> m_RenderPaint;
+        RenderPaint* m_RenderPaint = nullptr;
         ShapePaintMutator* m_PaintMutator = nullptr;
 
     public:
+        ~ShapePaint();
         StatusCode onAddedClean(CoreContext* context) override;
 
         float renderOpacity() const { return m_PaintMutator->renderOpacity(); }
@@ -30,8 +31,6 @@
 
         virtual void draw(Renderer* renderer, CommandPath* path) = 0;
 
-        RenderPaint* renderPaint() { return m_RenderPaint.get(); }
-    
         /// Get the component that represents the ShapePaintMutator for this
         /// ShapePaint. It'll be one of SolidColor, LinearGradient, or
         /// RadialGradient.
diff --git a/include/rive/shapes/paint/stroke_effect.hpp b/include/rive/shapes/paint/stroke_effect.hpp
index 1db1423..bc40a4c 100644
--- a/include/rive/shapes/paint/stroke_effect.hpp
+++ b/include/rive/shapes/paint/stroke_effect.hpp
@@ -1,16 +1,12 @@
 #ifndef _RIVE_STROKE_EFFECT_HPP_
 #define _RIVE_STROKE_EFFECT_HPP_
-
-#include "rive/rive_types.hpp"
-
 namespace rive {
-    class Factory;
     class RenderPath;
     class MetricsPath;
 
     class StrokeEffect {
     public:
-        virtual RenderPath* effectPath(MetricsPath* source, Factory*) = 0;
+        virtual RenderPath* effectPath(MetricsPath* source) = 0;
         virtual void invalidateEffect() = 0;
     };
 } // namespace rive
diff --git a/include/rive/shapes/paint/trim_path.hpp b/include/rive/shapes/paint/trim_path.hpp
index 7289590..9806228 100644
--- a/include/rive/shapes/paint/trim_path.hpp
+++ b/include/rive/shapes/paint/trim_path.hpp
@@ -2,18 +2,19 @@
 #define _RIVE_TRIM_PATH_HPP_
 #include "rive/generated/shapes/paint/trim_path_base.hpp"
 #include "rive/shapes/paint/stroke_effect.hpp"
-#include "rive/renderer.hpp"
 #include <stdio.h>
 
 namespace rive {
     class TrimPath : public TrimPathBase, public StrokeEffect {
     private:
-        std::unique_ptr<RenderPath> m_TrimmedPath;
+        RenderPath* m_TrimmedPath;
         RenderPath* m_RenderPath = nullptr;
 
     public:
+        TrimPath();
+        ~TrimPath();
         StatusCode onAddedClean(CoreContext* context) override;
-        RenderPath* effectPath(MetricsPath* source, Factory*) override;
+        RenderPath* effectPath(MetricsPath* source) override;
         void invalidateEffect() override;
 
         void startChanged() override;
diff --git a/include/rive/shapes/shape.hpp b/include/rive/shapes/shape.hpp
index 8caf7f8..a4e6f37 100644
--- a/include/rive/shapes/shape.hpp
+++ b/include/rive/shapes/shape.hpp
@@ -18,8 +18,6 @@
 
         bool m_WantDifferencePath = false;
 
-        Artboard* getArtboard() override { return artboard(); }
-
     public:
         Shape();
         void buildDependencies() override;
diff --git a/include/rive/shapes/shape_paint_container.hpp b/include/rive/shapes/shape_paint_container.hpp
index 89abf04..1f1eede 100644
--- a/include/rive/shapes/shape_paint_container.hpp
+++ b/include/rive/shapes/shape_paint_container.hpp
@@ -5,7 +5,6 @@
 #include <vector>
 
 namespace rive {
-    class Artboard;
     class ShapePaint;
     class Component;
 
@@ -15,10 +14,6 @@
         friend class ShapePaint;
 
     protected:
-        // Need this to access our artboard. We are treated as a mixin, either
-        // as a Shape or Artboard, so both of those will override this.
-        virtual Artboard* getArtboard() = 0;
-    
         PathSpace m_DefaultPathSpace = PathSpace::Neither;
         std::vector<ShapePaint*> m_ShapePaints;
         void addPaint(ShapePaint* paint);
diff --git a/skia/renderer/include/skia_factory.hpp b/skia/renderer/include/skia_factory.hpp
deleted file mode 100644
index 14abfa8..0000000
--- a/skia/renderer/include/skia_factory.hpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef _RIVE_SKIA_FACTORY_HPP_
-#define _RIVE_SKIA_FACTORY_HPP_
-
-#include "rive/factory.hpp"
-
-namespace rive {
-
-class SkiaFactory : public Factory {
-    rcp<RenderBuffer> makeBufferU16(Span<const uint16_t>) override;
-    rcp<RenderBuffer> makeBufferU32(Span<const uint32_t>) override;
-    rcp<RenderBuffer> makeBufferF32(Span<const float>) override;
-
-    rcp<RenderShader> makeLinearGradient(float sx, float sy,
-                                         float ex, float ey,
-                                         const ColorInt colors[],    // [count]
-                                         const float stops[],        // [count]
-                                         int count,
-                                         RenderTileMode,
-                                         const Mat2D* localMatrix = nullptr) override;
-
-    rcp<RenderShader> makeRadialGradient(float cx, float cy, float radius,
-                                         const ColorInt colors[],    // [count]
-                                         const float stops[],        // [count]
-                                         int count,
-                                         RenderTileMode,
-                                         const Mat2D* localMatrix = nullptr) override;
-
-    std::unique_ptr<RenderPath> makeRenderPath(Span<const Vec2D> points,
-                                               Span<const uint8_t> verbs,
-                                               FillRule) override;
-
-    std::unique_ptr<RenderPath> makeEmptyRenderPath() override;
-
-    std::unique_ptr<RenderPaint> makeRenderPaint() override;
-
-    std::unique_ptr<RenderImage> decodeImage(Span<const uint8_t>) override;
-};
-
-} // namespace rive
-#endif
diff --git a/skia/renderer/include/skia_renderer.hpp b/skia/renderer/include/skia_renderer.hpp
index 706e663..b6ea76b 100644
--- a/skia/renderer/include/skia_renderer.hpp
+++ b/skia/renderer/include/skia_renderer.hpp
@@ -1,11 +1,59 @@
 #ifndef _RIVE_SKIA_RENDERER_HPP_
 #define _RIVE_SKIA_RENDERER_HPP_
 
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkImage.h"
 #include "rive/renderer.hpp"
-
-class SkCanvas;
+#include <vector>
 
 namespace rive {
+    class SkiaRenderPath : public RenderPath {
+    private:
+        SkPath m_Path;
+
+    public:
+        SkiaRenderPath() {}
+        SkiaRenderPath(SkPath&& path) : m_Path(std::move(path)) {}
+
+        const SkPath& path() const { return m_Path; }
+        void reset() override;
+        void addRenderPath(RenderPath* path, const Mat2D& transform) override;
+        void fillRule(FillRule value) override;
+        void moveTo(float x, float y) override;
+        void lineTo(float x, float y) override;
+        void cubicTo(float ox, float oy, float ix, float iy, float x, float y) override;
+        virtual void close() override;
+    };
+
+    class SkiaRenderPaint : public RenderPaint {
+    private:
+        SkPaint m_Paint;
+
+    public:
+        const SkPaint& paint() const { return m_Paint; }
+        SkiaRenderPaint();
+        void style(RenderPaintStyle style) override;
+        void color(unsigned int value) override;
+        void thickness(float value) override;
+        void join(StrokeJoin value) override;
+        void cap(StrokeCap value) override;
+        void blendMode(BlendMode value) override;
+        void shader(rcp<RenderShader>) override;
+    };
+
+    class SkiaRenderImage : public RenderImage {
+    private:
+        sk_sp<SkImage> m_SkImage;
+
+    public:
+        sk_sp<SkImage> skImage() const { return m_SkImage; };
+        bool decode(Span<const uint8_t>) override;
+        rcp<RenderShader>
+        makeShader(RenderTileMode tx, RenderTileMode ty, const Mat2D* localMatrix) const override;
+    };
+
     class SkiaRenderer : public Renderer {
     protected:
         SkCanvas* m_Canvas;
diff --git a/skia/renderer/include/to_skia.hpp b/skia/renderer/include/to_skia.hpp
index 365b4c0..c19bf7e 100644
--- a/skia/renderer/include/to_skia.hpp
+++ b/skia/renderer/include/to_skia.hpp
@@ -4,7 +4,6 @@
 #include "SkPaint.h"
 #include "rive/math/mat2d.hpp"
 #include "rive/math/vec2d.hpp"
-#include "rive/renderer.hpp"
 #include "rive/shapes/paint/stroke_cap.hpp"
 #include "rive/shapes/paint/stroke_join.hpp"
 #include "rive/shapes/paint/blend_mode.hpp"
diff --git a/skia/renderer/src/skia_factory.cpp b/skia/renderer/src/skia_factory.cpp
deleted file mode 100644
index 9f6b49b..0000000
--- a/skia/renderer/src/skia_factory.cpp
+++ /dev/null
@@ -1,339 +0,0 @@
-#include "skia_factory.hpp"
-
-#include "SkCanvas.h"
-#include "SkData.h"
-#include "SkGradientShader.h"
-#include "SkImage.h"
-#include "SkPaint.h"
-#include "SkPath.h"
-#include "SkVertices.h"
-#include "rive/math/vec2d.hpp"
-#include "rive/shapes/paint/color.hpp"
-#include "to_skia.hpp"
-
-using namespace rive;
-
-class SkiaRenderPath : public RenderPath {
-private:
-    SkPath m_Path;
-
-public:
-    SkiaRenderPath() {}
-    SkiaRenderPath(SkPath&& path) : m_Path(std::move(path)) {}
-
-    const SkPath& path() const { return m_Path; }
-
-    void reset() override;
-    void addRenderPath(RenderPath* path, const Mat2D& transform) override;
-    void fillRule(FillRule value) override;
-    void moveTo(float x, float y) override;
-    void lineTo(float x, float y) override;
-    void cubicTo(float ox, float oy, float ix, float iy, float x, float y) override;
-    virtual void close() override;
-};
-
-class SkiaRenderPaint : public RenderPaint {
-private:
-    SkPaint m_Paint;
-
-public:
-    SkiaRenderPaint();
-
-    const SkPaint& paint() const { return m_Paint; }
-
-    void style(RenderPaintStyle style) override;
-    void color(unsigned int value) override;
-    void thickness(float value) override;
-    void join(StrokeJoin value) override;
-    void cap(StrokeCap value) override;
-    void blendMode(BlendMode value) override;
-    void shader(rcp<RenderShader>) override;
-};
-
-class SkiaRenderImage : public RenderImage {
-private:
-    sk_sp<SkImage> m_SkImage;
-
-public:
-    SkiaRenderImage(sk_sp<SkImage> image);
-
-    sk_sp<SkImage> skImage() const { return m_SkImage; };
-
-    rcp<RenderShader>
-    makeShader(RenderTileMode tx, RenderTileMode ty, const Mat2D* localMatrix) const override;
-};
-
-class SkiaRenderer : public Renderer {
-protected:
-    SkCanvas* m_Canvas;
-
-public:
-    SkiaRenderer(SkCanvas* canvas) : m_Canvas(canvas) {}
-
-    void save() override;
-    void restore() override;
-    void transform(const Mat2D& transform) override;
-    void clipPath(RenderPath* path) override;
-    void drawPath(RenderPath* path, RenderPaint* paint) override;
-    void drawImage(const RenderImage*, BlendMode, float opacity) override;
-    void drawImageMesh(const RenderImage*,
-                       rcp<RenderBuffer> vertices_f32,
-                       rcp<RenderBuffer> uvCoords_f32,
-                       rcp<RenderBuffer> indices_u16,
-                       BlendMode,
-                       float opacity) override;
-};
-
-class SkiaBuffer : public RenderBuffer {
-    const size_t m_ElemSize;
-    void* m_Buffer;
-
-public:
-    SkiaBuffer(const void* src, size_t count, size_t elemSize) :
-        RenderBuffer(count), m_ElemSize(elemSize) {
-        size_t bytes = count * elemSize;
-        m_Buffer = malloc(bytes);
-        memcpy(m_Buffer, src, bytes);
-    }
-
-    ~SkiaBuffer() { free(m_Buffer); }
-
-    const float* f32s() const {
-        assert(m_ElemSize == sizeof(float));
-        return static_cast<const float*>(m_Buffer);
-    }
-
-    const uint16_t* u16s() const {
-        assert(m_ElemSize == sizeof(uint16_t));
-        return static_cast<const uint16_t*>(m_Buffer);
-    }
-
-    const SkPoint* points() const { return reinterpret_cast<const SkPoint*>(this->f32s()); }
-
-    static const SkiaBuffer* Cast(const RenderBuffer* buffer) {
-        return reinterpret_cast<const SkiaBuffer*>(buffer);
-    }
-};
-
-template <typename T> rcp<RenderBuffer> make_buffer(Span<T> span) {
-    return rcp<RenderBuffer>(new SkiaBuffer(span.data(), span.size(), sizeof(T)));
-}
-
-class SkiaRenderShader : public RenderShader {
-public:
-    SkiaRenderShader(sk_sp<SkShader> sh) : shader(std::move(sh)) {}
-
-    sk_sp<SkShader> shader;
-};
-
-void SkiaRenderPath::fillRule(FillRule value) { m_Path.setFillType(ToSkia::convert(value)); }
-
-void SkiaRenderPath::reset() { m_Path.reset(); }
-void SkiaRenderPath::addRenderPath(RenderPath* path, const Mat2D& transform) {
-    m_Path.addPath(reinterpret_cast<SkiaRenderPath*>(path)->m_Path, ToSkia::convert(transform));
-}
-
-void SkiaRenderPath::moveTo(float x, float y) { m_Path.moveTo(x, y); }
-void SkiaRenderPath::lineTo(float x, float y) { m_Path.lineTo(x, y); }
-void SkiaRenderPath::cubicTo(float ox, float oy, float ix, float iy, float x, float y) {
-    m_Path.cubicTo(ox, oy, ix, iy, x, y);
-}
-void SkiaRenderPath::close() { m_Path.close(); }
-
-SkiaRenderPaint::SkiaRenderPaint() { m_Paint.setAntiAlias(true); }
-
-void SkiaRenderPaint::style(RenderPaintStyle style) {
-    switch (style) {
-        case RenderPaintStyle::fill:
-            m_Paint.setStyle(SkPaint::Style::kFill_Style);
-            break;
-        case RenderPaintStyle::stroke:
-            m_Paint.setStyle(SkPaint::Style::kStroke_Style);
-            break;
-    }
-}
-void SkiaRenderPaint::color(unsigned int value) { m_Paint.setColor(value); }
-void SkiaRenderPaint::thickness(float value) { m_Paint.setStrokeWidth(value); }
-void SkiaRenderPaint::join(StrokeJoin value) { m_Paint.setStrokeJoin(ToSkia::convert(value)); }
-void SkiaRenderPaint::cap(StrokeCap value) { m_Paint.setStrokeCap(ToSkia::convert(value)); }
-
-void SkiaRenderPaint::blendMode(BlendMode value) { m_Paint.setBlendMode(ToSkia::convert(value)); }
-
-void SkiaRenderPaint::shader(rcp<RenderShader> rsh) {
-    SkiaRenderShader* sksh = (SkiaRenderShader*)rsh.get();
-    m_Paint.setShader(sksh ? sksh->shader : nullptr);
-}
-
-void SkiaRenderer::save() { m_Canvas->save(); }
-void SkiaRenderer::restore() { m_Canvas->restore(); }
-void SkiaRenderer::transform(const Mat2D& transform) {
-    m_Canvas->concat(ToSkia::convert(transform));
-}
-void SkiaRenderer::drawPath(RenderPath* path, RenderPaint* paint) {
-    m_Canvas->drawPath(reinterpret_cast<SkiaRenderPath*>(path)->path(),
-                       reinterpret_cast<SkiaRenderPaint*>(paint)->paint());
-}
-
-void SkiaRenderer::clipPath(RenderPath* path) {
-    m_Canvas->clipPath(reinterpret_cast<SkiaRenderPath*>(path)->path(), true);
-}
-
-void SkiaRenderer::drawImage(const RenderImage* image, BlendMode blendMode, float opacity) {
-    SkPaint paint;
-    paint.setAlphaf(opacity);
-    paint.setBlendMode(ToSkia::convert(blendMode));
-    auto skiaImage = reinterpret_cast<const SkiaRenderImage*>(image);
-    SkSamplingOptions sampling(SkFilterMode::kLinear);
-    m_Canvas->drawImage(skiaImage->skImage(), 0.0f, 0.0f, sampling, &paint);
-}
-
-#define SKIA_BUG_13047
-
-void SkiaRenderer::drawImageMesh(const RenderImage* image,
-                                 rcp<RenderBuffer> vertices,
-                                 rcp<RenderBuffer> uvCoords,
-                                 rcp<RenderBuffer> indices,
-                                 BlendMode blendMode,
-                                 float opacity) {
-    // need our vertices and uvs to agree
-    assert(vertices->count() == uvCoords->count());
-    // vertices and uvs are arrays of floats, so we need their counts to be
-    // even, since we treat them as arrays of points
-    assert((vertices->count() & 1) == 0);
-
-    const int vertexCount = vertices->count() >> 1;
-
-    SkMatrix scaleM;
-
-    const SkPoint* uvs = SkiaBuffer::Cast(uvCoords.get())->points();
-
-#ifdef SKIA_BUG_13047
-    // The local matrix is ignored for drawVertices, so we have to manually scale
-    // the UVs to match Skia's convention...
-    std::vector<SkPoint> scaledUVs(vertexCount);
-    for (int i = 0; i < vertexCount; ++i) {
-        scaledUVs[i] = {uvs[i].fX * image->width(), uvs[i].fY * image->height()};
-    }
-    uvs = scaledUVs.data();
-#else
-    // We do this because our UVs are normalized, but Skia expects them to be
-    // sized to the shader (i.e. 0..width, 0..height).
-    // To accomdate this, we effectively scaling the image down to 0..1 to
-    // match the scale of the UVs.
-    scaleM = SkMatrix::Scale(2.0f / image->width(), 2.0f / image->height());
-#endif
-
-    auto skiaImage = reinterpret_cast<const SkiaRenderImage*>(image)->skImage();
-    const SkSamplingOptions sampling(SkFilterMode::kLinear);
-    auto shader = skiaImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, sampling, &scaleM);
-
-    SkPaint paint;
-    paint.setAlphaf(opacity);
-    paint.setBlendMode(ToSkia::convert(blendMode));
-    paint.setShader(shader);
-
-    const SkColor* no_colors = nullptr;
-    auto vertexMode = SkVertices::kTriangles_VertexMode;
-    // clang-format off
-    auto vt = SkVertices::MakeCopy(vertexMode,
-                                   vertexCount,
-                                   SkiaBuffer::Cast(vertices.get())->points(),
-                                   uvs,
-                                   no_colors,
-                                   indices->count(),
-                                   SkiaBuffer::Cast(indices.get())->u16s());
-    // clang-format on
-
-    // The blend mode is ignored if we don't have colors && uvs
-    m_Canvas->drawVertices(vt, SkBlendMode::kModulate, paint);
-}
-
-SkiaRenderImage::SkiaRenderImage(sk_sp<SkImage> image) : m_SkImage(std::move(image)) {
-    m_Width = m_SkImage->width();
-    m_Height = m_SkImage->height();
-}
-
-rcp<RenderShader>
-SkiaRenderImage::makeShader(RenderTileMode tx, RenderTileMode ty, const Mat2D* localMatrix) const {
-    const SkMatrix lm = localMatrix ? ToSkia::convert(*localMatrix) : SkMatrix();
-    const SkSamplingOptions options(SkFilterMode::kLinear);
-    auto sh = m_SkImage->makeShader(ToSkia::convert(tx), ToSkia::convert(ty), options, &lm);
-    return rcp<RenderShader>(new SkiaRenderShader(std::move(sh)));
-}
-
-// Factory
-
-rcp<RenderBuffer> SkiaFactory::makeBufferU16(Span<const uint16_t> data) {
-    return make_buffer(data);
-}
-
-rcp<RenderBuffer> SkiaFactory::makeBufferU32(Span<const uint32_t> data) {
-    return make_buffer(data);
-}
-
-rcp<RenderBuffer> SkiaFactory::makeBufferF32(Span<const float> data) {
-    return make_buffer(data);
-}
-
-rcp<RenderShader> SkiaFactory::makeLinearGradient(float sx, float sy,
-                                                float ex, float ey,
-                                                const ColorInt colors[],    // [count]
-                                                const float stops[],        // [count]
-                                                int count,
-                                                RenderTileMode mode,
-                                                const Mat2D* localMatrix) {
-    const SkPoint pts[] = {{sx, sy}, {ex, ey}};
-    const SkMatrix lm = localMatrix ? ToSkia::convert(*localMatrix) : SkMatrix();
-    auto sh = SkGradientShader::MakeLinear(
-        pts, (const SkColor*)colors, stops, count, ToSkia::convert(mode), 0, &lm);
-    return rcp<RenderShader>(new SkiaRenderShader(std::move(sh)));
-}
-
-rcp<RenderShader> SkiaFactory::makeRadialGradient(float cx, float cy, float radius,
-                                                const ColorInt colors[],    // [count]
-                                                const float stops[],        // [count]
-                                                int count,
-                                                RenderTileMode mode,
-                                                const Mat2D* localMatrix) {
-    const SkMatrix lm = localMatrix ? ToSkia::convert(*localMatrix) : SkMatrix();
-    auto sh = SkGradientShader::MakeRadial(
-        {cx, cy}, radius, (const SkColor*)colors, stops, count, ToSkia::convert(mode), 0, &lm);
-    return rcp<RenderShader>(new SkiaRenderShader(std::move(sh)));
-}
-
-std::unique_ptr<RenderPath> SkiaFactory::makeRenderPath(Span<const Vec2D> points,
-                                                        Span<const uint8_t> verbs,
-                                                        FillRule fillRule) {
-    const bool isVolatile = false;  // ???
-    const SkScalar* conicWeights = nullptr;
-    const int conicWeightCount = 0;
-    return std::make_unique<SkiaRenderPath>(SkPath::Make(reinterpret_cast<const SkPoint*>(points.data()),
-                                                         points.count(),
-                                                         verbs.data(),
-                                                         verbs.count(),
-                                                         conicWeights,
-                                                         conicWeightCount,
-                                                         ToSkia::convert(fillRule),
-                                                         isVolatile));
-}
-
-std::unique_ptr<RenderPath> SkiaFactory::makeEmptyRenderPath() {
-    return std::make_unique<SkiaRenderPath>();
-}
-
-std::unique_ptr<RenderPaint> SkiaFactory::makeRenderPaint() {
-    return std::make_unique<SkiaRenderPaint>();
-}
-
-std::unique_ptr<RenderImage> SkiaFactory::decodeImage(Span<const uint8_t> encoded) {
-    sk_sp<SkData> data = SkData::MakeWithoutCopy(encoded.data(), encoded.size());
-    auto image = SkImage::MakeFromEncoded(data);
-
-    // Our optimized skia buld seems to have broken lazy-image decode.
-    // As a work-around for now, force the image to be decoded.
-    if (image) {
-        image = image->makeRasterImage();
-    }
-
-    return image ? std::make_unique<SkiaRenderImage>(std::move(image)) : nullptr;
-}
diff --git a/skia/renderer/src/skia_renderer.cpp b/skia/renderer/src/skia_renderer.cpp
new file mode 100644
index 0000000..8628a85
--- /dev/null
+++ b/skia/renderer/src/skia_renderer.cpp
@@ -0,0 +1,265 @@
+#include "skia_renderer.hpp"
+#include "SkData.h"
+#include "SkGradientShader.h"
+#include "SkPath.h"
+#include "SkVertices.h"
+#include "rive/math/vec2d.hpp"
+#include "rive/shapes/paint/color.hpp"
+#include "to_skia.hpp"
+
+using namespace rive;
+
+class SkiaBuffer : public RenderBuffer {
+    const size_t m_ElemSize;
+    void* m_Buffer;
+
+public:
+    SkiaBuffer(const void* src, size_t count, size_t elemSize) :
+        RenderBuffer(count), m_ElemSize(elemSize) {
+        size_t bytes = count * elemSize;
+        m_Buffer = malloc(bytes);
+        memcpy(m_Buffer, src, bytes);
+    }
+
+    ~SkiaBuffer() { free(m_Buffer); }
+
+    const float* f32s() const {
+        assert(m_ElemSize == sizeof(float));
+        return static_cast<const float*>(m_Buffer);
+    }
+
+    const uint16_t* u16s() const {
+        assert(m_ElemSize == sizeof(uint16_t));
+        return static_cast<const uint16_t*>(m_Buffer);
+    }
+
+    const SkPoint* points() const { return reinterpret_cast<const SkPoint*>(this->f32s()); }
+
+    static const SkiaBuffer* Cast(const RenderBuffer* buffer) {
+        return reinterpret_cast<const SkiaBuffer*>(buffer);
+    }
+};
+
+template <typename T> rcp<RenderBuffer> make_buffer(Span<T> span) {
+    return rcp<RenderBuffer>(new SkiaBuffer(span.data(), span.size(), sizeof(T)));
+}
+
+class SkiaRenderShader : public RenderShader {
+public:
+    SkiaRenderShader(sk_sp<SkShader> sh) : shader(std::move(sh)) {}
+
+    sk_sp<SkShader> shader;
+};
+
+void SkiaRenderPath::fillRule(FillRule value) { m_Path.setFillType(ToSkia::convert(value)); }
+
+void SkiaRenderPath::reset() { m_Path.reset(); }
+void SkiaRenderPath::addRenderPath(RenderPath* path, const Mat2D& transform) {
+    m_Path.addPath(reinterpret_cast<SkiaRenderPath*>(path)->m_Path, ToSkia::convert(transform));
+}
+
+void SkiaRenderPath::moveTo(float x, float y) { m_Path.moveTo(x, y); }
+void SkiaRenderPath::lineTo(float x, float y) { m_Path.lineTo(x, y); }
+void SkiaRenderPath::cubicTo(float ox, float oy, float ix, float iy, float x, float y) {
+    m_Path.cubicTo(ox, oy, ix, iy, x, y);
+}
+void SkiaRenderPath::close() { m_Path.close(); }
+
+SkiaRenderPaint::SkiaRenderPaint() { m_Paint.setAntiAlias(true); }
+
+void SkiaRenderPaint::style(RenderPaintStyle style) {
+    switch (style) {
+        case RenderPaintStyle::fill:
+            m_Paint.setStyle(SkPaint::Style::kFill_Style);
+            break;
+        case RenderPaintStyle::stroke:
+            m_Paint.setStyle(SkPaint::Style::kStroke_Style);
+            break;
+    }
+}
+void SkiaRenderPaint::color(unsigned int value) { m_Paint.setColor(value); }
+void SkiaRenderPaint::thickness(float value) { m_Paint.setStrokeWidth(value); }
+void SkiaRenderPaint::join(StrokeJoin value) { m_Paint.setStrokeJoin(ToSkia::convert(value)); }
+void SkiaRenderPaint::cap(StrokeCap value) { m_Paint.setStrokeCap(ToSkia::convert(value)); }
+
+void SkiaRenderPaint::blendMode(BlendMode value) { m_Paint.setBlendMode(ToSkia::convert(value)); }
+
+void SkiaRenderPaint::shader(rcp<RenderShader> rsh) {
+    SkiaRenderShader* sksh = (SkiaRenderShader*)rsh.get();
+    m_Paint.setShader(sksh ? sksh->shader : nullptr);
+}
+
+void SkiaRenderer::save() { m_Canvas->save(); }
+void SkiaRenderer::restore() { m_Canvas->restore(); }
+void SkiaRenderer::transform(const Mat2D& transform) {
+    m_Canvas->concat(ToSkia::convert(transform));
+}
+void SkiaRenderer::drawPath(RenderPath* path, RenderPaint* paint) {
+    m_Canvas->drawPath(reinterpret_cast<SkiaRenderPath*>(path)->path(),
+                       reinterpret_cast<SkiaRenderPaint*>(paint)->paint());
+}
+
+void SkiaRenderer::clipPath(RenderPath* path) {
+    m_Canvas->clipPath(reinterpret_cast<SkiaRenderPath*>(path)->path(), true);
+}
+
+void SkiaRenderer::drawImage(const RenderImage* image, BlendMode blendMode, float opacity) {
+    SkPaint paint;
+    paint.setAlphaf(opacity);
+    paint.setBlendMode(ToSkia::convert(blendMode));
+    auto skiaImage = reinterpret_cast<const SkiaRenderImage*>(image);
+    SkSamplingOptions sampling(SkFilterMode::kLinear);
+    m_Canvas->drawImage(skiaImage->skImage(), 0.0f, 0.0f, sampling, &paint);
+}
+
+#define SKIA_BUG_13047
+
+void SkiaRenderer::drawImageMesh(const RenderImage* image,
+                                 rcp<RenderBuffer> vertices,
+                                 rcp<RenderBuffer> uvCoords,
+                                 rcp<RenderBuffer> indices,
+                                 BlendMode blendMode,
+                                 float opacity) {
+    // need our vertices and uvs to agree
+    assert(vertices->count() == uvCoords->count());
+    // vertices and uvs are arrays of floats, so we need their counts to be
+    // even, since we treat them as arrays of points
+    assert((vertices->count() & 1) == 0);
+
+    const int vertexCount = vertices->count() >> 1;
+
+    SkMatrix scaleM;
+
+    const SkPoint* uvs = SkiaBuffer::Cast(uvCoords.get())->points();
+
+#ifdef SKIA_BUG_13047
+    // The local matrix is ignored for drawVertices, so we have to manually scale
+    // the UVs to match Skia's convention...
+    std::vector<SkPoint> scaledUVs(vertexCount);
+    for (int i = 0; i < vertexCount; ++i) {
+        scaledUVs[i] = {uvs[i].fX * image->width(), uvs[i].fY * image->height()};
+    }
+    uvs = scaledUVs.data();
+#else
+    // We do this because our UVs are normalized, but Skia expects them to be
+    // sized to the shader (i.e. 0..width, 0..height).
+    // To accomdate this, we effectively scaling the image down to 0..1 to
+    // match the scale of the UVs.
+    scaleM = SkMatrix::Scale(2.0f / image->width(), 2.0f / image->height());
+#endif
+
+    auto skiaImage = reinterpret_cast<const SkiaRenderImage*>(image)->skImage();
+    const SkSamplingOptions sampling(SkFilterMode::kLinear);
+    auto shader = skiaImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, sampling, &scaleM);
+
+    SkPaint paint;
+    paint.setAlphaf(opacity);
+    paint.setBlendMode(ToSkia::convert(blendMode));
+    paint.setShader(shader);
+
+    const SkColor* no_colors = nullptr;
+    auto vertexMode = SkVertices::kTriangles_VertexMode;
+    // clang-format off
+    auto vt = SkVertices::MakeCopy(vertexMode,
+                                   vertexCount,
+                                   SkiaBuffer::Cast(vertices.get())->points(),
+                                   uvs,
+                                   no_colors,
+                                   indices->count(),
+                                   SkiaBuffer::Cast(indices.get())->u16s());
+    // clang-format on
+
+    // The blend mode is ignored if we don't have colors && uvs
+    m_Canvas->drawVertices(vt, SkBlendMode::kModulate, paint);
+}
+
+bool SkiaRenderImage::decode(Span<const uint8_t> encodedData) {
+
+    sk_sp<SkData> data = SkData::MakeWithoutCopy(encodedData.data(), encodedData.size());
+    m_SkImage = SkImage::MakeFromEncoded(data);
+
+    // Our optimized skia buld seems to have broken lazy-image decode.
+    // As a work-around for now, force the image to be decoded.
+    if (m_SkImage) {
+        m_SkImage = m_SkImage->makeRasterImage();
+    }
+
+    if (m_SkImage) {
+        m_Width = m_SkImage->width();
+        m_Height = m_SkImage->height();
+        return true;
+    }
+    return false;
+}
+
+rcp<RenderShader>
+SkiaRenderImage::makeShader(RenderTileMode tx, RenderTileMode ty, const Mat2D* localMatrix) const {
+    const SkMatrix lm = localMatrix ? ToSkia::convert(*localMatrix) : SkMatrix();
+    const SkSamplingOptions options(SkFilterMode::kLinear);
+    auto sh = m_SkImage->makeShader(ToSkia::convert(tx), ToSkia::convert(ty), options, &lm);
+    return rcp<RenderShader>(new SkiaRenderShader(std::move(sh)));
+}
+
+namespace rive {
+    rcp<RenderBuffer> makeBufferU16(Span<const uint16_t> data) { return make_buffer(data); }
+    rcp<RenderBuffer> makeBufferU32(Span<const uint32_t> data) { return make_buffer(data); }
+    rcp<RenderBuffer> makeBufferF32(Span<const float> data) { return make_buffer(data); }
+
+    RenderPath*
+    makeRenderPath(Span<const Vec2D> points, Span<const uint8_t> verbs, FillRule fillrule) {
+        return new SkiaRenderPath(SkPath::Make((const SkPoint*)points.data(),
+                                               points.size(),
+                                               verbs.data(),
+                                               verbs.size(),
+                                               nullptr,
+                                               0, // conics
+                                               ToSkia::convert(fillrule),
+                                               false));
+    }
+
+    RenderPath* makeRenderPath() { return new SkiaRenderPath(); }
+    RenderPaint* makeRenderPaint() { return new SkiaRenderPaint(); }
+    RenderImage* makeRenderImage() { return new SkiaRenderImage(); }
+
+    rcp<RenderShader> makeLinearGradient(float sx,
+                                         float sy,
+                                         float ex,
+                                         float ey,
+                                         const ColorInt colors[],
+                                         const float stops[],
+                                         int count,
+                                         RenderTileMode mode,
+                                         const Mat2D* localm) {
+        const SkPoint pts[] = {{sx, sy}, {ex, ey}};
+        const SkMatrix lm = localm ? ToSkia::convert(*localm) : SkMatrix();
+        auto sh = SkGradientShader::MakeLinear(
+            pts, (const SkColor*)colors, stops, count, ToSkia::convert(mode), 0, &lm);
+        return rcp<RenderShader>(new SkiaRenderShader(std::move(sh)));
+    }
+
+    rcp<RenderShader> makeRadialGradient(float cx,
+                                         float cy,
+                                         float radius,
+                                         const ColorInt colors[],
+                                         const float stops[],
+                                         int count,
+                                         RenderTileMode mode,
+                                         const Mat2D* localm) {
+        const SkMatrix lm = localm ? ToSkia::convert(*localm) : SkMatrix();
+        auto sh = SkGradientShader::MakeRadial(
+            {cx, cy}, radius, (const SkColor*)colors, stops, count, ToSkia::convert(mode), 0, &lm);
+        return rcp<RenderShader>(new SkiaRenderShader(std::move(sh)));
+    }
+
+    rcp<RenderShader> makeSweepGradient(float cx,
+                                        float cy,
+                                        const ColorInt colors[],
+                                        const float stops[],
+                                        int count,
+                                        RenderTileMode mode,
+                                        const Mat2D* localm) {
+        const SkMatrix lm = localm ? ToSkia::convert(*localm) : SkMatrix();
+        auto sh = SkGradientShader::MakeSweep(cx, cy, (const SkColor*)colors, stops, count, 0, &lm);
+        return rcp<RenderShader>(new SkiaRenderShader(std::move(sh)));
+    }
+} // namespace rive
diff --git a/skia/viewer/src/main.cpp b/skia/viewer/src/main.cpp
index 0115a33..eb2e6e2 100644
--- a/skia/viewer/src/main.cpp
+++ b/skia/viewer/src/main.cpp
@@ -23,7 +23,6 @@
 #include "rive/file.hpp"
 #include "rive/layout.hpp"
 #include "rive/math/aabb.hpp"
-#include "skia_factory.hpp"
 #include "skia_renderer.hpp"
 
 #include "imgui/backends/imgui_impl_glfw.h"
@@ -32,8 +31,6 @@
 #include <cmath>
 #include <stdio.h>
 
-rive::SkiaFactory skiaFactory;
-
 std::string filename;
 std::unique_ptr<rive::File> currentFile;
 std::unique_ptr<rive::ArtboardInstance> artboardInstance;
@@ -71,7 +68,7 @@
     stateMachineIndex = index;
     animationIndex = -1;
     assert(fileBytes.size() != 0);
-    auto file = rive::File::import(rive::toSpan(fileBytes), &skiaFactory);
+    auto file = rive::File::import(rive::toSpan(fileBytes));
     if (!file) {
         fileBytes.clear();
         fprintf(stderr, "failed to import file\n");
@@ -95,7 +92,7 @@
     animationIndex = index;
     stateMachineIndex = -1;
     assert(fileBytes.size() != 0);
-    auto file = rive::File::import(rive::toSpan(fileBytes), &skiaFactory);
+    auto file = rive::File::import(rive::toSpan(fileBytes));
     if (!file) {
         fileBytes.clear();
         fprintf(stderr, "failed to import file\n");
diff --git a/src/artboard.cpp b/src/artboard.cpp
index 91437b0..37a3745 100644
--- a/src/artboard.cpp
+++ b/src/artboard.cpp
@@ -521,7 +521,6 @@
     std::unique_ptr<ArtboardInstance> artboardClone(new ArtboardInstance);
     artboardClone->copy(*this);
 
-    artboardClone->m_Factory = m_Factory;
     artboardClone->m_FrameOrigin = m_FrameOrigin;
 
     std::vector<Core*>& cloneObjects = artboardClone->m_Objects;
diff --git a/src/assets/image_asset.cpp b/src/assets/image_asset.cpp
index 7fe6304..6988c1b 100644
--- a/src/assets/image_asset.cpp
+++ b/src/assets/image_asset.cpp
@@ -1,17 +1,16 @@
 #include "rive/assets/image_asset.hpp"
-#include "rive/artboard.hpp"
-#include "rive/factory.hpp"
+#include "rive/renderer.hpp"
 
 using namespace rive;
 
-ImageAsset::~ImageAsset() {}
+ImageAsset::ImageAsset() : m_RenderImage(makeRenderImage()) {}
 
-bool ImageAsset::decode(Span<const uint8_t> data, Factory* factory) {
+ImageAsset::~ImageAsset() { delete m_RenderImage; }
+bool ImageAsset::decode(const uint8_t* bytes, std::size_t size) {
 #ifdef TESTING
-    decodedByteSize = data.size();
+    decodedByteSize = size;
 #endif
-    m_RenderImage = factory->decodeImage(data);
-    return m_RenderImage != nullptr;
+    return m_RenderImage->decode({bytes, size});
 }
 
 std::string ImageAsset::fileExtension() { return "png"; }
diff --git a/src/file.cpp b/src/file.cpp
index b59eee0..6a17dbb 100644
--- a/src/file.cpp
+++ b/src/file.cpp
@@ -108,18 +108,12 @@
     return object;
 }
 
-File::File(Factory* factory, FileAssetResolver* assetResolver)
-    : m_Factory(factory)
-    , m_AssetResolver(assetResolver)
-{
-    assert(factory);
-}
+File::File(FileAssetResolver* assetResolver) : m_AssetResolver(assetResolver) {}
 
 File::~File() {}
 
 std::unique_ptr<File>
-File::import(Span<const uint8_t> bytes, Factory* factory,
-             ImportResult* result, FileAssetResolver* assetResolver) {
+File::import(Span<const uint8_t> bytes, ImportResult* result, FileAssetResolver* assetResolver) {
     BinaryReader reader(bytes);
     RuntimeHeader header;
     if (!RuntimeHeader::read(reader, header)) {
@@ -141,7 +135,7 @@
         }
         return nullptr;
     }
-    auto file = std::unique_ptr<File>(new File(factory, assetResolver));
+    auto file = std::unique_ptr<File>(new File(assetResolver));
     auto readResult = file->read(reader, header);
     if (readResult != ImportResult::success) {
         file.reset(nullptr);
@@ -165,11 +159,9 @@
                 case Backboard::typeKey:
                     m_Backboard.reset(object->as<Backboard>());
                     break;
-                case Artboard::typeKey: {
-                    Artboard* ab = object->as<Artboard>();
-                    ab->m_Factory = m_Factory;
-                    m_Artboards.push_back(std::unique_ptr<Artboard>(ab));
-                } break;
+                case Artboard::typeKey:
+                    m_Artboards.push_back(std::unique_ptr<Artboard>(object->as<Artboard>()));
+                    break;
             }
         } else {
             fprintf(stderr, "Failed to import object of type %d\n", object->coreType());
@@ -234,7 +226,7 @@
                 stackObject = new StateMachineEventImporter(object->as<StateMachineEvent>());
                 break;
             case ImageAsset::typeKey:
-                stackObject = new FileAssetImporter(object->as<FileAsset>(), m_AssetResolver, m_Factory);
+                stackObject = new FileAssetImporter(object->as<FileAsset>(), m_AssetResolver);
                 stackType = FileAsset::typeKey;
                 break;
         }
diff --git a/src/importers/file_asset_importer.cpp b/src/importers/file_asset_importer.cpp
index 0d66c3c..0357271 100644
--- a/src/importers/file_asset_importer.cpp
+++ b/src/importers/file_asset_importer.cpp
@@ -7,15 +7,12 @@
 
 using namespace rive;
 
-FileAssetImporter::FileAssetImporter(FileAsset* fileAsset, FileAssetResolver* assetResolver, Factory* factory) :
-    m_FileAsset(fileAsset),
-    m_FileAssetResolver(assetResolver),
-    m_Factory(factory)
-{}
+FileAssetImporter::FileAssetImporter(FileAsset* fileAsset, FileAssetResolver* assetResolver) :
+    m_FileAsset(fileAsset), m_FileAssetResolver(assetResolver) {}
 
 void FileAssetImporter::loadContents(const FileAssetContents& contents) {
     auto data = contents.bytes();
-    if (m_FileAsset->decode(data, m_Factory)) {
+    if (m_FileAsset->decode(data.begin(), data.size())) {
         m_LoadedContents = true;
     }
 }
diff --git a/src/shapes/clipping_shape.cpp b/src/shapes/clipping_shape.cpp
index 2889b4e..f9bbc7d 100644
--- a/src/shapes/clipping_shape.cpp
+++ b/src/shapes/clipping_shape.cpp
@@ -1,7 +1,6 @@
 #include "rive/shapes/clipping_shape.hpp"
 #include "rive/artboard.hpp"
 #include "rive/core_context.hpp"
-#include "rive/factory.hpp"
 #include "rive/node.hpp"
 #include "rive/renderer.hpp"
 #include "rive/shapes/path_composer.hpp"
@@ -48,7 +47,7 @@
         }
     }
 
-    m_RenderPath = artboard->factory()->makeEmptyRenderPath();
+    m_RenderPath = rive::makeRenderPath();
 
     return StatusCode::Ok;
 }
@@ -87,3 +86,5 @@
         }
     }
 }
+
+ClippingShape::~ClippingShape() { delete m_RenderPath; }
\ No newline at end of file
diff --git a/src/shapes/mesh.cpp b/src/shapes/mesh.cpp
index fa8234f..5f24336 100644
--- a/src/shapes/mesh.cpp
+++ b/src/shapes/mesh.cpp
@@ -3,8 +3,6 @@
 #include "rive/shapes/vertex.hpp"
 #include "rive/shapes/mesh_vertex.hpp"
 #include "rive/bones/skin.hpp"
-#include "rive/artboard.hpp"
-#include "rive/factory.hpp"
 #include "rive/span.hpp"
 #include <limits>
 
@@ -92,10 +90,8 @@
         uv[index++] = vertex->u();
         uv[index++] = vertex->v();
     }
-
-    auto factory = artboard()->factory();
-    m_UVRenderBuffer = factory->makeBufferF32(toSpan(uv));
-    m_IndexRenderBuffer = factory->makeBufferU16(toSpan(*m_IndexBuffer));
+    m_UVRenderBuffer = makeBufferF32(toSpan(uv));
+    m_IndexRenderBuffer = makeBufferU16(toSpan(*m_IndexBuffer));
 }
 
 void Mesh::update(ComponentDirt value) {
@@ -118,9 +114,7 @@
             vertices[index++] = translation[0];
             vertices[index++] = translation[1];
         }
-
-        auto factory = artboard()->factory();
-        m_VertexRenderBuffer = factory->makeBufferF32(toSpan(vertices));
+        m_VertexRenderBuffer = makeBufferF32(toSpan(vertices));
     }
 
     if (skin() == nullptr) {
diff --git a/src/shapes/metrics_path.cpp b/src/shapes/metrics_path.cpp
index 3574eef..6399d6c 100644
--- a/src/shapes/metrics_path.cpp
+++ b/src/shapes/metrics_path.cpp
@@ -354,9 +354,8 @@
     }
 }
 
-RenderMetricsPath::RenderMetricsPath(std::unique_ptr<RenderPath> path)
-    : m_RenderPath(std::move(path))
-{}
+RenderMetricsPath::RenderMetricsPath() : m_RenderPath(makeRenderPath()) {}
+RenderMetricsPath::~RenderMetricsPath() { delete m_RenderPath; }
 
 void RenderMetricsPath::addPath(CommandPath* path, const Mat2D& transform) {
     MetricsPath::addPath(path, transform);
diff --git a/src/shapes/paint/fill.cpp b/src/shapes/paint/fill.cpp
index d451982..e39fb78 100644
--- a/src/shapes/paint/fill.cpp
+++ b/src/shapes/paint/fill.cpp
@@ -16,5 +16,5 @@
     }
     auto renderPath = path->renderPath();
     renderPath->fillRule((FillRule)fillRule());
-    renderer->drawPath(renderPath, renderPaint());
+    renderer->drawPath(renderPath, m_RenderPaint);
 }
\ No newline at end of file
diff --git a/src/shapes/paint/linear_gradient.cpp b/src/shapes/paint/linear_gradient.cpp
index 24d0c6a..0e5b978 100644
--- a/src/shapes/paint/linear_gradient.cpp
+++ b/src/shapes/paint/linear_gradient.cpp
@@ -1,7 +1,5 @@
 #include "rive/shapes/paint/linear_gradient.hpp"
 #include "rive/math/vec2d.hpp"
-#include "rive/artboard.hpp"
-#include "rive/factory.hpp"
 #include "rive/node.hpp"
 #include "rive/renderer.hpp"
 #include "rive/shapes/paint/color.hpp"
@@ -94,8 +92,8 @@
 
 void LinearGradient::makeGradient(
     Vec2D start, Vec2D end, const ColorInt colors[], const float stops[], size_t count) {
-    auto factory = artboard()->factory();
-    renderPaint()->shader(factory->makeLinearGradient(
+    auto paint = renderPaint();
+    paint->shader(makeLinearGradient(
         start[0], start[1], end[0], end[1], colors, stops, count, RenderTileMode::clamp));
 }
 
diff --git a/src/shapes/paint/radial_gradient.cpp b/src/shapes/paint/radial_gradient.cpp
index 6c6b481..d327d4e 100644
--- a/src/shapes/paint/radial_gradient.cpp
+++ b/src/shapes/paint/radial_gradient.cpp
@@ -1,17 +1,16 @@
 #include "rive/shapes/paint/radial_gradient.hpp"
-#include "rive/artboard.hpp"
-#include "rive/factory.hpp"
+#include "rive/renderer.hpp"
 
 using namespace rive;
 
 void RadialGradient::makeGradient(
     Vec2D start, Vec2D end, const ColorInt colors[], const float stops[], size_t count) {
-    auto factory = artboard()->factory();
-    renderPaint()->shader(factory->makeRadialGradient(start[0],
-                                                      start[1],
-                                                      Vec2D::distance(start, end),
-                                                      colors,
-                                                      stops,
-                                                      count,
-                                                      RenderTileMode::clamp));
+    auto paint = renderPaint();
+    paint->shader(makeRadialGradient(start[0],
+                                     start[1],
+                                     Vec2D::distance(start, end),
+                                     colors,
+                                     stops,
+                                     count,
+                                     RenderTileMode::clamp));
 }
diff --git a/src/shapes/paint/shape_paint.cpp b/src/shapes/paint/shape_paint.cpp
index b1d2890..174d82a 100644
--- a/src/shapes/paint/shape_paint.cpp
+++ b/src/shapes/paint/shape_paint.cpp
@@ -1,11 +1,12 @@
 #include "rive/shapes/paint/shape_paint.hpp"
 #include "rive/shapes/shape_paint_container.hpp"
 
-#include "rive/artboard.hpp"
-#include "rive/factory.hpp"
+#include "rive/renderer.hpp"
 
 using namespace rive;
 
+ShapePaint::~ShapePaint() { delete m_RenderPaint; }
+
 StatusCode ShapePaint::onAddedClean(CoreContext* context) {
     auto container = ShapePaintContainer::from(parent());
     if (container == nullptr) {
@@ -18,10 +19,7 @@
 RenderPaint* ShapePaint::initRenderPaint(ShapePaintMutator* mutator) {
     assert(m_RenderPaint == nullptr);
     m_PaintMutator = mutator;
-
-    auto factory = mutator->component()->artboard()->factory();
-    m_RenderPaint = factory->makeRenderPaint();
-    return m_RenderPaint.get();
+    return m_RenderPaint = makeRenderPaint();
 }
 
 void ShapePaint::blendMode(BlendMode value) {
diff --git a/src/shapes/paint/stroke.cpp b/src/shapes/paint/stroke.cpp
index 4588cc4..ff48cf7 100644
--- a/src/shapes/paint/stroke.cpp
+++ b/src/shapes/paint/stroke.cpp
@@ -1,4 +1,3 @@
-#include "rive/artboard.hpp"
 #include "rive/shapes/paint/stroke.hpp"
 #include "rive/shapes/paint/stroke_cap.hpp"
 #include "rive/shapes/paint/stroke_effect.hpp"
@@ -28,11 +27,10 @@
 
     if (m_Effect != nullptr) {
         /// We're guaranteed to get a metrics path here if we have an effect.
-        auto factory = artboard()->factory();
-        path = m_Effect->effectPath(reinterpret_cast<MetricsPath*>(path), factory);
+        path = m_Effect->effectPath(reinterpret_cast<MetricsPath*>(path));
     }
 
-    renderer->drawPath(path->renderPath(), renderPaint());
+    renderer->drawPath(path->renderPath(), m_RenderPaint);
 }
 
 void Stroke::thicknessChanged() {
diff --git a/src/shapes/paint/trim_path.cpp b/src/shapes/paint/trim_path.cpp
index 3087dde..3d2e8d8 100644
--- a/src/shapes/paint/trim_path.cpp
+++ b/src/shapes/paint/trim_path.cpp
@@ -1,10 +1,12 @@
 #include "rive/shapes/paint/trim_path.hpp"
 #include "rive/shapes/metrics_path.hpp"
 #include "rive/shapes/paint/stroke.hpp"
-#include "rive/factory.hpp"
 
 using namespace rive;
 
+TrimPath::TrimPath() : m_TrimmedPath(makeRenderPath()) {}
+TrimPath::~TrimPath() { delete m_TrimmedPath; }
+
 StatusCode TrimPath::onAddedClean(CoreContext* context) {
     if (!parent()->is<Stroke>()) {
         return StatusCode::InvalidObject;
@@ -15,7 +17,7 @@
     return StatusCode::Ok;
 }
 
-RenderPath* TrimPath::effectPath(MetricsPath* source, Factory* factory) {
+RenderPath* TrimPath::effectPath(MetricsPath* source) {
     if (m_RenderPath != nullptr) {
         return m_RenderPath;
     }
@@ -23,12 +25,7 @@
     // Source is always a containing (shape) path.
     const std::vector<MetricsPath*>& subPaths = source->paths();
 
-    if (!m_TrimmedPath) {
-        m_TrimmedPath = factory->makeEmptyRenderPath();
-    } else {
-        m_TrimmedPath->reset();
-    }
-
+    m_TrimmedPath->reset();
     auto renderOffset = std::fmod(std::fmod(offset(), 1.0f) + 1.0f, 1.0f);
     switch (modeValue()) {
         case 1: {
@@ -53,7 +50,7 @@
                 auto pathLength = path->length();
 
                 if (startLength < pathLength) {
-                    path->trim(startLength, endLength, true, m_TrimmedPath.get());
+                    path->trim(startLength, endLength, true, m_TrimmedPath);
                     endLength -= pathLength;
                     startLength = 0;
                 } else {
@@ -79,17 +76,17 @@
                     startLength -= pathLength;
                     endLength -= pathLength;
                 }
-                path->trim(startLength, endLength, true, m_TrimmedPath.get());
+                path->trim(startLength, endLength, true, m_TrimmedPath);
                 while (endLength > pathLength) {
                     startLength = 0;
                     endLength -= pathLength;
-                    path->trim(startLength, endLength, true, m_TrimmedPath.get());
+                    path->trim(startLength, endLength, true, m_TrimmedPath);
                 }
             }
         } break;
     }
 
-    m_RenderPath = m_TrimmedPath.get();
+    m_RenderPath = m_TrimmedPath;
     return m_RenderPath;
 }
 
diff --git a/src/shapes/shape_paint_container.cpp b/src/shapes/shape_paint_container.cpp
index 8dd83da..044b40d 100644
--- a/src/shapes/shape_paint_container.cpp
+++ b/src/shapes/shape_paint_container.cpp
@@ -1,6 +1,5 @@
 #include "rive/shapes/shape_paint_container.hpp"
 #include "rive/artboard.hpp"
-#include "rive/factory.hpp"
 #include "rive/component.hpp"
 #include "rive/renderer.hpp"
 #include "rive/shapes/metrics_path.hpp"
@@ -9,7 +8,6 @@
 #include "rive/shapes/shape.hpp"
 
 using namespace rive;
-
 ShapePaintContainer* ShapePaintContainer::from(Component* component) {
     switch (component->coreType()) {
         case Artboard::typeKey:
@@ -60,12 +58,11 @@
         }
     }
 
-    auto factory = getArtboard()->factory();
     if (needForEffects && needForRender) {
-        return std::unique_ptr<CommandPath>(new RenderMetricsPath(factory->makeEmptyRenderPath()));
+        return std::unique_ptr<CommandPath>(new RenderMetricsPath());
     } else if (needForEffects) {
         return std::unique_ptr<CommandPath>(new OnlyMetricsPath());
     } else {
-        return std::unique_ptr<CommandPath>(factory->makeEmptyRenderPath());
+        return std::unique_ptr<CommandPath>(rive::makeRenderPath());
     }
 }
diff --git a/test/bound_bones_test.cpp b/test/bound_bones_test.cpp
index d34b330..4a92b48 100644
--- a/test/bound_bones_test.cpp
+++ b/test/bound_bones_test.cpp
@@ -7,7 +7,7 @@
 #include <rive/shapes/points_path.hpp>
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
-#include "no_op_factory.hpp"
+#include "no_op_renderer.hpp"
 #include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
diff --git a/test/image_asset_test.cpp b/test/image_asset_test.cpp
index e65d25f..a681d9d 100644
--- a/test/image_asset_test.cpp
+++ b/test/image_asset_test.cpp
@@ -5,7 +5,6 @@
 #include <rive/shapes/image.hpp>
 #include <rive/assets/image_asset.hpp>
 #include <rive/relative_local_asset_resolver.hpp>
-#include "no_op_factory.hpp"
 #include "no_op_renderer.hpp"
 #include "rive_file_reader.hpp"
 #include <catch.hpp>
@@ -42,12 +41,10 @@
 }
 
 TEST_CASE("out of band image assets loads correctly", "[assets]") {
-    rive::NoOpFactory gEmptyFactory;
-    
     std::string filename = "../../test/assets/out_of_band/walle.riv";
-    rive::RelativeLocalAssetResolver resolver(filename, &gEmptyFactory);
+    rive::RelativeLocalAssetResolver resolver(filename);
 
-    auto file = ReadRiveFile(filename.c_str(), &gEmptyFactory, &resolver);
+    auto file = ReadRiveFile(filename.c_str(), &resolver);
 
     auto node = file->artboard()->find("walle");
     REQUIRE(node != nullptr);
diff --git a/test/instancing_test.cpp b/test/instancing_test.cpp
index 93fe204..85d2e4f 100644
--- a/test/instancing_test.cpp
+++ b/test/instancing_test.cpp
@@ -3,7 +3,6 @@
 #include <rive/shapes/clipping_shape.hpp>
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
-#include "no_op_factory.hpp"
 #include "no_op_renderer.hpp"
 #include "rive_file_reader.hpp"
 #include <catch.hpp>
diff --git a/test/linear_animation_instance_test.cpp b/test/linear_animation_instance_test.cpp
index 469b4ae..f8a5b77 100644
--- a/test/linear_animation_instance_test.cpp
+++ b/test/linear_animation_instance_test.cpp
@@ -1,15 +1,13 @@
 #include <rive/animation/loop.hpp>
 #include <rive/animation/linear_animation.hpp>
 #include <rive/animation/linear_animation_instance.hpp>
-#include "no_op_factory.hpp"
 #include <catch.hpp>
 #include <cstdio>
 
 TEST_CASE("LinearAnimationInstance oneShot", "[animation]") {
-    rive::NoOpFactory emptyFactory;
     // For each of these tests, we cons up a dummy artboard/instance
     // just to make the animations happy.
-    rive::Artboard ab(&emptyFactory);
+    rive::Artboard ab;
     auto abi = ab.instance();
 
     rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
@@ -40,8 +38,7 @@
 }
 
 TEST_CASE("LinearAnimationInstance oneShot <-", "[animation]") {
-    rive::NoOpFactory emptyFactory;
-    rive::Artboard ab(&emptyFactory);
+    rive::Artboard ab;
     auto abi = ab.instance();
 
     rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
@@ -88,8 +85,7 @@
 }
 
 TEST_CASE("LinearAnimationInstance loop ->", "[animation]") {
-    rive::NoOpFactory emptyFactory;
-    rive::Artboard ab(&emptyFactory);
+    rive::Artboard ab;
     auto abi = ab.instance();
 
     rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
@@ -120,8 +116,7 @@
 }
 
 TEST_CASE("LinearAnimationInstance loop <-", "[animation]") {
-    rive::NoOpFactory emptyFactory;
-    rive::Artboard ab(&emptyFactory);
+    rive::Artboard ab;
     auto abi = ab.instance();
 
     rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
@@ -162,8 +157,7 @@
 }
 
 TEST_CASE("LinearAnimationInstance loop <- work area", "[animation]") {
-    rive::NoOpFactory emptyFactory;
-    rive::Artboard ab(&emptyFactory);
+    rive::Artboard ab;
     auto abi = ab.instance();
 
     rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
@@ -217,8 +211,7 @@
 }
 
 TEST_CASE("LinearAnimationInstance pingpong ->", "[animation]") {
-    rive::NoOpFactory emptyFactory;
-    rive::Artboard ab(&emptyFactory);
+    rive::Artboard ab;
     auto abi = ab.instance();
 
     rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
@@ -250,8 +243,7 @@
 }
 
 TEST_CASE("LinearAnimationInstance pingpong <-", "[animation]") {
-    rive::NoOpFactory emptyFactory;
-    rive::Artboard ab(&emptyFactory);
+    rive::Artboard ab;
     auto abi = ab.instance();
 
     rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
@@ -295,8 +287,7 @@
 }
 
 TEST_CASE("LinearAnimationInstance override loop", "[animation]") {
-    rive::NoOpFactory emptyFactory;
-    rive::Artboard ab(&emptyFactory);
+    rive::Artboard ab;
     auto abi = ab.instance();
 
     rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
diff --git a/test/no_op_factory.cpp b/test/no_op_factory.cpp
deleted file mode 100644
index 2f088f2..0000000
--- a/test/no_op_factory.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-#include "no_op_factory.hpp"
-#include "no_op_renderer.hpp"
-
-using namespace rive;
-
-NoOpFactory gNoOpFactory;
-
-rcp<RenderBuffer> NoOpFactory::makeBufferU16(Span<const uint16_t>) { return nullptr; }
-rcp<RenderBuffer> NoOpFactory::makeBufferU32(Span<const uint32_t>) { return nullptr; }
-rcp<RenderBuffer> NoOpFactory::makeBufferF32(Span<const float>) { return nullptr; }
-
-rcp<RenderShader> NoOpFactory::makeLinearGradient(float sx, float sy,
-                                                float ex, float ey,
-                                                const ColorInt colors[],    // [count]
-                                                const float stops[],        // [count]
-                                                int count,
-                                                RenderTileMode,
-                                                const Mat2D* localMatrix) { return nullptr; }
-
-rcp<RenderShader> NoOpFactory::makeRadialGradient(float cx, float cy, float radius,
-                                                const ColorInt colors[],    // [count]
-                                                const float stops[],        // [count]
-                                                int count,
-                                                RenderTileMode,
-                                                const Mat2D* localMatrix) { return nullptr; }
-
-std::unique_ptr<RenderPath> NoOpFactory::makeRenderPath(Span<const Vec2D> points,
-                                                        Span<const uint8_t> verbs,
-                                                        FillRule) {
-    return std::make_unique<NoOpRenderPath>();
-}
-
-std::unique_ptr<RenderPath> NoOpFactory::makeEmptyRenderPath() {
-    return std::make_unique<NoOpRenderPath>();
-}
-
-std::unique_ptr<RenderPaint> NoOpFactory::makeRenderPaint() {
-    return std::make_unique<NoOpRenderPaint>();
-}
-
-std::unique_ptr<RenderImage> NoOpFactory::decodeImage(Span<const uint8_t>) {
-    return std::make_unique<NoOpRenderImage>();
-}
diff --git a/test/no_op_factory.hpp b/test/no_op_factory.hpp
deleted file mode 100644
index 59095be..0000000
--- a/test/no_op_factory.hpp
+++ /dev/null
@@ -1,43 +0,0 @@
-#ifndef _RIVE_NOOP_FACTORY_HPP_
-#define _RIVE_NOOP_FACTORY_HPP_
-
-#include <rive/renderer.hpp>
-#include <rive/factory.hpp>
-
-namespace rive {
-
-    class NoOpFactory : public Factory {
-        rcp<RenderBuffer> makeBufferU16(Span<const uint16_t>) override;
-        rcp<RenderBuffer> makeBufferU32(Span<const uint32_t>) override;
-        rcp<RenderBuffer> makeBufferF32(Span<const float>) override;
-
-        rcp<RenderShader> makeLinearGradient(float sx, float sy,
-                                                     float ex, float ey,
-                                                     const ColorInt colors[],    // [count]
-                                                     const float stops[],        // [count]
-                                                     int count,
-                                                     RenderTileMode,
-                                                     const Mat2D* localMatrix = nullptr) override;
-
-        rcp<RenderShader> makeRadialGradient(float cx, float cy, float radius,
-                                                     const ColorInt colors[],    // [count]
-                                                     const float stops[],        // [count]
-                                                     int count,
-                                                     RenderTileMode,
-                                                     const Mat2D* localMatrix = nullptr) override;
-
-        std::unique_ptr<RenderPath> makeRenderPath(Span<const Vec2D> points,
-                                                   Span<const uint8_t> verbs,
-                                                   FillRule) override;
-
-        std::unique_ptr<RenderPath> makeEmptyRenderPath() override;
-
-        std::unique_ptr<RenderPaint> makeRenderPaint() override;
-
-        std::unique_ptr<RenderImage> decodeImage(Span<const uint8_t>) override;
-    };
-
-    static NoOpFactory gNoOpFactory;
-
-} // namespace rive
-#endif
diff --git a/test/no_op_renderer.cpp b/test/no_op_renderer.cpp
new file mode 100644
index 0000000..b6ec756
--- /dev/null
+++ b/test/no_op_renderer.cpp
@@ -0,0 +1,44 @@
+#include "no_op_renderer.hpp"
+#include <rive/renderer.hpp>
+
+namespace rive {
+    RenderPaint* makeRenderPaint() { return new NoOpRenderPaint(); }
+    RenderPath* makeRenderPath() { return new NoOpRenderPath(); }
+    RenderImage* makeRenderImage() { return new NoOpRenderImage(); }
+
+    rcp<RenderShader> makeLinearGradient(float sx,
+                                         float sy,
+                                         float ex,
+                                         float ey,
+                                         const ColorInt colors[],
+                                         const float stops[],
+                                         int count,
+                                         RenderTileMode,
+                                         const Mat2D* localMatrix) {
+        return nullptr;
+    }
+
+    rcp<RenderShader> makeRadialGradient(float cx,
+                                         float cy,
+                                         float radius,
+                                         const ColorInt colors[],
+                                         const float stops[],
+                                         int count,
+                                         RenderTileMode,
+                                         const Mat2D* localMatrix) {
+        return nullptr;
+    }
+
+    rcp<RenderShader> makeSweepGradient(float cx,
+                                        float cy,
+                                        const ColorInt colors[],
+                                        const float stops[],
+                                        int count,
+                                        const Mat2D* localMatrix) {
+        return nullptr;
+    }
+
+    rcp<RenderBuffer> makeBufferU16(Span<const uint16_t>) { return nullptr; }
+    rcp<RenderBuffer> makeBufferU32(Span<const uint32_t>) { return nullptr; }
+    rcp<RenderBuffer> makeBufferF32(Span<const float>) { return nullptr; }
+} // namespace rive
diff --git a/test/no_op_renderer.hpp b/test/no_op_renderer.hpp
index b5e9bb5..9237c4c 100644
--- a/test/no_op_renderer.hpp
+++ b/test/no_op_renderer.hpp
@@ -1,13 +1,12 @@
 #ifndef _RIVE_NOOP_RENDERER_HPP_
 #define _RIVE_NOOP_RENDERER_HPP_
-
 #include <rive/renderer.hpp>
-#include <rive/factory.hpp>
 #include <vector>
 
 namespace rive {
     class NoOpRenderImage : public RenderImage {
     public:
+        bool decode(Span<const uint8_t>) override { return true; }
         rcp<RenderShader> makeShader(RenderTileMode, RenderTileMode, const Mat2D*) const override {
             return nullptr;
         }
diff --git a/test/path_test.cpp b/test/path_test.cpp
index fb0e5fb..15f9218 100644
--- a/test/path_test.cpp
+++ b/test/path_test.cpp
@@ -6,14 +6,12 @@
 #include <rive/shapes/path_composer.hpp>
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
-#include "no_op_factory.hpp"
 #include "no_op_renderer.hpp"
 #include <catch.hpp>
 #include <cstdio>
 
 TEST_CASE("rectangle path builds expected commands", "[path]") {
-    rive::NoOpFactory emptyFactory;
-    rive::Artboard artboard(&emptyFactory);
+    rive::Artboard* artboard = new rive::Artboard();
     rive::Shape* shape = new rive::Shape();
     rive::Rectangle* rectangle = new rive::Rectangle();
 
@@ -23,14 +21,14 @@
     rectangle->height(200.0f);
     rectangle->cornerRadiusTL(0.0f);
 
-    artboard.addObject(&artboard);
-    artboard.addObject(shape);
-    artboard.addObject(rectangle);
+    artboard->addObject(artboard);
+    artboard->addObject(shape);
+    artboard->addObject(rectangle);
     rectangle->parentId(1);
 
-    REQUIRE(artboard.initialize() == rive::StatusCode::Ok);
+    REQUIRE(artboard->initialize() == rive::StatusCode::Ok);
 
-    artboard.advance(0.0f);
+    artboard->advance(0.0f);
 
     REQUIRE(rectangle->commandPath() != nullptr);
 
@@ -44,11 +42,12 @@
     REQUIRE(path->commands[4].command == rive::NoOpPathCommandType::LineTo);
     REQUIRE(path->commands[5].command == rive::NoOpPathCommandType::LineTo);
     REQUIRE(path->commands[6].command == rive::NoOpPathCommandType::Close);
+
+    delete artboard;
 }
 
 TEST_CASE("rounded rectangle path builds expected commands", "[path]") {
-    rive::NoOpFactory emptyFactory;
-    rive::Artboard artboard(&emptyFactory);
+    rive::Artboard* artboard = new rive::Artboard();
     rive::Shape* shape = new rive::Shape();
     rive::Rectangle* rectangle = new rive::Rectangle();
 
@@ -59,14 +58,14 @@
     rectangle->cornerRadiusTL(20.0f);
     rectangle->linkCornerRadius(true);
 
-    artboard.addObject(&artboard);
-    artboard.addObject(shape);
-    artboard.addObject(rectangle);
+    artboard->addObject(artboard);
+    artboard->addObject(shape);
+    artboard->addObject(rectangle);
     rectangle->parentId(1);
 
-    artboard.initialize();
+    artboard->initialize();
 
-    artboard.advance(0.0f);
+    artboard->advance(0.0f);
 
     REQUIRE(rectangle->commandPath() != nullptr);
 
@@ -106,11 +105,12 @@
     REQUIRE(path->commands[9].command == rive::NoOpPathCommandType::LineTo);
 
     REQUIRE(path->commands[10].command == rive::NoOpPathCommandType::Close);
+
+    delete artboard;
 }
 
 TEST_CASE("ellipse path builds expected commands", "[path]") {
-    rive::NoOpFactory emptyFactory;
-    rive::Artboard artboard(&emptyFactory);
+    rive::Artboard* artboard = new rive::Artboard();
     rive::Ellipse* ellipse = new rive::Ellipse();
     rive::Shape* shape = new rive::Shape();
 
@@ -119,14 +119,14 @@
     ellipse->width(100.0f);
     ellipse->height(200.0f);
 
-    artboard.addObject(&artboard);
-    artboard.addObject(shape);
-    artboard.addObject(ellipse);
+    artboard->addObject(artboard);
+    artboard->addObject(shape);
+    artboard->addObject(ellipse);
     ellipse->parentId(1);
 
-    artboard.initialize();
+    artboard->initialize();
 
-    artboard.advance(0.0f);
+    artboard->advance(0.0f);
 
     REQUIRE(ellipse->commandPath() != nullptr);
 
@@ -187,4 +187,6 @@
     REQUIRE(path->commands[5].y == -100.0f);
 
     REQUIRE(path->commands[6].command == rive::NoOpPathCommandType::Close);
+
+    delete artboard;
 }
\ No newline at end of file
diff --git a/test/rive_file_reader.hpp b/test/rive_file_reader.hpp
index 4294290..cec7a41 100644
--- a/test/rive_file_reader.hpp
+++ b/test/rive_file_reader.hpp
@@ -3,16 +3,9 @@
 
 #include <rive/file.hpp>
 #include "rive_testing.hpp"
-#include "no_op_factory.hpp"
 
 static inline std::unique_ptr<rive::File>
-ReadRiveFile(const char path[],
-             rive::Factory* factory = nullptr,
-             rive::FileAssetResolver* resolver = nullptr) {
-    if (!factory) {
-        factory = &rive::gNoOpFactory;
-    }
-
+ReadRiveFile(const char path[], rive::FileAssetResolver* resolver = nullptr) {
     FILE* fp = fopen(path, "rb");
     REQUIRE(fp != nullptr);
 
@@ -24,7 +17,7 @@
     fclose(fp);
 
     rive::ImportResult result;
-    auto file = rive::File::import(rive::toSpan(bytes), factory, &result, resolver);
+    auto file = rive::File::import(rive::toSpan(bytes), &result, resolver);
     REQUIRE(result == rive::ImportResult::success);
     REQUIRE(file.get() != nullptr);
     REQUIRE(file->artboard() != nullptr);