Metal renderer is now working (no textures yet).

master
Yuval Dolev 2020-01-07 02:55:28 +02:00
parent efad772401
commit a34d95b848
2 changed files with 231 additions and 85 deletions

View File

@ -8,17 +8,26 @@
#include "AAPLShaderTypes.h"
#define function static
struct Metal_Renderer{
MTKView *view;
id<MTLDevice> device;
id<MTLRenderPipelineState> pipeline_state;
id<MTLCommandQueue> command_queue;
id<MTLBuffer> buffer;
id<MTLCaptureScope> capture_scope;
};
////////////////////////////////
global_const u32 metal_max_vertices = (1<<16);
// TODO(yuval): Convert this to a struct when I handle my own caching solution
@interface Metal_Buffer : NSObject
@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;
@end
@interface Metal_Renderer : NSObject<MTKViewDelegate>
@property (nonatomic) Render_Target *target;
- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView*)mtkView;
- (Metal_Buffer*)get_reusable_buffer_with_size:(NSUInteger)size;
- (void)add_reusable_buffer:(Metal_Buffer*)buffer;
@end
////////////////////////////////
global_const char *metal__shaders_source = R"(
#include <metal_stdlib>
@ -29,10 +38,10 @@ using namespace metal;
////////////////////////////////
typedef struct{
packed_float2 xy;
packed_float3 uvw;
uint32_t color;
float half_thickness;
float2 xy [[attribute(0)]];
float3 uvw [[attribute(1)]];
uint32_t color [[attribute(2)]];
float half_thickness [[attribute(3)]];
} Vertex;
// NOTE(yuval): Vertex shader outputs and fragment shader inputs
@ -51,33 +60,32 @@ float4 position [[position]];
////////////////////////////////
vertex Rasterizer_Data
vertex_shader(uint vertex_id [[vertex_id]], constant Vertex *vertices [[buffer(0)]],
vertex_shader(Vertex in [[stage_in]],
constant float4x4 &proj [[buffer(1)]]){
constant Vertex *in = &vertices[vertex_id];
Rasterizer_Data out;
// 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
out.color.b = ((float((in->color ) & 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.a = ((float((in->color >> 24u) & 0xFFu)) / 255.0);
out.color.b = ((float((in.color ) & 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.a = ((float((in.color >> 24u) & 0xFFu)) / 255.0);
// NOTE(yuval): Pass uvw coordinates to the fragment shader
out.uvw = in->uvw;
out.uvw = in.uvw;
// NOTE(yuval): Calculate adjusted half dim
float2 center = in->uvw.xy;
float2 half_dim = abs(in->xy - center);
out.adjusted_half_dim = (half_dim - in->uvw.zz + float2(0.5, 0.5));
float2 center = in.uvw.xy;
float2 half_dim = abs(in.xy - center);
out.adjusted_half_dim = (half_dim - in.uvw.zz + float2(0.5, 0.5));
// 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
out.xy = in->xy;
out.xy = in.xy;
return(out);
}
@ -113,12 +121,48 @@ shape_value *= has_thickness;
}
)";
function void
metal_init(Metal_Renderer *renderer, MTKView *view){
////////////////////////////////
@implementation Metal_Buffer
- (instancetype)initWithSize:(u32)size usingDevice:(id<MTLDevice>)device{
self = [super init];
if (self == nil){
return(nil);
}
// NOTE(yuval): Create the vertex buffer
MTLResourceOptions options = MTLCPUCacheModeWriteCombined|MTLResourceStorageModeManaged;
_buffer = [device newBufferWithLength:size options:options];
_size = size;
// NOTE(yuval): Set the last_reuse_time to the current time
_last_reuse_time = [NSDate date].timeIntervalSince1970;
return(self);
}
@end
////////////////////////////////
@implementation Metal_Renderer{
id<MTLDevice> device;
id<MTLRenderPipelineState> pipeline_state;
id<MTLCommandQueue> command_queue;
id<MTLCaptureScope> capture_scope;
NSMutableArray<Metal_Buffer*> *buffer_cache;
NSTimeInterval last_buffer_cache_purge_time;
}
- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView*)mtk_view{
self = [super init];
if (self == nil){
return(nil);
}
NSError *error = nil;
renderer->view = view;
renderer->device = view.device;
device = mtk_view.device;
// NOTE(yuval): Compile the shaders
id<MTLFunction> vertex_function = nil;
@ -129,7 +173,7 @@ metal_init(Metal_Renderer *renderer, MTKView *view){
MTLCompileOptions *options = [[MTLCompileOptions alloc] init];
options.fastMathEnabled = YES;
id<MTLLibrary> shader_library = [renderer->device newLibraryWithSource:shaders_source_str
id<MTLLibrary> 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"];
@ -141,55 +185,78 @@ metal_init(Metal_Renderer *renderer, MTKView *view){
// NOTE(yuval): Configure the pipeline descriptor
{
MTLVertexDescriptor *vertexDescriptor = [MTLVertexDescriptor vertexDescriptor];
vertexDescriptor.attributes[0].offset = OffsetOfMember(Render_Vertex, xy);
vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position
vertexDescriptor.attributes[0].bufferIndex = 0;
vertexDescriptor.attributes[1].offset = OffsetOfMember(Render_Vertex, uvw);
vertexDescriptor.attributes[1].format = MTLVertexFormatFloat3; // texCoords
vertexDescriptor.attributes[1].bufferIndex = 0;
vertexDescriptor.attributes[2].offset = OffsetOfMember(Render_Vertex, color);
vertexDescriptor.attributes[2].format = MTLVertexFormatUInt; // color
vertexDescriptor.attributes[2].bufferIndex = 0;
vertexDescriptor.attributes[3].offset = OffsetOfMember(Render_Vertex, half_thickness);
vertexDescriptor.attributes[3].format = MTLVertexFormatFloat; // position
vertexDescriptor.attributes[3].bufferIndex = 0;
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;
pipeline_state_descriptor.fragmentFunction = fragment_function;
pipeline_state_descriptor.colorAttachments[0].pixelFormat = view.colorPixelFormat;
pipeline_state_descriptor.vertexDescriptor = vertexDescriptor;
pipeline_state_descriptor.colorAttachments[0].pixelFormat = mtk_view.colorPixelFormat;
pipeline_state_descriptor.colorAttachments[0].blendingEnabled = YES;
pipeline_state_descriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
pipeline_state_descriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
pipeline_state_descriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
pipeline_state_descriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
pipeline_state_descriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
pipeline_state_descriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
/*pipeline_state_descriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
pipeline_state_descriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;*/
renderer->pipeline_state = [renderer->device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor
pipeline_state = [device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor
error:&error];
}
Assert(error == nil);
// NOTE(yuval): Create the command queue
renderer->command_queue = [renderer->device newCommandQueue];
command_queue = [device newCommandQueue];
// NOTE(yuval): Create the vertex buffer
{
u32 buffer_size = (metal_max_vertices * sizeof(Render_Vertex));
MTLResourceOptions options = MTLCPUCacheModeWriteCombined|MTLResourceStorageModeManaged;
renderer->buffer = [renderer->device newBufferWithLength:buffer_size
options:options];
}
// NOTE(yuval): Initialize buffer caching
buffer_cache = [NSMutableArray array];
last_buffer_cache_purge_time = [NSDate date].timeIntervalSince1970;
// NOTE(yuval): Create a capture scope for gpu frame capture
renderer->capture_scope = [[MTLCaptureManager sharedCaptureManager]
newCaptureScopeWithDevice:renderer->device];
renderer->capture_scope.label = @"4coder Metal Capture Scope";
capture_scope = [[MTLCaptureManager sharedCaptureManager]
newCaptureScopeWithDevice:device];
capture_scope.label = @"4coder Metal Capture Scope";
return(self);
}
function void
metal_render(Metal_Renderer *renderer, Render_Target *t){
[renderer->capture_scope beginScope];
- (void)mtkView:(nonnull MTKView*)view drawableSizeWillChange:(CGSize)size{
// NOTE(yuval): Nothing to do here because we use the render target's dimentions for rendering
}
- (void)drawInMTKView:(nonnull MTKView*)view{
printf("Metal Renderer Draw!\n");
i32 width = t->width;
i32 height = t->height;
[capture_scope beginScope];
Font_Set* font_set = (Font_Set*)t->font_set;
i32 width = _target->width;
i32 height = _target->height;
Font_Set* font_set = (Font_Set*)_target->font_set;
// NOTE(yuval): Create the command buffer
id<MTLCommandBuffer> command_buffer = [renderer->command_queue commandBuffer];
id<MTLCommandBuffer> 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 = renderer->view.currentRenderPassDescriptor;
MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor;
if (render_pass_descriptor != nil){
render_pass_descriptor.colorAttachments[0].clearColor = MTLClearColorMake(1.0f, 0.0f, 1.0f, 1.0f);
@ -202,7 +269,7 @@ metal_render(Metal_Renderer *renderer, Render_Target *t){
[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:renderer->pipeline_state];
[render_encoder setRenderPipelineState:pipeline_state];
// NOTE(yuval): Calculate and pass in the projection matrix
float left = 0, right = (float)width;
@ -216,22 +283,44 @@ metal_render(Metal_Renderer *renderer, Render_Target *t){
-(near_depth / (far_depth - near_depth)), 1.0f
};
for (Render_Group *group = t->group_first;
// NOTE(yuval): Calculate required vertex buffer size
i32 all_vertex_count = 0;
for (Render_Group *group = _target->group_first;
group;
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;
i32 count = 0;
for (Render_Group *group = _target->group_first;
group;
group = group->next, ++count){
// 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;
CGSize frame = [renderer->view drawableSize];
printf("Drawable Size - w:%f h:%f\n", frame.width, frame.height);
NSUInteger x0 = (NSUInteger)Min(Max(0, box.x0), frame.width - 1);
NSUInteger x1 = (NSUInteger)Min(Max(0, box.x1), frame.width);
NSUInteger y0 = (NSUInteger)Min(Max(0, box.y0), frame.height - 1);
NSUInteger y1 = (NSUInteger)Min(Max(0, box.y1), frame.height);
scissor_rect.x = x0;
scissor_rect.y = y0;
scissor_rect.width = (x1 - x0);
@ -254,7 +343,9 @@ metal_render(Metal_Renderer *renderer, Render_Target *t){
// NOTE(yuval): Copy the vertex data to the vertex buffer
{
u8 *cursor = (u8*)[renderer->buffer contents];
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;
node;
node = node->next){
@ -262,32 +353,85 @@ metal_render(Metal_Renderer *renderer, Render_Target *t){
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): Pass the vertex buffer to the vertex shader
[render_encoder setVertexBuffer:renderer->buffer
offset:0
atIndex:0];
// NOTE(yuval): Pass the projection matrix to the vertex shader
[render_encoder setVertexBytes:&proj
length:sizeof(proj)
atIndex:1];
// 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)); //((((vertex_count * sizeof(Render_Vertex)) + 256) / 256) * 256);
}
}
[render_encoder endEncoding];
// NOTE(yuval): Schedule a present once the framebuffer is complete using the current drawable
[command_buffer presentDrawable:renderer->view.currentDrawable];
[command_buffer presentDrawable:view.currentDrawable];
[command_buffer addCompletedHandler:^(id<MTLCommandBuffer>){
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];
[renderer->capture_scope endScope];
}
[capture_scope endScope];
}
- (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
NSTimeInterval now = [NSDate date].timeIntervalSince1970;
// NOTE(yuval): Purge old buffers that haven't been useful for a while
if ((now - last_buffer_cache_purge_time) > 1.0){
NSMutableArray *survivors = [NSMutableArray array];
for (Metal_Buffer *candidate in buffer_cache){
if (candidate.last_reuse_time > last_buffer_cache_purge_time){
[survivors addObject:candidate];
}
}
buffer_cache = [survivors mutableCopy];
last_buffer_cache_purge_time = now;
}
// NOTE(yuval): See if we have a buffer we can reuse
Metal_Buffer *best_candidate = 0;
for (Metal_Buffer *candidate in buffer_cache){
if ((candidate.size >= size) && ((!best_candidate) || (best_candidate.last_reuse_time > candidate.last_reuse_time))){
best_candidate = candidate;
}
}
Metal_Buffer *result;
if (best_candidate){
[buffer_cache removeObject:best_candidate];
best_candidate.last_reuse_time = now;
result = best_candidate;
} else{
result = [[Metal_Buffer alloc] initWithSize:size usingDevice:device];
}
// NOTE(yuval): No luck; make a new buffer
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
[buffer_cache addObject:buffer];
}
@end

View File

@ -1,6 +1,6 @@
#import "metal/4ed_metal_render.mm"
global Metal_Renderer metal_renderer;
global Metal_Renderer *metal_renderer;
global MTKView *metal_view;
function void
@ -10,22 +10,24 @@ mac_metal_init(NSWindow *window){
metal_view = [[MTKView alloc] initWithFrame:[content_view bounds]];
[metal_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[metal_view setPaused:YES];
[metal_view setEnableSetNeedsDisplay:NO];
metal_view.device = MTLCreateSystemDefaultDevice();
// NOTE(yuval): Add the Metal view as a subview of the window
[content_view addSubview:metal_view];
// NOTE(yuval): Create the Metal renderer
//metal_renderer = [[FCoderMetalRenderer alloc] initWithMetalKitView:metal_view];
metal_init(&metal_renderer, metal_view);
// NOTE(yuval): Create the Metal renderer and set it as the Metal view's delegate
metal_renderer = [[Metal_Renderer alloc] initWithMetalKitView:metal_view];
metal_view.delegate = metal_renderer;
}
function void
mac_metal_render(Render_Target* target){
u64 begin_time = system_now_time();
//[metal_renderer drawInMTKView:metal_view];
metal_render(&metal_renderer, target);
metal_renderer.target = target;
[metal_view draw];
u64 end_time = system_now_time();
printf("Metal Render Time: %fs\n\n", mac_get_time_diff_sec(begin_time, end_time));
}