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:
Stuart Carnie 2018-06-23 23:53:45 -07:00
parent c10aab9830
commit 367c15f488
10 changed files with 581 additions and 305 deletions

View File

@ -23,7 +23,8 @@
- (void)drawableSizeWillChange:(CGSize)size;
- (void)beginFrame;
- (void)drawFrame;
- (void)drawViews;
- (void)endFrame;
#pragma mark - view management

View File

@ -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

View File

@ -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 */

View File

@ -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;
}

View File

@ -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 */

View File

@ -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);
}

View File

@ -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 */

View File

@ -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;

View File

@ -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,

View File

@ -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
};