Fix font rendering on mac

This commit is contained in:
Peter Slattery 2025-07-07 13:54:26 -07:00
parent 6469fe5a0f
commit 9e58435637
2 changed files with 158 additions and 134 deletions

View File

@ -164,10 +164,10 @@ ft__bad_rect_pack_next(Bad_Rect_Pack *pack, Vec2_i32 dim){
// NOTE(simon, 28/02/24): We are now sure that the character will fit. // NOTE(simon, 28/02/24): We are now sure that the character will fit.
pack->current_line_h = Max(pack->current_line_h, dim.y); pack->current_line_h = Max(pack->current_line_h, dim.y);
result = pack->p; result = pack->p;
pack->p.x += dim.x; pack->p.x += dim.x;
pack->dim.x = Max( pack->dim.x, pack->p.x ); pack->dim.x = Max( pack->dim.x, pack->p.x );
} }
return(result); return(result);
} }
@ -330,55 +330,66 @@ ft__font_make_face(Arena *arena, Face_Description *description, f32 scale_factor
Texture_Kind texture_kind = TextureKind_Mono; Texture_Kind texture_kind = TextureKind_Mono;
u32 texture = graphics_get_texture(pack.dim, texture_kind); u32 texture = graphics_get_texture(pack.dim, texture_kind);
Vec3_f32 texture_dim = V3f32(pack.dim); /* NOTE simon (06/01/25): This assumes that every platforms don't use 0 as a valid texture id.
face->texture_dim = texture_dim; This is valid for OpenGL and the DX11 implementaion. Someone needs to check the MAC versions. */
if (texture != 0 ){
{ face->texture_kind = texture_kind;
Vec3_i32 p = V3i32((i32)face->white.uv.x0, (i32)face->white.uv.y0, (i32)face->white.w); face->texture = texture;
Vec3_i32 dim = V3i32(white.dim.x, white.dim.y, 1);
graphics_fill_texture(texture_kind, texture, p, dim, white.data);
face->white.uv.x1 = (face->white.uv.x0 + face->white.uv.x1)/texture_dim.x;
face->white.uv.y1 = (face->white.uv.y0 + face->white.uv.y1)/texture_dim.y;
face->white.uv.x0 = face->white.uv.x0/texture_dim.x;
face->white.uv.y0 = face->white.uv.y0/texture_dim.y;
face->white.w /= texture_dim.z;
}
for (u16 i = 0; i < index_count; i += 1){ Vec3_f32 texture_dim = V3f32(pack.dim);
Vec3_i32 p = V3i32((i32)face->bounds[i].uv.x0, (i32)face->bounds[i].uv.y0, (i32)face->bounds[i].w); face->texture_dim = texture_dim;
Vec3_i32 dim = V3i32(glyph_bitmaps[i].dim.x, glyph_bitmaps[i].dim.y, 1);
graphics_fill_texture(texture_kind, texture, p, dim, glyph_bitmaps[i].data);
face->bounds[i].uv.x1 = (face->bounds[i].uv.x0 + face->bounds[i].uv.x1)/texture_dim.x;
face->bounds[i].uv.y1 = (face->bounds[i].uv.y0 + face->bounds[i].uv.y1)/texture_dim.y;
face->bounds[i].uv.x0 = face->bounds[i].uv.x0/texture_dim.x;
face->bounds[i].uv.y0 = face->bounds[i].uv.y0/texture_dim.y;
}
{ {
Face_Advance_Map *advance_map = &face->advance_map; Vec3_i32 p = V3i32((i32)face->white.uv.x0, (i32)face->white.uv.y0, (i32)face->white.w);
Vec3_i32 dim = V3i32(white.dim.x, white.dim.y, 1);
graphics_fill_texture(texture_kind, texture, p, dim, white.data);
face->white.uv.x1 = (face->white.uv.x0 + face->white.uv.x1)/texture_dim.x;
face->white.uv.y1 = (face->white.uv.y0 + face->white.uv.y1)/texture_dim.y;
face->white.uv.x0 = face->white.uv.x0/texture_dim.x;
face->white.uv.y0 = face->white.uv.y0/texture_dim.y;
}
met->space_advance = font_get_glyph_advance(advance_map, met, ' ', 0); for (u16 i = 0; i < index_count; i += 1){
met->decimal_digit_advance = Vec3_i32 p = V3i32((i32)face->bounds[i].uv.x0, (i32)face->bounds[i].uv.y0, (i32)face->bounds[i].w);
font_get_max_glyph_advance_range(advance_map, met, '0', '9', 0); Vec3_i32 dim = V3i32(glyph_bitmaps[i].dim.x, glyph_bitmaps[i].dim.y, 1);
met->hex_digit_advance = graphics_fill_texture(texture_kind, texture, p, dim, glyph_bitmaps[i].data);
font_get_max_glyph_advance_range(advance_map, met, 'A', 'F', 0); face->bounds[i].uv.x1 = (face->bounds[i].uv.x0 + face->bounds[i].uv.x1)/texture_dim.x;
met->hex_digit_advance = face->bounds[i].uv.y1 = (face->bounds[i].uv.y0 + face->bounds[i].uv.y1)/texture_dim.y;
Max(met->hex_digit_advance, met->decimal_digit_advance); face->bounds[i].uv.x0 = face->bounds[i].uv.x0/texture_dim.x;
met->byte_sub_advances[0] = face->bounds[i].uv.y0 = face->bounds[i].uv.y0/texture_dim.y;
font_get_glyph_advance(advance_map, met, '\\', 0); }
met->byte_sub_advances[1] = met->hex_digit_advance;
met->byte_sub_advances[2] = met->hex_digit_advance; {
met->byte_advance = Face_Advance_Map *advance_map = &face->advance_map;
met->byte_sub_advances[0] +
met->byte_sub_advances[1] + met->space_advance = font_get_glyph_advance(advance_map, met, ' ', 0);
met->byte_sub_advances[2]; met->decimal_digit_advance =
met->normal_lowercase_advance = font_get_max_glyph_advance_range(advance_map, met, '0', '9', 0);
font_get_average_glyph_advance_range(advance_map, met, 'a', 'z', 0); met->hex_digit_advance =
met->normal_uppercase_advance = font_get_max_glyph_advance_range(advance_map, met, 'A', 'F', 0);
font_get_average_glyph_advance_range(advance_map, met, 'A', 'Z', 0); met->hex_digit_advance =
met->normal_advance = (26*met->normal_lowercase_advance + Max(met->hex_digit_advance, met->decimal_digit_advance);
26*met->normal_uppercase_advance + met->byte_sub_advances[0] =
10*met->decimal_digit_advance)/62.f; font_get_glyph_advance(advance_map, met, '\\', 0);
met->byte_sub_advances[1] = met->hex_digit_advance;
met->byte_sub_advances[2] = met->hex_digit_advance;
met->byte_advance =
met->byte_sub_advances[0] +
met->byte_sub_advances[1] +
met->byte_sub_advances[2];
met->normal_lowercase_advance =
font_get_average_glyph_advance_range(advance_map, met, 'a', 'z', 0);
met->normal_uppercase_advance =
font_get_average_glyph_advance_range(advance_map, met, 'A', 'Z', 0);
met->normal_advance = (26*met->normal_lowercase_advance +
26*met->normal_uppercase_advance +
10*met->decimal_digit_advance)/62.f;
}
} else {
pop_array(arena, Face, 1);
face = 0;
} }
} }
@ -388,4 +399,3 @@ ft__font_make_face(Arena *arena, Face_Description *description, f32 scale_factor
} }
// BOTTOM // BOTTOM

View File

@ -10,7 +10,7 @@
struct Metal_Buffer{ struct Metal_Buffer{
Node node; Node node;
id<MTLBuffer> buffer; id<MTLBuffer> buffer;
u32 size; u32 size;
u64 last_reuse_time; u64 last_reuse_time;
@ -23,7 +23,7 @@ typedef id<MTLTexture> Metal_Texture;
// NOTE(yuval): This is a locator used to describe where a specific slot is located. // NOTE(yuval): This is a locator used to describe where a specific slot is located.
union Metal_Texture_Slot_Locator{ union Metal_Texture_Slot_Locator{
u32 packed; u32 packed;
struct{ struct{
u16 bucket_index; u16 bucket_index;
u16 slot_index; u16 slot_index;
@ -34,7 +34,7 @@ union Metal_Texture_Slot_Locator{
struct Metal_Texture_Slot{ struct Metal_Texture_Slot{
// NOTE(yuval): This is a pointer to the next texture in the free texture slots list // NOTE(yuval): This is a pointer to the next texture in the free texture slots list
Metal_Texture_Slot *next; Metal_Texture_Slot *next;
Metal_Texture texture; Metal_Texture texture;
Metal_Texture_Slot_Locator locator; Metal_Texture_Slot_Locator locator;
}; };
@ -52,12 +52,12 @@ struct Metal_Texture_Slot_List{
Metal_Texture_Slot_Bucket *first_bucket; Metal_Texture_Slot_Bucket *first_bucket;
Metal_Texture_Slot_Bucket *last_bucket; Metal_Texture_Slot_Bucket *last_bucket;
u16 bucket_count; u16 bucket_count;
Metal_Texture_Slot *first_free_slot; Metal_Texture_Slot *first_free_slot;
Metal_Texture_Slot *last_free_slot; Metal_Texture_Slot *last_free_slot;
}; };
global_const u32 metal__invalid_texture_slot_locator = (u32)-1; global_const u32 metal__invalid_texture_slot_locator = 0;
//////////////////////////////// ////////////////////////////////
@ -67,6 +67,7 @@ global_const u32 metal__invalid_texture_slot_locator = (u32)-1;
- (u32)get_texture_of_dim:(Vec3_i32)dim kind:(Texture_Kind)kind; - (u32)get_texture_of_dim:(Vec3_i32)dim kind:(Texture_Kind)kind;
- (b32)fill_texture:(u32)texture kind:(Texture_Kind)kind pos:(Vec3_i32)p dim:(Vec3_i32)dim data:(void*)data; - (b32)fill_texture:(u32)texture kind:(Texture_Kind)kind pos:(Vec3_i32)p dim:(Vec3_i32)dim data:(void*)data;
- (void)bind_texture:(u32)handle encoder:(id<MTLRenderCommandEncoder>)render_encoder; - (void)bind_texture:(u32)handle encoder:(id<MTLRenderCommandEncoder>)render_encoder;
- (void)free_texture:(u32)handle;
- (Metal_Texture_Slot*)get_texture_slot_at_locator:(Metal_Texture_Slot_Locator)locator; - (Metal_Texture_Slot*)get_texture_slot_at_locator:(Metal_Texture_Slot_Locator)locator;
- (Metal_Texture_Slot*)get_texture_slot_at_handle:(u32)handle; - (Metal_Texture_Slot*)get_texture_slot_at_handle:(u32)handle;
@ -175,15 +176,15 @@ return(out_color);
function Metal_Buffer* function Metal_Buffer*
metal__make_buffer(u32 size, id<MTLDevice> device){ metal__make_buffer(u32 size, id<MTLDevice> device){
Metal_Buffer *result = (Metal_Buffer*)malloc(sizeof(Metal_Buffer)); Metal_Buffer *result = (Metal_Buffer*)malloc(sizeof(Metal_Buffer));
// NOTE(yuval): Create the vertex buffer // NOTE(yuval): Create the vertex buffer
MTLResourceOptions options = MTLCPUCacheModeWriteCombined|MTLResourceStorageModeManaged; MTLResourceOptions options = MTLCPUCacheModeWriteCombined|MTLResourceStorageModeManaged;
result->buffer = [device newBufferWithLength:size options:options]; result->buffer = [device newBufferWithLength:size options:options];
result->size = size; result->size = size;
// NOTE(yuval): Set the last_reuse_time to the current time // NOTE(yuval): Set the last_reuse_time to the current time
result->last_reuse_time = system_now_time(); result->last_reuse_time = system_now_time();
return result; return result;
} }
@ -191,15 +192,15 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
@implementation Metal_Renderer{ @implementation Metal_Renderer{
Render_Target *_target; Render_Target *_target;
id<MTLDevice> _device; id<MTLDevice> _device;
id<MTLRenderPipelineState> _pipeline_state; id<MTLRenderPipelineState> _pipeline_state;
id<MTLCommandQueue> _command_queue; id<MTLCommandQueue> _command_queue;
id<MTLCaptureScope> _capture_scope; id<MTLCaptureScope> _capture_scope;
Node _buffer_cache; Node _buffer_cache;
u64 _last_buffer_cache_purge_time; u64 _last_buffer_cache_purge_time;
Metal_Texture_Slot_List _texture_slots; Metal_Texture_Slot_List _texture_slots;
} }
@ -208,33 +209,33 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
if (self == nil){ if (self == nil){
return(nil); return(nil);
} }
_target = target; _target = target;
NSError *error = nil; NSError *error = nil;
_device = mtk_view.device; _device = mtk_view.device;
// NOTE(yuval): Compile the shaders // NOTE(yuval): Compile the shaders
id<MTLFunction> vertex_function = nil; id<MTLFunction> vertex_function = nil;
id<MTLFunction> fragment_function = nil; id<MTLFunction> fragment_function = nil;
{ {
NSString *shaders_source_str = [NSString stringWithUTF8String:metal__shaders_source]; NSString *shaders_source_str = [NSString stringWithUTF8String:metal__shaders_source];
MTLCompileOptions *options = [[MTLCompileOptions alloc] init]; MTLCompileOptions *options = [[MTLCompileOptions alloc] init];
options.fastMathEnabled = YES; options.fastMathEnabled = YES;
id<MTLLibrary> shader_library = [_device newLibraryWithSource:shaders_source_str id<MTLLibrary> shader_library = [_device newLibraryWithSource:shaders_source_str
options:options error:&error]; options:options error:&error];
vertex_function = [shader_library newFunctionWithName:@"vertex_shader"]; vertex_function = [shader_library newFunctionWithName:@"vertex_shader"];
fragment_function = [shader_library newFunctionWithName:@"fragment_shader"]; fragment_function = [shader_library newFunctionWithName:@"fragment_shader"];
[options release]; [options release];
} }
Assert(error == nil); Assert(error == nil);
Assert((vertex_function != nil) && (fragment_function != nil)); Assert((vertex_function != nil) && (fragment_function != nil));
// NOTE(yuval): Configure the pipeline descriptor // NOTE(yuval): Configure the pipeline descriptor
{ {
MTLVertexDescriptor *vertexDescriptor = [MTLVertexDescriptor vertexDescriptor]; MTLVertexDescriptor *vertexDescriptor = [MTLVertexDescriptor vertexDescriptor];
@ -253,7 +254,7 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
vertexDescriptor.layouts[0].stepRate = 1; vertexDescriptor.layouts[0].stepRate = 1;
vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
vertexDescriptor.layouts[0].stride = sizeof(Render_Vertex); vertexDescriptor.layouts[0].stride = sizeof(Render_Vertex);
MTLRenderPipelineDescriptor *pipeline_state_descriptor = [[MTLRenderPipelineDescriptor alloc] init]; MTLRenderPipelineDescriptor *pipeline_state_descriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipeline_state_descriptor.label = @"4coder Metal Renderer Pipeline"; pipeline_state_descriptor.label = @"4coder Metal Renderer Pipeline";
pipeline_state_descriptor.vertexFunction = vertex_function; pipeline_state_descriptor.vertexFunction = vertex_function;
@ -267,23 +268,28 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
pipeline_state_descriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; pipeline_state_descriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
pipeline_state_descriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; pipeline_state_descriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
pipeline_state_descriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; pipeline_state_descriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
_pipeline_state = [_device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor _pipeline_state = [_device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor
error:&error]; error:&error];
} }
Assert(error == nil); Assert(error == nil);
// NOTE(yuval): Create the command queue // NOTE(yuval): Create the command queue
_command_queue = [_device newCommandQueue]; _command_queue = [_device newCommandQueue];
// NOTE(yuval): Initialize buffer caching // NOTE(yuval): Initialize buffer caching
dll_init_sentinel(&_buffer_cache); dll_init_sentinel(&_buffer_cache);
_last_buffer_cache_purge_time = system_now_time(); _last_buffer_cache_purge_time = system_now_time();
// NOTE(yuval): Initialize the texture slot list // NOTE(yuval): Initialize the texture slot list
block_zero_struct(&_texture_slots); block_zero_struct(&_texture_slots);
// NOTE(simon): Other platforms use 0 as invalid handle, so we allocate the first texture here (it should be 0),
// and never use it so we can use 0 as the invalid handle.
u32 reserved_texture_slot_do_not_use = [self get_texture_of_dim:V3i32(2, 2, 1) kind:TextureKind_Mono];
Assert( reserved_texture_slot_do_not_use == 0 );
// NOTE(yuval): Create the fallback texture // NOTE(yuval): Create the fallback texture
_target->fallback_texture_id = [self get_texture_of_dim:V3i32(2, 2, 1) _target->fallback_texture_id = [self get_texture_of_dim:V3i32(2, 2, 1)
kind:TextureKind_Mono]; kind:TextureKind_Mono];
@ -293,12 +299,12 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
pos:V3i32(0, 0, 0) pos:V3i32(0, 0, 0)
dim:V3i32(2, 2, 1) dim:V3i32(2, 2, 1)
data:white_block]; data:white_block];
// NOTE(yuval): Create a capture scope for gpu frame capture // NOTE(yuval): Create a capture scope for gpu frame capture
_capture_scope = [[MTLCaptureManager sharedCaptureManager] _capture_scope = [[MTLCaptureManager sharedCaptureManager]
newCaptureScopeWithDevice:_device]; newCaptureScopeWithDevice:_device];
_capture_scope.label = @"4coder Metal Capture Scope"; _capture_scope.label = @"4coder Metal Capture Scope";
return(self); return(self);
} }
@ -310,14 +316,14 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
#if FRED_INTERNAL #if FRED_INTERNAL
[_capture_scope beginScope]; [_capture_scope beginScope];
#endif #endif
// HACK(yuval): This is the best way I found to force valid width and height without drawing on the next draw cycle (1 frame delay). // HACK(yuval): This is the best way I found to force valid width and height without drawing on the next draw cycle (1 frame delay).
CGSize drawable_size = [view drawableSize]; CGSize drawable_size = [view drawableSize];
i32 width = (i32)Min(_target->width, drawable_size.width); i32 width = (i32)Min(_target->width, drawable_size.width);
i32 height = (i32)Min(_target->height, drawable_size.height); i32 height = (i32)Min(_target->height, drawable_size.height);
Font_Set *font_set = (Font_Set*)_target->font_set; Font_Set *font_set = (Font_Set*)_target->font_set;
// NOTE(yuval): Free any textures in the target's texture free list // NOTE(yuval): Free any textures in the target's texture free list
for (Render_Free_Texture *free_texture = _target->free_texture_first; for (Render_Free_Texture *free_texture = _target->free_texture_first;
free_texture; free_texture;
@ -329,27 +335,27 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
} }
_target->free_texture_first = 0; _target->free_texture_first = 0;
_target->free_texture_last = 0; _target->free_texture_last = 0;
// NOTE(yuval): Create the command buffer // NOTE(yuval): Create the command buffer
id<MTLCommandBuffer> command_buffer = [_command_queue commandBuffer]; id<MTLCommandBuffer> command_buffer = [_command_queue commandBuffer];
command_buffer.label = @"4coder Metal Render Command"; command_buffer.label = @"4coder Metal Render Command";
// NOTE(yuval): Obtain the render pass descriptor from the renderer's view // NOTE(yuval): Obtain the render pass descriptor from the renderer's view
MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor; MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor;
if (render_pass_descriptor != nil){ if (render_pass_descriptor != nil){
render_pass_descriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0f, 0.0f, 0.0f, 1.0f); render_pass_descriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0f, 0.0f, 0.0f, 1.0f);
// NOTE(yuval): Create the render command encoder // NOTE(yuval): Create the render command encoder
id<MTLRenderCommandEncoder> render_encoder = id<MTLRenderCommandEncoder> render_encoder =
[command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor]; [command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor];
render_encoder.label = @"4coder Render Encoder"; render_encoder.label = @"4coder Render Encoder";
// NOTE(yuval): Set the region of the drawable to draw into // NOTE(yuval): Set the region of the drawable to draw into
[render_encoder setViewport:(MTLViewport){0.0, 0.0, (double)width, (double)height, 0.0, 1.0}]; [render_encoder setViewport:(MTLViewport){0.0, 0.0, (double)width, (double)height, 0.0, 1.0}];
// NOTE(yuval): Set the render pipeline to use for drawing // NOTE(yuval): Set the render pipeline to use for drawing
[render_encoder setRenderPipelineState:_pipeline_state]; [render_encoder setRenderPipelineState:_pipeline_state];
// NOTE(yuval): Calculate the projection matrix // NOTE(yuval): Calculate the projection matrix
float left = 0, right = (float)width; float left = 0, right = (float)width;
float bottom = (float)height, top = 0; float bottom = (float)height, top = 0;
@ -361,7 +367,7 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
-((right + left) / (right - left)), -((top + bottom) / (top - bottom)), -((right + left) / (right - left)), -((top + bottom) / (top - bottom)),
-(near_depth / (far_depth - near_depth)), 1.0f -(near_depth / (far_depth - near_depth)), 1.0f
}; };
// NOTE(yuval): Calculate required vertex buffer size // NOTE(yuval): Calculate required vertex buffer size
i32 all_vertex_count = 0; i32 all_vertex_count = 0;
for (Render_Group *group = _target->group_first; for (Render_Group *group = _target->group_first;
@ -369,22 +375,22 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
group = group->next){ group = group->next){
all_vertex_count += group->vertex_list.vertex_count; all_vertex_count += group->vertex_list.vertex_count;
} }
u32 vertex_buffer_size = (all_vertex_count * sizeof(Render_Vertex)); u32 vertex_buffer_size = (all_vertex_count * sizeof(Render_Vertex));
// NOTE(yuval): Find & Get a vertex buffer matching the required size // NOTE(yuval): Find & Get a vertex buffer matching the required size
Metal_Buffer *buffer = [self get_reusable_buffer_with_size:vertex_buffer_size]; Metal_Buffer *buffer = [self get_reusable_buffer_with_size:vertex_buffer_size];
// NOTE(yuval): Pass the vertex buffer to the vertex shader // NOTE(yuval): Pass the vertex buffer to the vertex shader
[render_encoder setVertexBuffer:buffer->buffer [render_encoder setVertexBuffer:buffer->buffer
offset:0 offset:0
atIndex:0]; atIndex:0];
// NOTE(yuval): Pass the projection matrix to the vertex shader // NOTE(yuval): Pass the projection matrix to the vertex shader
[render_encoder setVertexBytes:&proj [render_encoder setVertexBytes:&proj
length:sizeof(proj) length:sizeof(proj)
atIndex:1]; atIndex:1];
u32 buffer_offset = 0; u32 buffer_offset = 0;
for (Render_Group *group = _target->group_first; for (Render_Group *group = _target->group_first;
group; group;
@ -392,21 +398,21 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
// NOTE(yuval): Set scissor rect // NOTE(yuval): Set scissor rect
{ {
Rect_i32 box = Ri32(group->clip_box); Rect_i32 box = Ri32(group->clip_box);
NSUInteger x0 = (NSUInteger)Min(Max(0, box.x0), width - 1); NSUInteger x0 = (NSUInteger)Min(Max(0, box.x0), width - 1);
NSUInteger x1 = (NSUInteger)Min(Max(0, box.x1), width); NSUInteger x1 = (NSUInteger)Min(Max(0, box.x1), width);
NSUInteger y0 = (NSUInteger)Min(Max(0, box.y0), height - 1); NSUInteger y0 = (NSUInteger)Min(Max(0, box.y0), height - 1);
NSUInteger y1 = (NSUInteger)Min(Max(0, box.y1), height); NSUInteger y1 = (NSUInteger)Min(Max(0, box.y1), height);
MTLScissorRect scissor_rect; MTLScissorRect scissor_rect;
scissor_rect.x = x0; scissor_rect.x = x0;
scissor_rect.y = y0; scissor_rect.y = y0;
scissor_rect.width = (x1 - x0); scissor_rect.width = (x1 - x0);
scissor_rect.height = (y1 - y0); scissor_rect.height = (y1 - y0);
[render_encoder setScissorRect:scissor_rect]; [render_encoder setScissorRect:scissor_rect];
} }
i32 vertex_count = group->vertex_list.vertex_count; i32 vertex_count = group->vertex_list.vertex_count;
if (vertex_count > 0){ if (vertex_count > 0){
// NOTE(yuval): Bind a texture // NOTE(yuval): Bind a texture
@ -422,10 +428,10 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
encoder:render_encoder]; encoder:render_encoder];
} }
} }
// NOTE(yuval): Copy the vertex data to the vertex buffer // NOTE(yuval): Copy the vertex data to the vertex buffer
{ {
u8 *group_buffer_contents = (u8*)[buffer->buffer contents] + buffer_offset; u8 *group_buffer_contents = (u8*)[buffer->buffer contents] + buffer_offset;
u8 *cursor = group_buffer_contents; u8 *cursor = group_buffer_contents;
for (Render_Vertex_Array_Node *node = group->vertex_list.first; for (Render_Vertex_Array_Node *node = group->vertex_list.first;
@ -435,39 +441,39 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
memcpy(cursor, node->vertices, size); memcpy(cursor, node->vertices, size);
cursor += size; cursor += size;
} }
NSUInteger data_size = (NSUInteger)(cursor - group_buffer_contents); NSUInteger data_size = (NSUInteger)(cursor - group_buffer_contents);
NSRange modify_range = NSMakeRange(buffer_offset, data_size); NSRange modify_range = NSMakeRange(buffer_offset, data_size);
[buffer->buffer didModifyRange:modify_range]; [buffer->buffer didModifyRange:modify_range];
} }
// NOTE(yuval): Set the vertex buffer offset to the beginning of the group's vertices // NOTE(yuval): Set the vertex buffer offset to the beginning of the group's vertices
[render_encoder setVertexBufferOffset:buffer_offset atIndex:0]; [render_encoder setVertexBufferOffset:buffer_offset atIndex:0];
// NOTE(yuval): Draw the vertices // NOTE(yuval): Draw the vertices
[render_encoder drawPrimitives:MTLPrimitiveTypeTriangle [render_encoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0 vertexStart:0
vertexCount:vertex_count]; vertexCount:vertex_count];
buffer_offset += (vertex_count * sizeof(Render_Vertex)); buffer_offset += (vertex_count * sizeof(Render_Vertex));
} }
} }
[render_encoder endEncoding]; [render_encoder endEncoding];
// NOTE(yuval): Schedule a present once the framebuffer is complete using the current drawable // NOTE(yuval): Schedule a present once the framebuffer is complete using the current drawable
[command_buffer presentDrawable:view.currentDrawable]; [command_buffer presentDrawable:view.currentDrawable];
[command_buffer addCompletedHandler:^(id<MTLCommandBuffer>){ [command_buffer addCompletedHandler:^(id<MTLCommandBuffer>){
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self add_reusable_buffer:buffer]; [self add_reusable_buffer:buffer];
}); });
}]; }];
} }
// NOTE(yuval): Finalize rendering here and push the command buffer to the GPU // NOTE(yuval): Finalize rendering here and push the command buffer to the GPU
[command_buffer commit]; [command_buffer commit];
#if FRED_INTERNAL #if FRED_INTERNAL
[_capture_scope endScope]; [_capture_scope endScope];
#endif #endif
@ -475,14 +481,14 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
- (u32)get_texture_of_dim:(Vec3_i32)dim kind:(Texture_Kind)kind{ - (u32)get_texture_of_dim:(Vec3_i32)dim kind:(Texture_Kind)kind{
u32 handle = metal__invalid_texture_slot_locator; u32 handle = metal__invalid_texture_slot_locator;
// NOTE(yuval): Check for a free texture slot and allocate another slot bucket if no free slot has been found // NOTE(yuval): Check for a free texture slot and allocate another slot bucket if no free slot has been found
if (!_texture_slots.first_free_slot){ if (!_texture_slots.first_free_slot){
// NOTE(yuval): Assert that the next bucket's index can fit in a u16 // NOTE(yuval): Assert that the next bucket's index can fit in a u16
Assert(_texture_slots.bucket_count < ((u16)-1)); Assert(_texture_slots.bucket_count < ((u16)-1));
Metal_Texture_Slot_Bucket *bucket = (Metal_Texture_Slot_Bucket*)system_memory_allocate(sizeof(Metal_Texture_Slot_Bucket), file_name_line_number_lit_u8); Metal_Texture_Slot_Bucket *bucket = (Metal_Texture_Slot_Bucket*)system_memory_allocate(sizeof(Metal_Texture_Slot_Bucket), file_name_line_number_lit_u8);
for (u16 slot_index = 0; for (u16 slot_index = 0;
slot_index < ArrayCount(bucket->slots); slot_index < ArrayCount(bucket->slots);
++slot_index){ ++slot_index){
@ -490,20 +496,20 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
block_zero_struct(slot); block_zero_struct(slot);
slot->locator.bucket_index = _texture_slots.bucket_count; slot->locator.bucket_index = _texture_slots.bucket_count;
slot->locator.slot_index = slot_index; slot->locator.slot_index = slot_index;
sll_queue_push(_texture_slots.first_free_slot, _texture_slots.last_free_slot, slot); sll_queue_push(_texture_slots.first_free_slot, _texture_slots.last_free_slot, slot);
} }
sll_queue_push(_texture_slots.first_bucket, _texture_slots.last_bucket, bucket); sll_queue_push(_texture_slots.first_bucket, _texture_slots.last_bucket, bucket);
_texture_slots.bucket_count += 1; _texture_slots.bucket_count += 1;
} }
// NOTE(yuval): Get the first free texture slot and remove it from the free list (a slot is guarenteed to exist because we assert that above). // NOTE(yuval): Get the first free texture slot and remove it from the free list (a slot is guarenteed to exist because we assert that above).
if (_texture_slots.first_free_slot){ if (_texture_slots.first_free_slot){
Metal_Texture_Slot *texture_slot = _texture_slots.first_free_slot; Metal_Texture_Slot *texture_slot = _texture_slots.first_free_slot;
sll_queue_pop(_texture_slots.first_free_slot, _texture_slots.last_free_slot); sll_queue_pop(_texture_slots.first_free_slot, _texture_slots.last_free_slot);
texture_slot->next = 0; texture_slot->next = 0;
// NOTE(yuval): Create a texture descriptor. // NOTE(yuval): Create a texture descriptor.
MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init]; MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init];
texture_descriptor.textureType = MTLTextureType2DArray; texture_descriptor.textureType = MTLTextureType2DArray;
@ -515,21 +521,21 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
// NOTE(yuval): Create the texture from the device using the descriptor and add it to the textures array. // NOTE(yuval): Create the texture from the device using the descriptor and add it to the textures array.
Metal_Texture texture = [_device newTextureWithDescriptor:texture_descriptor]; Metal_Texture texture = [_device newTextureWithDescriptor:texture_descriptor];
texture_slot->texture = texture; texture_slot->texture = texture;
handle = texture_slot->locator.packed; handle = texture_slot->locator.packed;
} }
return handle; return handle;
} }
- (b32)fill_texture:(u32)handle kind:(Texture_Kind)kind pos:(Vec3_i32)p dim:(Vec3_i32)dim data:(void*)data{ - (b32)fill_texture:(u32)handle kind:(Texture_Kind)kind pos:(Vec3_i32)p dim:(Vec3_i32)dim data:(void*)data{
b32 result = false; b32 result = false;
if (data){ if (data){
Metal_Texture_Slot *texture_slot = [self get_texture_slot_at_handle:handle]; Metal_Texture_Slot *texture_slot = [self get_texture_slot_at_handle:handle];
if (texture_slot){ if (texture_slot){
Metal_Texture texture = texture_slot->texture; Metal_Texture texture = texture_slot->texture;
if (texture != 0){ if (texture != 0){
// https://developer.apple.com/documentation/metal/mtlregion // https://developer.apple.com/documentation/metal/mtlregion
// for 2d texture origin.z is 0, and depth is 1 // for 2d texture origin.z is 0, and depth is 1
@ -537,7 +543,7 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
{(NSUInteger)p.x, (NSUInteger)p.y, 0}, {(NSUInteger)p.x, (NSUInteger)p.y, 0},
{(NSUInteger)dim.x, (NSUInteger)dim.y, 1} {(NSUInteger)dim.x, (NSUInteger)dim.y, 1}
}; };
// NOTE(yuval): Fill the texture with data // NOTE(yuval): Fill the texture with data
[texture replaceRegion:replace_region [texture replaceRegion:replace_region
mipmapLevel:0 mipmapLevel:0
@ -550,7 +556,7 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
} }
} }
} }
return(result); return(result);
} }
@ -565,41 +571,49 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
} }
} }
- (void)free_texture:(u32)handle{
Metal_Texture_Slot *texture_slot = [self get_texture_slot_at_handle:handle];
if (texture_slot){
sll_queue_push(_texture_slots.first_free_slot, _texture_slots.last_free_slot, texture_slot);
}
}
- (Metal_Texture_Slot*)get_texture_slot_at_locator:(Metal_Texture_Slot_Locator)locator{ - (Metal_Texture_Slot*)get_texture_slot_at_locator:(Metal_Texture_Slot_Locator)locator{
Metal_Texture_Slot *result = 0; Metal_Texture_Slot *result = 0;
if (locator.packed != metal__invalid_texture_slot_locator){ if (locator.packed != metal__invalid_texture_slot_locator){
Metal_Texture_Slot_Bucket *bucket = _texture_slots.first_bucket; Metal_Texture_Slot_Bucket *bucket = _texture_slots.first_bucket;
for (u16 bucket_index = 0; for (u16 bucket_index = 0;
(bucket_index < locator.bucket_index) && bucket; (bucket_index < locator.bucket_index) && bucket;
++bucket_index, bucket = bucket->next); ++bucket_index, bucket = bucket->next);
if (bucket && (locator.slot_index < metal__texture_slots_per_bucket)){ if (bucket && (locator.slot_index < metal__texture_slots_per_bucket)){
result = &bucket->slots[locator.slot_index]; result = &bucket->slots[locator.slot_index];
} }
} }
return(result); return(result);
} }
- (Metal_Texture_Slot*)get_texture_slot_at_handle:(u32)handle{ - (Metal_Texture_Slot*)get_texture_slot_at_handle:(u32)handle{
Metal_Texture_Slot_Locator locator; Metal_Texture_Slot_Locator locator;
locator.packed = handle; locator.packed = handle;
Metal_Texture_Slot *result = [self get_texture_slot_at_locator:locator]; Metal_Texture_Slot *result = [self get_texture_slot_at_locator:locator];
return(result); return(result);
} }
- (Metal_Buffer*)get_reusable_buffer_with_size:(NSUInteger)size{ - (Metal_Buffer*)get_reusable_buffer_with_size:(NSUInteger)size{
// NOTE(yuval): This routine is a modified version of Dear ImGui's MetalContext::dequeueReusableBufferOfLength in imgui_impl_metal.mm // NOTE(yuval): This routine is a modified version of Dear ImGui's MetalContext::dequeueReusableBufferOfLength in imgui_impl_metal.mm
u64 now = system_now_time(); u64 now = system_now_time();
// NOTE(yuval): Purge old buffers that haven't been useful for a while // NOTE(yuval): Purge old buffers that haven't been useful for a while
if ((now - _last_buffer_cache_purge_time) > 1000000){ if ((now - _last_buffer_cache_purge_time) > 1000000){
Node prev_buffer_cache = _buffer_cache; Node prev_buffer_cache = _buffer_cache;
dll_init_sentinel(&_buffer_cache); dll_init_sentinel(&_buffer_cache);
for (Node *node = prev_buffer_cache.next; for (Node *node = prev_buffer_cache.next;
node != &_buffer_cache; node != &_buffer_cache;
node = node->next){ node = node->next){
@ -608,10 +622,10 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
dll_insert(&_buffer_cache, node); dll_insert(&_buffer_cache, node);
} }
} }
_last_buffer_cache_purge_time = now; _last_buffer_cache_purge_time = now;
} }
// NOTE(yuval): See if we have a buffer we can reuse // NOTE(yuval): See if we have a buffer we can reuse
Metal_Buffer *best_candidate = 0; Metal_Buffer *best_candidate = 0;
for (Node *node = _buffer_cache.next; for (Node *node = _buffer_cache.next;
@ -622,7 +636,7 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
best_candidate = candidate; best_candidate = candidate;
} }
} }
Metal_Buffer *result; Metal_Buffer *result;
if (best_candidate){ if (best_candidate){
// NOTE(yuval): A best candidate has been found! Remove it from the buffer list and set its last reuse time. // NOTE(yuval): A best candidate has been found! Remove it from the buffer list and set its last reuse time.
@ -633,13 +647,13 @@ metal__make_buffer(u32 size, id<MTLDevice> device){
// NOTE(yuval): No luck; make a new buffer. // NOTE(yuval): No luck; make a new buffer.
result = metal__make_buffer(size, _device); result = metal__make_buffer(size, _device);
} }
return(result); return(result);
} }
- (void)add_reusable_buffer:(Metal_Buffer*)buffer{ - (void)add_reusable_buffer:(Metal_Buffer*)buffer{
// NOTE(yuval): This routine is a modified version of Dear ImGui's MetalContext::enqueueReusableBuffer in imgui_impl_metal.mm // NOTE(yuval): This routine is a modified version of Dear ImGui's MetalContext::enqueueReusableBuffer in imgui_impl_metal.mm
dll_insert(&_buffer_cache, &buffer->node); dll_insert(&_buffer_cache, &buffer->node);
} }
@end @end