diff --git a/gfx/common/metal/Renderer.h b/gfx/common/metal/Renderer.h index 05c50eb65e..5142e7f09a 100644 --- a/gfx/common/metal/Renderer.h +++ b/gfx/common/metal/Renderer.h @@ -23,7 +23,8 @@ - (void)drawableSizeWillChange:(CGSize)size; - (void)beginFrame; -- (void)drawFrame; +- (void)drawViews; +- (void)endFrame; #pragma mark - view management diff --git a/gfx/common/metal/Renderer.m b/gfx/common/metal/Renderer.m index 7c668b7251..f7d53af9d7 100644 --- a/gfx/common/metal/Renderer.m +++ b/gfx/common/metal/Renderer.m @@ -42,7 +42,7 @@ // other state Uniforms _uniforms; - BOOL _begin; + BOOL _begin, _end; } - (instancetype)initWithDevice:(id)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 cb = _context.commandBuffer; - cb.label = @"renderer cb"; - - for (id v in _views) { - if (!v.visible) continue; - if ([v respondsToSelector:@selector(drawWithContext:)]) { - [v drawWithContext:_context]; - } - } - - BOOL pendingDraws = NO; - for (id v in _views) { - if (v.visible && (v.drawState & ViewDrawStateEncoder) != 0) { - pendingDraws = YES; - break; - } - } - - if (pendingDraws) { - id drawable = _context.nextDrawable; - _t_rpd.colorAttachments[0].texture = drawable.texture; - - id rce = [cb renderCommandEncoderWithDescriptor:_t_rpd]; - [rce setVertexBytes:&_uniforms length:sizeof(_uniforms) atIndex:BufferIndexUniforms]; - - for (id 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 _) { dispatch_semaphore_signal(inflight); @@ -218,6 +160,68 @@ [_context end]; } +- (void)drawViews +{ + @autoreleasepool { + assert(_begin && !_end); + _begin = NO; + _end = YES; + + id cb = _context.commandBuffer; + cb.label = @"renderer cb"; + + for (id v in _views) { + if (!v.visible) continue; + if ([v respondsToSelector:@selector(drawWithContext:)]) { + [v drawWithContext:_context]; + } + } + + BOOL pendingDraws = NO; + for (id v in _views) { + if (v.visible && (v.drawState & ViewDrawStateEncoder) != 0) { + pendingDraws = YES; + break; + } + } + + if (pendingDraws) { + id drawable = _context.nextDrawable; + _t_rpd.colorAttachments[0].texture = drawable.texture; + + id rce = [cb renderCommandEncoderWithDescriptor:_t_rpd]; + [rce setVertexBytes:&_uniforms length:sizeof(_uniforms) atIndex:BufferIndexUniforms]; + + for (id 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 @@ -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 diff --git a/gfx/common/metal/RendererCommon.h b/gfx/common/metal/RendererCommon.h index 1d5f009598..8a443a2161 100644 --- a/gfx/common/metal/RendererCommon.h +++ b/gfx/common/metal/RendererCommon.h @@ -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 */ diff --git a/gfx/common/metal/RendererCommon.m b/gfx/common/metal/RendererCommon.m index ded26f74c6..4491e8ea44 100644 --- a/gfx/common/metal/RendererCommon.m +++ b/gfx/common/metal/RendererCommon.m @@ -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; +} diff --git a/gfx/common/metal/ShaderTypes.h b/gfx/common/metal/ShaderTypes.h index a5cb0ca6c3..f6ffdc9eb9 100644 --- a/gfx/common/metal/ShaderTypes.h +++ b/gfx/common/metal/ShaderTypes.h @@ -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 */ diff --git a/gfx/common/metal/Shaders.metal b/gfx/common/metal/Shaders.metal index 31238b5dd8..365408ebc1 100644 --- a/gfx/common/metal/Shaders.metal +++ b/gfx/common/metal/Shaders.metal @@ -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 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); +} diff --git a/gfx/common/metal_common.h b/gfx/common/metal_common.h index 63ac8cbc3f..aa64ea6209 100644 --- a/gfx/common/metal_common.h +++ b/gfx/common/metal_common.h @@ -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 */ diff --git a/gfx/common/metal_common.m b/gfx/common/metal_common.m index 786fe0f71d..55e24128bb 100644 --- a/gfx/common/metal_common.m +++ b/gfx/common/metal_common.m @@ -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; diff --git a/gfx/drivers/metal.m b/gfx/drivers/metal.m index 05a85199bb..eb18c55670 100644 --- a/gfx/drivers/metal.m +++ b/gfx/drivers/metal.m @@ -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, diff --git a/gfx/drivers_font/metal_raster_font.m b/gfx/drivers_font/metal_raster_font.m index d9e89d6a64..f04ccb4b74 100644 --- a/gfx/drivers_font/metal_raster_font.m +++ b/gfx/drivers_font/metal_raster_font.m @@ -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 _buffer; + id _texture; + + MTLRenderPassDescriptor *_rpd; + id _state; + id _sampler; + + Context *_context; + + Uniforms _uniforms; + id _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 cb = _context.commandBuffer; + id 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 };