diff options
author | omniscient <17525998+omnisci3nce@users.noreply.github.com> | 2024-10-17 19:18:38 +1100 |
---|---|---|
committer | omniscient <17525998+omnisci3nce@users.noreply.github.com> | 2024-10-17 19:18:38 +1100 |
commit | c557763010a8976680f37609ca10e666ff849cd9 (patch) | |
tree | f2fa05b3e45c5fdc3565d654d485b2c46717324e | |
parent | 3e1aea0243f54e0b68baa3b19ac19f3d965484e0 (diff) |
metal triangle!
-rw-r--r-- | Makefile | 17 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | assets/shaders/triangle.metal | 38 | ||||
-rw-r--r-- | examples/triangle.c | 46 | ||||
-rw-r--r-- | include/celeritas.h | 77 | ||||
-rw-r--r-- | src/backend_mtl.m | 150 | ||||
-rw-r--r-- | src/core.c | 13 | ||||
-rw-r--r-- | src/log.c | 2 | ||||
-rw-r--r-- | src/mem.c | 89 |
9 files changed, 387 insertions, 47 deletions
@@ -18,6 +18,11 @@ EXAMPLES_DIR := examples SRCS := $(wildcard $(SRC_DIR)/*.c $(SRC_DIR)/**/*.c) OBJS := $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(SRCS)) +# Shader files +METAL_SHADERS := $(wildcard $(SHADER_DIR)/*.metal) +METAL_AIR_FILES := $(patsubst $(SHADER_DIR)/%.metal,$(SHADER_OUT_DIR)/%.air,$(METAL_SHADERS)) +METAL_LIB := $(SHADER_OUT_DIR)/default.metallib + # Library outputs STATIC_LIB := $(BUILD_DIR)/libceleritas.a ifeq ($(UNAME_S),Darwin) @@ -57,13 +62,21 @@ shared: $(SHARED_LIB) static: $(STATIC_LIB) +# Shaders +$(SHADER_OUT_DIR)/%.air: $(SHADER_DIR)/%.metal + @mkdir -p $(SHADER_OUT_DIR) + xcrun -sdk macosx metal -c $< -o $@ + +$(METAL_LIB): $(METAL_AIR_FILES) + xcrun -sdk macosx metallib $^ -o $(SHADER_OUT_DIR)/default.metallib + .PHONY: all all: shared static .PHONY: triangle -triangle: $(EXAMPLES_DIR)/triangle.c $(SHARED_LIB) +triangle: $(EXAMPLES_DIR)/triangle.c $(SHARED_LIB) $(SHADER_OUT_DIR)/triangle.air $(METAL_LIB) @mkdir -p $(BUILD_DIR) - $(CC) $(CFLAGS) $(EXAMPLES_DIR)/triangle.c -L$(BUILD_DIR) -lceleritas $(LDFLAGS) + $(CC) $(CFLAGS) $(EXAMPLES_DIR)/triangle.c -L$(BUILD_DIR) -lceleritas $(LDFLAGS) -o $(BUILD_DIR)/triangle.bin MTL_DEBUG_LAYER=1 build/triangle.bin .PHONY: clean @@ -37,4 +37,4 @@ Renderer Goals: - Check symbols in an 'archive' (static library) - `nm -C build/libcore.a` - Generate compiler_commands.json - - `xmake project -k compile_commands` + - `bear -- make` diff --git a/assets/shaders/triangle.metal b/assets/shaders/triangle.metal index 6055705..6522360 100644 --- a/assets/shaders/triangle.metal +++ b/assets/shaders/triangle.metal @@ -1,33 +1,17 @@ #include <metal_stdlib> - using namespace metal; -struct VertexIn { - float2 position; - float3 color; -}; - -struct VertexOut { - float4 computedPosition [[position]]; - float3 fragColor; -}; - -// Vertex shader -vertex VertexOut basic_vertex( - const device VertexIn* vertex_array [[ buffer(0) ]], - unsigned int vid [[ vertex_id ]] - ) { - VertexIn v = vertex_array[vid]; - - VertexOut outVertex = VertexOut(); - outVertex.computedPosition = float4(v.position.xy, 0.0, 1.0); - outVertex.fragColor = v.color; - return outVertex; +vertex float4 +vertexShader(uint vertexID [[vertex_id]], + constant simd::float3* vertexPositions) +{ + float4 vertexOutPositions = float4(vertexPositions[vertexID][0], + vertexPositions[vertexID][1], + vertexPositions[vertexID][2], + 1.0f); + return vertexOutPositions; } -// Fragment shader -fragment float4 basic_fragment( - VertexOut interpolated [[stage_in]] -) { - return float4(interpolated.fragColor, 1.0); +fragment float4 fragmentShader(float4 vertexOutPositions [[stage_in]]) { + return float4(182.0f/255.0f, 240.0f/255.0f, 228.0f/255.0f, 1.0f); }
\ No newline at end of file diff --git a/examples/triangle.c b/examples/triangle.c index ccbcea3..c304562 100644 --- a/examples/triangle.c +++ b/examples/triangle.c @@ -2,11 +2,57 @@ #include <celeritas.h> +static vec4 vertices[] = { + {-0.5f, -0.5f, 0.0f, 1.0}, + { 0.5f, -0.5f, 0.0f, 1.0}, + { 0.0f, 0.5f, 0.0f, 1.0} +}; + +pipeline_handle draw_pipeline; +buf_handle tri_vert_buffer; + +void draw() { + render_pass_desc d = {}; + gpu_encoder* enc = ral_render_encoder(d); + ral_encode_bind_pipeline(enc, draw_pipeline); + ral_encode_set_vertex_buf(enc, tri_vert_buffer); + ral_encode_draw_tris(enc, 0, 3); + ral_encoder_finish_and_submit(enc); +} + int main() { core_bringup("Celeritas Example: Triangle", NULL); + // create rendering pipeline + gfx_pipeline_desc pipeline_desc = { + .label = "Triangle drawing pipeline", + .vertex_desc = NULL, // TODO + .vertex = { + .source = NULL, + .is_spirv = false, + .entry_point = "vertexShader", + .shader_stage = VISIBILITY_VERTEX, + }, + .fragment = { + .source = NULL, + .is_spirv = false, + .entry_point = "fragmentShader", + .shader_stage = VISIBILITY_FRAGMENT, + }, + }; + + draw_pipeline = ral_gfx_pipeline_create(pipeline_desc); + + // create our buffer to hold vertices + printf("size of vertices %ld\n", sizeof(vec4) * 3); + tri_vert_buffer = ral_buffer_create(sizeof(vec4) * 3, &vertices); + while (!app_should_exit()) { glfwPollEvents(); + + ral_frame_start(); + ral_frame_draw(&draw); + ral_frame_end(); } return 0; diff --git a/include/celeritas.h b/include/celeritas.h index c157918..d4dab98 100644 --- a/include/celeritas.h +++ b/include/celeritas.h @@ -6,6 +6,8 @@ #include <stddef.h> #include <stdint.h> #include <stdio.h> +#include <assert.h> +#include <string.h> // Third party dependency includes #include <glfw3.h> @@ -127,7 +129,28 @@ void* void_pool_alloc(void_pool* pool, u32* out_raw_handle); void void_pool_dealloc(void_pool* pool, u32 raw_handle); u32 void_pool_insert(void_pool* pool, void* item); -// TODO: Typed pool +#define TYPED_POOL(T, Name) \ + typedef struct Name##_pool { \ + void_pool inner; \ + } Name##_pool; \ + \ + static Name##_pool Name##_pool_create(void* storage, u64 cap, u64 entry_size) { \ + void_pool p = void_pool_create(storage, "\"" #Name "\"", cap, entry_size); \ + return (Name##_pool){ .inner = p }; \ + } \ + static inline T* Name##_pool_get(Name##_pool* pool, Name##_handle handle) { \ + return (T*)void_pool_get(&pool->inner, handle.raw); \ + } \ + static inline T* Name##_pool_alloc(Name##_pool* pool, Name##_handle* out_handle) { \ + return (T*)void_pool_alloc(&pool->inner, &out_handle->raw); \ + } \ + static inline void Name##_pool_dealloc(Name##_pool* pool, Name##_handle handle) { \ + void_pool_dealloc(&pool->inner, handle.raw); \ + } \ + static Name##_handle Name##_pool_insert(Name##_pool* pool, T* item) { \ + u32 raw_handle = void_pool_insert(pool, item); \ + return (Name##_handle){ .raw = raw_handle }; \ + } // --- Strings @@ -234,15 +257,15 @@ inlined vec3 vec3_div(vec3 u, f32 s); DEFINE_HANDLE(buf_handle); DEFINE_HANDLE(tex_handle); DEFINE_HANDLE(pipeline_handle); +DEFINE_HANDLE(compute_pipeline_handle); #define MAX_VERTEX_ATTRIBUTES 16 #define MAX_SHADER_BINDINGS 16 // Backend-specific structs typedef struct gpu_swapchain gpu_swapchain; -typedef struct gpu_compute_pipeline gpu_compute_pipeline; -typedef struct gpu_gfx_pipeline gpu_gfx_pipeline; -typedef struct gpu_encoder gpu_encoder; // Command encoder +typedef struct gpu_encoder gpu_encoder; // Render command encoder +typedef struct gpu_compute_encoder gpu_compute_encoder; typedef struct gpu_buffer gpu_buffer; typedef struct gpu_texture gpu_texture; @@ -325,21 +348,30 @@ typedef struct shader_data_layout { size_t binding_count; } shader_data_layout; -typedef struct shader_desc { - // TODO -} shader_desc; +typedef struct shader_function { + const char* source; + bool is_spirv; + const char* entry_point; + shader_vis shader_stage; +} shader_function; typedef enum cull_mode { CULL_BACK_FACE, CULL_FRONT_FACE } cull_mode; typedef struct gfx_pipeline_desc { const char* label; vertex_desc vertex_desc; - shader_desc vs; - shader_desc fs; + shader_function vertex; + shader_function fragment; // ShaderDataLayout data_layouts[MAX_SHADER_DATA_LAYOUTS]; // u32 data_layouts_count; } gfx_pipeline_desc; +typedef struct compute_pipeline_desc { /* TODO */ } compute_pipeline_desc; + +typedef struct render_pass_desc { + +} render_pass_desc; + // --- RAL Functions // Resources @@ -348,12 +380,39 @@ void ral_buffer_destroy(buf_handle handle); tex_handle ral_texture_create(texture_desc desc, bool create_view, const void* data); void ral_texture_destroy(tex_handle handle); +// Encoders / cmd buffers +/** @brief grabs a new command encoder from the pool of available ones and begins recording */ +gpu_encoder* ral_render_encoder(render_pass_desc rpass_desc); + +gpu_compute_encoder ral_compute_encoder(); + +void ral_encoder_finish(gpu_encoder* enc); +void ral_encoder_submit(gpu_encoder* enc); +void ral_encoder_finish_and_submit(gpu_encoder* enc); + +pipeline_handle ral_gfx_pipeline_create(gfx_pipeline_desc desc); +void ral_gfx_pipeline_destroy(pipeline_handle handle); + +compute_pipeline_handle ral_compute_pipeline_create(compute_pipeline_desc); +void ral_compute_pipeline_destroy(compute_pipeline_handle handle); + +// Encoding +void ral_encode_bind_pipeline(gpu_encoder* enc, pipeline_handle pipeline); +void ral_encode_set_vertex_buf(gpu_encoder* enc, buf_handle vbuf); +void ral_encode_set_index_buf(gpu_encoder* enc, buf_handle ibuf); +void ral_encode_draw_tris(gpu_encoder* enc, size_t start, size_t count); + // Backend lifecycle void ral_backend_init(const char* window_name, struct GLFWwindow* window); void ral_backend_shutdown(); // Frame lifecycle + +typedef void (*scoped_draw_commands)(); // callback that we run our draw commands within. +// allows us to wrap some api-specific behaviour + void ral_frame_start(); +void ral_frame_draw(scoped_draw_commands draw_fn); void ral_frame_end(); // --- Containers (Forward declared as internals are unnecessary for external header) diff --git a/src/backend_mtl.m b/src/backend_mtl.m index b3cd224..6ff5058 100644 --- a/src/backend_mtl.m +++ b/src/backend_mtl.m @@ -1,7 +1,6 @@ -#define GPU_METAL 1 +#include <celeritas.h> #ifdef GPU_METAL -#include <celeritas.h> #define MTL_DEBUG_LAYER 1 @@ -16,6 +15,8 @@ #define GLFW_EXPOSE_NATIVE_COCOA #import <GLFW/glfw3native.h> +NAMESPACED_LOGGER(metal); + // --- RAL types struct gpu_swapchain { @@ -23,6 +24,22 @@ struct gpu_swapchain { CAMetalLayer* swapchain; }; +struct gpu_encoder { + id<MTLCommandBuffer> cmd_buffer; + id<MTLRenderCommandEncoder> cmd_encoder; +}; + +typedef struct metal_pipeline { + id<MTLRenderPipelineState> pso; +} metal_pipeline; + +typedef struct metal_buffer { + id<MTLBuffer> id; +} metal_buffer; + +TYPED_POOL(metal_buffer, buf); +TYPED_POOL(metal_pipeline, pipeline); + typedef struct metal_context { GLFWwindow* window; NSWindow* metal_window; @@ -30,20 +47,25 @@ typedef struct metal_context { id<MTLDevice> device; id<CAMetalDrawable> surface; gpu_swapchain default_swapchain; + id<MTLLibrary> default_library; id<MTLCommandQueue> command_queue; + + /* pools */ + buf_pool bufpool; + pipeline_pool psopool; // pso = pipeline state object } metal_context; static metal_context ctx; void ral_backend_init(const char* window_name, struct GLFWwindow* window) { - printf("loading Metal backend\n"); + TRACE("loading Metal backend"); - printf("gpu device creation\n"); + TRACE("gpu device creation"); const id<MTLDevice> gpu = MTLCreateSystemDefaultDevice(); ctx.device = gpu; - printf("window init\n"); + TRACE("window init"); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwMakeContextCurrent(window); NSWindow* nswindow = glfwGetCocoaWindow(window); @@ -55,14 +77,130 @@ void ral_backend_init(const char* window_name, struct GLFWwindow* window) { metal_layer.pixelFormat = MTLPixelFormatBGRA8Unorm; ctx.metal_window.contentView.layer = metal_layer; ctx.metal_window.contentView.wantsLayer = true; + ctx.default_swapchain.swapchain = metal_layer; - printf("command queue creation\n"); + TRACE("command queue creation"); const id<MTLCommandQueue> queue = [ctx.device newCommandQueue]; ctx.command_queue = queue; + + TRACE("resource pool init"); + metal_buffer* buffer_storage = malloc(sizeof(metal_buffer) * 100); + ctx.bufpool = buf_pool_create(buffer_storage, 100, sizeof(metal_buffer)); + + metal_pipeline* pipeline_storage = malloc(sizeof(metal_pipeline) * 100); + ctx.psopool = pipeline_pool_create(pipeline_storage, 100, sizeof(metal_pipeline)); + + TRACE("create default metal lib"); + NSError* nserr = 0x0; + id<MTLLibrary> default_library = [ctx.device newLibraryWithFile:@"build/shaders/default.metallib" error:&nserr]; + if (!default_library) { + ERROR("Error loading metal lib\n"); + exit(1); + } + ctx.default_library = default_library; + + INFO("Successfully initialised Metal RAL backend"); } void ral_backend_shutdown() { // no-op } +buf_handle ral_buffer_create(u64 size, const void *data) { + buf_handle handle; + metal_buffer* buffer = buf_pool_alloc(&ctx.bufpool, &handle); + buffer->id = [ctx.device newBufferWithBytes:data length:size options:MTLResourceStorageModeShared]; + + return handle; +} + +pipeline_handle ral_gfx_pipeline_create(gfx_pipeline_desc desc) { + TRACE("creating graphics pipeline"); + + pipeline_handle handle; + metal_pipeline* p = pipeline_pool_alloc(&ctx.psopool, &handle); + + @autoreleasepool { + // setup vertex and fragment shaders + NSString* vertex_entry_point = [NSString stringWithUTF8String:desc.vertex.entry_point]; + id<MTLFunction> vertex_func = [ctx.default_library newFunctionWithName:vertex_entry_point]; + assert(vertex_func); + + NSString* fragment_entry_point = [NSString stringWithUTF8String:desc.fragment.entry_point]; + id<MTLFunction> fragment_func = [ctx.default_library newFunctionWithName:fragment_entry_point]; + assert(fragment_func); + + NSError* err = 0x0; + MTLRenderPipelineDescriptor* pld = [[MTLRenderPipelineDescriptor alloc] init]; + // in auto release pool so dont need to call release() + + [pld setLabel:@"Pipeline"]; + [pld setVertexFunction:vertex_func]; + [pld setFragmentFunction:fragment_func]; + pld.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm; + pld.colorAttachments[0].blendingEnabled = YES; + assert(pld); + + id<MTLRenderPipelineState> pso = [ctx.device newRenderPipelineStateWithDescriptor:pld error:&err]; + assert(pso); + p->pso = pso; + } + + return handle; +} + +gpu_encoder* ral_render_encoder(render_pass_desc rpass_desc) { + id<MTLCommandBuffer> buffer = [ctx.command_queue commandBuffer]; + + // create renderpass descriptor + MTLRenderPassDescriptor* rpd = [[MTLRenderPassDescriptor alloc] init]; + MTLRenderPassColorAttachmentDescriptor* cd = rpd.colorAttachments[0]; + [cd setTexture:ctx.surface.texture]; + [cd setLoadAction:MTLLoadActionClear]; + MTLClearColor clearColor = MTLClearColorMake(41.0f/255.0f, 42.0f/255.0f, 48.0f/255.0f, 1.0); + [cd setClearColor:clearColor]; + [cd setStoreAction:MTLStoreActionStore]; + + id<MTLRenderCommandEncoder> encoder = [buffer renderCommandEncoderWithDescriptor:rpd]; + + gpu_encoder* enc = malloc(sizeof(gpu_encoder)); + enc->cmd_buffer = buffer; + enc->cmd_encoder = encoder; + + return enc; +} + +void ral_encoder_finish_and_submit(gpu_encoder* enc) { + [enc->cmd_encoder endEncoding]; + [enc->cmd_buffer presentDrawable:ctx.surface]; + [enc->cmd_buffer commit]; + [enc->cmd_buffer waitUntilCompleted]; +} + +void ral_encode_bind_pipeline(gpu_encoder *enc, pipeline_handle pipeline) { + metal_pipeline* p = pipeline_pool_get(&ctx.psopool, pipeline); + [enc->cmd_encoder setRenderPipelineState:p->pso]; +} + +void ral_encode_set_vertex_buf(gpu_encoder *enc, buf_handle vbuf) { + metal_buffer* b = buf_pool_get(&ctx.bufpool, vbuf); + [enc->cmd_encoder setVertexBuffer:b->id offset:0 atIndex:0 ]; +} + +void ral_encode_draw_tris(gpu_encoder* enc, size_t start, size_t count) { + MTLPrimitiveType tri_primitive = MTLPrimitiveTypeTriangle; + [enc->cmd_encoder drawPrimitives:tri_primitive vertexStart:start vertexCount:count]; +} + +void ral_frame_start() {} + +void ral_frame_draw(scoped_draw_commands draw_fn) { + @autoreleasepool { + ctx.surface = [ctx.default_swapchain.swapchain nextDrawable]; + draw_fn(); + } +} + +void ral_frame_end() {} + #endif
\ No newline at end of file @@ -1,11 +1,18 @@ // The engine "core" #include <celeritas.h> +#include <stdlib.h> NAMESPACED_LOGGER(core); core g_core = {0}; +#ifdef GPU_METAL +static const char* gapi = "Metal"; +#else +static const char* gapi = "Vulkan"; +#endif + // forward declares void key_callback(GLFWwindow* win, int key, int scancode, int action, int mods); @@ -14,7 +21,11 @@ void core_bringup(const char* window_name, struct GLFWwindow* optional_window) { INFO("Create GLFW window"); glfwInit(); - GLFWwindow* glfw_window = glfwCreateWindow(800, 600, window_name, NULL, NULL); + + char* full_window_name = malloc(sizeof(char) * 100); + int _offset = sprintf(full_window_name, "%s (%s)", window_name, gapi); + + GLFWwindow* glfw_window = glfwCreateWindow(800, 600, full_window_name, NULL, NULL); g_core.window = glfw_window; // This may move into a renderer struct @@ -7,5 +7,5 @@ static const char* log_level_strings[] = { void log_output(char* module, loglevel level, const char* message, ...) { char out_msg[4096]; - printf("[%s] %s Msg: %s\n", module, log_level_strings[level], message); + printf("[%s] %s - %s\n", module, log_level_strings[level], message); } @@ -0,0 +1,89 @@ +#include <celeritas.h> + +void_pool void_pool_create(void* storage, const char* debug_label, u64 capacity, u64 entry_size) { + size_t memory_requirements = capacity * entry_size; + // void* backing_buf = arena_alloc(a, memory_requirements); + + assert(entry_size >= sizeof(void_pool_header)); // TODO: create my own assert with error message + + void_pool pool = { .capacity = capacity, + .entry_size = entry_size, + .count = 0, + .backing_buffer = storage, + .free_list_head = NULL, + .debug_label = debug_label }; + + void_pool_free_all(&pool); + + return pool; +} + +void void_pool_free_all(void_pool* pool) { + // set all entries to be free + for (u64 i = 0; i < pool->capacity; i++) { + void* ptr = &pool->backing_buffer[i * pool->entry_size]; + void_pool_header* free_node = + (void_pool_header*)ptr; // we reuse the actual entry itself to hold the header + if (i == (pool->capacity - 1)) { + // if the last one we make its next pointer NULL indicating its full + free_node->next = NULL; + } + free_node->next = pool->free_list_head; + // now the head points to this entry + pool->free_list_head = free_node; + } +} + +void* void_pool_get(void_pool* pool, u32 raw_handle) { + // An handle is an index into the array essentially + void* ptr = pool->backing_buffer + (raw_handle * pool->entry_size); + return ptr; +} + +void* void_pool_alloc(void_pool* pool, u32* out_raw_handle) { + // get the next free node + if (pool->count == pool->capacity) { + // WARN("Pool is full!"); + return NULL; + } + if (pool->free_list_head == NULL) { + // ERROR("%s Pool is full (head = null)", pool->debug_label); + return NULL; + } + void_pool_header* free_node = pool->free_list_head; + + // What index does this become? + uintptr_t start = (uintptr_t)pool->backing_buffer; + uintptr_t cur = (uintptr_t)free_node; + // TRACE("%ld %ld ", start, cur); + assert(cur > start); + u32 index = (u32)((cur - start) / pool->entry_size); + /* printf("Index %d\n", index); */ + if (out_raw_handle != NULL) { + *out_raw_handle = index; + } + + pool->free_list_head = free_node->next; + + memset(free_node, 0, pool->entry_size); + pool->count++; + return (void*)free_node; +} + +void void_pool_dealloc(void_pool* pool, u32 raw_handle) { + // push free node back onto the free list + void* ptr = void_pool_get(pool, raw_handle); + void_pool_header* freed_node = (void_pool_header*)ptr; + + freed_node->next = pool->free_list_head; + pool->free_list_head = freed_node; + + pool->count--; +} + +u32 void_pool_insert(void_pool* pool, void* item) { + u32 raw_handle; + void* item_dest = void_pool_alloc(pool, &raw_handle); + memcpy(item_dest, item, pool->entry_size); + return raw_handle; +} |