mirror of
https://github.com/rive-app/rive-ios.git
synced 2026-01-18 17:11:28 +01:00
The Rive renderer is stable and ready for production now. This cuts the runtime size from 7.1 MB to 2.3. Diffs= 32f7a05eb Remove Skia from iOS runtime (#6072) Co-authored-by: Chris Dalton <99840794+csmartdalton@users.noreply.github.com>
378 lines
11 KiB
Plaintext
378 lines
11 KiB
Plaintext
/*
|
|
* Copyright 2023 Rive
|
|
*/
|
|
|
|
#import <RenderContextManager.h>
|
|
#import <RenderContext.h>
|
|
#import <Rive.h>
|
|
#import <RivePrivateHeaders.h>
|
|
#import <RiveFactory.h>
|
|
|
|
#import <PlatformCGImage.h>
|
|
|
|
#include "utils/auto_cf.hpp"
|
|
#include "cg_factory.hpp"
|
|
#include "cg_renderer.hpp"
|
|
#include "rive/pls/pls.hpp"
|
|
|
|
@implementation RenderContext
|
|
|
|
- (rive::Factory*)factory
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
- (rive::Renderer*)beginFrame:(MTKView*)view
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
- (void)endFrame:(MTKView*)view withCompletion:(_Nullable MTLCommandBufferHandler)completionHandler;
|
|
{}
|
|
|
|
@end
|
|
|
|
#include "rive/pls/metal/pls_render_context_metal_impl.h"
|
|
#include "rive/pls/pls_image.hpp"
|
|
#include "rive/pls/pls_renderer.hpp"
|
|
|
|
@interface RiveRendererContext : RenderContext
|
|
- (rive::Renderer*)beginFrame:(MTKView*)view;
|
|
@end
|
|
|
|
@implementation RiveRendererContext
|
|
{
|
|
rive::pls::PLSRenderContext* _plsContext;
|
|
std::unique_ptr<rive::pls::PLSRenderer> _renderer;
|
|
rive::rcp<rive::pls::PLSRenderTargetMetal> _renderTarget;
|
|
}
|
|
|
|
static std::unique_ptr<rive::pls::PLSRenderContext> make_pls_context_native(id<MTLDevice> gpu)
|
|
{
|
|
if (![gpu supportsFamily:MTLGPUFamilyApple1])
|
|
{
|
|
NSLog(@"error: GPU is not Apple family");
|
|
return nullptr;
|
|
}
|
|
class PLSRenderContextNativeImpl : public rive::pls::PLSRenderContextMetalImpl
|
|
{
|
|
public:
|
|
PLSRenderContextNativeImpl(id<MTLDevice> gpu) :
|
|
PLSRenderContextMetalImpl(gpu, ContextOptions())
|
|
{}
|
|
|
|
protected:
|
|
rive::rcp<rive::pls::PLSTexture> decodeImageTexture(
|
|
rive::Span<const uint8_t> encodedBytes) override
|
|
{
|
|
PlatformCGImage image;
|
|
if (!PlatformCGImageDecode(encodedBytes.data(), encodedBytes.size(), &image))
|
|
{
|
|
return nullptr;
|
|
}
|
|
// CG only supports premultiplied alpha. Unmultiply now.
|
|
size_t imageSizeInBytes = image.height * image.width * 4;
|
|
for (size_t i = 0; i < imageSizeInBytes; i += 4)
|
|
{
|
|
auto rgba = rive::simd::load<uint8_t, 4>(&image.pixels[i]);
|
|
if (rgba.a != 0)
|
|
{
|
|
rgba = rgba * 255 / rgba.a;
|
|
}
|
|
}
|
|
uint32_t mipLevelCount = rive::math::msb(image.height | image.width);
|
|
return makeImageTexture(image.width, image.height, mipLevelCount, image.pixels.data());
|
|
}
|
|
};
|
|
auto plsContextImpl =
|
|
std::unique_ptr<PLSRenderContextNativeImpl>(new PLSRenderContextNativeImpl(gpu));
|
|
return std::make_unique<rive::pls::PLSRenderContext>(std::move(plsContextImpl));
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
// Make a single static PLSRenderContext, since it is also the factory and any objects it
|
|
// creates may outlive this 'RiveContext' instance.
|
|
static id<MTLDevice> s_plsGPU = MTLCreateSystemDefaultDevice();
|
|
static std::unique_ptr<rive::pls::PLSRenderContext> s_plsContext =
|
|
make_pls_context_native(s_plsGPU);
|
|
|
|
self = [super init];
|
|
self.metalDevice = s_plsGPU;
|
|
self.metalQueue = [s_plsGPU newCommandQueue];
|
|
self.depthStencilPixelFormat = MTLPixelFormatInvalid;
|
|
self.framebufferOnly = YES;
|
|
_plsContext = s_plsContext.get();
|
|
_renderer = std::make_unique<rive::pls::PLSRenderer>(_plsContext);
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
// Once nobody is referencing a RiveContext anymore, release the global PLSRenderContext's GPU
|
|
// resource.
|
|
_plsContext->releaseResources();
|
|
}
|
|
|
|
- (rive::Factory*)factory
|
|
{
|
|
return _plsContext;
|
|
}
|
|
|
|
- (rive::Renderer*)beginFrame:(MTKView*)view
|
|
{
|
|
id<CAMetalDrawable> surface = view.currentDrawable;
|
|
if (!surface.texture)
|
|
{
|
|
NSLog(@"error: no surface texture on MTKView");
|
|
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)
|
|
{
|
|
_renderTarget =
|
|
_plsContext->static_impl_cast<rive::pls::PLSRenderContextMetalImpl>()->makeRenderTarget(
|
|
view.colorPixelFormat, view.drawableSize.width, view.drawableSize.height);
|
|
}
|
|
_renderTarget->setTargetTexture(surface.texture);
|
|
|
|
_plsContext->beginFrame({
|
|
.renderTargetWidth = _renderTarget->width(),
|
|
.renderTargetHeight = _renderTarget->height(),
|
|
.loadAction = rive::pls::LoadAction::clear,
|
|
.clearColor = 0,
|
|
});
|
|
return _renderer.get();
|
|
}
|
|
|
|
- (void)endFrame:(MTKView*)view withCompletion:(_Nullable MTLCommandBufferHandler)completionHandler;
|
|
{
|
|
id<MTLCommandBuffer> flushCommandBuffer = [self.metalQueue commandBuffer];
|
|
_plsContext->flush({
|
|
.renderTarget = _renderTarget.get(),
|
|
.externalCommandBuffer = (__bridge void*)flushCommandBuffer,
|
|
});
|
|
|
|
[flushCommandBuffer presentDrawable:view.currentDrawable];
|
|
if (completionHandler)
|
|
{
|
|
[flushCommandBuffer addCompletedHandler:completionHandler];
|
|
}
|
|
[flushCommandBuffer commit];
|
|
}
|
|
|
|
@end
|
|
|
|
@interface CGRendererContext : RenderContext
|
|
- (rive::Renderer*)beginFrame:(MTKView*)view;
|
|
@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 = AutoCF(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:(MTKView*)view withCompletion:(_Nullable MTLCommandBufferHandler)completionHandler;
|
|
{
|
|
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 presentDrawable:view.currentDrawable];
|
|
if (completionHandler)
|
|
{
|
|
[commandBuffer addCompletedHandler:completionHandler];
|
|
}
|
|
[commandBuffer commit];
|
|
}
|
|
_renderTargetTexture = nil;
|
|
_renderer = nullptr;
|
|
_cgContext = nullptr;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RenderContextManager
|
|
{
|
|
__weak RiveRendererContext* _riveRendererContextWeakPtr;
|
|
__weak CGRendererContext* _cgContextWeakPtr;
|
|
}
|
|
|
|
// The context manager is a singleton.
|
|
+ (RenderContextManager*)shared
|
|
{
|
|
static RenderContextManager* single = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
single = [[self alloc] init];
|
|
});
|
|
return single;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
self.defaultRenderer = RendererType::riveRenderer;
|
|
return self;
|
|
}
|
|
|
|
- (RenderContext*)getDefaultContext
|
|
{
|
|
switch (self.defaultRenderer)
|
|
{
|
|
case RendererType::riveRenderer:
|
|
return [self getRiveRendererContext];
|
|
case RendererType::cgRenderer:
|
|
return [self getCGRendererContext];
|
|
}
|
|
RIVE_UNREACHABLE();
|
|
}
|
|
|
|
- (RenderContext*)getRiveRendererContext
|
|
{
|
|
// 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
|
|
RiveRendererContext* strongPtr = _riveRendererContextWeakPtr;
|
|
if (strongPtr == nil)
|
|
{
|
|
strongPtr = [[RiveRendererContext alloc] init];
|
|
_riveRendererContextWeakPtr = strongPtr;
|
|
}
|
|
return strongPtr;
|
|
}
|
|
|
|
- (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;
|
|
}
|
|
|
|
- (RiveFactory*)getDefaultFactory
|
|
{
|
|
return [[RiveFactory alloc] initWithFactory:[[self getDefaultContext] factory]];
|
|
}
|
|
|
|
- (RiveFactory*)getRiveRendererFactory
|
|
{
|
|
return [[RiveFactory alloc] initWithFactory:[[self getRiveRendererContext] factory]];
|
|
}
|
|
|
|
- (RiveFactory*)getCGFactory
|
|
{
|
|
return [[RiveFactory alloc] initWithFactory:[[self getCGRendererContext] factory]];
|
|
}
|
|
|
|
@end
|