mirror of
https://github.com/libretro/RetroArch
synced 2025-02-20 06:40:18 +00:00
feat(Metal): font rendering
* FPS and other OSD messages render correctly * add autoreleasepool around entire render loop to avoid memory leaks
This commit is contained in:
parent
c10aab9830
commit
367c15f488
@ -23,7 +23,8 @@
|
||||
- (void)drawableSizeWillChange:(CGSize)size;
|
||||
|
||||
- (void)beginFrame;
|
||||
- (void)drawFrame;
|
||||
- (void)drawViews;
|
||||
- (void)endFrame;
|
||||
|
||||
#pragma mark - view management
|
||||
|
||||
|
@ -42,7 +42,7 @@
|
||||
|
||||
// other state
|
||||
Uniforms _uniforms;
|
||||
BOOL _begin;
|
||||
BOOL _begin, _end;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDevice:(id<MTLDevice>)device layer:(CAMetalLayer *)layer
|
||||
@ -57,6 +57,7 @@
|
||||
|
||||
_conv = [[PixelConverter alloc] initWithContext:_context];
|
||||
_begin = NO;
|
||||
_end = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
@ -137,78 +138,19 @@
|
||||
|
||||
- (void)beginFrame
|
||||
{
|
||||
assert(!_begin);
|
||||
assert(!_begin && !_end);
|
||||
_begin = YES;
|
||||
dispatch_semaphore_wait(_inflightSemaphore, DISPATCH_TIME_FOREVER);
|
||||
[_context begin];
|
||||
[self _updateUniforms];
|
||||
}
|
||||
|
||||
- (void)drawFrame
|
||||
- (void)endFrame
|
||||
{
|
||||
@autoreleasepool {
|
||||
[self _render];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_render
|
||||
{
|
||||
assert(_begin);
|
||||
_begin = NO;
|
||||
|
||||
assert(!_begin && _end);
|
||||
_end = NO;
|
||||
|
||||
id<MTLCommandBuffer> cb = _context.commandBuffer;
|
||||
cb.label = @"renderer cb";
|
||||
|
||||
for (id<View> v in _views) {
|
||||
if (!v.visible) continue;
|
||||
if ([v respondsToSelector:@selector(drawWithContext:)]) {
|
||||
[v drawWithContext:_context];
|
||||
}
|
||||
}
|
||||
|
||||
BOOL pendingDraws = NO;
|
||||
for (id<View> v in _views) {
|
||||
if (v.visible && (v.drawState & ViewDrawStateEncoder) != 0) {
|
||||
pendingDraws = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingDraws) {
|
||||
id<CAMetalDrawable> drawable = _context.nextDrawable;
|
||||
_t_rpd.colorAttachments[0].texture = drawable.texture;
|
||||
|
||||
id<MTLRenderCommandEncoder> rce = [cb renderCommandEncoderWithDescriptor:_t_rpd];
|
||||
[rce setVertexBytes:&_uniforms length:sizeof(_uniforms) atIndex:BufferIndexUniforms];
|
||||
|
||||
for (id<View> v in _views) {
|
||||
if (!v.visible ||
|
||||
![v respondsToSelector:@selector(drawWithEncoder:)] ||
|
||||
(v.drawState & ViewDrawStateEncoder) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// set view state
|
||||
if (v.format == RPixelFormatBGRX8Unorm || v.format == RPixelFormatB5G6R5Unorm) {
|
||||
[rce setRenderPipelineState:_t_pipelineStateNoAlpha];
|
||||
}
|
||||
else {
|
||||
[rce setRenderPipelineState:_t_pipelineState];
|
||||
}
|
||||
|
||||
if (v.filter == RTextureFilterNearest) {
|
||||
[rce setFragmentSamplerState:_samplerStateNearest atIndex:SamplerIndexDraw];
|
||||
}
|
||||
else {
|
||||
[rce setFragmentSamplerState:_samplerStateLinear atIndex:SamplerIndexDraw];
|
||||
}
|
||||
|
||||
[v drawWithEncoder:rce];
|
||||
}
|
||||
|
||||
[rce endEncoding];
|
||||
}
|
||||
|
||||
__block dispatch_semaphore_t inflight = _inflightSemaphore;
|
||||
[cb addCompletedHandler:^(id<MTLCommandBuffer> _) {
|
||||
dispatch_semaphore_signal(inflight);
|
||||
@ -218,6 +160,68 @@
|
||||
[_context end];
|
||||
}
|
||||
|
||||
- (void)drawViews
|
||||
{
|
||||
@autoreleasepool {
|
||||
assert(_begin && !_end);
|
||||
_begin = NO;
|
||||
_end = YES;
|
||||
|
||||
id<MTLCommandBuffer> cb = _context.commandBuffer;
|
||||
cb.label = @"renderer cb";
|
||||
|
||||
for (id<View> v in _views) {
|
||||
if (!v.visible) continue;
|
||||
if ([v respondsToSelector:@selector(drawWithContext:)]) {
|
||||
[v drawWithContext:_context];
|
||||
}
|
||||
}
|
||||
|
||||
BOOL pendingDraws = NO;
|
||||
for (id<View> v in _views) {
|
||||
if (v.visible && (v.drawState & ViewDrawStateEncoder) != 0) {
|
||||
pendingDraws = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingDraws) {
|
||||
id<CAMetalDrawable> drawable = _context.nextDrawable;
|
||||
_t_rpd.colorAttachments[0].texture = drawable.texture;
|
||||
|
||||
id<MTLRenderCommandEncoder> rce = [cb renderCommandEncoderWithDescriptor:_t_rpd];
|
||||
[rce setVertexBytes:&_uniforms length:sizeof(_uniforms) atIndex:BufferIndexUniforms];
|
||||
|
||||
for (id<View> v in _views) {
|
||||
if (!v.visible ||
|
||||
![v respondsToSelector:@selector(drawWithEncoder:)] ||
|
||||
(v.drawState & ViewDrawStateEncoder) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// set view state
|
||||
if (v.format == RPixelFormatBGRX8Unorm || v.format == RPixelFormatB5G6R5Unorm) {
|
||||
[rce setRenderPipelineState:_t_pipelineStateNoAlpha];
|
||||
}
|
||||
else {
|
||||
[rce setRenderPipelineState:_t_pipelineState];
|
||||
}
|
||||
|
||||
if (v.filter == RTextureFilterNearest) {
|
||||
[rce setFragmentSamplerState:_samplerStateNearest atIndex:SamplerIndexDraw];
|
||||
}
|
||||
else {
|
||||
[rce setFragmentSamplerState:_samplerStateLinear atIndex:SamplerIndexDraw];
|
||||
}
|
||||
|
||||
[v drawWithEncoder:rce];
|
||||
}
|
||||
|
||||
[rce endEncoding];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - view APIs
|
||||
|
||||
- (void)bringViewToFront:(id<View>)view
|
||||
@ -256,27 +260,4 @@
|
||||
_layer.drawableSize = size;
|
||||
}
|
||||
|
||||
#pragma mark Matrix Math Utilities
|
||||
|
||||
extern inline matrix_float4x4 matrix_proj_ortho(float left, float right, float top, float bottom)
|
||||
{
|
||||
float near = 0;
|
||||
float far = 1;
|
||||
|
||||
float sx = 2 / (right - left);
|
||||
float sy = 2 / (top - bottom);
|
||||
float sz = 1 / (far - near);
|
||||
float tx = (right + left) / (left - right);
|
||||
float ty = (top + bottom) / (bottom - top);
|
||||
float tz = near / (far - near);
|
||||
|
||||
vector_float4 P = {sx, 0, 0, 0};
|
||||
vector_float4 Q = {0, sy, 0, 0};
|
||||
vector_float4 R = {0, 0, sz, 0};
|
||||
vector_float4 S = {tx, ty, tz, 1};
|
||||
|
||||
matrix_float4x4 mat = {P, Q, R, S};
|
||||
return mat;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -41,4 +41,6 @@ typedef NS_ENUM(NSUInteger, RTextureFilter) {
|
||||
RTextureFilterCount,
|
||||
};
|
||||
|
||||
extern matrix_float4x4 matrix_proj_ortho(float left, float right, float top, float bottom);
|
||||
|
||||
#endif /* RendererCommon_h */
|
||||
|
@ -52,4 +52,23 @@ NSString *NSStringFromRPixelFormat(RPixelFormat format)
|
||||
return RPixelStrings[format];
|
||||
}
|
||||
|
||||
|
||||
matrix_float4x4 matrix_proj_ortho(float left, float right, float top, float bottom)
|
||||
{
|
||||
float near = 0;
|
||||
float far = 1;
|
||||
|
||||
float sx = 2 / (right - left);
|
||||
float sy = 2 / (top - bottom);
|
||||
float sz = 1 / (far - near);
|
||||
float tx = (right + left) / (left - right);
|
||||
float ty = (top + bottom) / (bottom - top);
|
||||
float tz = near / (far - near);
|
||||
|
||||
vector_float4 P = {sx, 0, 0, 0};
|
||||
vector_float4 Q = {0, sy, 0, 0};
|
||||
vector_float4 R = {0, 0, sz, 0};
|
||||
vector_float4 S = {tx, ty, tz, 1};
|
||||
|
||||
matrix_float4x4 mat = {P, Q, R, S};
|
||||
return mat;
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ typedef NS_ENUM(NSInteger, VertexAttribute)
|
||||
{
|
||||
VertexAttributePosition = 0,
|
||||
VertexAttributeTexcoord = 1,
|
||||
VertexAttributeColor = 2,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, TextureIndex)
|
||||
@ -70,5 +71,17 @@ typedef struct
|
||||
matrix_float4x4 projectionMatrix;
|
||||
} Uniforms;
|
||||
|
||||
typedef struct {
|
||||
vector_float2 position METAL_ATTRIBUTE(VertexAttributePosition);
|
||||
vector_float2 texCoord METAL_ATTRIBUTE(VertexAttributeTexcoord);
|
||||
vector_float4 color METAL_ATTRIBUTE(VertexAttributeColor);
|
||||
} FontVertex;
|
||||
|
||||
typedef struct {
|
||||
vector_float4 position METAL_POSITION;
|
||||
vector_float2 texCoord;
|
||||
vector_float4 color;
|
||||
} FontFragmentIn;
|
||||
|
||||
#endif /* ShaderTypes_h */
|
||||
|
||||
|
@ -53,3 +53,22 @@ fragment float4 basic_fragment_ndc_tex(ColorInOut in [[stage_in]],
|
||||
half4 colorSample = tex.sample(samp, in.texCoord.xy);
|
||||
return float4(colorSample);
|
||||
}
|
||||
|
||||
#pragma mark - functions for rendering fonts
|
||||
|
||||
vertex FontFragmentIn font_vertex(const FontVertex in [[ stage_in ]], const device Uniforms &uniforms [[ buffer(BufferIndexUniforms) ]])
|
||||
{
|
||||
FontFragmentIn out;
|
||||
out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);
|
||||
out.texCoord = in.texCoord;
|
||||
out.color = in.color;
|
||||
return out;
|
||||
}
|
||||
|
||||
fragment float4 font_fragment(FontFragmentIn in [[ stage_in ]],
|
||||
texture2d<half> tex [[ texture(TextureIndexColor) ]],
|
||||
sampler samp [[ sampler(SamplerIndexDraw) ]])
|
||||
{
|
||||
half4 colorSample = tex.sample(samp, in.texCoord.xy);
|
||||
return float4(in.color.rgb, in.color.a * colorSample.r);
|
||||
}
|
||||
|
@ -65,12 +65,14 @@ extern MTLPixelFormat SelectOptimalPixelFormat(MTLPixelFormat fmt);
|
||||
@property (readonly) MetalMenu* menu;
|
||||
@property (readwrite) uint64_t frameCount;
|
||||
@property (readonly) FrameView* frameView;
|
||||
@property (readonly) Context* context;
|
||||
|
||||
- (instancetype)init NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (void)setVideo:(const video_info_t *)video;
|
||||
|
||||
- (void)beginFrame;
|
||||
- (void)drawViews;
|
||||
- (void)endFrame;
|
||||
|
||||
/*! @brief setNeedsResize triggers a display resize */
|
||||
|
@ -51,7 +51,6 @@
|
||||
FrameView *_frameView;
|
||||
|
||||
video_info_t _video;
|
||||
bool resize_chain;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
@ -66,12 +65,17 @@
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
RARCH_LOG("[MetalDriver]: destroyed\n");
|
||||
if (_viewport) {
|
||||
free(_viewport);
|
||||
_viewport = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (Context *)context {
|
||||
return _renderer.context;
|
||||
}
|
||||
|
||||
#pragma mark - video
|
||||
|
||||
- (void)setVideo:(const video_info_t *)video
|
||||
@ -100,8 +104,6 @@
|
||||
[_renderer sendViewToBack:_frameView];
|
||||
[_frameView setFilteringIndex:0 smooth:video->smooth];
|
||||
}
|
||||
|
||||
resize_chain = YES;
|
||||
}
|
||||
|
||||
- (void)beginFrame
|
||||
@ -111,9 +113,13 @@
|
||||
[_renderer beginFrame];
|
||||
}
|
||||
|
||||
- (void)drawViews {
|
||||
[_renderer drawViews];
|
||||
}
|
||||
|
||||
- (void)endFrame
|
||||
{
|
||||
[_renderer drawFrame];
|
||||
[_renderer endFrame];
|
||||
}
|
||||
|
||||
- (void)setNeedsResize
|
||||
@ -136,27 +142,6 @@
|
||||
|
||||
}
|
||||
|
||||
extern inline matrix_float4x4 matrix_proj_ortho1(float left, float right, float top, float bottom)
|
||||
{
|
||||
float near = 0;
|
||||
float far = 1;
|
||||
|
||||
float sx = 2 / (right - left);
|
||||
float sy = 2 / (top - bottom);
|
||||
float sz = 1 / (far - near);
|
||||
float tx = (right + left) / (left - right);
|
||||
float ty = (top + bottom) / (bottom - top);
|
||||
float tz = near / (far - near);
|
||||
|
||||
vector_float4 P = {sx, 0, 0, 0};
|
||||
vector_float4 Q = {0, sy, 0, 0};
|
||||
vector_float4 R = {0, 0, sz, 0};
|
||||
vector_float4 S = {tx, ty, tz, 1};
|
||||
|
||||
matrix_float4x4 mat = {P, Q, R, S};
|
||||
return mat;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MetalMenu
|
||||
@ -295,7 +280,7 @@ typedef struct ALIGN(16)
|
||||
_drawState = ViewDrawStateAll;
|
||||
}
|
||||
_visible = YES;
|
||||
_engine.mvp = matrix_proj_ortho1(0, 1, 0, 1);
|
||||
_engine.mvp = matrix_proj_ortho(0, 1, 0, 1);
|
||||
[self _initSamplers];
|
||||
|
||||
self.size = d.size;
|
||||
|
@ -73,6 +73,8 @@ static void *metal_init(const video_info_t *video,
|
||||
|
||||
*input = NULL;
|
||||
*input_data = NULL;
|
||||
|
||||
font_driver_init_osd((__bridge_retained void *)md, false, video->is_threaded, FONT_DRIVER_RENDER_METAL_API);
|
||||
|
||||
return (__bridge_retained void *)md;
|
||||
}
|
||||
@ -83,20 +85,40 @@ static bool metal_frame(void *data, const void *frame,
|
||||
unsigned pitch, const char *msg, video_frame_info_t *video_info)
|
||||
{
|
||||
MetalDriver *md = (__bridge MetalDriver *)data;
|
||||
[md beginFrame];
|
||||
@autoreleasepool {
|
||||
[md beginFrame];
|
||||
|
||||
FrameView *v = md.frameView;
|
||||
v.frameCount = frame_count;
|
||||
v.size = CGSizeMake(frame_width, frame_height);
|
||||
[v updateFrame:frame pitch:pitch];
|
||||
FrameView *v = md.frameView;
|
||||
v.frameCount = frame_count;
|
||||
v.size = CGSizeMake(frame_width, frame_height);
|
||||
[v updateFrame:frame pitch:pitch];
|
||||
|
||||
#if defined(HAVE_MENU)
|
||||
if (md.menu.enabled) {
|
||||
menu_driver_frame(video_info);
|
||||
#if defined(HAVE_MENU)
|
||||
if (md.menu.enabled) {
|
||||
menu_driver_frame(video_info);
|
||||
}
|
||||
#endif
|
||||
|
||||
[md drawViews];
|
||||
|
||||
if (video_info->statistics_show)
|
||||
{
|
||||
struct font_params* osd_params = (struct font_params*)&video_info->osd_stat_params;
|
||||
|
||||
if (osd_params)
|
||||
{
|
||||
font_driver_render_msg(video_info, NULL, video_info->stat_text,
|
||||
(const struct font_params*)&video_info->osd_stat_params);
|
||||
}
|
||||
}
|
||||
|
||||
if (msg && *msg)
|
||||
{
|
||||
font_driver_render_msg(video_info, NULL, msg, NULL);
|
||||
}
|
||||
|
||||
[md endFrame];
|
||||
}
|
||||
#endif
|
||||
|
||||
[md endFrame];
|
||||
|
||||
return YES;
|
||||
}
|
||||
@ -148,6 +170,7 @@ static void metal_free(void *data)
|
||||
{
|
||||
MetalDriver *md = (__bridge_transfer MetalDriver *)data;
|
||||
md = nil;
|
||||
font_driver_free_osd();
|
||||
}
|
||||
|
||||
static void metal_set_viewport(void *data, unsigned viewport_width,
|
||||
|
@ -22,248 +22,479 @@
|
||||
|
||||
#include "../font_driver.h"
|
||||
|
||||
typedef struct {
|
||||
int stride;
|
||||
void * mapped;
|
||||
} metal_texture_t;
|
||||
|
||||
typedef struct
|
||||
@interface MetalRaster : NSObject
|
||||
{
|
||||
const font_renderer_driver_t *font_driver;
|
||||
void *font_data;
|
||||
metal_texture_t texture;
|
||||
struct font_atlas *atlas;
|
||||
} font_ctx_t;
|
||||
|
||||
@interface MetalRaster: NSObject {
|
||||
font_ctx_t *_font;
|
||||
const font_renderer_driver_t *_font_driver;
|
||||
void *_font_data;
|
||||
struct font_atlas *_atlas;
|
||||
|
||||
NSUInteger _stride;
|
||||
id<MTLBuffer> _buffer;
|
||||
id<MTLTexture> _texture;
|
||||
|
||||
MTLRenderPassDescriptor *_rpd;
|
||||
id<MTLRenderPipelineState> _state;
|
||||
id<MTLSamplerState> _sampler;
|
||||
|
||||
Context *_context;
|
||||
|
||||
Uniforms _uniforms;
|
||||
id<MTLBuffer> _vert;
|
||||
unsigned _vertices;
|
||||
}
|
||||
|
||||
@property (readwrite) MetalDriver *metal;
|
||||
@property (readwrite) font_ctx_t *font;
|
||||
@property (readonly) struct font_atlas *atlas;
|
||||
@property (readwrite) bool needsUpdate;
|
||||
|
||||
- (instancetype)initWithDriver:(MetalDriver *)metal fontPath:(const char *)font_path fontSize:(unsigned)font_size;
|
||||
|
||||
- (int)getWidthForMessage:(const char *)msg length:(unsigned int)length scale:(float)scale;
|
||||
- (const struct font_glyph *)getGlyph:(uint32_t)code;
|
||||
@end
|
||||
|
||||
@implementation MetalRaster
|
||||
|
||||
- (instancetype)initWithDriver:(MetalDriver *)metal fontPath:(const char *)font_path fontSize:(unsigned)font_size {
|
||||
if (self = [super init])
|
||||
{
|
||||
- (instancetype)initWithDriver:(MetalDriver *)metal fontPath:(const char *)font_path fontSize:(unsigned)font_size
|
||||
{
|
||||
if (self = [super init]) {
|
||||
if (metal == nil)
|
||||
return nil;
|
||||
|
||||
|
||||
_metal = metal;
|
||||
_font = (font_ctx_t *)calloc(1, sizeof(font_ctx_t));
|
||||
if (!font_renderer_create_default((const void**)&_font->font_driver,
|
||||
&_font->font_data, font_path, font_size))
|
||||
{
|
||||
_context = metal.context;
|
||||
if (!font_renderer_create_default((const void **)&_font_driver,
|
||||
&_font_data, font_path, font_size)) {
|
||||
RARCH_WARN("Couldn't initialize font renderer.\n");
|
||||
return nil;
|
||||
}
|
||||
|
||||
_font->atlas = _font->font_driver->get_atlas(_font->font_data);
|
||||
|
||||
// font->texture = vulkan_create_texture(font->vk, NULL,
|
||||
// font->atlas->width, font->atlas->height, VK_FORMAT_R8_UNORM, font->atlas->buffer,
|
||||
// NULL /*&swizzle*/, VULKAN_TEXTURE_STAGING);
|
||||
//
|
||||
// vulkan_map_persistent_texture(
|
||||
// font->vk->context->device, &font->texture);
|
||||
//
|
||||
// font->texture_optimal = vulkan_create_texture(font->vk, NULL,
|
||||
// font->atlas->width, font->atlas->height, VK_FORMAT_R8_UNORM, NULL,
|
||||
// NULL /*&swizzle*/, VULKAN_TEXTURE_DYNAMIC);
|
||||
//
|
||||
|
||||
_uniforms.projectionMatrix = matrix_proj_ortho(0, 1, 0, 1);
|
||||
_atlas = _font_driver->get_atlas(_font_data);
|
||||
_stride = _atlas->width;
|
||||
_buffer = [_context.device newBufferWithBytes:_atlas->buffer
|
||||
length:(NSUInteger)(_atlas->width * _atlas->height)
|
||||
options:MTLResourceStorageModeManaged];
|
||||
|
||||
MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR8Unorm
|
||||
width:_atlas->width
|
||||
height:_atlas->height
|
||||
mipmapped:NO];
|
||||
|
||||
_texture = [_buffer newTextureWithDescriptor:td offset:0 bytesPerRow:_stride];
|
||||
|
||||
_vert = [_context.device newBufferWithLength:sizeof(FontVertex) * 500 options:MTLResourceStorageModeManaged];
|
||||
_needsUpdate = true;
|
||||
if (![self _initializeState]) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_font) {
|
||||
if (_font->font_driver && _font->font_data) {
|
||||
_font->font_driver->free(_font->font_data);
|
||||
_font->font_data = NULL;
|
||||
_font->font_driver = NULL;
|
||||
}
|
||||
|
||||
free(_font);
|
||||
_font = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
static void metal_raster_font_free_font(void *data, bool is_threaded);
|
||||
|
||||
static void *metal_raster_font_init_font(void *data,
|
||||
const char *font_path, float font_size,
|
||||
bool is_threaded)
|
||||
- (bool)_initializeState
|
||||
{
|
||||
MetalRaster *r = [[MetalRaster alloc] initWithDriver:(__bridge MetalDriver *)data fontPath:font_path fontSize:font_size];
|
||||
|
||||
if (!r)
|
||||
return NULL;
|
||||
|
||||
return (__bridge_retained void *)r;
|
||||
}
|
||||
|
||||
static void metal_raster_font_free_font(void *data, bool is_threaded)
|
||||
{
|
||||
MetalRaster * r = (__bridge_transfer MetalRaster *)data;
|
||||
r = nil;
|
||||
}
|
||||
|
||||
static INLINE void metal_raster_font_update_glyph(MetalRaster *r, const struct font_glyph *glyph)
|
||||
{
|
||||
font_ctx_t * font = r.font;
|
||||
|
||||
if(font->atlas->dirty)
|
||||
{
|
||||
MTLVertexDescriptor *vd = [MTLVertexDescriptor new];
|
||||
vd.attributes[0].offset = 0;
|
||||
vd.attributes[0].format = MTLVertexFormatFloat2;
|
||||
vd.attributes[1].offset = offsetof(FontVertex, texCoord);
|
||||
vd.attributes[1].format = MTLVertexFormatFloat2;
|
||||
vd.attributes[2].offset = offsetof(FontVertex, color);
|
||||
vd.attributes[2].format = MTLVertexFormatFloat4;
|
||||
vd.layouts[0].stride = sizeof(FontVertex);
|
||||
vd.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
|
||||
|
||||
MTLRenderPipelineDescriptor *psd = [MTLRenderPipelineDescriptor new];
|
||||
psd.label = @"font pipeline";
|
||||
|
||||
MTLRenderPipelineColorAttachmentDescriptor *ca = psd.colorAttachments[0];
|
||||
ca.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
||||
ca.blendingEnabled = YES;
|
||||
ca.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha;
|
||||
ca.sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
|
||||
ca.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
||||
ca.destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
||||
|
||||
psd.sampleCount = 1;
|
||||
psd.vertexDescriptor = vd;
|
||||
psd.vertexFunction = [_context.library newFunctionWithName:@"font_vertex"];
|
||||
psd.fragmentFunction = [_context.library newFunctionWithName:@"font_fragment"];
|
||||
|
||||
NSError *err;
|
||||
_state = [_context.device newRenderPipelineStateWithDescriptor:psd error:&err];
|
||||
if (err != nil) {
|
||||
RARCH_ERR("[MetalRaster]: error creating pipeline state: %s\n", err.localizedDescription.UTF8String);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
_rpd = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
_rpd.colorAttachments[0].loadAction = MTLLoadActionDontCare;
|
||||
_rpd.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||||
}
|
||||
|
||||
{
|
||||
MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new];
|
||||
sd.minFilter = MTLSamplerMinMagFilterLinear;
|
||||
sd.magFilter = MTLSamplerMinMagFilterLinear;
|
||||
_sampler = [_context.device newSamplerStateWithDescriptor:sd];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)updateGlyph:(const struct font_glyph *)glyph
|
||||
{
|
||||
if (_atlas->dirty) {
|
||||
unsigned row;
|
||||
for (row = glyph->atlas_offset_y; row < (glyph->atlas_offset_y + glyph->height); row++)
|
||||
{
|
||||
uint8_t *src = font->atlas->buffer + row * font->atlas->width + glyph->atlas_offset_x;
|
||||
uint8_t *dst = (uint8_t*)font->texture.mapped + row * font->texture.stride + glyph->atlas_offset_x;
|
||||
for (row = glyph->atlas_offset_y; row < (glyph->atlas_offset_y + glyph->height); row++) {
|
||||
uint8_t *src = _atlas->buffer + row * _atlas->width + glyph->atlas_offset_x;
|
||||
uint8_t *dst = (uint8_t *)_buffer.contents + row * _stride + glyph->atlas_offset_x;
|
||||
memcpy(dst, src, glyph->width);
|
||||
}
|
||||
|
||||
font->atlas->dirty = false;
|
||||
r.needsUpdate = true;
|
||||
|
||||
NSUInteger offset = glyph->atlas_offset_y;
|
||||
NSUInteger len = glyph->height * _stride;
|
||||
[_buffer didModifyRange:NSMakeRange(offset, len)];
|
||||
|
||||
_atlas->dirty = false;
|
||||
_needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
static int metal_get_message_width(void *data, const char *msg,
|
||||
unsigned msg_len, float scale)
|
||||
- (int)getWidthForMessage:(const char *)msg length:(unsigned int)length scale:(float)scale
|
||||
{
|
||||
MetalRaster * r = (__bridge MetalRaster *)data;
|
||||
font_ctx_t *font = r.font;
|
||||
|
||||
unsigned i;
|
||||
int delta_x = 0;
|
||||
|
||||
if (!font)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < msg_len; i++)
|
||||
{
|
||||
const struct font_glyph *glyph =
|
||||
font->font_driver->get_glyph(font->font_data, (uint8_t)msg[i]);
|
||||
|
||||
for (unsigned i = 0; i < length; i++) {
|
||||
const struct font_glyph *glyph = _font_driver->get_glyph(_font_data, (uint8_t)msg[i]);
|
||||
if (!glyph) /* Do something smarter here ... */
|
||||
glyph = font->font_driver->get_glyph(font->font_data, '?');
|
||||
|
||||
|
||||
if (glyph)
|
||||
{
|
||||
metal_raster_font_update_glyph(r, glyph);
|
||||
glyph = _font_driver->get_glyph(_font_data, '?');
|
||||
|
||||
|
||||
if (glyph) {
|
||||
[self updateGlyph:glyph];
|
||||
delta_x += glyph->advance_x;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return delta_x * scale;
|
||||
}
|
||||
|
||||
static void metal_raster_font_render_line(
|
||||
MetalRaster *r, const char *msg, unsigned msg_len,
|
||||
float scale, const float color[4], float pos_x,
|
||||
float pos_y, unsigned text_align)
|
||||
- (const struct font_glyph *)getGlyph:(uint32_t)code
|
||||
{
|
||||
|
||||
if (!_font_driver->ident)
|
||||
return NULL;
|
||||
|
||||
const struct font_glyph *glyph = _font_driver->get_glyph((void *)_font_driver, code);
|
||||
if (glyph) {
|
||||
[self updateGlyph:glyph];
|
||||
}
|
||||
|
||||
return glyph;
|
||||
}
|
||||
|
||||
static void metal_raster_font_render_message(
|
||||
MetalRaster *r, const char *msg, float scale,
|
||||
const float color[4], float pos_x, float pos_y,
|
||||
unsigned text_align)
|
||||
typedef struct color
|
||||
{
|
||||
font_ctx_t *font = r.font;
|
||||
float r, g, b, a;
|
||||
} color_t;
|
||||
|
||||
int lines = 0;
|
||||
float line_height;
|
||||
static INLINE void write_quad(FontVertex *pv,
|
||||
float x, float y, float width, float height,
|
||||
float tex_x, float tex_y, float tex_width, float tex_height,
|
||||
const vector_float4 *color)
|
||||
{
|
||||
unsigned i;
|
||||
static const float strip[2 * 6] = {
|
||||
0.0f, 0.0f,
|
||||
0.0f, 1.0f,
|
||||
1.0f, 0.0f,
|
||||
1.0f, 1.0f,
|
||||
1.0f, 0.0f,
|
||||
0.0f, 1.0f,
|
||||
};
|
||||
|
||||
for (i = 0; i < 6; i++) {
|
||||
pv[i].position.x = x + strip[2 * i + 0] * width;
|
||||
pv[i].position.y = y + strip[2 * i + 1] * height;
|
||||
pv[i].texCoord.x = tex_x + strip[2 * i + 0] * tex_width;
|
||||
pv[i].texCoord.y = tex_y + strip[2 * i + 1] * tex_height;
|
||||
pv[i].color = *color;
|
||||
}
|
||||
}
|
||||
|
||||
if (!msg || !*msg || !r.metal)
|
||||
return;
|
||||
- (void)_renderLine:(const char *)msg
|
||||
video:(video_frame_info_t *)video
|
||||
length:(NSUInteger)length
|
||||
scale:(float)scale
|
||||
color:(vector_float4)color
|
||||
posX:(float)posX
|
||||
posY:(float)posY
|
||||
aligned:(unsigned)aligned
|
||||
{
|
||||
const char* msg_end = msg + length;
|
||||
int x = roundf(posX * _metal.viewport->width);
|
||||
int y = roundf((1.0f - posY) * _metal.viewport->height);
|
||||
int delta_x = 0;
|
||||
int delta_y = 0;
|
||||
float inv_tex_size_x = 1.0f / _texture.width;
|
||||
float inv_tex_size_y = 1.0f / _texture.height;
|
||||
float inv_win_width = 1.0f / _metal.viewport->width;
|
||||
float inv_win_height = 1.0f / _metal.viewport->height;
|
||||
|
||||
switch (aligned) {
|
||||
case TEXT_ALIGN_RIGHT:
|
||||
x -= [self getWidthForMessage:msg length:length scale:scale];
|
||||
break;
|
||||
|
||||
case TEXT_ALIGN_CENTER:
|
||||
x -= [self getWidthForMessage:msg length:length scale:scale] / 2;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
FontVertex *v = (FontVertex *)_vert.contents;
|
||||
|
||||
while (msg < msg_end) {
|
||||
unsigned code = utf8_walk(&msg);
|
||||
const struct font_glyph *glyph = _font_driver->get_glyph(_font_data, code);
|
||||
|
||||
if (!glyph) /* Do something smarter here ... */
|
||||
glyph = _font_driver->get_glyph(_font_data, '?');
|
||||
|
||||
if (!glyph)
|
||||
continue;
|
||||
|
||||
[self updateGlyph:glyph];
|
||||
|
||||
int off_x, off_y, tex_x, tex_y, width, height;
|
||||
off_x = glyph->draw_offset_x;
|
||||
off_y = glyph->draw_offset_y;
|
||||
tex_x = glyph->atlas_offset_x;
|
||||
tex_y = glyph->atlas_offset_y;
|
||||
width = glyph->width;
|
||||
height = glyph->height;
|
||||
|
||||
write_quad(v + _vertices,
|
||||
(x + off_x + delta_x * scale) * inv_win_width,
|
||||
(y + off_y + delta_y * scale) * inv_win_height,
|
||||
width * scale * inv_win_width,
|
||||
height * scale * inv_win_height,
|
||||
tex_x * inv_tex_size_x,
|
||||
tex_y * inv_tex_size_y,
|
||||
width * inv_tex_size_x,
|
||||
height * inv_tex_size_y,
|
||||
&color);
|
||||
|
||||
_vertices += 6;
|
||||
|
||||
delta_x += glyph->advance_x;
|
||||
delta_y += glyph->advance_y;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_flush {
|
||||
[_vert didModifyRange:NSMakeRange(0, sizeof(FontVertex)*_vertices)];
|
||||
_rpd.colorAttachments[0].texture = _context.nextDrawable.texture;
|
||||
|
||||
id<MTLCommandBuffer> cb = _context.commandBuffer;
|
||||
id<MTLRenderCommandEncoder> rce = [cb renderCommandEncoderWithDescriptor:_rpd];
|
||||
[rce pushDebugGroup:@"render fonts"];
|
||||
[rce setRenderPipelineState:_state];
|
||||
[rce setVertexBytes:&_uniforms length:sizeof(_uniforms) atIndex:BufferIndexUniforms];
|
||||
[rce setVertexBuffer:_vert offset:0 atIndex:BufferIndexPositions];
|
||||
[rce setFragmentTexture:_texture atIndex:TextureIndexColor];
|
||||
[rce setFragmentSamplerState:_sampler atIndex:SamplerIndexDraw];
|
||||
[rce drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:_vertices];
|
||||
[rce popDebugGroup];
|
||||
[rce endEncoding];
|
||||
_vertices = 0;
|
||||
}
|
||||
|
||||
- (void)renderMessage:(const char *)msg
|
||||
video:(video_frame_info_t *)video
|
||||
scale:(float)scale
|
||||
color:(vector_float4)color
|
||||
posX:(float)posX
|
||||
posY:(float)posY
|
||||
aligned:(unsigned)aligned
|
||||
{
|
||||
/* If the font height is not supported just draw as usual */
|
||||
if (!font->font_driver->get_line_height)
|
||||
{
|
||||
if (r.metal)
|
||||
metal_raster_font_render_line(r, msg, (unsigned)strlen(msg),
|
||||
scale, color, pos_x, pos_y, text_align);
|
||||
if (!_font_driver->get_line_height) {
|
||||
[self _renderLine:msg video:video length:strlen(msg) scale:scale color:color posX:posX posY:posY aligned:aligned];
|
||||
return;
|
||||
}
|
||||
|
||||
line_height = (float) font->font_driver->get_line_height(font->font_data) *
|
||||
scale / r.metal.viewport->height;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
|
||||
int lines = 0;
|
||||
float line_height = _font_driver->get_line_height(_font_data) * scale / video->height;
|
||||
|
||||
for (;;) {
|
||||
const char *delim = strchr(msg, '\n');
|
||||
|
||||
|
||||
/* Draw the line */
|
||||
if (delim)
|
||||
{
|
||||
unsigned msg_len = (unsigned)(delim - msg);
|
||||
if (r.metal)
|
||||
metal_raster_font_render_line(r, msg, msg_len,
|
||||
scale, color, pos_x, pos_y - (float)lines * line_height,
|
||||
text_align);
|
||||
if (delim) {
|
||||
unsigned msg_len = delim - msg;
|
||||
[self _renderLine:msg
|
||||
video:video
|
||||
length:msg_len
|
||||
scale:scale
|
||||
color:color
|
||||
posX:posX
|
||||
posY:posY - (float)lines * line_height
|
||||
aligned:aligned];
|
||||
msg += msg_len + 1;
|
||||
lines++;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned msg_len = (unsigned)strlen(msg);
|
||||
if (r.metal)
|
||||
metal_raster_font_render_line(r, msg, msg_len,
|
||||
scale, color, pos_x, pos_y - (float)lines * line_height,
|
||||
text_align);
|
||||
else {
|
||||
unsigned msg_len = strlen(msg);
|
||||
[self _renderLine:msg
|
||||
video:video
|
||||
length:msg_len
|
||||
scale:scale
|
||||
color:color
|
||||
posX:posX
|
||||
posY:posY - (float)lines * line_height
|
||||
aligned:aligned];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void metal_raster_font_render_msg(
|
||||
video_frame_info_t *video_info,
|
||||
void *data, const char *msg,
|
||||
const struct font_params *params)
|
||||
- (void)renderMessage:(const char *)msg
|
||||
video:(video_frame_info_t *)video
|
||||
params:(const struct font_params *)params
|
||||
{
|
||||
MetalRaster *r = (__bridge MetalRaster *)data;
|
||||
|
||||
if (!r || !msg || !*msg)
|
||||
if (!msg || !*msg)
|
||||
return;
|
||||
|
||||
float x, y, scale, drop_mod, drop_alpha;
|
||||
int drop_x, drop_y;
|
||||
enum text_alignment text_align;
|
||||
vector_float4 color, color_dark;
|
||||
unsigned width = video->width;
|
||||
unsigned height = video->height;
|
||||
|
||||
if (params) {
|
||||
x = params->x;
|
||||
y = params->y;
|
||||
scale = params->scale;
|
||||
text_align = params->text_align;
|
||||
drop_x = params->drop_x;
|
||||
drop_y = params->drop_y;
|
||||
drop_mod = params->drop_mod;
|
||||
drop_alpha = params->drop_alpha;
|
||||
color.x = FONT_COLOR_GET_RED(params->color) / 255.0f;
|
||||
color.y = FONT_COLOR_GET_GREEN(params->color) / 255.0f;
|
||||
color.z = FONT_COLOR_GET_BLUE(params->color) / 255.0f;
|
||||
color.w = FONT_COLOR_GET_ALPHA(params->color) / 255.0f;
|
||||
}
|
||||
else {
|
||||
x = video->font_msg_pos_x;
|
||||
y = video->font_msg_pos_y;
|
||||
scale = 1.0f;
|
||||
text_align = TEXT_ALIGN_LEFT;
|
||||
|
||||
color.x = video->font_msg_color_r;
|
||||
color.y = video->font_msg_color_g;
|
||||
color.z = video->font_msg_color_b;
|
||||
color.w = 1.0;
|
||||
|
||||
drop_x = -2;
|
||||
drop_y = -2;
|
||||
drop_mod = 0.3f;
|
||||
drop_alpha = 1.0f;
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
|
||||
NSUInteger max_glyphs = strlen(msg);
|
||||
if (drop_x || drop_y)
|
||||
max_glyphs *= 2;
|
||||
|
||||
NSUInteger needed = sizeof(FontVertex) * max_glyphs * 6;
|
||||
if (_vert.length < needed)
|
||||
{
|
||||
_vert = [_context.device newBufferWithLength:needed options:MTLResourceStorageModeManaged];
|
||||
}
|
||||
|
||||
if (drop_x || drop_y) {
|
||||
color_dark.x = color.x * drop_mod;
|
||||
color_dark.y = color.y * drop_mod;
|
||||
color_dark.z = color.z * drop_mod;
|
||||
color_dark.w = color.w * drop_alpha;
|
||||
|
||||
[self renderMessage:msg
|
||||
video:video
|
||||
scale:scale
|
||||
color:color_dark
|
||||
posX:x + scale * drop_x / width
|
||||
posY:y + scale * drop_y / height
|
||||
aligned:text_align];
|
||||
}
|
||||
|
||||
[self renderMessage:msg
|
||||
video:video
|
||||
scale:scale
|
||||
color:color
|
||||
posX:x
|
||||
posY:y
|
||||
aligned:text_align];
|
||||
|
||||
[self _flush];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void metal_raster_font_free_font(void *data, bool is_threaded);
|
||||
|
||||
static void *metal_raster_font_init_font(void *data,
|
||||
const char *font_path, float font_size,
|
||||
bool is_threaded)
|
||||
{
|
||||
MetalRaster *r = [[MetalRaster alloc] initWithDriver:(__bridge_transfer MetalDriver *)data fontPath:font_path fontSize:(unsigned)font_size];
|
||||
|
||||
if (!r)
|
||||
return NULL;
|
||||
|
||||
return (__bridge_retained void *)r;
|
||||
}
|
||||
|
||||
static void metal_raster_font_free_font(void *data, bool is_threaded)
|
||||
{
|
||||
MetalRaster *r = (__bridge_transfer MetalRaster *)data;
|
||||
r = nil;
|
||||
}
|
||||
|
||||
static int metal_get_message_width(void *data, const char *msg,
|
||||
unsigned msg_len, float scale)
|
||||
{
|
||||
MetalRaster *r = (__bridge MetalRaster *)data;
|
||||
return [r getWidthForMessage:msg length:msg_len scale:scale];
|
||||
}
|
||||
|
||||
static void metal_raster_font_render_msg(
|
||||
video_frame_info_t *video_info,
|
||||
void *data, const char *msg,
|
||||
const struct font_params *params)
|
||||
{
|
||||
MetalRaster *r = (__bridge MetalRaster *)data;
|
||||
[r renderMessage:msg video:video_info params:params];
|
||||
}
|
||||
|
||||
static const struct font_glyph *metal_raster_font_get_glyph(
|
||||
void *data, uint32_t code)
|
||||
void *data, uint32_t code)
|
||||
{
|
||||
const struct font_glyph* glyph;
|
||||
MetalRaster * r = (__bridge MetalRaster *)data;
|
||||
font_ctx_t *font = r.font;
|
||||
|
||||
if (!font || !font->font_driver)
|
||||
return NULL;
|
||||
|
||||
if (!font->font_driver->ident)
|
||||
return NULL;
|
||||
|
||||
glyph = font->font_driver->get_glyph((void*)font->font_driver, code);
|
||||
|
||||
if(glyph)
|
||||
metal_raster_font_update_glyph(r, glyph);
|
||||
|
||||
return glyph;
|
||||
MetalRaster *r = (__bridge MetalRaster *)data;
|
||||
return [r getGlyph:code];
|
||||
}
|
||||
|
||||
static void metal_raster_font_flush_block(unsigned width, unsigned height,
|
||||
void *data, video_frame_info_t *video_info)
|
||||
void *data, video_frame_info_t *video_info)
|
||||
{
|
||||
(void)data;
|
||||
}
|
||||
@ -274,12 +505,12 @@ static void metal_raster_font_bind_block(void *data, void *userdata)
|
||||
}
|
||||
|
||||
font_renderer_t metal_raster_font = {
|
||||
metal_raster_font_init_font,
|
||||
metal_raster_font_free_font,
|
||||
metal_raster_font_render_msg,
|
||||
"Metal raster",
|
||||
metal_raster_font_get_glyph,
|
||||
metal_raster_font_bind_block,
|
||||
metal_raster_font_flush_block,
|
||||
metal_get_message_width
|
||||
.init = metal_raster_font_init_font,
|
||||
.free = metal_raster_font_free_font,
|
||||
.render_msg = metal_raster_font_render_msg,
|
||||
.ident = "Metal raster",
|
||||
.get_glyph = metal_raster_font_get_glyph,
|
||||
.bind_block = metal_raster_font_bind_block,
|
||||
.flush = metal_raster_font_flush_block,
|
||||
.get_message_width = metal_get_message_width
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user