diff --git a/code/4ed_font_provider_freetype.cpp b/code/4ed_font_provider_freetype.cpp index 22d2f333..2efe9b04 100644 --- a/code/4ed_font_provider_freetype.cpp +++ b/code/4ed_font_provider_freetype.cpp @@ -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. pack->current_line_h = Max(pack->current_line_h, dim.y); - result = pack->p; - pack->p.x += dim.x; + result = pack->p; + pack->p.x += dim.x; pack->dim.x = Max( pack->dim.x, pack->p.x ); - } + } return(result); } @@ -330,55 +330,66 @@ ft__font_make_face(Arena *arena, Face_Description *description, f32 scale_factor Texture_Kind texture_kind = TextureKind_Mono; u32 texture = graphics_get_texture(pack.dim, texture_kind); - Vec3_f32 texture_dim = V3f32(pack.dim); - face->texture_dim = texture_dim; + /* NOTE simon (06/01/25): This assumes that every platforms don't use 0 as a valid texture id. + This is valid for OpenGL and the DX11 implementaion. Someone needs to check the MAC versions. */ + if (texture != 0 ){ - { - 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; - face->white.w /= texture_dim.z; - } + face->texture_kind = texture_kind; + face->texture = texture; - for (u16 i = 0; i < index_count; i += 1){ - Vec3_i32 p = V3i32((i32)face->bounds[i].uv.x0, (i32)face->bounds[i].uv.y0, (i32)face->bounds[i].w); - 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; - } + Vec3_f32 texture_dim = V3f32(pack.dim); + face->texture_dim = texture_dim; - { - 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); - met->decimal_digit_advance = - font_get_max_glyph_advance_range(advance_map, met, '0', '9', 0); - met->hex_digit_advance = - font_get_max_glyph_advance_range(advance_map, met, 'A', 'F', 0); - met->hex_digit_advance = - Max(met->hex_digit_advance, met->decimal_digit_advance); - met->byte_sub_advances[0] = - 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; + for (u16 i = 0; i < index_count; i += 1){ + Vec3_i32 p = V3i32((i32)face->bounds[i].uv.x0, (i32)face->bounds[i].uv.y0, (i32)face->bounds[i].w); + 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; + + met->space_advance = font_get_glyph_advance(advance_map, met, ' ', 0); + met->decimal_digit_advance = + font_get_max_glyph_advance_range(advance_map, met, '0', '9', 0); + met->hex_digit_advance = + font_get_max_glyph_advance_range(advance_map, met, 'A', 'F', 0); + met->hex_digit_advance = + Max(met->hex_digit_advance, met->decimal_digit_advance); + met->byte_sub_advances[0] = + 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 - diff --git a/code/metal/4ed_metal_render.mm b/code/metal/4ed_metal_render.mm index 2633823a..562fecda 100644 --- a/code/metal/4ed_metal_render.mm +++ b/code/metal/4ed_metal_render.mm @@ -10,7 +10,7 @@ struct Metal_Buffer{ Node node; - + id buffer; u32 size; u64 last_reuse_time; @@ -23,7 +23,7 @@ typedef id Metal_Texture; // NOTE(yuval): This is a locator used to describe where a specific slot is located. union Metal_Texture_Slot_Locator{ u32 packed; - + struct{ u16 bucket_index; u16 slot_index; @@ -34,7 +34,7 @@ union Metal_Texture_Slot_Locator{ struct Metal_Texture_Slot{ // NOTE(yuval): This is a pointer to the next texture in the free texture slots list Metal_Texture_Slot *next; - + Metal_Texture texture; Metal_Texture_Slot_Locator locator; }; @@ -52,12 +52,12 @@ struct Metal_Texture_Slot_List{ Metal_Texture_Slot_Bucket *first_bucket; Metal_Texture_Slot_Bucket *last_bucket; u16 bucket_count; - + Metal_Texture_Slot *first_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; - (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)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_handle:(u32)handle; @@ -175,15 +176,15 @@ return(out_color); function Metal_Buffer* metal__make_buffer(u32 size, id device){ Metal_Buffer *result = (Metal_Buffer*)malloc(sizeof(Metal_Buffer)); - + // NOTE(yuval): Create the vertex buffer MTLResourceOptions options = MTLCPUCacheModeWriteCombined|MTLResourceStorageModeManaged; result->buffer = [device newBufferWithLength:size options:options]; result->size = size; - + // NOTE(yuval): Set the last_reuse_time to the current time result->last_reuse_time = system_now_time(); - + return result; } @@ -191,15 +192,15 @@ metal__make_buffer(u32 size, id device){ @implementation Metal_Renderer{ Render_Target *_target; - + id _device; id _pipeline_state; id _command_queue; id _capture_scope; - + Node _buffer_cache; u64 _last_buffer_cache_purge_time; - + Metal_Texture_Slot_List _texture_slots; } @@ -208,33 +209,33 @@ metal__make_buffer(u32 size, id device){ if (self == nil){ return(nil); } - + _target = target; - + NSError *error = nil; - + _device = mtk_view.device; - + // NOTE(yuval): Compile the shaders id vertex_function = nil; id fragment_function = nil; { NSString *shaders_source_str = [NSString stringWithUTF8String:metal__shaders_source]; - + MTLCompileOptions *options = [[MTLCompileOptions alloc] init]; options.fastMathEnabled = YES; - + id shader_library = [_device newLibraryWithSource:shaders_source_str options:options error:&error]; vertex_function = [shader_library newFunctionWithName:@"vertex_shader"]; fragment_function = [shader_library newFunctionWithName:@"fragment_shader"]; - + [options release]; } - + Assert(error == nil); Assert((vertex_function != nil) && (fragment_function != nil)); - + // NOTE(yuval): Configure the pipeline descriptor { MTLVertexDescriptor *vertexDescriptor = [MTLVertexDescriptor vertexDescriptor]; @@ -253,7 +254,7 @@ metal__make_buffer(u32 size, id device){ vertexDescriptor.layouts[0].stepRate = 1; vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; vertexDescriptor.layouts[0].stride = sizeof(Render_Vertex); - + MTLRenderPipelineDescriptor *pipeline_state_descriptor = [[MTLRenderPipelineDescriptor alloc] init]; pipeline_state_descriptor.label = @"4coder Metal Renderer Pipeline"; pipeline_state_descriptor.vertexFunction = vertex_function; @@ -267,23 +268,28 @@ metal__make_buffer(u32 size, id device){ pipeline_state_descriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; pipeline_state_descriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; pipeline_state_descriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; - + _pipeline_state = [_device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor error:&error]; } - + Assert(error == nil); - + // NOTE(yuval): Create the command queue _command_queue = [_device newCommandQueue]; - + // NOTE(yuval): Initialize buffer caching dll_init_sentinel(&_buffer_cache); _last_buffer_cache_purge_time = system_now_time(); - + // NOTE(yuval): Initialize the texture slot list 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 _target->fallback_texture_id = [self get_texture_of_dim:V3i32(2, 2, 1) kind:TextureKind_Mono]; @@ -293,12 +299,12 @@ metal__make_buffer(u32 size, id device){ pos:V3i32(0, 0, 0) dim:V3i32(2, 2, 1) data:white_block]; - + // NOTE(yuval): Create a capture scope for gpu frame capture _capture_scope = [[MTLCaptureManager sharedCaptureManager] newCaptureScopeWithDevice:_device]; _capture_scope.label = @"4coder Metal Capture Scope"; - + return(self); } @@ -310,14 +316,14 @@ metal__make_buffer(u32 size, id device){ #if FRED_INTERNAL [_capture_scope beginScope]; #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). CGSize drawable_size = [view drawableSize]; i32 width = (i32)Min(_target->width, drawable_size.width); i32 height = (i32)Min(_target->height, drawable_size.height); - + Font_Set *font_set = (Font_Set*)_target->font_set; - + // NOTE(yuval): Free any textures in the target's texture free list for (Render_Free_Texture *free_texture = _target->free_texture_first; free_texture; @@ -329,27 +335,27 @@ metal__make_buffer(u32 size, id device){ } _target->free_texture_first = 0; _target->free_texture_last = 0; - + // NOTE(yuval): Create the command buffer id command_buffer = [_command_queue commandBuffer]; command_buffer.label = @"4coder Metal Render Command"; - + // NOTE(yuval): Obtain the render pass descriptor from the renderer's view MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor; if (render_pass_descriptor != nil){ render_pass_descriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0f, 0.0f, 0.0f, 1.0f); - + // NOTE(yuval): Create the render command encoder id render_encoder = [command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor]; render_encoder.label = @"4coder Render Encoder"; - + // 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}]; - + // NOTE(yuval): Set the render pipeline to use for drawing [render_encoder setRenderPipelineState:_pipeline_state]; - + // NOTE(yuval): Calculate the projection matrix float left = 0, right = (float)width; float bottom = (float)height, top = 0; @@ -361,7 +367,7 @@ metal__make_buffer(u32 size, id device){ -((right + left) / (right - left)), -((top + bottom) / (top - bottom)), -(near_depth / (far_depth - near_depth)), 1.0f }; - + // NOTE(yuval): Calculate required vertex buffer size i32 all_vertex_count = 0; for (Render_Group *group = _target->group_first; @@ -369,22 +375,22 @@ metal__make_buffer(u32 size, id device){ group = group->next){ all_vertex_count += group->vertex_list.vertex_count; } - + u32 vertex_buffer_size = (all_vertex_count * sizeof(Render_Vertex)); - + // NOTE(yuval): Find & Get a vertex buffer matching the required size Metal_Buffer *buffer = [self get_reusable_buffer_with_size:vertex_buffer_size]; - + // NOTE(yuval): Pass the vertex buffer to the vertex shader [render_encoder setVertexBuffer:buffer->buffer offset:0 atIndex:0]; - + // NOTE(yuval): Pass the projection matrix to the vertex shader [render_encoder setVertexBytes:&proj length:sizeof(proj) atIndex:1]; - + u32 buffer_offset = 0; for (Render_Group *group = _target->group_first; group; @@ -392,21 +398,21 @@ metal__make_buffer(u32 size, id device){ // NOTE(yuval): Set scissor rect { Rect_i32 box = Ri32(group->clip_box); - + NSUInteger x0 = (NSUInteger)Min(Max(0, box.x0), width - 1); NSUInteger x1 = (NSUInteger)Min(Max(0, box.x1), width); NSUInteger y0 = (NSUInteger)Min(Max(0, box.y0), height - 1); NSUInteger y1 = (NSUInteger)Min(Max(0, box.y1), height); - + MTLScissorRect scissor_rect; scissor_rect.x = x0; scissor_rect.y = y0; scissor_rect.width = (x1 - x0); scissor_rect.height = (y1 - y0); - + [render_encoder setScissorRect:scissor_rect]; } - + i32 vertex_count = group->vertex_list.vertex_count; if (vertex_count > 0){ // NOTE(yuval): Bind a texture @@ -422,10 +428,10 @@ metal__make_buffer(u32 size, id device){ encoder:render_encoder]; } } - + // NOTE(yuval): Copy the vertex data to the vertex buffer { - + u8 *group_buffer_contents = (u8*)[buffer->buffer contents] + buffer_offset; u8 *cursor = group_buffer_contents; for (Render_Vertex_Array_Node *node = group->vertex_list.first; @@ -435,39 +441,39 @@ metal__make_buffer(u32 size, id device){ memcpy(cursor, node->vertices, size); cursor += size; } - + NSUInteger data_size = (NSUInteger)(cursor - group_buffer_contents); NSRange modify_range = NSMakeRange(buffer_offset, data_size); [buffer->buffer didModifyRange:modify_range]; } - + // NOTE(yuval): Set the vertex buffer offset to the beginning of the group's vertices [render_encoder setVertexBufferOffset:buffer_offset atIndex:0]; - + // NOTE(yuval): Draw the vertices [render_encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:vertex_count]; - + buffer_offset += (vertex_count * sizeof(Render_Vertex)); } } - + [render_encoder endEncoding]; - + // NOTE(yuval): Schedule a present once the framebuffer is complete using the current drawable [command_buffer presentDrawable:view.currentDrawable]; - + [command_buffer addCompletedHandler:^(id){ dispatch_async(dispatch_get_main_queue(), ^{ [self add_reusable_buffer:buffer]; }); }]; } - + // NOTE(yuval): Finalize rendering here and push the command buffer to the GPU [command_buffer commit]; - + #if FRED_INTERNAL [_capture_scope endScope]; #endif @@ -475,14 +481,14 @@ metal__make_buffer(u32 size, id device){ - (u32)get_texture_of_dim:(Vec3_i32)dim kind:(Texture_Kind)kind{ 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 if (!_texture_slots.first_free_slot){ // NOTE(yuval): Assert that the next bucket's index can fit in a u16 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); - + for (u16 slot_index = 0; slot_index < ArrayCount(bucket->slots); ++slot_index){ @@ -490,20 +496,20 @@ metal__make_buffer(u32 size, id device){ block_zero_struct(slot); slot->locator.bucket_index = _texture_slots.bucket_count; 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_bucket, _texture_slots.last_bucket, bucket); _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). if (_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); texture_slot->next = 0; - + // NOTE(yuval): Create a texture descriptor. MTLTextureDescriptor *texture_descriptor = [[MTLTextureDescriptor alloc] init]; texture_descriptor.textureType = MTLTextureType2DArray; @@ -515,21 +521,21 @@ metal__make_buffer(u32 size, id device){ // 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]; texture_slot->texture = texture; - + handle = texture_slot->locator.packed; } - + return handle; } - (b32)fill_texture:(u32)handle kind:(Texture_Kind)kind pos:(Vec3_i32)p dim:(Vec3_i32)dim data:(void*)data{ b32 result = false; - + if (data){ Metal_Texture_Slot *texture_slot = [self get_texture_slot_at_handle:handle]; if (texture_slot){ Metal_Texture texture = texture_slot->texture; - + if (texture != 0){ // https://developer.apple.com/documentation/metal/mtlregion // for 2d texture origin.z is 0, and depth is 1 @@ -537,7 +543,7 @@ metal__make_buffer(u32 size, id device){ {(NSUInteger)p.x, (NSUInteger)p.y, 0}, {(NSUInteger)dim.x, (NSUInteger)dim.y, 1} }; - + // NOTE(yuval): Fill the texture with data [texture replaceRegion:replace_region mipmapLevel:0 @@ -550,7 +556,7 @@ metal__make_buffer(u32 size, id device){ } } } - + return(result); } @@ -565,41 +571,49 @@ metal__make_buffer(u32 size, id 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 *result = 0; - + if (locator.packed != metal__invalid_texture_slot_locator){ Metal_Texture_Slot_Bucket *bucket = _texture_slots.first_bucket; for (u16 bucket_index = 0; (bucket_index < locator.bucket_index) && bucket; ++bucket_index, bucket = bucket->next); - + if (bucket && (locator.slot_index < metal__texture_slots_per_bucket)){ result = &bucket->slots[locator.slot_index]; } } - + return(result); } - (Metal_Texture_Slot*)get_texture_slot_at_handle:(u32)handle{ Metal_Texture_Slot_Locator locator; locator.packed = handle; - + Metal_Texture_Slot *result = [self get_texture_slot_at_locator:locator]; return(result); } - (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 - + u64 now = system_now_time(); - + // NOTE(yuval): Purge old buffers that haven't been useful for a while if ((now - _last_buffer_cache_purge_time) > 1000000){ Node prev_buffer_cache = _buffer_cache; dll_init_sentinel(&_buffer_cache); - + for (Node *node = prev_buffer_cache.next; node != &_buffer_cache; node = node->next){ @@ -608,10 +622,10 @@ metal__make_buffer(u32 size, id device){ dll_insert(&_buffer_cache, node); } } - + _last_buffer_cache_purge_time = now; } - + // NOTE(yuval): See if we have a buffer we can reuse Metal_Buffer *best_candidate = 0; for (Node *node = _buffer_cache.next; @@ -622,7 +636,7 @@ metal__make_buffer(u32 size, id device){ best_candidate = candidate; } } - + Metal_Buffer *result; if (best_candidate){ // 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 device){ // NOTE(yuval): No luck; make a new buffer. result = metal__make_buffer(size, _device); } - + return(result); } - (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 - + dll_insert(&_buffer_cache, &buffer->node); } -@end +@end \ No newline at end of file