Basic metal renderer (only rendering a triangle as of right now).

This commit is contained in:
Yuval Dolev 2020-01-04 03:24:52 +02:00
parent 4e0549f270
commit 0fceec19a9
8 changed files with 332 additions and 13 deletions

View File

@ -389,7 +389,7 @@ build(Arena *arena, u32 flags, u32 arch, char *code_path, char **code_files, cha
#define CLANG_LIBS_COMMON \ #define CLANG_LIBS_COMMON \
"-framework Cocoa -framework QuartzCore " \ "-framework Cocoa -framework QuartzCore " \
"-framework CoreServices " \ "-framework CoreServices " \
"-framework OpenGL -framework IOKit " "-framework OpenGL -framework IOKit -framework Metal -framework MetalKit "
#define CLANG_LIBS_X64 CLANG_LIBS_COMMON \ #define CLANG_LIBS_X64 CLANG_LIBS_COMMON \
FOREIGN "/x64/libfreetype-mac.a" FOREIGN "/x64/libfreetype-mac.a"
@ -398,7 +398,7 @@ FOREIGN "/x64/libfreetype-mac.a"
FOREIGN "/x86/libfreetype-mac.a" FOREIGN "/x86/libfreetype-mac.a"
#else #else
# error gcc options not set for this platform # error clang options not set for this platform
#endif #endif
internal void internal void

136
metal/4ed_metal_render.mm Normal file
View File

@ -0,0 +1,136 @@
/* 4coder Metal render implementation */
#undef clamp
#undef function
#import <simd/simd.h>
#import <MetalKit/MetalKit.h>
// 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<MTKViewDelegate>
- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView;
@end
@implementation FCoderMetalRenderer{
id<MTLDevice> _device;
// The render pipeline generated from the vertex and fragment shaders in the .metal shader file.
id<MTLRenderPipelineState> _pipelineState;
// The command queue used to pass commands to the device.
id<MTLCommandQueue> _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<MTLLibrary> defaultLibrary = [_device newLibraryWithFile:@"shaders/AAPLShaders.metallib"
error:&error];
Assert(error == nil);
id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
id<MTLFunction> 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<MTLCommandBuffer> 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<MTLRenderCommandEncoder> 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

33
metal/AAPLShaderTypes.h Normal file
View File

@ -0,0 +1,33 @@
/*
See LICENSE folder for this samples 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 <simd/simd.h>
#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 */

64
metal/AAPLShaders.metal Normal file
View File

@ -0,0 +1,64 @@
/*
See LICENSE folder for this samples licensing information.
Abstract:
Metal shaders used for this sample
*/
#include <metal_stdlib>
#include <simd/simd.h>
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;
}

View File

@ -285,6 +285,9 @@ gl_render(Render_Target *t){
t->free_texture_first = 0; t->free_texture_first = 0;
t->free_texture_last = 0; t->free_texture_last = 0;
u32 all_vertex_count = 0;
u64 begin_draw = system_now_time();
for (Render_Group *group = t->group_first; for (Render_Group *group = t->group_first;
group != 0; group != 0;
group = group->next){ group = group->next){
@ -339,9 +342,18 @@ gl_render(Render_Target *t){
glDisableVertexAttribArray(gpu_program.vertex_c); glDisableVertexAttribArray(gpu_program.vertex_c);
glDisableVertexAttribArray(gpu_program.vertex_ht); 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(); 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 // BOTTOM

View File

@ -273,11 +273,24 @@ mac_to_object(Plat_Handle handle){
#include <OpenGL/gl.h> #include <OpenGL/gl.h>
#import "mac_4ed_opengl.mm" #import "mac_4ed_opengl.mm"
#import "mac_4ed_metal.mm"
#include "4ed_font_provider_freetype.h" #include "4ed_font_provider_freetype.h"
#include "4ed_font_provider_freetype.cpp" #include "4ed_font_provider_freetype.cpp"
#import "mac_4ed_functions.mm" #import "mac_4ed_functions.mm"
////////////////////////////////
global Key_Code keycode_lookup_table[255];
function void
mac_key_code_init(void){
}
//////////////////////////////// ////////////////////////////////
function void function void
@ -326,8 +339,6 @@ mac_resize(NSWindow *window){
//////////////////////////////// ////////////////////////////////
// TODO(yuval): mac_resize(bounds.size.width, bounds.size.height);
@implementation FCoderAppDelegate @implementation FCoderAppDelegate
- (void)applicationDidFinishLaunching:(id)sender{ - (void)applicationDidFinishLaunching:(id)sender{
} }
@ -376,6 +387,13 @@ mac_resize(NSWindow *window){
} }
- (void)drawRect:(NSRect)bounds{ - (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 // NOTE(yuval): Read comment in win32_4ed.cpp's main loop
system_mutex_release(mac_vars.global_frame_mutex); system_mutex_release(mac_vars.global_frame_mutex);
@ -422,7 +440,8 @@ mac_resize(NSWindow *window){
[NSApp terminate:nil]; [NSApp terminate:nil];
} }
mac_gl_render(&target); // mac_gl_render(&target);
mac_metal_render(&target);
mac_vars.first = false; mac_vars.first = false;
@ -670,17 +689,19 @@ main(int arg_count, char **args){
NSView* content_view = [mac_vars.window contentView]; NSView* content_view = [mac_vars.window contentView];
// NOTE(yuval): Initialize the renderer
mac_gl_init(mac_vars.window);
// NOTE(yuval): Create the 4coder view // NOTE(yuval): Create the 4coder view
mac_vars.view = [[FCoderView alloc] init]; mac_vars.view = [[FCoderView alloc] init];
[mac_vars.view setFrame:[content_view bounds]]; [mac_vars.view setFrame:[content_view bounds]];
[mac_vars.view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; [mac_vars.view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
// NOTE(yuval): Display window and view
[content_view addSubview:mac_vars.view]; [content_view addSubview:mac_vars.view];
[mac_vars.window makeKeyAndOrderFront:nil]; [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); mac_resize(w, h);
// //

View File

@ -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));
}

View File

@ -5,6 +5,11 @@
#define GL_FUNC(N,R,P) typedef R (CALL_CONVENTION N##_Function)P; N##_Function *N = 0; #define GL_FUNC(N,R,P) typedef R (CALL_CONVENTION N##_Function)P; N##_Function *N = 0;
#include "mac_4ed_opengl_funcs.h" #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" #include "opengl/4ed_opengl_render.cpp"
@interface OpenGLView : NSOpenGLView @interface OpenGLView : NSOpenGLView
@ -12,8 +17,6 @@
- (void)render:(Render_Target*)target; - (void)render:(Render_Target*)target;
@end @end
global OpenGLView *opengl_view;
@implementation OpenGLView{ @implementation OpenGLView{
b32 glIsInitialized; b32 glIsInitialized;
} }
@ -95,14 +98,35 @@ global OpenGLView *opengl_view;
- (void)render:(Render_Target*)target{ - (void)render:(Render_Target*)target{
Assert(glIsInitialized); Assert(glIsInitialized);
u64 context_lock_begin = system_now_time();
CGLLockContext([[self openGLContext] CGLContextObj]); 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]; [[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); 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]; [[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]); 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 @end
global OpenGLView *opengl_view;
function void function void
mac_gl_init(NSWindow *window){ mac_gl_init(NSWindow *window){
// NOTE(yuval): Create OpenGLView // NOTE(yuval): Create OpenGLView
@ -125,8 +149,8 @@ mac_gl_init(NSWindow *window){
function void function void
mac_gl_render(Render_Target* target){ mac_gl_render(Render_Target* target){
f64 begin_time = system_now_time() / 1000000.0; u64 begin_time = system_now_time();
[opengl_view render:target]; [opengl_view render:target];
f64 end_time = system_now_time() / 1000000.0; u64 end_time = system_now_time();
printf("Render Time: %fs\n", (end_time - begin_time)); printf("Render Time: %fs\n\n", mac_get_time_diff_sec(begin_time, end_time));
} }