Implemented my own vertex buffers management, also started working on textures.

This commit is contained in:
Yuval Dolev 2020-01-08 01:45:36 +02:00
parent a34d95b848
commit a18ef3197a
2 changed files with 88 additions and 79 deletions

View File

@ -10,14 +10,19 @@
//////////////////////////////// ////////////////////////////////
// TODO(yuval): Convert this to a struct when I handle my own caching solution struct Metal_Buffer{
@interface Metal_Buffer : NSObject Node node;
@property (nonatomic, strong) id<MTLBuffer> buffer;
@property (nonatomic, readonly) u32 size;
@property (nonatomic, assign) NSTimeInterval last_reuse_time;
- (instancetype)initWithSize:(u32)size usingDevice:(id<MTLDevice>)device; id<MTLBuffer> buffer;
@end u32 size;
u64 last_reuse_time;
};
struct Metal_Texture{
};
////////////////////////////////
@interface Metal_Renderer : NSObject<MTKViewDelegate> @interface Metal_Renderer : NSObject<MTKViewDelegate>
@property (nonatomic) Render_Target *target; @property (nonatomic) Render_Target *target;
@ -39,7 +44,7 @@ using namespace metal;
typedef struct{ typedef struct{
float2 xy [[attribute(0)]]; float2 xy [[attribute(0)]];
float3 uvw [[attribute(1)]]; float3 uvw [[attribute(1)]];
uint32_t color [[attribute(2)]]; uint32_t color [[attribute(2)]];
float half_thickness [[attribute(3)]]; float half_thickness [[attribute(3)]];
} Vertex; } Vertex;
@ -50,11 +55,11 @@ typedef struct{
float4 position [[position]]; float4 position [[position]];
// NOTE(yuval): Fragment shader inputs // NOTE(yuval): Fragment shader inputs
float4 color; float4 color;
float3 uvw; float3 uvw;
float2 xy; float2 xy;
float2 adjusted_half_dim; float2 adjusted_half_dim;
float half_thickness; float half_thickness;
} Rasterizer_Data; } Rasterizer_Data;
//////////////////////////////// ////////////////////////////////
@ -62,32 +67,32 @@ float4 position [[position]];
vertex Rasterizer_Data vertex Rasterizer_Data
vertex_shader(Vertex in [[stage_in]], vertex_shader(Vertex in [[stage_in]],
constant float4x4 &proj [[buffer(1)]]){ constant float4x4 &proj [[buffer(1)]]){
Rasterizer_Data out; Rasterizer_Data out;
// NOTE(yuval): Calculate position in NDC // NOTE(yuval): Calculate position in NDC
out.position = proj * float4(in.xy, 0.0, 1.0); out.position = proj * float4(in.xy, 0.0, 1.0);
// NOTE(yuval): Convert color to float4 format // NOTE(yuval): Convert color to float4 format
out.color.b = ((float((in.color ) & 0xFFu)) / 255.0); out.color.b = ((float((in.color ) & 0xFFu)) / 255.0);
out.color.g = ((float((in.color >> 8u) & 0xFFu)) / 255.0); out.color.g = ((float((in.color >> 8u) & 0xFFu)) / 255.0);
out.color.r = ((float((in.color >> 16u) & 0xFFu)) / 255.0); out.color.r = ((float((in.color >> 16u) & 0xFFu)) / 255.0);
out.color.a = ((float((in.color >> 24u) & 0xFFu)) / 255.0); out.color.a = ((float((in.color >> 24u) & 0xFFu)) / 255.0);
// NOTE(yuval): Pass uvw coordinates to the fragment shader // NOTE(yuval): Pass uvw coordinates to the fragment shader
out.uvw = in.uvw; out.uvw = in.uvw;
// NOTE(yuval): Calculate adjusted half dim // NOTE(yuval): Calculate adjusted half dim
float2 center = in.uvw.xy; float2 center = in.uvw.xy;
float2 half_dim = abs(in.xy - center); float2 half_dim = abs(in.xy - center);
out.adjusted_half_dim = (half_dim - in.uvw.zz + float2(0.5, 0.5)); out.adjusted_half_dim = (half_dim - in.uvw.zz + float2(0.5, 0.5));
// NOTE(yuval): Pass half_thickness to the fragment shader // NOTE(yuval): Pass half_thickness to the fragment shader
out.half_thickness = in.half_thickness; out.half_thickness = in.half_thickness;
// NOTE(yuval): Pass xy to the fragment shader // NOTE(yuval): Pass xy to the fragment shader
out.xy = in.xy; out.xy = in.xy;
return(out); return(out);
} }
//////////////////////////////// ////////////////////////////////
@ -115,32 +120,28 @@ sd = (abs(sd + in.half_thickness) - in.half_thickness);
float shape_value = (1.0 - smoothstep(-1.0, 0.0, sd)); float shape_value = (1.0 - smoothstep(-1.0, 0.0, sd));
shape_value *= has_thickness; shape_value *= has_thickness;
// TOOD(yuval): Add sample_value to alpha // TOOD(yuval): Add sample_value to alpha
float4 out_color = in.color;// float4(in.color.xyz, in.color.a * (shape_value)); float4 out_color = in.color;// float4(in.color.xyz, in.color.a * (shape_value));
return(out_color); return(out_color);
} }
)"; )";
//////////////////////////////// ////////////////////////////////
@implementation Metal_Buffer function Metal_Buffer*
- (instancetype)initWithSize:(u32)size usingDevice:(id<MTLDevice>)device{ metal__make_buffer(u32 size, id<MTLDevice> device){
self = [super init]; Metal_Buffer *result = (Metal_Buffer*)malloc(sizeof(Metal_Buffer));
if (self == nil){
return(nil);
}
// NOTE(yuval): Create the vertex buffer // NOTE(yuval): Create the vertex buffer
MTLResourceOptions options = MTLCPUCacheModeWriteCombined|MTLResourceStorageModeManaged; MTLResourceOptions options = MTLCPUCacheModeWriteCombined|MTLResourceStorageModeManaged;
_buffer = [device newBufferWithLength:size options:options]; result->buffer = [device newBufferWithLength:size options:options];
_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
_last_reuse_time = [NSDate date].timeIntervalSince1970; result->last_reuse_time = system_now_time();
return(self); return result;
} }
@end
//////////////////////////////// ////////////////////////////////
@ -150,8 +151,8 @@ shape_value *= has_thickness;
id<MTLCommandQueue> command_queue; id<MTLCommandQueue> command_queue;
id<MTLCaptureScope> capture_scope; id<MTLCaptureScope> capture_scope;
NSMutableArray<Metal_Buffer*> *buffer_cache; Node buffer_cache;
NSTimeInterval last_buffer_cache_purge_time; u64 last_buffer_cache_purge_time;
} }
- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView*)mtk_view{ - (nonnull instancetype)initWithMetalKitView:(nonnull MTKView*)mtk_view{
@ -182,6 +183,7 @@ shape_value *= has_thickness;
} }
Assert(error == nil); Assert(error == nil);
Assert((vertex_function != nil) && (fragment_function != nil));
// NOTE(yuval): Configure the pipeline descriptor // NOTE(yuval): Configure the pipeline descriptor
{ {
@ -226,8 +228,8 @@ shape_value *= has_thickness;
command_queue = [device newCommandQueue]; command_queue = [device newCommandQueue];
// NOTE(yuval): Initialize buffer caching // NOTE(yuval): Initialize buffer caching
buffer_cache = [NSMutableArray array]; dll_init_sentinel(&buffer_cache);
last_buffer_cache_purge_time = [NSDate date].timeIntervalSince1970; last_buffer_cache_purge_time = system_now_time();
// 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]
@ -249,7 +251,7 @@ shape_value *= has_thickness;
i32 width = _target->width; i32 width = _target->width;
i32 height = _target->height; i32 height = _target->height;
Font_Set* font_set = (Font_Set*)_target->font_set; Font_Set *font_set = (Font_Set*)_target->font_set;
// 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];
@ -297,7 +299,7 @@ shape_value *= has_thickness;
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];
@ -344,7 +346,7 @@ shape_value *= has_thickness;
// 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;
node; node;
@ -356,7 +358,7 @@ shape_value *= has_thickness;
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
@ -367,7 +369,7 @@ shape_value *= has_thickness;
vertexStart:0 vertexStart:0
vertexCount:vertex_count]; vertexCount:vertex_count];
buffer_offset += (vertex_count * sizeof(Render_Vertex)); //((((vertex_count * sizeof(Render_Vertex)) + 256) / 256) * 256); buffer_offset += (vertex_count * sizeof(Render_Vertex));
} }
} }
@ -392,46 +394,53 @@ shape_value *= has_thickness;
- (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
NSTimeInterval now = [NSDate date].timeIntervalSince1970; 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) > 1.0){ if ((now - last_buffer_cache_purge_time) > 1000000){
NSMutableArray *survivors = [NSMutableArray array]; Node prev_buffer_cache = buffer_cache;
for (Metal_Buffer *candidate in buffer_cache){ dll_init_sentinel(&buffer_cache);
if (candidate.last_reuse_time > last_buffer_cache_purge_time){
[survivors addObject:candidate]; for (Node *node = prev_buffer_cache.next;
node != &buffer_cache;
node = node->next){
Metal_Buffer *candidate = CastFromMember(Metal_Buffer, node, node);
if (candidate->last_reuse_time > last_buffer_cache_purge_time){
dll_insert(&buffer_cache, node);
} }
} }
buffer_cache = [survivors mutableCopy];
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 (Metal_Buffer *candidate in buffer_cache){ for (Node *node = buffer_cache.next;
if ((candidate.size >= size) && ((!best_candidate) || (best_candidate.last_reuse_time > candidate.last_reuse_time))){ node != &buffer_cache;
node = node->next){
Metal_Buffer *candidate = CastFromMember(Metal_Buffer, node, node);
if ((candidate->size >= size) && ((!best_candidate) || (best_candidate->last_reuse_time > candidate->last_reuse_time))){
best_candidate = candidate; best_candidate = candidate;
} }
} }
Metal_Buffer *result; Metal_Buffer *result;
if (best_candidate){ if (best_candidate){
[buffer_cache removeObject:best_candidate]; // NOTE(yuval): A best candidate has been found! Remove it from the buffer list and set its last reuse time.
best_candidate.last_reuse_time = now; dll_remove(&best_candidate->node);
best_candidate->last_reuse_time = now;
result = best_candidate; result = best_candidate;
} else{ } else{
result = [[Metal_Buffer alloc] initWithSize:size usingDevice:device]; // NOTE(yuval): No luck; make a new buffer.
result = metal__make_buffer(size, device);
} }
// NOTE(yuval): No luck; make a new buffer
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
[buffer_cache addObject:buffer]; dll_insert(&buffer_cache, &buffer->node);
} }
@end @end

View File

@ -460,8 +460,8 @@ this only gets called for window creation and other extraordinary events.
return(YES); return(YES);
} }
- (void)keyDown:(NSEvent *)event{ - (void)keyDown:(NSEvent*)event{
NSString* characters = [event characters]; NSString *characters = [event characters];
if ([characters length] != 0) { if ([characters length] != 0) {
u32 character_code = [characters characterAtIndex:0]; u32 character_code = [characters characterAtIndex:0];