Skip to content

Commit

Permalink
Add a CG render context to the iOS runtime
Browse files Browse the repository at this point in the history
Diffs=
42f393436 Add a CG render context to the iOS runtime (#5998)

Co-authored-by: Chris Dalton <[email protected]>
  • Loading branch information
csmartdalton and csmartdalton committed Sep 15, 2023
1 parent b1cba70 commit a795c2f
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2c2ea44922f91b784ec16549ce1cda0346438fd0
42f39343647f68371989628c3c24da8f2ba31171
2 changes: 1 addition & 1 deletion .rive_renderer
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4bc1e5c1d5af8327b4d5afc8f53afd6d0a7a7d45
01aa67c9996f1fa96ac8091040dfc91d432192e9
5 changes: 4 additions & 1 deletion RiveRuntime.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
83DE4CA02AAA072B00B88B72 /* PlatformCGImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83DE4C9F2AAA072B00B88B72 /* PlatformCGImage.mm */; };
83DE4CA22AAA077200B88B72 /* PlatformCGImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 83DE4CA12AAA077200B88B72 /* PlatformCGImage.h */; };
83DE4CA72AAAE72100B88B72 /* RenderContext.hh in Headers */ = {isa = PBXBuildFile; fileRef = 83DE4CA62AAAE72000B88B72 /* RenderContext.hh */; };
83DE4CBA2AB4B26D00B88B72 /* AutoCF.h in Headers */ = {isa = PBXBuildFile; fileRef = 83DE4CB92AB4B26D00B88B72 /* AutoCF.h */; };
C34609FC27FF9114002DBCB7 /* RiveFile+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34609FB27FF9114002DBCB7 /* RiveFile+Extensions.swift */; };
C3468E5827EB9887008652FD /* RiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3468E5727EB9887008652FD /* RiveView.swift */; };
C3468E5A27ECC7C6008652FD /* RiveViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3468E5927ECC7C6008652FD /* RiveViewModel.swift */; };
Expand Down Expand Up @@ -130,6 +131,7 @@
83DE4C9F2AAA072B00B88B72 /* PlatformCGImage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformCGImage.mm; sourceTree = "<group>"; };
83DE4CA12AAA077200B88B72 /* PlatformCGImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlatformCGImage.h; sourceTree = "<group>"; };
83DE4CA62AAAE72000B88B72 /* RenderContext.hh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RenderContext.hh; sourceTree = "<group>"; };
83DE4CB92AB4B26D00B88B72 /* AutoCF.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AutoCF.h; sourceTree = "<group>"; };
C34609FB27FF9114002DBCB7 /* RiveFile+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveFile+Extensions.swift"; sourceTree = "<group>"; };
C3468E5727EB9887008652FD /* RiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveView.swift; sourceTree = "<group>"; };
C3468E5927ECC7C6008652FD /* RiveViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -178,6 +180,7 @@
046FB801264EB632000129B1 /* include */ = {
isa = PBXGroup;
children = (
83DE4CB92AB4B26D00B88B72 /* AutoCF.h */,
83DE4CA12AAA077200B88B72 /* PlatformCGImage.h */,
C9C741F224FC510200EF9516 /* Rive.h */,
046FB7E7264EAA5F000129B1 /* RiveFile.h */,
Expand Down Expand Up @@ -248,7 +251,6 @@
E57798A52A72C9C500FF25C3 /* RiveTextValueRun.mm */,
04BE5431264D243D00427B39 /* LayerState.mm */,
83DE4C902AA8DD7B00B88B72 /* RenderContextManager.mm */,
274175FC286DB9CE000A60D1 /* cg_skia_factory.cpp */,
E5964A972A9697B600140479 /* RiveEvent.mm */,
);
path = Renderer;
Expand Down Expand Up @@ -321,6 +323,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
83DE4CBA2AB4B26D00B88B72 /* AutoCF.h in Headers */,
046FB7F7264EAA60000129B1 /* RiveFile.h in Headers */,
046FB7FC264EAA61000129B1 /* RiveArtboard.h in Headers */,
2A7079352726277C00C035A1 /* rive_renderer_view.hh in Headers */,
Expand Down
26 changes: 1 addition & 25 deletions Source/Renderer/PlatformCGImage.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,9 @@
*/

#import <PlatformCGImage.h>
#import <AutoCF.h>
#include "rive/core/type_conversions.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

// Helper that remembers to call CFRelease when an object goes out of scope.
template <typename T> class AutoCF
{
T m_Obj;

public:
AutoCF(T obj) : m_Obj(obj) {}
~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; }
};

bool PlatformCGImageDecode(const uint8_t* encodedBytes,
size_t encodedSizeInBytes,
PlatformCGImage* platformImage)
Expand Down
160 changes: 156 additions & 4 deletions Source/Renderer/RenderContextManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#import <RenderContextManager.h>
#import <RenderContext.hh>

#import <AutoCF.h>
#import <PlatformCGImage.h>

@implementation RenderContext
Expand Down Expand Up @@ -32,6 +33,8 @@ - (void)endFrame
#include "include/gpu/mtl/GrMtlBackendContext.h"
#include "skia_renderer.hpp"
#include "skia_factory.hpp"
#include "cg_factory.hpp"
#include "cg_renderer.hpp"

@interface SkiaContext : RenderContext
- (rive::Factory*)factory;
Expand Down Expand Up @@ -95,8 +98,8 @@ - (instancetype)init
return pixels;
}
};
static CGSkiaFactory gFactory;
return &gFactory;
static CGSkiaFactory factory;
return &factory;
}

static sk_sp<SkSurface> mtk_view_to_sk_surface(MTKView* mtkView, GrDirectContext* grContext)
Expand Down Expand Up @@ -250,6 +253,16 @@ - (void)dealloc
return nullptr;
}

switch (view.colorPixelFormat)
{
case MTLPixelFormatBGRA8Unorm:
case MTLPixelFormatRGBA8Unorm:
break;
default:
NSLog(@"error: unsupported colorPixelFormat on MTKView");
return nullptr;
}

if (_renderTarget == nullptr || _renderTarget->width() != view.drawableSize.width ||
_renderTarget->height() != view.drawableSize.height)
{
Expand All @@ -274,12 +287,130 @@ - (void)endFrame

#endif // !defined(RIVE_NO_PLS)

@interface CGRendererContext : RenderContext
- (rive::Renderer*)beginFrame:(MTKView*)view;
- (void)endFrame;
@end

constexpr static int kBufferRingSize = 3;

@implementation CGRendererContext
{
id<MTLTexture> _renderTargetTexture;
id<MTLBuffer> _buffers[kBufferRingSize];
int _currentBufferIdx;
AutoCF<CGContextRef> _cgContext;
std::unique_ptr<rive::CGRenderer> _renderer;
}

- (instancetype)init
{
self = [super init];

_renderTargetTexture = nil;
for (int i = 0; i < kBufferRingSize; ++i)
{
_buffers[i] = nil;
}
_currentBufferIdx = -1;

self.metalDevice = MTLCreateSystemDefaultDevice();
if (!self.metalDevice)
{
NSLog(@"Metal is not supported on this device");
return nil;
}
self.metalQueue = [self.metalDevice newCommandQueue];
self.depthStencilPixelFormat = MTLPixelFormatInvalid;
self.framebufferOnly = NO;
return self;
}

- (rive::Factory*)factory
{
static rive::CGFactory factory;
return &factory;
}

- (rive::Renderer*)beginFrame:(MTKView*)view
{
uint32_t cgBitmapInfo;
switch (view.colorPixelFormat)
{
case MTLPixelFormatBGRA8Unorm:
cgBitmapInfo = kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst;
break;
case MTLPixelFormatRGBA8Unorm:
cgBitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
break;
default:
NSLog(@"error: unsupported colorPixelFormat on MTKView");
return nullptr;
}

id<CAMetalDrawable> surface = view.currentDrawable;
_renderTargetTexture = surface.texture;
if (!_renderTargetTexture)
{
NSLog(@"error: no surface texture on MTKView");
return nullptr;
}

_currentBufferIdx = (_currentBufferIdx + 1) % kBufferRingSize;
size_t bufferSize = _renderTargetTexture.height * _renderTargetTexture.width * 4;
if (_buffers[_currentBufferIdx] == nil ||
_buffers[_currentBufferIdx].allocatedSize != bufferSize)
{
_buffers[_currentBufferIdx] =
[self.metalDevice newBufferWithLength:bufferSize options:MTLResourceStorageModeShared];
}
AutoCF<CGColorSpaceRef> colorSpace = CGColorSpaceCreateDeviceRGB();
_cgContext.adopt(CGBitmapContextCreate(_buffers[_currentBufferIdx].contents,
_renderTargetTexture.width,
_renderTargetTexture.height,
8,
_renderTargetTexture.width * 4,
colorSpace,
cgBitmapInfo));

_renderer = std::make_unique<rive::CGRenderer>(
_cgContext, _renderTargetTexture.width, _renderTargetTexture.height);
return _renderer.get();
}

- (void)endFrame
{
if (_cgContext != nil)
{
id<MTLCommandBuffer> commandBuffer = [self.metalQueue commandBuffer];
id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
[blitEncoder copyFromBuffer:_buffers[_currentBufferIdx]
sourceOffset:0
sourceBytesPerRow:_renderTargetTexture.width * 4
sourceBytesPerImage:_renderTargetTexture.height * _renderTargetTexture.width * 4
sourceSize:MTLSizeMake(
_renderTargetTexture.width, _renderTargetTexture.height, 1)
toTexture:_renderTargetTexture
destinationSlice:0
destinationLevel:0
destinationOrigin:MTLOriginMake(0, 0, 0)];
[blitEncoder endEncoding];
[commandBuffer commit];
}
_renderTargetTexture = nil;
_renderer = nullptr;
_cgContext.adopt(nullptr);
}

@end

@implementation RenderContextManager
{
__weak SkiaContext* _skiaContextWeakPtr;
#if !defined(RIVE_NO_PLS)
__weak RiveRendererContext* _riveRendererContextWeakPtr;
#endif
__weak CGRendererContext* _cgContextWeakPtr;
}

// The context manager is a singleton.
Expand All @@ -301,8 +432,15 @@ - (instancetype)init

- (RenderContext*)getDefaultContext
{
return self.defaultRenderer == RendererType::skiaRenderer ? [self getSkiaContext]
: [self getRiveRendererContext];
switch (self.defaultRenderer)
{
case RendererType::skiaRenderer:
return [self getSkiaContext];
case RendererType::riveRenderer:
return [self getRiveRendererContext];
case RendererType::cgRenderer:
return [self getCGRendererContext];
}
}

- (RenderContext*)getSkiaContext
Expand Down Expand Up @@ -341,4 +479,18 @@ - (RenderContext*)getRiveRendererContext
#endif
}

- (RenderContext*)getCGRendererContext
{
// Convert our weak reference to strong before trying to work with it. A weak pointer is liable
// to be released out from under us at any moment.
// https://stackoverflow.com/questions/15674320/understanding-weak-reference
CGRendererContext* strongPtr = _cgContextWeakPtr;
if (strongPtr == nil)
{
strongPtr = [[CGRendererContext alloc] init];
_cgContextWeakPtr = strongPtr;
}
return strongPtr;
}

@end
39 changes: 39 additions & 0 deletions Source/Renderer/include/AutoCF.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2023 Rive
*/

#pragma once

#include <TargetConditionals.h>

#if TARGET_OS_IPHONE
#include <CoreGraphics/CoreGraphics.h>
#include <ImageIO/ImageIO.h>
#elif TARGET_OS_MAC
#include <ApplicationServices/ApplicationServices.h>
#endif

// Helper that remembers to call CFRelease when an object goes out of scope.
template <typename T> class AutoCF
{
public:
AutoCF() = default;
AutoCF(T obj) : m_Obj(obj) {}
AutoCF(const AutoCF&) = delete;
AutoCF& operator=(const AutoCF&) = delete;
~AutoCF() { adopt(nullptr); }

void adopt(T 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; }

private:
T m_Obj = nullptr;
};
3 changes: 2 additions & 1 deletion Source/Renderer/include/RenderContextManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

#import <Foundation/Foundation.h>

typedef NS_ENUM(NSInteger, RendererType) { skiaRenderer, riveRenderer };
typedef NS_ENUM(NSInteger, RendererType) { skiaRenderer, riveRenderer, cgRenderer };

@class RenderContext;

Expand All @@ -19,4 +19,5 @@ typedef NS_ENUM(NSInteger, RendererType) { skiaRenderer, riveRenderer };
- (RenderContext*)getDefaultContext;
- (RenderContext*)getSkiaContext;
- (RenderContext*)getRiveRendererContext;
- (RenderContext*)getCGRendererContext;
@end
11 changes: 7 additions & 4 deletions Source/Renderer/rive_renderer_view.mm
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,14 @@ - (void)drawRect:(CGRect)rect
}

_renderer = [_renderContext beginFrame:self];
_renderer->save();
[self drawRive:rect size:self.drawableSize];
_renderer->restore();
if (_renderer != nil)
{
_renderer->save();
[self drawRive:rect size:self.drawableSize];
_renderer->restore();
}
[_renderContext endFrame];
_renderer = nullptr;
_renderer = nil;

id<MTLCommandBuffer> commandBuffer = [_renderContext.metalQueue commandBuffer];
[commandBuffer presentDrawable:[self currentDrawable]];
Expand Down

0 comments on commit a795c2f

Please sign in to comment.