4coder/metal/4ed_metal_render.mm

293 lines
11 KiB
Plaintext

/* 4coder Metal render implementation */
#undef clamp
#undef function
#import <simd/simd.h>
#import <MetalKit/MetalKit.h>
#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);
global_const char *metal__shaders_source = R"(
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
////////////////////////////////
typedef struct{
packed_float2 xy;
packed_float3 uvw;
uint32_t color;
float half_thickness;
} Vertex;
// NOTE(yuval): Vertex shader outputs and fragment shader inputs
typedef struct{
// NOTE(yuval): Vertex shader output
float4 position [[position]];
// NOTE(yuval): Fragment shader inputs
float4 color;
float3 uvw;
float2 xy;
float2 adjusted_half_dim;
float half_thickness;
} Rasterizer_Data;
////////////////////////////////
vertex Rasterizer_Data
vertex_shader(uint vertex_id [[vertex_id]], constant Vertex *vertices [[buffer(0)]],
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);
// 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);
// NOTE(yuval): Pass uvw coordinates to the fragment shader
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));
// NOTE(yuval): Pass half_thickness to the fragment shader
out.half_thickness = in->half_thickness;
// NOTE(yuval): Pass xy to the fragment shader
out.xy = in->xy;
return(out);
}
////////////////////////////////
float
rectangle_sd(float2 p, float2 b){
float2 d = (abs(p) - b);
float result = (length(max(d, float2(0.0, 0.0))) + min(max(d.x, d.y), 0.0));
return(result);
}
fragment float4
fragment_shader(Rasterizer_Data in [[stage_in]]){
float has_thickness = step(0.49, in.half_thickness);
// float does_not_have_thickness = (1.0 - has_thickness);
// TODO(yuval): Sample texture here.
float2 center = in.uvw.xy;
float roundness = in.uvw.z;
float sd = rectangle_sd(in.xy - center, in.adjusted_half_dim);
sd = sd - roundness;
sd = (abs(sd + in.half_thickness) - in.half_thickness);
float shape_value = (1.0 - smoothstep(-1.0, 0.0, sd));
shape_value *= has_thickness;
// TOOD(yuval): Add sample_value to alpha
float4 out_color = in.color;// float4(in.color.xyz, in.color.a * (shape_value));
return(out_color);
}
)";
function void
metal_init(Metal_Renderer *renderer, MTKView *view){
NSError *error = nil;
renderer->view = view;
renderer->device = view.device;
// NOTE(yuval): Compile the shaders
id<MTLFunction> vertex_function = nil;
id<MTLFunction> fragment_function = nil;
{
NSString *shaders_source_str = [NSString stringWithUTF8String:metal__shaders_source];
MTLCompileOptions *options = [[MTLCompileOptions alloc] init];
options.fastMathEnabled = YES;
id<MTLLibrary> shader_library = [renderer->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);
// NOTE(yuval): Configure the pipeline descriptor
{
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.colorAttachments[0].blendingEnabled = YES;
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;
renderer->pipeline_state = [renderer->device newRenderPipelineStateWithDescriptor:pipeline_state_descriptor
error:&error];
}
Assert(error == nil);
// NOTE(yuval): Create the command queue
renderer->command_queue = [renderer->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): Create a capture scope for gpu frame capture
renderer->capture_scope = [[MTLCaptureManager sharedCaptureManager]
newCaptureScopeWithDevice:renderer->device];
renderer->capture_scope.label = @"4coder Metal Capture Scope";
}
function void
metal_render(Metal_Renderer *renderer, Render_Target *t){
[renderer->capture_scope beginScope];
i32 width = t->width;
i32 height = t->height;
Font_Set* font_set = (Font_Set*)t->font_set;
// NOTE(yuval): Create the command buffer
id<MTLCommandBuffer> command_buffer = [renderer->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;
if (render_pass_descriptor != nil){
render_pass_descriptor.colorAttachments[0].clearColor = MTLClearColorMake(1.0f, 0.0f, 1.0f, 1.0f);
// NOTE(yuval): Create the render command encoder
id<MTLRenderCommandEncoder> 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:renderer->pipeline_state];
// NOTE(yuval): Calculate and pass in the projection matrix
float left = 0, right = (float)width;
float bottom = (float)height, top = 0;
float near_depth = -1.0f, far_depth = 1.0f;
float proj[16] = {
2.0f / (right - left), 0.0f, 0.0f, 0.0f,
0.0f, 2.0f / (top - bottom), 0.0f, 0.0f,
0.0f, 0.0f, -1.0f / (far_depth - near_depth), 0.0f,
-((right + left) / (right - left)), -((top + bottom) / (top - bottom)),
-(near_depth / (far_depth - near_depth)), 1.0f
};
for (Render_Group *group = t->group_first;
group;
group = group->next){
// NOTE(yuval): Set scissor rect
{
Rect_i32 box = Ri32(group->clip_box);
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);
scissor_rect.height = (y1 - y0);
[render_encoder setScissorRect:scissor_rect];
}
i32 vertex_count = group->vertex_list.vertex_count;
if (vertex_count > 0){
// TODO(yuval): Bind a texture
{
Face* face = font_set_face_from_id(font_set, group->face_id);
if (face != 0){
// TODO(yuval): Bind face texture
} else{
// TODO(yuval): Bind default texture
}
}
// NOTE(yuval): Copy the vertex data to the vertex buffer
{
u8 *cursor = (u8*)[renderer->buffer contents];
for (Render_Vertex_Array_Node *node = group->vertex_list.first;
node;
node = node->next){
i32 size = node->vertex_count * sizeof(*node->vertices);
memcpy(cursor, node->vertices, size);
cursor += size;
}
}
// 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): Draw the vertices
[render_encoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:vertex_count];
}
}
[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 commit];
[renderer->capture_scope endScope];
}