Files
rive-ios/Source/Renderer/RenderContextManager.mm
luigi-rosso 27de5abf4b Ios out of band
few bits to sort out
- [x] make our mix of simulator/emulator consistent, settling on emulator
- [x] passing the factory in works great for just in time asset decoding, but its not amazing when you want to decode ahead of time.
- [x] couple of places left to pass this function signature through. (Question) is there a neater way to get this done, feels a bit like we are going back to parameter explosion a bit?
- [x] should do a few examples, i think the complexity grows quite a bit in this one as you add caching, or callbacks
- [x] should get the cached images/fonts to draw on init as well, either warming up cache, or jitting
- [x] examples loading assets from the bundle (also there seem to be actual asset things too? should we use those?!)
- [x] add test
- [x] re-add "preview" project & rev the preview project once this has been deployed. (do this after new ios deploy)
- [x] fix up race condition (see comment)

https://github.com/rive-app/rive/assets/1216025/2c14330f-e8a4-481b-bc27-4807cabe3b82

(simple example, both swift ui and standard)

![CleanShot 2023-11-20 at 16 54 59](https://github.com/rive-app/rive/assets/1216025/a71e207c-30ad-44dd-9e4b-ad7431b22186)

Diffs=
fabb7f97f Ios out of band (#6232)

Co-authored-by: Gordon Hayes <pggordonhayes@gmail.com>
Co-authored-by: Maxwell Talbot <talbot.maxwell@gmail.com>
2023-12-05 21:23:08 +00:00

540 lines
16 KiB
Plaintext

/*
* Copyright 2023 Rive
*/
#import <RenderContextManager.h>
#import <RenderContext.h>
#import <PlatformCGImage.h>
#include "utils/auto_cf.hpp"
@implementation RenderContext
- (rive::Factory*)factory
{
return nil;
}
- (rive::Renderer*)beginFrame:(MTKView*)view
{
return nil;
}
- (void)endFrame
{}
@end
// skia throws out a bunch of documentation warnings for us
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdocumentation"
#include "include/core/SkCanvas.h"
#include "include/core/SkSurface.h"
#include "include/core/SkSurfaceProps.h"
#include "include/gpu/GrBackendSurface.h"
#include "include/gpu/GrDirectContext.h"
#include "include/gpu/mtl/GrMtlBackendContext.h"
#include "skia_renderer.hpp"
#include "skia_factory.hpp"
#pragma clang diagnostic pop
#include "cg_factory.hpp"
#include "cg_renderer.hpp"
#include "Rive.h"
#include "RivePrivateHeaders.h"
@interface SkiaContext : RenderContext
- (rive::Factory*)factory;
- (rive::Renderer*)beginFrame:(MTKView*)view;
- (void)endFrame;
@end
@implementation SkiaContext
{
sk_sp<GrDirectContext> _graphicsContext;
sk_sp<SkSurface> _sksurface;
std::unique_ptr<rive::SkiaRenderer> _renderer;
}
- (instancetype)init
{
self = [super init];
self.metalDevice = MTLCreateSystemDefaultDevice();
if (!self.metalDevice)
{
NSLog(@"Metal is not supported on this device");
return nil;
}
self.metalQueue = [self.metalDevice newCommandQueue];
self.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
self.framebufferOnly = NO;
GrMtlBackendContext metalBackendContext;
metalBackendContext.fDevice = sk_ret_cfp((__bridge const void*)self.metalDevice);
metalBackendContext.fQueue = sk_ret_cfp((__bridge const void*)self.metalQueue);
_graphicsContext = GrDirectContext::MakeMetal(metalBackendContext, GrContextOptions());
if (!_graphicsContext)
{
NSLog(@"GrDirectContext::MakeMetal failed");
return nil;
}
return self;
}
- (rive::Factory*)factory
{
struct CGSkiaFactory : public rive::SkiaFactory
{
std::vector<uint8_t> platformDecode(rive::Span<const uint8_t> span,
rive::SkiaFactory::ImageInfo* info) override
{
std::vector<uint8_t> pixels;
PlatformCGImage image;
if (PlatformCGImageDecode(span.data(), span.size(), &image))
{
info->alphaType = image.opaque ? AlphaType::opaque : AlphaType::premul;
info->colorType = ColorType::rgba;
info->width = image.width;
info->height = image.height;
info->rowBytes = image.width * 4;
pixels = std::move(image.pixels);
}
return pixels;
}
};
static CGSkiaFactory factory;
return &factory;
}
static sk_sp<SkSurface> mtk_view_to_sk_surface(MTKView* mtkView, GrDirectContext* grContext)
{
if (!grContext || MTLPixelFormatDepth32Float_Stencil8 != [mtkView depthStencilPixelFormat] ||
MTLPixelFormatBGRA8Unorm != [mtkView colorPixelFormat])
{
return nullptr;
}
const SkColorType colorType = kBGRA_8888_SkColorType;
sk_sp<SkColorSpace> colorSpace = nullptr;
const GrSurfaceOrigin origin = kTopLeft_GrSurfaceOrigin;
const SkSurfaceProps surfaceProps(SkSurfaceProps::kUseDeviceIndependentFonts_Flag,
SkPixelGeometry::kUnknown_SkPixelGeometry);
int sampleCount = (int)[mtkView sampleCount];
return SkSurface::MakeFromMTKView(grContext,
(__bridge GrMTLHandle)mtkView,
origin,
sampleCount,
colorType,
colorSpace,
&surfaceProps);
}
- (rive::Renderer*)beginFrame:(MTKView*)view
{
_sksurface = mtk_view_to_sk_surface(view, _graphicsContext.get());
if (!_sksurface)
{
NSLog(@"error: failed to create SkSurface from MTKView.");
return nil;
}
auto canvas = _sksurface->getCanvas();
canvas->clear(SkColor((0x00000000)));
_renderer = std::make_unique<rive::SkiaRenderer>(canvas);
return _renderer.get();
}
- (void)endFrame
{
if (_sksurface != nullptr)
{
_sksurface->flushAndSubmit();
}
_sksurface = nullptr;
_renderer = nullptr;
}
@end
#include "rive/pls/pls.hpp"
#if !defined(RIVE_NO_PLS)
#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;
- (void)endFrame;
@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, id<MTLCommandQueue> queue)
{
if (![gpu supportsFamily:MTLGPUFamilyApple1])
{
NSLog(@"error: GPU is not Apple family");
return nullptr;
}
class PLSRenderContextNativeImpl : public rive::pls::PLSRenderContextMetalImpl
{
public:
PLSRenderContextNativeImpl(id<MTLDevice> gpu, id<MTLCommandQueue> queue) :
PLSRenderContextMetalImpl(gpu, queue)
{}
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, queue));
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 id<MTLCommandQueue> s_plsQueue = [s_plsGPU newCommandQueue];
static std::unique_ptr<rive::pls::PLSRenderContext> s_plsContext =
make_pls_context_native(s_plsGPU, s_plsQueue);
self = [super init];
self.metalDevice = s_plsGPU;
self.metalQueue = s_plsQueue;
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->resetGPUResources();
}
- (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);
rive::pls::PLSRenderContext::FrameDescriptor frameDescriptor;
frameDescriptor.renderTarget = _renderTarget;
_plsContext->beginFrame(std::move(frameDescriptor));
return _renderer.get();
}
- (void)endFrame
{
_plsContext->flush();
}
@end
#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 = 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
{
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 = nullptr;
}
@end
@implementation RenderContextManager
{
__weak SkiaContext* _skiaContextWeakPtr;
#if !defined(RIVE_NO_PLS)
__weak RiveRendererContext* _riveRendererContextWeakPtr;
#endif
__weak CGRendererContext* _cgContextWeakPtr;
}
bool _riveRendererSupportWarningDisplayed = false;
// 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::skiaRenderer;
return self;
}
- (RenderContext*)getDefaultContext
{
switch (self.defaultRenderer)
{
case RendererType::skiaRenderer:
return [self getSkiaContext];
case RendererType::riveRenderer:
#if TARGET_OS_SIMULATOR
if (_riveRendererSupportWarningDisplayed == false)
{
NSLog(@"warning: Rive Renderer is not supported by the simulator. Falling back on "
@"Skia "
@"within the simulator.");
_riveRendererSupportWarningDisplayed = true;
}
return [self getSkiaContext];
#else
return [self getRiveRendererContext];
#endif
case RendererType::cgRenderer:
return [self getCGRendererContext];
}
}
- (RenderContext*)getSkiaContext
{
// 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
SkiaContext* strongPtr = _skiaContextWeakPtr;
if (strongPtr == nil)
{
strongPtr = [[SkiaContext alloc] init];
_skiaContextWeakPtr = strongPtr;
}
return strongPtr;
}
- (RenderContext*)getRiveRendererContext
{
#if defined(RIVE_NO_PLS)
NSLog(@"error: build does not include Rive Renderer");
return nil;
#elif TARGET_OS_SIMULATOR
NSLog(@"error: Rive Renderer is not supported on the simulator");
return nil;
#else
// 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;
#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;
}
- (RiveFactory*)getDefaultFactory
{
return [[RiveFactory alloc] initWithFactory:[[self getDefaultContext] factory]];
}
- (RiveFactory*)getRiveRendererFactory
{
return [[RiveFactory alloc] initWithFactory:[[self getRiveRendererContext] factory]];
}
- (RiveFactory*)getSkiaFactory
{
return [[RiveFactory alloc] initWithFactory:[[self getSkiaContext] factory]];
}
- (RiveFactory*)getCGFactory
{
return [[RiveFactory alloc] initWithFactory:[[self getCGRendererContext] factory]];
}
@end