From 0fceec19a9cf4ffa988c24c356b04b8410c6e0f7 Mon Sep 17 00:00:00 2001 From: Yuval Dolev Date: Sat, 4 Jan 2020 03:24:52 +0200 Subject: [PATCH] Basic metal renderer (only rendering a triangle as of right now). --- bin/4ed_build.cpp | 4 +- metal/4ed_metal_render.mm | 136 +++++++++++++++++++++++++++++++++ metal/AAPLShaderTypes.h | 33 ++++++++ metal/AAPLShaders.metal | 64 ++++++++++++++++ opengl/4ed_opengl_render.cpp | 12 +++ platform_mac/mac_4ed.mm | 33 ++++++-- platform_mac/mac_4ed_metal.mm | 29 +++++++ platform_mac/mac_4ed_opengl.mm | 34 +++++++-- 8 files changed, 332 insertions(+), 13 deletions(-) create mode 100644 metal/4ed_metal_render.mm create mode 100644 metal/AAPLShaderTypes.h create mode 100644 metal/AAPLShaders.metal create mode 100644 platform_mac/mac_4ed_metal.mm diff --git a/bin/4ed_build.cpp b/bin/4ed_build.cpp index 03a9a1b2..0a98abdb 100644 --- a/bin/4ed_build.cpp +++ b/bin/4ed_build.cpp @@ -389,7 +389,7 @@ build(Arena *arena, u32 flags, u32 arch, char *code_path, char **code_files, cha #define CLANG_LIBS_COMMON \ "-framework Cocoa -framework QuartzCore " \ "-framework CoreServices " \ -"-framework OpenGL -framework IOKit " +"-framework OpenGL -framework IOKit -framework Metal -framework MetalKit " #define CLANG_LIBS_X64 CLANG_LIBS_COMMON \ FOREIGN "/x64/libfreetype-mac.a" @@ -398,7 +398,7 @@ FOREIGN "/x64/libfreetype-mac.a" FOREIGN "/x86/libfreetype-mac.a" #else -# error gcc options not set for this platform +# error clang options not set for this platform #endif internal void diff --git a/metal/4ed_metal_render.mm b/metal/4ed_metal_render.mm new file mode 100644 index 00000000..e715a8fe --- /dev/null +++ b/metal/4ed_metal_render.mm @@ -0,0 +1,136 @@ +/* 4coder Metal render implementation */ + +#undef clamp +#undef function +#import +#import + +// Header shared between C code here, which executes Metal API commands, and .metal files, which +// uses these types as inputs to the shaders. +#import "AAPLShaderTypes.h" + +#define function static +#define clamp(a,x,b) clamp_((a),(x),(b)) + +@interface FCoderMetalRenderer : NSObject +- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView; +@end + +@implementation FCoderMetalRenderer{ + id _device; + + // The render pipeline generated from the vertex and fragment shaders in the .metal shader file. + id _pipelineState; + + // The command queue used to pass commands to the device. + id _commandQueue; + + // The current size of the view, used as an input to the vertex shader. + vector_uint2 _viewportSize; +} + +- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView{ + self = [super init]; + if(self) + { + NSError *error = nil; + + _device = mtkView.device; + + // Load all the shader files with a .metal file extension in the project. + id defaultLibrary = [_device newLibraryWithFile:@"shaders/AAPLShaders.metallib" + error:&error]; + Assert(error == nil); + + id vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"]; + id fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"]; + + // Configure a pipeline descriptor that is used to create a pipeline state. + MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineStateDescriptor.label = @"Simple Pipeline"; + pipelineStateDescriptor.vertexFunction = vertexFunction; + pipelineStateDescriptor.fragmentFunction = fragmentFunction; + pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat; + + _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor + error:&error]; + + // Pipeline State creation could fail if the pipeline descriptor isn't set up properly. + // If the Metal API validation is enabled, you can find out more information about what + // went wrong. (Metal API validation is enabled by default when a debug build is run + // from Xcode.) + NSAssert(_pipelineState, @"Failed to created pipeline state: %@", error); + + // Create the command queue + _commandQueue = [_device newCommandQueue]; + + u32 max_buffer_size = (u32)[_device maxBufferLength]; + printf("Max Buffer Size: %u - Which is %lu vertices\n", max_buffer_size, (max_buffer_size / sizeof(Render_Vertex))); + } + + return self; +} + +/// Called whenever view changes orientation or is resized +- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size{ + // Save the size of the drawable to pass to the vertex shader. + +} + +/// Called whenever the view needs to render a frame. +- (void)drawInMTKView:(nonnull MTKView *)view{ + CGSize size = [view drawableSize]; + _viewportSize.x = size.width; + _viewportSize.y = size.height; + + static const AAPLVertex triangleVertices[] = + { + // 2D positions, RGBA colors + { { 250, -250 }, { 1, 0, 0, 1 } }, + { { -250, -250 }, { 0, 1, 0, 1 } }, + { { 0, 250 }, { 0, 0, 1, 1 } }, + }; + + // Create a new command buffer for each render pass to the current drawable. + id commandBuffer = [_commandQueue commandBuffer]; + commandBuffer.label = @"MyCommand"; + + // Obtain a renderPassDescriptor generated from the view's drawable textures. + MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor; + + if(renderPassDescriptor != nil) + { + // Create a render command encoder. + id renderEncoder = + [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + renderEncoder.label = @"MyRenderEncoder"; + + // Set the region of the drawable to draw into. + [renderEncoder setViewport:(MTLViewport){0.0, 0.0, (double)_viewportSize.x, (double)_viewportSize.y, 0.0, 1.0 }]; + + [renderEncoder setRenderPipelineState:_pipelineState]; + + // Pass in the parameter data. + [renderEncoder setVertexBytes:triangleVertices + length:sizeof(triangleVertices) + atIndex:AAPLVertexInputIndexVertices]; + + [renderEncoder setVertexBytes:&_viewportSize + length:sizeof(_viewportSize) + atIndex:AAPLVertexInputIndexViewportSize]; + + // Draw the triangle. + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle + vertexStart:0 + vertexCount:3]; + + [renderEncoder endEncoding]; + + // Schedule a present once the framebuffer is complete using the current drawable. + [commandBuffer presentDrawable:view.currentDrawable]; + } + + // Finalize rendering here & push the command buffer to the GPU. + [commandBuffer commit]; +} +@end \ No newline at end of file diff --git a/metal/AAPLShaderTypes.h b/metal/AAPLShaderTypes.h new file mode 100644 index 00000000..be46a81a --- /dev/null +++ b/metal/AAPLShaderTypes.h @@ -0,0 +1,33 @@ +/* +See LICENSE folder for this sample’s licensing information. + +Abstract: +Header containing types and enum constants shared between Metal shaders and C/ObjC source +*/ + +#ifndef AAPLShaderTypes_h +#define AAPLShaderTypes_h + +#undef clamp +#include +#define clamp(a,x,b) clamp_((a),(x),(b)) + +// Buffer index values shared between shader and C code to ensure Metal shader buffer inputs +// match Metal API buffer set calls. +typedef enum AAPLVertexInputIndex +{ + AAPLVertexInputIndexVertices = 0, + AAPLVertexInputIndexViewportSize = 1, +} AAPLVertexInputIndex; + +// This structure defines the layout of vertices sent to the vertex +// shader. This header is shared between the .metal shader and C code, to guarantee that +// the layout of the vertex array in the C code matches the layout that the .metal +// vertex shader expects. +typedef struct +{ + vector_float2 position; + vector_float4 color; +} AAPLVertex; + +#endif /* AAPLShaderTypes_h */ diff --git a/metal/AAPLShaders.metal b/metal/AAPLShaders.metal new file mode 100644 index 00000000..60c3736b --- /dev/null +++ b/metal/AAPLShaders.metal @@ -0,0 +1,64 @@ +/* +See LICENSE folder for this sample’s licensing information. + +Abstract: +Metal shaders used for this sample +*/ + +#include +#include + +using namespace metal; + +// Include header shared between this Metal shader code and C code executing Metal API commands. +#import "AAPLShaderTypes.h" + +// Vertex shader outputs and fragment shader inputs +typedef struct +{ + // The [[position]] attribute of this member indicates that this value + // is the clip space position of the vertex when this structure is + // returned from the vertex function. + float4 position [[position]]; + + // Since this member does not have a special attribute, the rasterizer + // interpolates its value with the values of the other triangle vertices + // and then passes the interpolated value to the fragment shader for each + // fragment in the triangle. + float4 color; + +} RasterizerData; + +vertex RasterizerData +vertexShader(uint vertexID [[vertex_id]], + constant AAPLVertex *vertices [[buffer(AAPLVertexInputIndexVertices)]], + constant vector_uint2 *viewportSizePointer [[buffer(AAPLVertexInputIndexViewportSize)]]) +{ + RasterizerData out; + + // Index into the array of positions to get the current vertex. + // The positions are specified in pixel dimensions (i.e. a value of 100 + // is 100 pixels from the origin). + float2 pixelSpacePosition = vertices[vertexID].position.xy; + + // Get the viewport size and cast to float. + vector_float2 viewportSize = vector_float2(*viewportSizePointer); + + + // To convert from positions in pixel space to positions in clip-space, + // divide the pixel coordinates by half the size of the viewport. + out.position = vector_float4(0.0, 0.0, 0.0, 1.0); + out.position.xy = pixelSpacePosition / (viewportSize / 2.0); + + // Pass the input color directly to the rasterizer. + out.color = vertices[vertexID].color; + + return out; +} + +fragment float4 fragmentShader(RasterizerData in [[stage_in]]) +{ + // Return the interpolated color. + return in.color; +} + diff --git a/opengl/4ed_opengl_render.cpp b/opengl/4ed_opengl_render.cpp index e3413ae0..7c2d5524 100644 --- a/opengl/4ed_opengl_render.cpp +++ b/opengl/4ed_opengl_render.cpp @@ -285,6 +285,9 @@ gl_render(Render_Target *t){ t->free_texture_first = 0; t->free_texture_last = 0; + u32 all_vertex_count = 0; + + u64 begin_draw = system_now_time(); for (Render_Group *group = t->group_first; group != 0; group = group->next){ @@ -339,9 +342,18 @@ gl_render(Render_Target *t){ glDisableVertexAttribArray(gpu_program.vertex_c); glDisableVertexAttribArray(gpu_program.vertex_ht); } + + all_vertex_count += vertex_count; } + u64 end_draw = system_now_time(); + printf("Draw time: %fs\n", mac_get_time_diff_sec(begin_draw, end_draw)); + u64 begin_flush = system_now_time(); glFlush(); + u64 end_flush = system_now_time(); + printf("Flush time: %fs\n", mac_get_time_diff_sec(begin_flush, end_flush)); + + printf("Drawn %d Vertices\n", all_vertex_count); } // BOTTOM diff --git a/platform_mac/mac_4ed.mm b/platform_mac/mac_4ed.mm index 958d1399..cae3a944 100644 --- a/platform_mac/mac_4ed.mm +++ b/platform_mac/mac_4ed.mm @@ -273,11 +273,24 @@ mac_to_object(Plat_Handle handle){ #include #import "mac_4ed_opengl.mm" +#import "mac_4ed_metal.mm" + #include "4ed_font_provider_freetype.h" #include "4ed_font_provider_freetype.cpp" #import "mac_4ed_functions.mm" + + +//////////////////////////////// + +global Key_Code keycode_lookup_table[255]; + +function void +mac_key_code_init(void){ + +} + //////////////////////////////// function void @@ -326,8 +339,6 @@ mac_resize(NSWindow *window){ //////////////////////////////// -// TODO(yuval): mac_resize(bounds.size.width, bounds.size.height); - @implementation FCoderAppDelegate - (void)applicationDidFinishLaunching:(id)sender{ } @@ -376,6 +387,13 @@ mac_resize(NSWindow *window){ } - (void)drawRect:(NSRect)bounds{ + /* NOTE(yuval): Force the graphics context to clear to black so we don't +get a flash of white until the app is ready to draw. In practice on modern macOS, +this only gets called for window creation and other extraordinary events. +(Taken From SDL) */ + [[NSColor blackColor] setFill]; + NSRectFill(bounds); + // NOTE(yuval): Read comment in win32_4ed.cpp's main loop system_mutex_release(mac_vars.global_frame_mutex); @@ -422,7 +440,8 @@ mac_resize(NSWindow *window){ [NSApp terminate:nil]; } - mac_gl_render(&target); + // mac_gl_render(&target); + mac_metal_render(&target); mac_vars.first = false; @@ -670,17 +689,19 @@ main(int arg_count, char **args){ NSView* content_view = [mac_vars.window contentView]; - // NOTE(yuval): Initialize the renderer - mac_gl_init(mac_vars.window); - // NOTE(yuval): Create the 4coder view mac_vars.view = [[FCoderView alloc] init]; [mac_vars.view setFrame:[content_view bounds]]; [mac_vars.view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + // NOTE(yuval): Display window and view [content_view addSubview:mac_vars.view]; [mac_vars.window makeKeyAndOrderFront:nil]; + // NOTE(yuval): Initialize the renderer + mac_gl_init(mac_vars.window); + mac_metal_init(mac_vars.window); + mac_resize(w, h); // diff --git a/platform_mac/mac_4ed_metal.mm b/platform_mac/mac_4ed_metal.mm new file mode 100644 index 00000000..05ed482e --- /dev/null +++ b/platform_mac/mac_4ed_metal.mm @@ -0,0 +1,29 @@ +#import "metal/4ed_metal_render.mm" + +global MTKView *metal_view; +global FCoderMetalRenderer *metal_renderer; + +function void +mac_metal_init(NSWindow *window){ + // NOTE(yuval): Create Metal view + NSView *content_view = [window contentView]; + + metal_view = [[MTKView alloc] initWithFrame:[content_view bounds]]; + [metal_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + + 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]; +} + +function void +mac_metal_render(Render_Target* target){ + u64 begin_time = system_now_time(); + [metal_renderer drawInMTKView:metal_view]; + u64 end_time = system_now_time(); + printf("Metal Render Time: %fs\n\n", mac_get_time_diff_sec(begin_time, end_time)); +} \ No newline at end of file diff --git a/platform_mac/mac_4ed_opengl.mm b/platform_mac/mac_4ed_opengl.mm index dd89c079..ba1f4dee 100644 --- a/platform_mac/mac_4ed_opengl.mm +++ b/platform_mac/mac_4ed_opengl.mm @@ -5,6 +5,11 @@ #define GL_FUNC(N,R,P) typedef R (CALL_CONVENTION N##_Function)P; N##_Function *N = 0; #include "mac_4ed_opengl_funcs.h" +f64 mac_get_time_diff_sec(u64 begin, u64 end){ + f64 result = ((end - begin) / 1000000.0); + return result; +} + #include "opengl/4ed_opengl_render.cpp" @interface OpenGLView : NSOpenGLView @@ -12,8 +17,6 @@ - (void)render:(Render_Target*)target; @end -global OpenGLView *opengl_view; - @implementation OpenGLView{ b32 glIsInitialized; } @@ -95,14 +98,35 @@ global OpenGLView *opengl_view; - (void)render:(Render_Target*)target{ Assert(glIsInitialized); + u64 context_lock_begin = system_now_time(); CGLLockContext([[self openGLContext] CGLContextObj]); + u64 context_lock_end = system_now_time(); + printf("Context lock time: %fs\n", mac_get_time_diff_sec(context_lock_begin, context_lock_end)); + + u64 make_current_context_begin = system_now_time(); [[self openGLContext] makeCurrentContext]; + u64 make_current_context_end = system_now_time(); + printf("Make current context time: %fs\n", mac_get_time_diff_sec(make_current_context_begin, make_current_context_end)); + + u64 gl_render_begin = system_now_time(); gl_render(target); + u64 gl_render_end = system_now_time(); + printf("GL render time: %fs\n", mac_get_time_diff_sec(gl_render_begin, gl_render_end)); + + u64 gl_flush_buffer_begin = system_now_time(); [[self openGLContext] flushBuffer]; + u64 gl_flush_buffer_end = system_now_time(); + printf("GL flush buffer time: %fs\n", mac_get_time_diff_sec(gl_flush_buffer_begin, gl_flush_buffer_end)); + + u64 context_unlock_begin = system_now_time(); CGLUnlockContext([[self openGLContext] CGLContextObj]); + u64 context_unlock_end = system_now_time(); + printf("Context unlock time: %fs\n", mac_get_time_diff_sec(context_unlock_begin, context_unlock_end)); } @end +global OpenGLView *opengl_view; + function void mac_gl_init(NSWindow *window){ // NOTE(yuval): Create OpenGLView @@ -125,8 +149,8 @@ mac_gl_init(NSWindow *window){ function void mac_gl_render(Render_Target* target){ - f64 begin_time = system_now_time() / 1000000.0; + u64 begin_time = system_now_time(); [opengl_view render:target]; - f64 end_time = system_now_time() / 1000000.0; - printf("Render Time: %fs\n", (end_time - begin_time)); + u64 end_time = system_now_time(); + printf("Render Time: %fs\n\n", mac_get_time_diff_sec(begin_time, end_time)); } \ No newline at end of file