Files
rive-ios/Source/Renderer/RenderContextManager.mm
csmartdalton 98241a4d9d Remove Skia from iOS runtime
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>
2024-07-24 18:03:14 +00:00

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