Add coregraphics renderer
CoreGraphics renderer
- new files for the new factory and renderer
- testing hack in skia_host.cpp
- extend TestingWindow to support coregraphics
- change clear() to be non-const
- this lets us use coregraphics in gms and goldens
Diffs=
0e9a756b3 Experiment with coregraphics renderer
diff --git a/.rive_head b/.rive_head
index 9b4a9c3..da794fc 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-7ae06e8b84d9d1cb3d97248db66e468d1f2b3db7
+0e9a756b373456564d442a959742b506b08adf44
diff --git a/skia/renderer/include/cg_factory.hpp b/skia/renderer/include/cg_factory.hpp
new file mode 100644
index 0000000..d8534c9
--- /dev/null
+++ b/skia/renderer/include/cg_factory.hpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#ifndef _RIVE_CG_FACTORY_HPP_
+#define _RIVE_CG_FACTORY_HPP_
+
+#include "rive/factory.hpp"
+#include <vector>
+
+namespace rive {
+
+ class CGFactory : public Factory {
+ public:
+ 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]
+ size_t count,
+ RenderTileMode,
+ const Mat2D* localMatrix = nullptr) override;
+
+ rcp<RenderShader> makeRadialGradient(float cx,
+ float cy,
+ float radius,
+ const ColorInt colors[], // [count]
+ const float stops[], // [count]
+ size_t count,
+ RenderTileMode,
+ const Mat2D* localMatrix = nullptr) override;
+
+ std::unique_ptr<RenderPath>
+ makeRenderPath(Span<const Vec2D> points, Span<const PathVerb> 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/cg_renderer.hpp b/skia/renderer/include/cg_renderer.hpp
new file mode 100644
index 0000000..e80c734
--- /dev/null
+++ b/skia/renderer/include/cg_renderer.hpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#ifndef _RIVE_CG_RENDERER_HPP_
+#define _RIVE_CG_RENDERER_HPP_
+
+#include "rive/renderer.hpp"
+
+#if defined(RIVE_BUILD_FOR_OSX)
+#include <ApplicationServices/ApplicationServices.h>
+#elif defined(RIVE_BUILD_FOR_IOS)
+#include <CoreGraphics/CoreGraphics.h>
+#include <ImageIO/ImageIO.h>
+#endif
+
+namespace rive {
+ class CGRenderer : public Renderer {
+ protected:
+ CGContextRef m_ctx;
+
+ public:
+ CGRenderer(CGContextRef ctx, int width, int height);
+ ~CGRenderer() override;
+
+ 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;
+ };
+} // namespace rive
+#endif
diff --git a/skia/renderer/include/mac_utils.hpp b/skia/renderer/include/mac_utils.hpp
index 139a1ba..05408c7 100644
--- a/skia/renderer/include/mac_utils.hpp
+++ b/skia/renderer/include/mac_utils.hpp
@@ -54,30 +54,51 @@
}
template <typename T> class AutoCF {
- T m_Obj;
+ T m_obj;
public:
- AutoCF(T obj = nullptr) : m_Obj(obj) {}
- ~AutoCF() {
- if (m_Obj)
- CFRelease(m_Obj);
+ AutoCF(T obj = nullptr) : m_obj(obj) {}
+ AutoCF(const AutoCF& other) {
+ if (other.m_obj) {
+ CFRetain(other.m_obj);
+ }
+ m_obj = other.m_obj;
}
-
- AutoCF(const AutoCF&) = delete;
- void operator=(const AutoCF&) = delete;
-
- void reset(T obj) {
- if (obj != m_Obj) {
- if (m_Obj) {
- CFRelease(m_Obj);
- }
- m_Obj = obj;
+ AutoCF(AutoCF&& other) {
+ m_obj = other.m_obj;
+ other.m_obj = nullptr;
+ }
+ ~AutoCF() {
+ if (m_obj) {
+ CFRelease(m_obj);
}
}
- operator T() const { return m_Obj; }
- operator bool() const { return m_Obj != nullptr; }
- T get() const { return m_Obj; }
+ AutoCF& operator=(const AutoCF& other) {
+ if (m_obj != other.m_obj) {
+ if (other.m_obj) {
+ CFRetain(other.m_obj);
+ }
+ if (m_obj) {
+ CFRelease(m_obj);
+ }
+ m_obj = other.m_obj;
+ }
+ return *this;
+ }
+
+ void reset(T obj) {
+ if (obj != m_obj) {
+ if (m_obj) {
+ CFRelease(m_obj);
+ }
+ m_obj = obj;
+ }
+ }
+
+ operator T() const { return m_obj; }
+ operator bool() const { return m_obj != nullptr; }
+ T get() const { return m_obj; }
};
static inline float find_float(CFDictionaryRef dict, const void* key) {
@@ -110,8 +131,9 @@
}
namespace rive {
- CGImageRef DecodeToCGImage(Span<const uint8_t>);
-}
+ AutoCF<CGImageRef> DecodeToCGImage(Span<const uint8_t>);
+ AutoCF<CGImageRef> FlipCGImageInY(AutoCF<CGImageRef>);
+} // namespace rive
#endif
#endif
diff --git a/skia/renderer/src/cg_factory.cpp b/skia/renderer/src/cg_factory.cpp
new file mode 100644
index 0000000..94c4be8
--- /dev/null
+++ b/skia/renderer/src/cg_factory.cpp
@@ -0,0 +1,476 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#include "rive/rive_types.hpp"
+
+#ifdef RIVE_BUILD_FOR_APPLE
+
+#include "cg_factory.hpp"
+#include "cg_renderer.hpp"
+#include "mac_utils.hpp"
+
+#if defined(RIVE_BUILD_FOR_OSX)
+#include <ApplicationServices/ApplicationServices.h>
+#elif defined(RIVE_BUILD_FOR_IOS)
+#include <CoreGraphics/CoreGraphics.h>
+#include <ImageIO/ImageIO.h>
+#endif
+
+#include "rive/math/vec2d.hpp"
+#include "rive/shapes/paint/color.hpp"
+
+using namespace rive;
+
+static CGAffineTransform convert(const Mat2D& m) {
+ return CGAffineTransformMake(m[0], m[1], m[2], m[3], m[4], m[5]);
+}
+
+static CGPathDrawingMode convert(FillRule rule) {
+ return (rule == FillRule::nonZero) ? CGPathDrawingMode::kCGPathFill
+ : CGPathDrawingMode::kCGPathEOFill;
+}
+
+static CGLineJoin convert(StrokeJoin j) {
+ const CGLineJoin cg[] = {
+ CGLineJoin::kCGLineJoinMiter,
+ CGLineJoin::kCGLineJoinRound,
+ CGLineJoin::kCGLineJoinBevel,
+ };
+ return cg[(unsigned)j];
+}
+
+static CGLineCap convert(StrokeCap c) {
+ const CGLineCap cg[] = {
+ CGLineCap::kCGLineCapButt,
+ CGLineCap::kCGLineCapRound,
+ CGLineCap::kCGLineCapSquare,
+ };
+ return cg[(unsigned)c];
+}
+
+// clang-format off
+static CGBlendMode convert(BlendMode mode) {
+ CGBlendMode cg = kCGBlendModeNormal;
+ switch (mode) {
+ case BlendMode::srcOver: cg = kCGBlendModeNormal; break;
+ case BlendMode::screen: cg = kCGBlendModeScreen; break;
+ case BlendMode::overlay: cg = kCGBlendModeOverlay; break;
+ case BlendMode::darken: cg = kCGBlendModeDarken; break;
+ case BlendMode::lighten: cg = kCGBlendModeLighten; break;
+ case BlendMode::colorDodge: cg = kCGBlendModeColorDodge; break;
+ case BlendMode::colorBurn: cg = kCGBlendModeColorBurn; break;
+ case BlendMode::hardLight: cg = kCGBlendModeHardLight; break;
+ case BlendMode::softLight: cg = kCGBlendModeSoftLight; break;
+ case BlendMode::difference: cg = kCGBlendModeDifference; break;
+ case BlendMode::exclusion: cg = kCGBlendModeExclusion; break;
+ case BlendMode::multiply: cg = kCGBlendModeMultiply; break;
+ case BlendMode::hue: cg = kCGBlendModeHue; break;
+ case BlendMode::saturation: cg = kCGBlendModeSaturation; break;
+ case BlendMode::color: cg = kCGBlendModeColor; break;
+ case BlendMode::luminosity: cg = kCGBlendModeLuminosity; break;
+ }
+ return cg;
+}
+// clang-format on
+
+static void convertColor(ColorInt c, CGFloat rgba[]) {
+ constexpr float kByteToUnit = 1.0f / 255;
+ rgba[0] = colorRed(c) * kByteToUnit;
+ rgba[1] = colorGreen(c) * kByteToUnit;
+ rgba[2] = colorBlue(c) * kByteToUnit;
+ rgba[3] = colorAlpha(c) * kByteToUnit;
+}
+
+class CGRenderPath : public RenderPath {
+private:
+ AutoCF<CGMutablePathRef> m_path = CGPathCreateMutable();
+ CGPathDrawingMode m_fillMode = CGPathDrawingMode::kCGPathFill;
+
+public:
+ CGRenderPath() {}
+
+ CGRenderPath(Span<const Vec2D> pts, Span<const PathVerb> vbs, FillRule rule) {
+ m_fillMode = convert(rule);
+
+ auto p = pts.data();
+ for (auto v : vbs) {
+ switch ((PathVerb)v) {
+ case PathVerb::move:
+ CGPathMoveToPoint(m_path, nullptr, p[0].x, p[0].y);
+ p += 1;
+ break;
+ case PathVerb::line:
+ CGPathAddLineToPoint(m_path, nullptr, p[0].x, p[0].y);
+ p += 1;
+ break;
+ case PathVerb::quad:
+ CGPathAddQuadCurveToPoint(m_path, nullptr, p[0].x, p[0].y, p[1].x, p[1].y);
+ p += 2;
+ break;
+ case PathVerb::cubic:
+ CGPathAddCurveToPoint(
+ m_path, nullptr, p[0].x, p[0].y, p[1].x, p[1].y, p[2].x, p[2].y);
+ p += 3;
+ break;
+ case PathVerb::close:
+ CGPathCloseSubpath(m_path);
+ break;
+ }
+ }
+ assert(p == pts.end());
+ }
+
+ CGPathRef path() const { return m_path.get(); }
+ CGPathDrawingMode drawingMode(bool isStroke) const {
+ return isStroke ? CGPathDrawingMode::kCGPathStroke : m_fillMode;
+ }
+
+ void reset() override { m_path.reset(CGPathCreateMutable()); }
+ void addRenderPath(RenderPath* path, const Mat2D& mx) override {
+ auto transform = convert(mx);
+ CGPathAddPath(m_path, &transform, ((CGRenderPath*)path)->path());
+ }
+ void fillRule(FillRule value) override {
+ m_fillMode = (value == FillRule::nonZero) ? CGPathDrawingMode::kCGPathFill
+ : CGPathDrawingMode::kCGPathEOFill;
+ }
+ void moveTo(float x, float y) override { CGPathMoveToPoint(m_path, nullptr, x, y); }
+ void lineTo(float x, float y) override { CGPathAddLineToPoint(m_path, nullptr, x, y); }
+ void cubicTo(float ox, float oy, float ix, float iy, float x, float y) override {
+ CGPathAddCurveToPoint(m_path, nullptr, ox, oy, ix, iy, x, y);
+ }
+ void close() override { CGPathCloseSubpath(m_path); }
+};
+
+class CGRenderShader : public RenderShader {
+public:
+ CGRenderShader() {}
+
+ virtual void draw(CGContextRef) {}
+};
+
+class CGRenderPaint : public RenderPaint {
+private:
+ bool m_isStroke = false;
+ CGFloat m_rgba[4] = {0, 0, 0, 1};
+ float m_width = 1;
+ CGLineJoin m_join = kCGLineJoinMiter;
+ CGLineCap m_cap = kCGLineCapButt;
+ CGBlendMode m_blend = kCGBlendModeNormal;
+ rcp<RenderShader> m_shader;
+
+public:
+ CGRenderPaint() {}
+
+ bool isStroke() const { return m_isStroke; }
+
+ CGRenderShader* shader() const { return static_cast<CGRenderShader*>(m_shader.get()); }
+
+ void apply(CGContextRef ctx) {
+ if (m_isStroke) {
+ CGContextSetRGBStrokeColor(ctx, m_rgba[0], m_rgba[1], m_rgba[2], m_rgba[3]);
+ CGContextSetLineWidth(ctx, m_width);
+ CGContextSetLineJoin(ctx, m_join);
+ CGContextSetLineCap(ctx, m_cap);
+ } else {
+ CGContextSetRGBFillColor(ctx, m_rgba[0], m_rgba[1], m_rgba[2], m_rgba[3]);
+ }
+ CGContextSetBlendMode(ctx, m_blend);
+ }
+
+ void style(RenderPaintStyle style) override {
+ m_isStroke = (style == RenderPaintStyle::stroke);
+ }
+ void color(ColorInt value) override { convertColor(value, m_rgba); }
+ void thickness(float value) override { m_width = value; }
+ void join(StrokeJoin value) override { m_join = convert(value); }
+ void cap(StrokeCap value) override { m_cap = convert(value); }
+ void blendMode(BlendMode value) override { m_blend = convert(value); }
+ void shader(rcp<RenderShader> sh) override { m_shader = std::move(sh); }
+};
+
+static CGGradientRef convert(const ColorInt colors[], const float stops[], size_t count) {
+ AutoCF space = CGColorSpaceCreateDeviceRGB();
+ std::vector<CGFloat> floats(count * 5); // colors[4] + stops[1]
+ auto c = &floats[0];
+ auto s = &floats[count * 4];
+
+ for (size_t i = 0; i < count; ++i) {
+ convertColor(colors[i], &c[i * 4]);
+ }
+ if (stops) {
+ for (size_t i = 0; i < count; ++i) {
+ s[i] = stops[i];
+ }
+ }
+ return CGGradientCreateWithColorComponents(space, c, s, count);
+}
+
+class CGRadialGradientRenderShader : public CGRenderShader {
+ AutoCF<CGGradientRef> m_grad;
+ CGPoint m_center;
+ CGFloat m_radius;
+
+public:
+ CGRadialGradientRenderShader(float cx,
+ float cy,
+ float radius,
+ const ColorInt colors[],
+ const float stops[],
+ size_t count) :
+ m_grad(convert(colors, stops, count)) {
+ m_center = CGPointMake(cx, cy);
+ m_radius = radius;
+ }
+
+ void draw(CGContextRef ctx) override {
+ CGGradientDrawingOptions options = 0;
+ CGContextDrawRadialGradient(ctx, m_grad, m_center, 0, m_center, m_radius, options);
+ }
+};
+
+class CGLinearGradientRenderShader : public CGRenderShader {
+ AutoCF<CGGradientRef> m_grad;
+ CGPoint m_start, m_end;
+
+public:
+ CGLinearGradientRenderShader(float sx,
+ float sy,
+ float ex,
+ float ey,
+ const ColorInt colors[], // [count]
+ const float stops[], // [count]
+ size_t count) :
+ m_grad(convert(colors, stops, count)) {
+ m_start = CGPointMake(sx, sy);
+ m_end = CGPointMake(ex, ey);
+ }
+
+ void draw(CGContextRef ctx) override {
+ CGGradientDrawingOptions options = 0;
+ CGContextDrawLinearGradient(ctx, m_grad, m_start, m_end, options);
+ }
+};
+
+class CGRenderImage : public RenderImage {
+public:
+ AutoCF<CGImageRef> m_image;
+
+ CGRenderImage(const Span<const uint8_t> span) : m_image(FlipCGImageInY(DecodeToCGImage(span))) {
+ if (m_image) {
+ m_Width = CGImageGetWidth(m_image.get());
+ m_Height = CGImageGetHeight(m_image.get());
+ }
+ }
+
+ rcp<RenderShader>
+ makeShader(RenderTileMode tx, RenderTileMode ty, const Mat2D* localMatrix) const override {
+ return rcp<RenderShader>(new CGRenderShader);
+ }
+
+ static CGImageRef Cast(const RenderImage* image) {
+ return reinterpret_cast<const CGRenderImage*>(image)->m_image.get();
+ }
+};
+
+// todo: move this to common place
+class DataRenderBuffer : public RenderBuffer {
+ const size_t m_elemSize;
+ std::vector<uint32_t> m_storage; // store 32bits for alignment
+
+public:
+ DataRenderBuffer(const void* src, size_t count, size_t elemSize) :
+ RenderBuffer(count), m_elemSize(elemSize) {
+ const size_t bytes = count * elemSize;
+ m_storage.resize((bytes + 3) >> 2); // round up to next 32bit count
+ memcpy(m_storage.data(), src, bytes);
+ }
+
+ const float* f32s() const {
+ assert(m_elemSize == sizeof(float));
+ return reinterpret_cast<const float*>(m_storage.data());
+ }
+
+ const uint16_t* u16s() const {
+ assert(m_elemSize == sizeof(uint16_t));
+ return reinterpret_cast<const uint16_t*>(m_storage.data());
+ }
+
+ const Vec2D* vecs() const { return reinterpret_cast<const Vec2D*>(this->f32s()); }
+
+ size_t elemSize() const { return m_elemSize; }
+
+ static const DataRenderBuffer* Cast(const RenderBuffer* buffer) {
+ return static_cast<const DataRenderBuffer*>(buffer);
+ }
+};
+
+template <typename T> rcp<RenderBuffer> make_buffer(Span<T> span) {
+ return rcp<RenderBuffer>(new DataRenderBuffer(span.data(), span.size(), sizeof(T)));
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+CGRenderer::CGRenderer(CGContextRef ctx, int width, int height) : m_ctx(ctx) {
+ CGContextSaveGState(ctx);
+ Mat2D m(1, 0, 0, -1, 0, height);
+ CGContextConcatCTM(ctx, convert(m));
+}
+
+CGRenderer::~CGRenderer() { CGContextRestoreGState(m_ctx); }
+
+void CGRenderer::save() { CGContextSaveGState(m_ctx); }
+
+void CGRenderer::restore() { CGContextRestoreGState(m_ctx); }
+
+void CGRenderer::transform(const Mat2D& m) { CGContextConcatCTM(m_ctx, convert(m)); }
+
+void CGRenderer::drawPath(RenderPath* path, RenderPaint* paint) {
+ auto cgpaint = reinterpret_cast<CGRenderPaint*>(paint);
+ auto cgpath = reinterpret_cast<CGRenderPath*>(path);
+
+ cgpaint->apply(m_ctx);
+
+ CGContextBeginPath(m_ctx);
+ CGContextAddPath(m_ctx, cgpath->path());
+ if (auto sh = cgpaint->shader()) {
+ CGContextSaveGState(m_ctx);
+ CGContextClip(m_ctx);
+ sh->draw(m_ctx);
+ CGContextRestoreGState(m_ctx);
+ } else {
+ CGContextDrawPath(m_ctx, cgpath->drawingMode(cgpaint->isStroke()));
+ }
+}
+
+void CGRenderer::clipPath(RenderPath* path) {
+ auto cgpath = reinterpret_cast<CGRenderPath*>(path);
+
+ CGContextBeginPath(m_ctx);
+ CGContextAddPath(m_ctx, cgpath->path());
+ CGContextClip(m_ctx);
+}
+
+void CGRenderer::drawImage(const RenderImage* image, BlendMode blendMode, float opacity) {
+ auto bounds = CGRectMake(0, 0, image->width(), image->height());
+
+ CGContextSaveGState(m_ctx);
+ CGContextSetAlpha(m_ctx, opacity);
+ CGContextSetBlendMode(m_ctx, convert(blendMode));
+ CGContextDrawImage(m_ctx, bounds, CGRenderImage::Cast(image));
+ CGContextRestoreGState(m_ctx);
+}
+
+static Mat2D basis_matrix(Vec2D p0, Vec2D p1, Vec2D p2) {
+ auto e0 = p1 - p0;
+ auto e1 = p2 - p0;
+ return Mat2D(e0.x, e0.y, e1.x, e1.y, p0.x, p0.y);
+}
+
+void CGRenderer::drawImageMesh(const RenderImage* image,
+ rcp<RenderBuffer> vertices,
+ rcp<RenderBuffer> uvCoords,
+ rcp<RenderBuffer> indices,
+ BlendMode blendMode,
+ float opacity) {
+ const float sx = image->width();
+ const float sy = image->height();
+ auto const bounds = CGRectMake(0, 0, sx, sy);
+
+ auto scale = [sx, sy](Vec2D v) { return Vec2D{v.x * sx, v.y * sy}; };
+
+ auto triangles = indices->count() / 3;
+ auto ndx = DataRenderBuffer::Cast(indices.get())->u16s();
+ auto pts = DataRenderBuffer::Cast(vertices.get())->vecs();
+ auto uvs = DataRenderBuffer::Cast(uvCoords.get())->vecs();
+
+ // We use the path to set the clip for each triangle. Since calling
+ // CGContextClip() resets the path, we only need to this once at
+ // the beginning.
+ CGContextBeginPath(m_ctx);
+
+ CGContextSaveGState(m_ctx);
+ CGContextSetAlpha(m_ctx, opacity);
+ CGContextSetBlendMode(m_ctx, convert(blendMode));
+ CGContextSetShouldAntialias(m_ctx, false);
+
+ for (size_t i = 0; i < triangles; ++i) {
+ const auto index0 = *ndx++;
+ const auto index1 = *ndx++;
+ const auto index2 = *ndx++;
+
+ CGContextSaveGState(m_ctx);
+
+ const auto p0 = pts[index0];
+ const auto p1 = pts[index1];
+ const auto p2 = pts[index2];
+ CGContextMoveToPoint(m_ctx, p0.x, p0.y);
+ CGContextAddLineToPoint(m_ctx, p1.x, p1.y);
+ CGContextAddLineToPoint(m_ctx, p2.x, p2.y);
+ CGContextClip(m_ctx);
+
+ const auto v0 = scale(uvs[index0]);
+ const auto v1 = scale(uvs[index1]);
+ const auto v2 = scale(uvs[index2]);
+ auto mx = basis_matrix(p0, p1, p2) * basis_matrix(v0, v1, v2).invertOrIdentity();
+ CGContextConcatCTM(m_ctx, convert(mx));
+ CGContextDrawImage(m_ctx, bounds, CGRenderImage::Cast(image));
+
+ CGContextRestoreGState(m_ctx);
+ }
+
+ CGContextRestoreGState(m_ctx); // restore opacity, antialias, etc.
+}
+
+// Factory
+
+rcp<RenderBuffer> CGFactory::makeBufferU16(Span<const uint16_t> data) { return make_buffer(data); }
+
+rcp<RenderBuffer> CGFactory::makeBufferU32(Span<const uint32_t> data) { return make_buffer(data); }
+
+rcp<RenderBuffer> CGFactory::makeBufferF32(Span<const float> data) { return make_buffer(data); }
+
+rcp<RenderShader> CGFactory::makeLinearGradient(float sx,
+ float sy,
+ float ex,
+ float ey,
+ const ColorInt colors[], // [count]
+ const float stops[], // [count]
+ size_t count,
+ RenderTileMode,
+ const Mat2D*) {
+ return rcp<RenderShader>(
+ new CGLinearGradientRenderShader(sx, sy, ex, ey, colors, stops, count));
+}
+
+rcp<RenderShader> CGFactory::makeRadialGradient(float cx,
+ float cy,
+ float radius,
+ const ColorInt colors[], // [count]
+ const float stops[], // [count]
+ size_t count,
+ RenderTileMode mode,
+ const Mat2D* localMatrix) {
+ return rcp<RenderShader>(
+ new CGRadialGradientRenderShader(cx, cy, radius, colors, stops, count));
+}
+
+std::unique_ptr<RenderPath>
+CGFactory::makeRenderPath(Span<const Vec2D> points, Span<const PathVerb> verbs, FillRule fillRule) {
+ return std::make_unique<CGRenderPath>(points, verbs, fillRule);
+}
+
+std::unique_ptr<RenderPath> CGFactory::makeEmptyRenderPath() {
+ return std::make_unique<CGRenderPath>();
+}
+
+std::unique_ptr<RenderPaint> CGFactory::makeRenderPaint() {
+ return std::make_unique<CGRenderPaint>();
+}
+
+std::unique_ptr<RenderImage> CGFactory::decodeImage(Span<const uint8_t> encoded) {
+ return std::make_unique<CGRenderImage>(encoded);
+}
+
+#endif // APPLE
diff --git a/skia/renderer/src/mac_utils.cpp b/skia/renderer/src/mac_utils.cpp
index cddcf4e..893207c 100644
--- a/skia/renderer/src/mac_utils.cpp
+++ b/skia/renderer/src/mac_utils.cpp
@@ -11,7 +11,22 @@
#include <ImageIO/CGImageSource.h>
#endif
-CGImageRef rive::DecodeToCGImage(rive::Span<const uint8_t> span) {
+AutoCF<CGImageRef> rive::FlipCGImageInY(AutoCF<CGImageRef> image) {
+ if (!image) {
+ return nullptr;
+ }
+
+ auto w = CGImageGetWidth(image);
+ auto h = CGImageGetHeight(image);
+ AutoCF space = CGColorSpaceCreateDeviceRGB();
+ auto info = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
+ AutoCF ctx = CGBitmapContextCreate(nullptr, w, h, 8, 0, space, info);
+ CGContextConcatCTM(ctx, CGAffineTransformMake(1, 0, 0, -1, 0, h));
+ CGContextDrawImage(ctx, CGRectMake(0, 0, w, h), image);
+ return CGBitmapContextCreateImage(ctx);
+}
+
+AutoCF<CGImageRef> rive::DecodeToCGImage(rive::Span<const uint8_t> span) {
AutoCF data = CFDataCreate(nullptr, span.data(), span.size());
if (!data) {
printf("CFDataCreate failed\n");
@@ -24,10 +39,9 @@
return nullptr;
}
- auto image = CGImageSourceCreateImageAtIndex(source, 0, nullptr);
+ AutoCF image = CGImageSourceCreateImageAtIndex(source, 0, nullptr);
if (!image) {
printf("CGImageSourceCreateImageAtIndex failed\n");
- return nullptr;
}
return image;
}
diff --git a/viewer/src/skia/skia_host.cpp b/viewer/src/skia/skia_host.cpp
index 6f0021c..71abe50 100644
--- a/viewer/src/skia/skia_host.cpp
+++ b/viewer/src/skia/skia_host.cpp
@@ -7,7 +7,13 @@
#ifdef RIVE_RENDERER_SKIA
+#ifdef RIVE_BUILD_FOR_APPLE
+#include "cg_skia_factory.hpp"
+static rive::CGSkiaFactory skiaFactory;
+#else
#include "skia_factory.hpp"
+static rive::SkiaFactory skiaFactory;
+#endif
#include "skia_renderer.hpp"
#include "include/core/SkSurface.h"
@@ -20,6 +26,33 @@
sk_sp<SkSurface> makeSkiaSurface(GrDirectContext* context, int width, int height);
void skiaPresentSurface(sk_sp<SkSurface> surface);
+// Experimental flag, until we complete coregraphics_host
+#define TEST_CG_RENDERER
+
+#ifdef TEST_CG_RENDERER
+#include "cg_factory.hpp"
+#include "cg_renderer.hpp"
+#include "mac_utils.hpp"
+static void render_with_cg(SkCanvas* canvas, int w, int h, ViewerContent* content, double elapsed) {
+ // cons up a CGContext
+ auto pixels = SkData::MakeUninitialized(w * h * 4);
+ auto bytes = (uint8_t*)pixels->writable_data();
+ std::fill(bytes, bytes + pixels->size(), 0);
+ AutoCF space = CGColorSpaceCreateDeviceRGB();
+ auto info = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
+ AutoCF ctx = CGBitmapContextCreate(bytes, w, h, 8, w * 4, space, info);
+
+ // Wrap it with our renderer
+ rive::CGRenderer renderer(ctx, w, h);
+ content->handleDraw(&renderer, elapsed);
+ CGContextFlush(ctx);
+
+ // Draw the pixels into the canvas
+ auto img = SkImage::MakeRasterData(SkImageInfo::MakeN32Premul(w, h), pixels, w * 4);
+ canvas->drawImage(img, 0, 0, SkSamplingOptions(SkFilterMode::kNearest), nullptr);
+}
+#endif
+
class SkiaViewerHost : public ViewerHost {
public:
sk_sp<GrDirectContext> m_context;
@@ -59,9 +92,13 @@
paint.setColor(0xFF161616);
canvas->drawPaint(paint);
- rive::SkiaRenderer skiaRenderer(canvas);
if (content) {
+#ifdef TEST_CG_RENDERER
+ render_with_cg(canvas, m_dimensions.width(), m_dimensions.height(), content, elapsed);
+#else
+ rive::SkiaRenderer skiaRenderer(canvas);
content->handleDraw(&skiaRenderer, elapsed);
+#endif
}
canvas->flush();
@@ -73,8 +110,12 @@
std::unique_ptr<ViewerHost> ViewerHost::Make() { return std::make_unique<SkiaViewerHost>(); }
rive::Factory* ViewerHost::Factory() {
- static rive::SkiaFactory skiaFactory;
+#ifdef TEST_CG_RENDERER
+ static rive::CGFactory gFactory;
+ return &gFactory;
+#else
return &skiaFactory;
+#endif
}
#endif