Move over goldens_gpu
diff --git a/skia/goldens/build/premake5.lua b/skia/goldens/build/premake5.lua
index d6c3786..8a26cd4 100644
--- a/skia/goldens/build/premake5.lua
+++ b/skia/goldens/build/premake5.lua
@@ -93,6 +93,107 @@
     defines {"NDEBUG"}
     optimize "On"
 
+project "goldens_gpu"
+    kind "ConsoleApp"
+    language "C++"
+    cppdialect "C++17"
+    toolset "clang"
+    targetdir "bin/%{cfg.buildcfg}"
+    objdir "obj/%{cfg.buildcfg}"
+    includedirs {
+        "../../utils",
+        "../../dependencies/glfw/include",
+        "../src",
+        "../../../include",
+        "../../renderer/include",
+        "../../dependencies/",
+        "../../dependencies/skia",
+        "../../dependencies/skia/include/core",
+        "../../dependencies/skia/third_party/libpng",
+    }
+
+    if os.host() == 'macosx' then
+        links {
+            "Cocoa.framework",
+            "CoreFoundation.framework",
+            "CoreMedia.framework",
+            "CoreServices.framework",
+            "IOKit.framework",
+            "Security.framework",
+            "OpenGL.framework",
+            "bz2",
+            "iconv",
+            "lzma",
+            "rive_skia_renderer",
+            "rive",
+            "skia",
+            "glfw3",
+            "z"  -- lib av format
+        }
+        libdirs {
+            "../dependencies/glfw/build/src",
+        }
+    elseif os.host() == "windows" then
+        architecture "x64"
+        links {
+            "rive_skia_renderer",
+            "rive",
+            "skia.lib",
+            "glfw3.lib",
+            "opengl32.lib"
+        }
+        libdirs {
+            "../dependencies/glfw/build/src/Release",
+        }
+        defines {"_USE_MATH_DEFINES", "_CRT_SECURE_NO_WARNINGS"}
+        buildoptions {WINDOWS_CLANG_CL_SUPPRESSED_WARNINGS}
+        staticruntime "on"  -- Match Skia's /MT flag for link compatibility
+        runtime "Release"  -- Use /MT even in debug (/MTd is incompatible with Skia)
+    else
+        links {
+            "m",
+            "rive_skia_renderer",
+            "rive",
+            "skia",
+            "glfw3",
+            "z",
+            "dl",
+            "pthread",
+            "GL"
+        }
+        libdirs {
+            "../dependencies/glfw/build/src",
+        }
+    end
+
+    libdirs {
+        "../build/%{cfg.system}/bin/%{cfg.buildcfg}",
+        "../../dependencies/skia/out/static",
+        "../../renderer/build/%{cfg.system}/bin/%{cfg.buildcfg}",
+    }
+
+    files {
+        "../../utils/*.cpp",
+        "../src/goldens_gpu.cpp",
+        "../src/goldens_grid.cpp",
+    }
+
+    buildoptions {"-Wall", "-fno-rtti"}
+
+    defines {
+        "SK_GL",
+        "GL_SILENCE_DEPRECATION",  -- For glReadPixels()
+    }
+
+    filter "configurations:debug"
+    defines {"DEBUG"}
+    symbols "On"
+
+    filter "configurations:release"
+    defines {"RELEASE"}
+    defines {"NDEBUG"}
+    optimize "On"
+
 
 -- Clean Function --
 newaction {
diff --git a/skia/goldens/src/goldens_gpu.cpp b/skia/goldens/src/goldens_gpu.cpp
index 6de6425..3e8c3c9 100644
--- a/skia/goldens/src/goldens_gpu.cpp
+++ b/skia/goldens/src/goldens_gpu.cpp
@@ -13,7 +13,7 @@
 #include "skia/include/gpu/gl/GrGLInterface.h"
 #include "skia/include/gpu/gl/GrGLAssembleInterface.h"
 #include "skia/third_party/externals/libpng/png.h"
-#include "tools/write_png_file.hpp"
+#include "write_png_file.hpp"
 #include <iostream>
 #include <string>
 
diff --git a/skia/utils/renderer_target.cpp b/skia/utils/renderer_target.cpp
new file mode 100644
index 0000000..55fca40
--- /dev/null
+++ b/skia/utils/renderer_target.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#ifdef TESTING
+
+// Don't compile this file as part of the "tests" project.
+
+#else
+
+#include "renderer_target.hpp"
+
+#include "GLFW/glfw3.h"
+#include "skia_renderer.hpp"
+#include "skia/include/core/SkImage.h"
+#include "skia/include/core/SkPixmap.h"
+#include "skia/include/core/SkStream.h"
+#include "skia/include/core/SkSurface.h"
+#include "skia/include/gpu/GrDirectContext.h"
+#include "skia/include/gpu/gl/GrGLInterface.h"
+#include "skia/include/gpu/gl/GrGLAssembleInterface.h"
+#include "write_png_file.hpp"
+#include <vector>
+
+class SkiaRasterTarget : public RendererTarget {
+public:
+    std::unique_ptr<rive::Renderer> reset(int width, int height) override {
+        auto info = SkImageInfo::MakeN32Premul(width, height);
+        m_Surface = SkSurface::MakeRaster(info);
+        return std::make_unique<rive::SkiaRenderer>(m_Surface->getCanvas());
+    }
+
+    void flush() const override { m_Surface->getCanvas()->flush(); }
+
+    void dumpToPNG(const std::string& filepath) const override {
+        assert(m_Surface);
+        flush();
+        auto img = m_Surface->makeImageSnapshot();
+        std::vector<uint8_t> pixels(img->height() * img->width() * 4);
+        SkColorInfo colorInfo(kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr);
+        img->readPixels(nullptr,
+                        SkPixmap(SkImageInfo::Make({img->width(), img->height()}, colorInfo),
+                                 pixels.data(),
+                                 img->width() * 4),
+                        0,
+                        0);
+        WritePNGFile(pixels.data(), img->width(), img->height(), false, filepath.c_str());
+    }
+
+private:
+    sk_sp<SkSurface> m_Surface;
+};
+
+class SkiaGLTarget : public RendererTarget {
+public:
+    static GrGLFuncPtr GetGLProcAddress(void* ctx, const char name[]) {
+        return glfwGetProcAddress(name);
+    }
+
+    SkiaGLTarget() {
+        if (!glfwInit()) {
+            fprintf(stderr, "Failed to initialize GLFW.\n");
+            exit(-1);
+        }
+
+        glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
+        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
+        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
+        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
+
+        m_Window = glfwCreateWindow(256, 256, "Rive", nullptr, nullptr);
+        if (!m_Window) {
+            glfwTerminate();
+            fprintf(stderr, "Failed to create GLFW m_Window.\n");
+            exit(-1);
+        }
+
+        glfwMakeContextCurrent(m_Window);
+
+        m_GrContext =
+            GrDirectContext::MakeGL(GrGLMakeAssembledInterface(nullptr, GetGLProcAddress));
+    }
+
+    ~SkiaGLTarget() override {
+#if 0
+        // FIXME: This hangs on my Windows system ¯\_(ツ)_/¯
+        glfwPollEvents();
+        glfwDestroyWindow(m_Window);
+        glfwTerminate();
+#endif
+    }
+
+    std::unique_ptr<rive::Renderer> reset(int width, int height) override {
+        if (width != m_Width || height != m_Height) {
+            glfwSetWindowSize(m_Window, width, height);
+        }
+
+        glViewport(0, 0, width, height);
+
+        GrBackendRenderTarget backendRT(
+            width, height, 1 /*samples*/, 0 /*stencilBits*/, {0 /*fbo 0*/, GL_RGBA8});
+
+        SkSurfaceProps surfProps(0, kUnknown_SkPixelGeometry);
+
+        m_Surface = SkSurface::MakeFromBackendRenderTarget(m_GrContext.get(),
+                                                           backendRT,
+                                                           kBottomLeft_GrSurfaceOrigin,
+                                                           kRGBA_8888_SkColorType,
+                                                           nullptr,
+                                                           &surfProps);
+        m_Width = width;
+        m_Height = height;
+
+        return std::make_unique<rive::SkiaRenderer>(m_Surface->getCanvas());
+    }
+
+    void flush() const override { m_Surface->getCanvas()->flush(); }
+
+    void dumpToPNG(const std::string& filepath) const override {
+        flush();
+        std::vector<uint8_t> pixels(m_Height * m_Width * 4);
+        glReadPixels(0, 0, m_Width, m_Height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
+        glfwSwapBuffers(m_Window);
+        WritePNGFile(pixels.data(), m_Width, m_Height, true, filepath.c_str());
+    }
+
+private:
+    GLFWwindow* m_Window = nullptr;
+    sk_sp<GrDirectContext> m_GrContext;
+    sk_sp<SkSurface> m_Surface;
+    int m_Width = 0;
+    int m_Height = 0;
+};
+
+static std::unique_ptr<RendererTarget> s_RendererTarget;
+
+void RendererTarget::Init(Type type) {
+    assert(!s_RendererTarget);
+    switch (type) {
+        case Type::skia_cpu:
+            s_RendererTarget = std::make_unique<SkiaRasterTarget>();
+            break;
+        case Type::skia_gl:
+            s_RendererTarget = std::make_unique<SkiaGLTarget>();
+            break;
+    }
+}
+
+RendererTarget* RendererTarget::Get() {
+    assert(s_RendererTarget); // Call Init() first!
+    return s_RendererTarget.get();
+}
+
+#endif
diff --git a/skia/utils/renderer_target.hpp b/skia/utils/renderer_target.hpp
new file mode 100644
index 0000000..542b636
--- /dev/null
+++ b/skia/utils/renderer_target.hpp
@@ -0,0 +1,34 @@
+#ifndef RENDERER_TARGET_HPP
+#define RENDERER_TARGET_HPP
+
+#include <memory>
+#include <string>
+
+namespace rive {
+    class Renderer;
+};
+
+// Wraps a factory for rive::Renderer and a singleton target for it to render into (GL window, HTML
+// canvas, software buffer, etc.):
+//
+//   RendererTarget::Init(type);
+//   renderer = RendererTarget::Get()->reset(width, height);
+//   ...
+//
+class RendererTarget {
+public:
+    enum class Type {
+        skia_cpu,
+        skia_gl
+    };
+
+    static void Init(Type);
+    static RendererTarget* Get();
+
+    virtual std::unique_ptr<rive::Renderer> reset(int width, int height) = 0;
+    virtual void flush() const = 0;
+    virtual void dumpToPNG(const std::string& filepath) const = 0;
+    virtual ~RendererTarget() {}
+};
+
+#endif
diff --git a/skia/utils/write_png_file.cpp b/skia/utils/write_png_file.cpp
new file mode 100644
index 0000000..3dd3135
--- /dev/null
+++ b/skia/utils/write_png_file.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#ifdef TESTING
+
+// Don't compile this file as part of the "tests" project.
+
+#else
+
+#include "write_png_file.hpp"
+
+#include "skia/third_party/externals/libpng/png.h"
+#include <stdio.h>
+#include <vector>
+
+void WritePNGFile(uint8_t* pixels, int width, int height, bool flipY, const char* file_name) {
+    FILE* fp = fopen(file_name, "wb");
+    if (!fp) {
+        fprintf(stderr, "WritePNGFile: File %s could not be opened for writing", file_name);
+        return;
+    }
+
+    auto png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+    if (!png) {
+        fprintf(stderr, "WritePNGFile: png_create_write_struct failed");
+        return;
+    }
+
+    // Use pngcrush method 12 for better compression performance.
+    // https://stackoverflow.com/questions/22167397/improve-performance-with-libpng
+    png_set_compression_level(png, 2);
+    png_set_compression_strategy(png, 2);
+    png_set_filter(png, 0, PNG_FILTER_SUB);
+
+    auto info = png_create_info_struct(png);
+    if (!info) {
+        fprintf(stderr, "WritePNGFile: png_create_info_struct failed");
+        return;
+    }
+
+    if (setjmp(png_jmpbuf(png))) {
+        fprintf(stderr, "WritePNGFile: Error during init_io");
+        return;
+    }
+
+    png_init_io(png, fp);
+
+    // Write header.
+    if (setjmp(png_jmpbuf(png))) {
+        fprintf(stderr, "WritePNGFile: Error during writing header");
+        return;
+    }
+
+    png_set_IHDR(png,
+                 info,
+                 width,
+                 height,
+                 8,
+                 PNG_COLOR_TYPE_RGB_ALPHA,
+                 PNG_INTERLACE_NONE,
+                 PNG_COMPRESSION_TYPE_BASE,
+                 PNG_FILTER_TYPE_BASE);
+
+    png_write_info(png, info);
+
+    // Write bytes.
+    if (setjmp(png_jmpbuf(png))) {
+        fprintf(stderr, "WritePNGFile: Error during writing bytes");
+        return;
+    }
+
+    std::vector<uint8_t*> rows(height);
+    for (int y = 0; y < height; ++y) {
+        rows[y] = pixels + (flipY ? height - 1 - y : y) * width * 4;
+    }
+    png_write_image(png, rows.data());
+
+    // End write.
+    if (setjmp(png_jmpbuf(png))) {
+        fprintf(stderr, "WritePNGFile: Error during end of write");
+        return;
+    }
+
+    png_write_end(png, NULL);
+
+    fclose(fp);
+}
+
+#endif
diff --git a/skia/utils/write_png_file.hpp b/skia/utils/write_png_file.hpp
new file mode 100644
index 0000000..4d6e60c
--- /dev/null
+++ b/skia/utils/write_png_file.hpp
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#ifndef WRITE_PNG_FILE_HPP
+#define WRITE_PNG_FILE_HPP
+
+#include <stdint.h>
+
+void WritePNGFile(uint8_t* pixels, int width, int height, bool flipY, const char* file_name);
+
+#endif