summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--nogl.h141
-rw-r--r--rend.c604
-rw-r--r--rend.h290
-rw-r--r--rend_internal.h199
-rw-r--r--rend_vk.c2131
-rw-r--r--rend_vk_allocator.c118
-rw-r--r--rend_vk_arena.c264
-rw-r--r--rend_vk_device.c395
-rw-r--r--rend_vk_image.c125
-rw-r--r--rend_vk_internal.h177
-rw-r--r--unused code/rend_vk_buffer.c116
-rw-r--r--unused code/rend_vk_command_queue.c153
-rw-r--r--unused code/rend_vk_memory.c100
-rw-r--r--unused code/rend_vk_pool.c221
-rw-r--r--unused code/rend_vk_sbta.c331
15 files changed, 5365 insertions, 0 deletions
diff --git a/nogl.h b/nogl.h
new file mode 100644
index 0000000..ab45c90
--- /dev/null
+++ b/nogl.h
@@ -0,0 +1,141 @@
+/*
+Ideas for Rend 2.0 aka NoGL
+Inspiried by https://www.sebastianaaltonen.com/blog/no-graphics-api
+
+## Style Guide
+
+### Order of Code
+
+1. Includes.
+2. Macros.
+3. Types.
+4. Function declarations.
+5. Global variables.
+6. Function definitions.
+
+### Naming Conventions
+
+1. Use snake_case for variables and functions.
+2. Functions in the format `module_type_action`:
+ - `ui_align_left`
+ - `ui_button_create`
+ - `pl_window_size`
+ 3. Types are in PascalCase, prefixed also by the module name.
+ - `UI_Align`, `PL_Window`.
+ 4. Macros are in UPPERCASE_SNAKE_CASE.
+
+*/
+
+#include <stddef.h>
+#include <stdint.h>
+
+typedef struct NOGL_CommandBufferSpan {
+ struct NOGL_CommandBuffer* data;
+ size_t count;
+} NOGL_CommandBufferSpan;
+
+typedef enum NOGL_Memory {
+ NOGL_MEMORY_DEFAULT,
+ NOGL_MEMORY_UPLOAD,
+ NOGL_MEMORY_READBACK,
+} NOGL_Memory;
+
+typedef enum NOGL_Stage {
+ NOGL_STAGE_NONE,
+ NOGL_STAGE_TRANSFER,
+ NOGL_STAGE_COMPUTE,
+ NOGL_STAGE_VERTEX,
+ NOGL_STAGE_PIXEL,
+} NOGL_Stage;
+
+typedef enum NOGL_Op {
+ NOGL_OP_EQUAL,
+ NOGL_OP_NOT_EQUAL,
+ NOGL_OP_GREATER,
+ NOGL_OP_GREATER_EQUAL,
+ NOGL_OP_LESS,
+ NOGL_OP_LESS_EQUAL,
+} NOGL_Op;
+
+typedef enum NOGL_Signal {
+ NOGL_SIGNAL_FENCE,
+ NOGL_SIGNAL_TIMELINE,
+} NOGL_Signal;
+
+typedef uint32_t NOGL_HazardFlags;
+
+typedef struct NOGL_TextureDesc NOGL_TextureDesc;
+typedef struct NOGL_ViewDesc NOGL_ViewDesc;
+typedef struct NOGL_RasterDesc NOGL_RasterDesc;
+typedef struct NOGL_DepthStencilDesc NOGL_DepthStencilDesc;
+typedef struct NOGL_BlendDesc NOGL_BlendDesc;
+typedef struct NOGL_RenderPassDesc NOGL_RenderPassDesc;
+
+typedef struct NOGL_TextureSizeAlign {
+ size_t size;
+ size_t align;
+} NOGL_TextureSizeAlign;
+
+typedef struct NOGL_Texture { uint64_t handle; } NOGL_Texture;
+typedef struct NOGL_TextureDescriptor { uint64_t handle; } NOGL_TextureDescriptor;
+typedef struct NOGL_Pipeline { uint64_t handle; } NOGL_Pipeline;
+typedef struct NOGL_DepthStencilState { uint64_t handle; } NOGL_DepthStencilState;
+typedef struct NOGL_BlendState { uint64_t handle; } NOGL_BlendState;
+typedef struct NOGL_Queue { uint64_t handle; } NOGL_Queue;
+typedef struct NOGL_CommandBuffer { uint64_t handle; } NOGL_CommandBuffer;
+typedef struct NOGL_Semaphore { uint64_t handle; } NOGL_Semaphore;
+
+void* nogl_mem_alloc(size_t bytes, NOGL_Memory memory = NOGL_MEMORY_DEFAULT);
+void* nogl_mem_alloc_aligned(size_t bytes, size_t align, NOGL_Memory memory = NOGL_MEMORY_DEFAULT);
+void nogl_mem_free(void* ptr);
+void* nogl_mem_host_to_device_ptr(void* ptr);
+
+NOGL_TextureSizeAlign nogl_texture_size_align(NOGL_TextureDesc desc);
+NOGL_Texture nogl_texture_create(NOGL_TextureDesc desc, void* ptr_gpu);
+NOGL_TextureDescriptor nogl_texture_view_descriptor(NOGL_Texture texture, NOGL_ViewDesc desc);
+NOGL_TextureDescriptor nogl_texture_rw_view_descriptor(NOGL_Texture texture, NOGL_ViewDesc desc);
+
+NOGL_Pipeline nogl_pipeline_create_compute_spirv(uint8_t *bytes, size_t size);
+NOGL_Pipeline nogl_pipeline_create_graphics_spirv(uint8_t *vertex_bytes, size_t vertex_size, uint8_t *frag_bytes, size_t frag_size, NOGL_RasterDesc desc);
+NOGL_Pipeline nogl_pipeline_create_meshlet_spirv(uint8_t *meshlet_bytes, size_t meshlet_size, uint8_t *frag_bytes, size_t frag_size, NOGL_RasterDesc desc);
+void nogl_pipeline_destroy(NOGL_Pipeline pipeline);
+
+NOGL_DepthStencilState nogl_depth_stencil_state_create(NOGL_DepthStencilDesc desc);
+NOGL_BlendState nogl_blend_state_create(NOGL_BlendDesc desc);
+void nogl_depth_stencil_state_free(NOGL_DepthStencilState state);
+void nogl_blend_state_free(NOGL_BlendState state);
+
+NOGL_Queue nogl_queue_create(/* device & queue creation details omitted */);
+NOGL_CommandBuffer nogl_command_buffer_start(NOGL_Queue queue);
+void nogl_queue_submit(NOGL_Queue queue, NOGL_CommandBufferSpan command_buffers);
+
+NOGL_Semaphore nogl_semaphore_create(uint64_t init_value);
+void nogl_semaphore_wait(NOGL_Semaphore sema, uint64_t value);
+void nogl_semaphore_destroy(NOGL_Semaphore sema);
+
+void nogl_cmd_mem_copy(NOGL_CommandBuffer cb, void* dest_gpu, void* src_gpu, size_t bytes);
+void nogl_cmd_copy_to_texture(NOGL_CommandBuffer cb, void* dest_gpu, void* src_gpu, NOGL_Texture texture);
+void nogl_cmd_copy_from_texture(NOGL_CommandBuffer cb, void* dest_gpu, void* src_gpu, NOGL_Texture texture);
+
+void nogl_cmd_set_active_texture_heap_ptr(NOGL_CommandBuffer cb, void* ptr_gpu);
+
+void nogl_cmd_barrier(NOGL_CommandBuffer cb, NOGL_Stage before, NOGL_Stage after, NOGL_HazardFlags hazards = 0);
+void nogl_cmd_signal_after(NOGL_CommandBuffer cb, NOGL_Stage before, void* ptr_gpu, uint64_t value, NOGL_Signal signal);
+void nogl_cmd_wait_before(NOGL_CommandBuffer cb, NOGL_Stage after, void* ptr_gpu, uint64_t value, NOGL_Op op, NOGL_HazardFlags hazards, uint64_t mask);
+
+void nogl_cmd_set_pipeline(NOGL_CommandBuffer cb, NOGL_Pipeline pipeline);
+void nogl_cmd_set_depth_stencil_state(NOGL_CommandBuffer cb, NOGL_DepthStencilState state);
+void nogl_cmd_set_blend_state(NOGL_CommandBuffer cb, NOGL_BlendState state);
+
+void nogl_cmd_dispatch(NOGL_CommandBuffer cb, void* data_gpu, uint32_t count_x, uint32_t count_y, uint32_t count_z);
+void nogl_cmd_dispatch_indirect(NOGL_CommandBuffer cb, void* data_gpu, void* grid_dimensions_gpu);
+
+void nogl_cmd_render_pass_begin(NOGL_CommandBuffer cb, NOGL_RenderPassDesc desc);
+void nogl_cmd_render_pass_end(NOGL_CommandBuffer cb);
+
+void nogl_cmd_draw_indexed_instanced(NOGL_CommandBuffer cb, void* vertex_data_gpu, void* pixel_data_gpu, void* indices_gpu, uint32_t index_count, uint32_t instance_count);
+void nogl_cmd_draw_indexed_instanced_indirect(NOGL_CommandBuffer cb, void* vertex_data_gpu, void* pixel_data_gpu, void* indices_gpu, void* args_gpu);
+void nogl_cmd_draw_indexed_instanced_indirect_multi(NOGL_CommandBuffer cb, void* data_vx_gpu, uint32_t vx_stride, void* data_px_gpu, uint32_t px_stride, void* args_gpu, void* draw_count_gpu);
+
+void nogl_cmd_draw_meshlets(NOGL_CommandBuffer cb, void* meshlet_data_gpu, void* pixel_data_gpu, uint32_t count_x, uint32_t count_y, uint32_t count_z);
+void nogl_cmd_draw_meshlets_indirect(NOGL_CommandBuffer cb, void* meshlet_data_gpu, void* pixel_data_gpu, void* dim_gpu);
diff --git a/rend.c b/rend.c
new file mode 100644
index 0000000..68634d3
--- /dev/null
+++ b/rend.c
@@ -0,0 +1,604 @@
+#include <assert.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <unistd.h>
+
+#include "rend.h"
+#include "rend_internal.h"
+#include "rend_vk.c"
+#include "rend_vk_internal.h"
+
+/* clean up functions */
+
+static void rend__renderer_destroy_recursive(RendRenderer renderer);
+static void rend__pipeline_destroy_recursive(RendPipeline pipeline);
+static bool rend__renderer_init_recursive(RendRenderer renderer, RendBackendType backend, bool auto_pick);
+
+static RendRenderer rend_renderers_head = NULL;
+
+// TODO: automatically clear buffers and textures
+// static RendBuffer rend_buffer_head = NULL;
+// static RendTexture rend_texture_head = NULL;
+
+bool rend_backend_vk14_initialized = false;
+
+RendVTable rend_vtables[] = {
+ [REND_BACKEND_VULKAN_14] = {
+ .renderer_create = rend_vk14_renderer_create,
+ .renderer_destroy = rend_vk14_renderer_destroy,
+ .renderer_frame_begin = rend_vk14_renderer_frame_begin,
+ .renderer_frame_end = rend_vk14_renderer_frame_end,
+
+ .buffer_create_lifetime = rend_vk14_buffer_create_lifetime,
+ .buffer_destroy = rend_vk14_buffer_destroy,
+ .buffer_copy = rend_vk14_buffer_copy,
+
+ .texture_create = rend_vk14_texture_create,
+ .texture_destroy = rend_vk14_texture_destroy,
+ .texture_copy_buffer = rend_vk14_texture_copy_buffer,
+ .texture_blit = rend_vk14_texture_blit,
+
+ .pipeline_create = rend_vk14_pipeline_create,
+ .pipeline_bind = rend_vk14_pipeline_bind,
+ .pipeline_push_constants = rend_vk14_pipeline_push_constants,
+
+ .pipeline_bind_vertex_buffer = rend_vk14_pipeline_bind_vertex_buffer,
+ .pipeline_bind_index_buffer = rend_vk14_pipeline_bind_index_buffer,
+
+ .pipeline_dispatch = rend_vk14_pipeline_dispatch,
+ .pipeline_draw = rend_vk14_pipeline_draw,
+ .pipeline_draw_indexed = rend_vk14_pipeline_draw_indexed,
+ .pipeline_set_blend = rend_vk14_pipeline_set_blend,
+
+ .renderer_render_pass_begin = rend_vk14_renderer_render_pass_begin,
+ .renderer_render_pass_begin_texture = rend_vk14_renderer_render_pass_begin_texture,
+ .renderer_render_pass_end = rend_vk14_renderer_render_pass_end,
+ .renderer_render_pass_end_texture = rend_vk14_renderer_render_pass_end_texture,
+
+ .descriptor_write_buffer = rend_vk14_descriptor_write_buffer,
+ .descriptor_write_texture = rend_vk14_descriptor_write_texture,
+ },
+};
+
+extern void
+rend_quit()
+{
+ rend__renderer_destroy_recursive(rend_renderers_head);
+
+ if (rend_backend_vk14_initialized) {
+ rend_vk14_quit();
+ rend_backend_vk14_initialized = false;
+ }
+
+}
+
+extern RendRenderer
+rend_renderer_create(P_Window *target, RendBackendType backend, void* device, bool vsync, RendBindingInfo *bind_info)
+{
+ RendRenderer rend = rmalloc(sizeof *rend);
+ rend->next = rend_renderers_head;
+ rend->prev = NULL;
+ rend_renderers_head = rend;
+ if(rend->next) {
+ rend->next->prev = rend;
+ }
+
+ /* create vtable for backend */
+ rend__renderer_init_recursive(rend, backend, false);
+
+ rend->vsync = vsync;
+ rend->bind_info = *bind_info;
+ rend->window = target;
+
+ rend->pipeline_head = 0;
+
+ if (rend_vtables[rend->backend].renderer_create(rend, target)) {
+ return rend;
+ }
+
+ return NULL;
+}
+
+extern void
+rend_renderer_destroy(RendRenderer renderer)
+{
+ if (!renderer) return;
+
+ rend__pipeline_destroy_recursive(renderer->pipeline_head);
+
+ if (renderer->backend != 0) rend_vtables[renderer->backend].renderer_destroy(renderer);
+
+ if (renderer->prev) renderer->prev->next = renderer->next;
+ else rend_renderers_head = renderer->next;
+
+ if (renderer->next) renderer->next->prev = renderer->prev;
+
+ rfree(renderer);
+}
+
+extern bool
+rend_renderer_frame_begin(RendRenderer renderer)
+{
+ renderer->in_frame = 1;
+ return rend_vtables[renderer->backend].renderer_frame_begin(renderer);
+}
+
+extern void
+rend_renderer_frame_end(RendRenderer renderer, float *delta)
+{
+ renderer->in_frame = 0;
+ rend_vtables[renderer->backend].renderer_frame_end(renderer, delta);
+ renderer->frame_count++;
+}
+
+extern void
+rend_renderer_render_pass_begin(RendRenderer renderer, float r, float g ,float b, float a)
+{
+ renderer->in_pass = 1;
+ rend_vtables[renderer->backend].renderer_render_pass_begin(renderer, r, g, b, a);
+}
+
+extern void
+rend_renderer_render_pass_begin_texture(RendRenderer renderer, RendTexture *texture)
+{
+ renderer->in_pass = 1;
+ rend_vtables[renderer->backend].renderer_render_pass_begin_texture(renderer, texture);
+}
+
+extern void
+rend_renderer_render_pass_end_texture(RendRenderer renderer, RendTexture *texture)
+{
+ renderer->in_pass = 0;
+ rend_vtables[renderer->backend].renderer_render_pass_end_texture(renderer, texture);
+}
+
+
+extern void
+rend_renderer_render_pass_end(RendRenderer renderer)
+{
+ renderer->in_pass = 0;
+ rend_vtables[renderer->backend].renderer_render_pass_end(renderer);
+}
+
+
+extern void
+rend_descriptor_write_ubo(RendRenderer renderer, RendBuffer ubo, uint32_t binding, uint32_t slot)
+{
+ rend_vtables[renderer->backend].descriptor_write_buffer(renderer, ubo, binding, slot, 0, ubo.size, true);
+}
+
+extern void
+rend_descriptor_write_ssbo(RendRenderer renderer, RendBuffer ssbo, uint32_t binding, uint32_t slot)
+{
+ rend_vtables[renderer->backend].descriptor_write_buffer(renderer, ssbo, binding, slot, 0, ssbo.size, false);
+}
+
+extern RendBuffer
+rend_buffer_create(RendRenderer renderer, size_t size, RendBufferType type, bool gpu) {
+ RendBuffer buffer = rend_vtables[renderer->backend].buffer_create_lifetime(renderer, size, type, gpu, REND_LIFETIME_PERMANENT);
+ buffer.backend = renderer->backend;
+ return buffer;
+}
+
+extern void
+rend_buffer_destroy(RendBuffer *buffer) {
+ rend_vtables[buffer->backend].buffer_destroy(buffer);
+}
+
+extern void
+rend_buffer_write(RendRenderer renderer, RendBuffer *buffer, const void *data, size_t size, size_t offset)
+{
+ if (buffer->mapped_memory) {
+ uint8_t *ptr = buffer->mapped_memory;
+ memcpy(ptr + offset, data, size);
+ } else {
+ /* if the memory is bound to the device we must create and deallocate a transfer buffer */
+ RendBuffer transfer_buffer = rend_vtables[buffer->backend].buffer_create_lifetime(renderer, size, REND_BUFFER_TRANSFER, false, REND_LIFETIME_FRAME);
+ memcpy(transfer_buffer.mapped_memory, data, size);
+ rend_vtables[buffer->backend].buffer_copy(renderer, buffer, offset, &transfer_buffer, 0, size);
+ rend_vtables[buffer->backend].buffer_destroy(&transfer_buffer);
+ }
+}
+
+extern void
+rend_buffer_copy(RendRenderer renderer, RendBuffer *dest, size_t dest_offset, RendBuffer *src, size_t src_offset, size_t bytes)
+{
+ rend_vtables[renderer->backend].buffer_copy(renderer, dest, dest_offset, src, src_offset, bytes);
+}
+
+extern uint64_t
+rend_buffer_address(RendBuffer *buffer)
+{
+ assert(buffer != NULL);
+ return buffer->gpu_address;
+}
+
+extern RendTexture
+rend_texture_create(RendRenderer renderer, uint32_t width, uint32_t height, uint32_t depth, uint32_t mip_levels, uint32_t layers, RendFormat format)
+{
+ RendTexture tex = rend_vtables[renderer->backend].texture_create(renderer, width, height, depth, mip_levels, layers, format);
+ tex.backend = renderer->backend;
+ return tex;
+}
+
+extern RendTexture
+rend_texture_create_from_data(RendRenderer renderer, const void *data, uint32_t width, uint32_t height, RendFormat format)
+{
+ RendTexture tex = rend_vtables[renderer->backend].texture_create(renderer, width, height, 1, 1, 1, format);
+ tex.backend = renderer->backend;
+
+ uint32_t size = width * height * rend_format_size[format];
+
+ RendBuffer staging_buffer = rend_vtables[renderer->backend].buffer_create_lifetime(renderer, size, REND_BUFFER_TRANSFER, false, REND_LIFETIME_FRAME);
+ memcpy(staging_buffer.mapped_memory, data, size);
+ rend_vtables[renderer->backend].texture_copy_buffer(renderer, &tex, &staging_buffer);
+ rend_vtables[renderer->backend].buffer_destroy(&staging_buffer);
+ return tex;
+}
+
+extern void
+rend_texture_copy_data(RendRenderer renderer, RendTexture *texture, const void *data, size_t size)
+{
+ RendBuffer staging_buffer = rend_vtables[renderer->backend].buffer_create_lifetime(renderer, size, REND_BUFFER_TRANSFER, false, REND_LIFETIME_FRAME);
+ memcpy(staging_buffer.mapped_memory, data, size);
+ rend_vtables[renderer->backend].texture_copy_buffer(renderer, texture, &staging_buffer);
+ rend_vtables[renderer->backend].buffer_destroy(&staging_buffer);
+}
+
+
+extern void
+rend_texture_copy_buffer(RendRenderer renderer, RendTexture *texture, RendBuffer *buffer)
+{
+ rend_vtables[renderer->backend].texture_copy_buffer(renderer, texture, buffer);
+}
+
+extern void
+rend_texture_blit(RendRenderer renderer, RendTexture *src, RendTexture *dst, uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h, uint32_t dst_x, uint32_t dst_y, uint32_t dst_w, uint32_t dst_h)
+{
+ rend_vtables[renderer->backend].texture_blit(renderer, src, dst, src_x, src_y, src_w, src_h, dst_x, dst_y, dst_w, dst_h);
+}
+
+extern uint64_t
+rend_texture_id(RendTexture *texture)
+{
+ return texture->id;
+}
+
+extern void
+rend_texture_destroy(RendRenderer renderer, RendTexture *tex)
+{
+ rend_vtables[renderer->backend].texture_destroy(renderer, tex);
+}
+
+extern void
+rend_pipeline_push_constants(RendPipeline pipeline, void *push_data, size_t size)
+{
+ assert(pipeline != NULL && "Pipeline handle is NULL!");
+ assert(rend_vtables[pipeline->backend].pipeline_push_constants && "Backend function not implemented!");
+ rend_vtables[pipeline->backend].pipeline_push_constants(pipeline, push_data, size);
+}
+
+extern void
+rend_pipeline_bind_vertex_buffer(RendPipeline pipeline, uint32_t binding, RendBuffer buffer, size_t offset)
+{
+ rend_vtables[pipeline->backend].pipeline_bind_vertex_buffer(pipeline, binding, buffer, offset);
+}
+
+extern void
+rend_pipeline_bind_index_buffer(RendPipeline pipeline, RendBuffer buffer, size_t offset, RendIndexType index_type)
+{
+ rend_vtables[pipeline->backend].pipeline_bind_index_buffer(pipeline, buffer, offset, index_type);
+}
+
+extern void
+rend_pipeline_bind_texture(RendPipeline pipeline, RendTexture *texture, uint32_t binding, uint32_t slot)
+{
+ rend_vtables[pipeline->backend].descriptor_write_texture(pipeline->backend_ctx, texture, binding, slot);
+}
+
+extern void
+rend_descriptor_write_texture(RendRenderer renderer, RendTexture *texture, uint32_t binding, uint32_t slot)
+{
+ rend_vtables[renderer->backend].descriptor_write_texture(renderer->context, texture, binding, slot);
+}
+
+
+extern RendPipeline
+rend_pipeline_create_graphics_spirv(RendRenderer renderer, uint8_t *vertex_bytes, size_t vertex_size, uint8_t *frag_bytes, size_t frag_size, const RendVertexBinding *vertex_bindings, uint32_t vertex_binding_count, const RendVertexAttributes *vertex_attributes, uint32_t vertex_attribute_count, const RendPushConstantInfo *push_constants, uint32_t push_constant_count, RendPolygonMode polygon_mode, RendCullMode cull_mode, RendTopology topology, RendFormat color_format, bool depth_test_enable)
+{
+ RendPipeline pipeline = rmalloc(sizeof *pipeline);
+
+ Rend__PipelineConfig config = {
+ .vertex_bindings = vertex_bindings,
+ .vertex_binding_count = vertex_binding_count,
+ .vertex_attributes = vertex_attributes,
+ .vertex_attribute_count = vertex_attribute_count,
+ .push_constants = push_constants,
+ .push_constant_count = push_constant_count,
+ .polygon_mode = polygon_mode,
+ .cull_mode = cull_mode,
+ .topology = topology,
+ .depth_test_enable = depth_test_enable,
+
+ .color_format = color_format,
+ };
+
+ if (rend_vtables[renderer->backend].pipeline_create(renderer, pipeline, config, REND__PIPELINE_GRAPHICS, vertex_bytes, vertex_size, frag_bytes, frag_size, NULL, 0)) {
+ pipeline->backend = renderer->backend;
+
+ pipeline->next = renderer->pipeline_head;
+ pipeline->prev = NULL;
+ renderer->pipeline_head = pipeline;
+ pipeline->type = REND__PIPELINE_GRAPHICS;
+ if(pipeline->next) {
+ pipeline->next->prev = pipeline;
+ }
+
+ return pipeline;
+ }
+ rfree(pipeline);
+ return NULL;
+}
+
+extern RendPipeline
+rend_pipeline_create_graphics_bindless_spirv(RendRenderer renderer, uint8_t *vertex_bytes, size_t vertex_size, uint8_t *frag_bytes, size_t frag_size, const RendPushConstantInfo *push_constants, uint32_t push_constant_count, RendPolygonMode polygon_mode, RendCullMode cull_mode, RendTopology topology, RendFormat color_format, bool depth_test_enable)
+{
+ RendPipeline pipeline = rmalloc(sizeof *pipeline);
+
+ Rend__PipelineConfig config = {
+ .vertex_bindings = NULL,
+ .vertex_binding_count = 0,
+ .vertex_attributes = NULL,
+ .vertex_attribute_count = 0,
+ .push_constants = push_constants,
+ .push_constant_count = push_constant_count,
+ .polygon_mode = polygon_mode,
+ .cull_mode = cull_mode,
+ .topology = topology,
+ .depth_test_enable = depth_test_enable,
+
+ .color_format = color_format,
+ };
+
+ if (rend_vtables[renderer->backend].pipeline_create(renderer, pipeline, config, REND__PIPELINE_GRAPHICS, vertex_bytes, vertex_size, frag_bytes, frag_size, NULL, 0)) {
+ pipeline->backend = renderer->backend;
+
+ pipeline->next = renderer->pipeline_head;
+ pipeline->prev = NULL;
+ renderer->pipeline_head = pipeline;
+ pipeline->type = REND__PIPELINE_GRAPHICS;
+ if(pipeline->next) {
+ pipeline->next->prev = pipeline;
+ }
+
+ return pipeline;
+ }
+ rfree(pipeline);
+ return NULL;
+}
+
+extern RendPipeline
+rend_pipeline_create_compute_spirv(RendRenderer renderer, const uint8_t *compute_bytes, size_t compute_size, const RendPushConstantInfo *push_constants, uint32_t push_constant_count)
+{
+ RendPipeline pipeline = rmalloc(sizeof *pipeline);
+ Rend__PipelineConfig config = {
+ .push_constants = push_constants,
+ .push_constant_count = push_constant_count,
+ };
+
+ if (rend_vtables[renderer->backend].pipeline_create(renderer, pipeline, config, REND__PIPELINE_COMPUTE, compute_bytes, compute_size, NULL, 0, NULL, 0)) {
+ pipeline->backend = renderer->backend;
+ pipeline->type = REND__PIPELINE_COMPUTE;
+
+ pipeline->next = renderer->pipeline_head;
+ pipeline->prev = NULL;
+ renderer->pipeline_head = pipeline;
+ if(pipeline->next) {
+ pipeline->next->prev = pipeline;
+ }
+
+ return pipeline;
+ }
+ rfree(pipeline);
+ return NULL;
+}
+
+extern RendPipeline
+rend_pipeline_create_meshlet_spirv(RendRenderer renderer, uint8_t *meshlet_bytes, size_t meshlet_size, uint8_t *frag_bytes, size_t frag_size, const RendPushConstantInfo *push_constants, uint32_t push_constant_count, RendPolygonMode polygon_mode, RendCullMode cull_mode, bool depth_test_enable)
+{
+ RendPipeline pipeline = rmalloc(sizeof *pipeline);
+ Rend__PipelineConfig config = {
+ .push_constants = push_constants,
+ .push_constant_count = push_constant_count,
+ .polygon_mode = polygon_mode,
+ .cull_mode = cull_mode,
+ .depth_test_enable = depth_test_enable
+ };
+ if (rend_vtables[renderer->backend].pipeline_create(renderer, pipeline, config, REND__PIPELINE_MESH, meshlet_bytes, meshlet_size, frag_bytes, frag_size, NULL, 0)) {
+ pipeline->backend = renderer->backend;
+ pipeline->type = REND__PIPELINE_MESH;
+
+ pipeline->next = renderer->pipeline_head;
+ pipeline->prev = NULL;
+ renderer->pipeline_head = pipeline;
+ if(pipeline->next) {
+ pipeline->next->prev = pipeline;
+ }
+
+ return pipeline;
+ }
+ rfree(pipeline);
+ return NULL;
+}
+
+extern void
+rend_pipeline_bind(RendPipeline pipeline)
+{
+ rend_vtables[pipeline->backend].pipeline_bind(pipeline);
+}
+
+extern void
+rend_pipeline_dispatch(RendPipeline pipeline, uint32_t x, uint32_t y, uint32_t z)
+{
+ rend_vtables[pipeline->backend].pipeline_dispatch(pipeline, x, y, z);
+}
+
+extern void
+rend_pipeline_draw(RendPipeline pipeline, size_t count, uint32_t instance_count)
+{
+ rend_vtables[pipeline->backend].pipeline_draw(pipeline, count, instance_count);
+}
+
+extern void
+rend_pipeline_draw_indexed(RendPipeline pipeline, uint32_t index_count, uint32_t first_index, int32_t vertex_offset, uint32_t instance_count)
+{
+ rend_vtables[pipeline->backend].pipeline_draw_indexed(pipeline, index_count, first_index, vertex_offset, instance_count);
+}
+
+extern void
+rend_pipeline_set_blend(RendPipeline pipeline, bool blend)
+{
+ rend_vtables[pipeline->backend].pipeline_set_blend(pipeline, blend);
+}
+
+extern void
+rend_cmd_render_begin(RendRenderer renderer, float r, float g, float b, float a)
+{
+ renderer->in_pass = 1;
+ rend_vtables[renderer->backend].renderer_render_pass_begin(renderer, r, g, b, a);
+}
+
+extern void
+rend_cmd_render_begin_texture(RendRenderer renderer, RendTexture *texture)
+{
+ renderer->in_pass = 1;
+ rend_vtables[renderer->backend].renderer_render_pass_begin_texture(renderer, texture);
+}
+
+extern void
+rend_cmd_render_end(RendRenderer renderer)
+{
+ renderer->in_pass = 0;
+ rend_vtables[renderer->backend].renderer_render_pass_end(renderer);
+}
+
+extern void
+rend_cmd_render_end_texture(RendRenderer renderer, RendTexture *texture)
+{
+ renderer->in_pass = 0;
+ rend_vtables[renderer->backend].renderer_render_pass_end_texture(renderer, texture);
+}
+
+extern void
+rend_cmd_bind_pipeline(RendPipeline pipeline)
+{
+ rend_vtables[pipeline->backend].pipeline_bind(pipeline);
+}
+
+extern void
+rend_cmd_bind_vertex_buffer(RendPipeline pipeline, uint32_t binding, RendBuffer buffer, size_t offset)
+{
+ rend_vtables[pipeline->backend].pipeline_bind_vertex_buffer(pipeline, binding, buffer, offset);
+}
+
+extern void
+rend_cmd_bind_index_buffer(RendPipeline pipeline, RendBuffer buffer, size_t offset, RendIndexType index_type)
+{
+ rend_vtables[pipeline->backend].pipeline_bind_index_buffer(pipeline, buffer, offset, index_type);
+}
+
+extern void
+rend_cmd_push_constants(RendPipeline pipeline, void *push_data, size_t size)
+{
+ assert(pipeline != NULL && "Pipeline handle is NULL!");
+ assert(rend_vtables[pipeline->backend].pipeline_push_constants && "Backend function not implemented!");
+ rend_vtables[pipeline->backend].pipeline_push_constants(pipeline, push_data, size);
+}
+
+extern void
+rend_cmd_dispatch(RendPipeline pipeline, uint32_t x, uint32_t y, uint32_t z)
+{
+ rend_vtables[pipeline->backend].pipeline_dispatch(pipeline, x, y, z);
+}
+
+extern void
+rend_cmd_draw(RendPipeline pipeline, size_t count, uint32_t instance_count)
+{
+ rend_vtables[pipeline->backend].pipeline_draw(pipeline, count, instance_count);
+}
+
+extern void
+rend_cmd_draw_indexed(RendPipeline pipeline, uint32_t index_count, uint32_t first_index, int32_t vertex_offset, uint32_t instance_count)
+{
+ rend_vtables[pipeline->backend].pipeline_draw_indexed(pipeline, index_count, first_index, vertex_offset, instance_count);
+}
+
+extern void
+rend_cmd_blit(RendRenderer renderer, RendTexture *src, RendTexture *dst, uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h, uint32_t dst_x, uint32_t dst_y, uint32_t dst_w, uint32_t dst_h)
+{
+ assert(renderer->in_frame && "Must be called while rendering a frame!");
+ assert(!renderer->in_pass && "Must be called outside a render pass!");
+ renderer->in_pass = true;
+ rend_vtables[renderer->backend].texture_blit(renderer, src, dst, src_x, src_y, src_w, src_h, dst_x, dst_y, dst_w, dst_h);
+}
+
+static bool
+rend__renderer_init_recursive(RendRenderer renderer, RendBackendType backend, bool auto_pick)
+{
+ /* set autopick to true and set backend to first backend*/
+ if (backend == REND_BACKEND_AUTO) {
+ backend++;
+ auto_pick = true;
+ }
+
+ switch (backend) {
+ default:
+ /* unless using auto pick to pick renderers, crash */
+ if (!auto_pick) REND__CRASH("Invalid backend type!");
+
+ case REND_BACKEND_VULKAN_14:
+ if (rend_vk14_init()) {
+ rend_backend_vk14_initialized = true;
+ renderer->backend = REND_BACKEND_VULKAN_14;
+ return true;
+ }
+ break;
+ }
+
+ if (auto_pick) {
+ backend++;
+ if (backend >= REND_BACKEND_COUNT) {
+ return false;
+ }
+
+ /* recursively try next backend */
+ return rend__renderer_init_recursive(renderer, backend, auto_pick);
+ }
+
+ return false;
+}
+
+static void
+rend__renderer_destroy_recursive(RendRenderer renderer)
+{
+ if (renderer == NULL) return;
+
+ rend__renderer_destroy_recursive(renderer->next);
+
+ if (renderer->backend != 0) {
+ rend_vtables[renderer->backend].renderer_destroy(renderer);
+ }
+
+ rend__pipeline_destroy_recursive(renderer->pipeline_head);
+ rfree(renderer);
+}
+
+static void
+rend__pipeline_destroy_recursive(RendPipeline pipeline)
+{
+ if (pipeline == NULL) return;
+ rend__pipeline_destroy_recursive(pipeline->next);
+ rfree(pipeline);
+}
diff --git a/rend.h b/rend.h
new file mode 100644
index 0000000..0d859d3
--- /dev/null
+++ b/rend.h
@@ -0,0 +1,290 @@
+/* ===========================================================================
+ * REND - Renderer Library - Copyright (c) 2026 Vasco Alves
+ *
+ * DESCRIPTION:
+ * - High level graphics rendering API around Vulkan 1.4
+ * - Pushes data to the GPU in a highly configurable and STABLE fashion.
+ * - Is not responsible for initializing windows.
+ * - Is not responsible for compiling shaders.
+ * - Depends on PODIUM to be cross-platform.
+ *
+ * =========================================================================== */
+
+#ifndef _REND_H_
+#define _REND_H_
+
+#define REND_MAJOR 1 // breaking API changes
+#define REND_MINOR 0 // non-breaking features
+#define REND_PATCH 1 // non-breaking patches and bug fixes
+
+#define P_MODULE_VULKAN
+#define P_MODULE_MATH
+#include "podium.h"
+
+typedef struct rend_renderer_t* RendRenderer; // renderer target handle
+typedef struct rend_pipeline_t* RendPipeline; // represents a baked shader + gpu pipeline state (blend mode, depth, vertex format)
+
+typedef struct RendMemory RendMemory;
+typedef struct RendSpecs RendSpecs;
+typedef struct RendBuffer RendBuffer;
+typedef struct RendTexture RendTexture;
+
+/* Typedef enums as 16 bit unsigned integers */
+typedef uint16_t RendBackendType;
+typedef uint16_t RendLifetime;
+typedef uint16_t RendFormat;
+typedef uint16_t RendTopology;
+typedef uint16_t RendCullMode;
+typedef uint16_t RendPolygonMode;
+typedef uint16_t RendBufferType;
+
+typedef enum RendInputRate { REND_INPUT_RATE_INSTANCE, REND_INPUT_RATE_VERTEX } RendInputRate;
+typedef enum RendIndexType { REND_INDEX_UINT16 = 16, REND_INDEX_UINT32 = 32 } RendIndexType;
+
+typedef struct {
+ uint64_t binding;
+ uint64_t stride;
+ uint8_t input_rate;
+} RendVertexBinding;
+
+typedef struct {
+ uint64_t location;
+ uint64_t binding;
+ uint64_t offset;
+ RendFormat format;
+} RendVertexAttributes;
+
+typedef struct {
+ uint32_t offset;
+ uint32_t size;
+} RendPushConstantInfo;
+
+#define REND_MAX_BINDINGS 8
+
+typedef struct {
+ uint32_t ubo_bindings[REND_MAX_BINDINGS];
+ uint32_t ubo_array_sizes[REND_MAX_BINDINGS];
+ uint32_t ubo_binding_count;
+
+ uint32_t ssbo_bindings[REND_MAX_BINDINGS];
+ uint32_t ssbo_array_sizes[REND_MAX_BINDINGS];
+ uint32_t ssbo_binding_count;
+
+ uint32_t texture_bindings[REND_MAX_BINDINGS];
+ uint32_t texture_array_sizes[REND_MAX_BINDINGS];
+ uint32_t texture_binding_count;
+} RendBindingInfo; // the binding of rend: rebirth
+
+/* Clean up */
+extern void rend_quit(void); // Will free ALL resources created by the library such as RendRenderer, RendPipeline, RendBuffer and RendTexture.
+
+/* Renderer */
+extern RendRenderer rend_renderer_create(P_Window*, RendBackendType backend, void* device, bool vsync, RendBindingInfo *bind_info); // Create Renderer that renders to a target window with
+extern void rend_renderer_destroy(RendRenderer renderer); // Destroy renderer. Unless you need to freely create and destroy renderers, you can rely on rend_quit to clean up.
+extern bool rend_renderer_frame_begin(RendRenderer renderer); // May fail. Acquires backbuffer and starts recording commands!
+extern void rend_renderer_frame_end(RendRenderer renderer, float *delta); // Stops recording commands and presents the contents to the screen.
+
+/* Write to Descriptor Sets */
+extern void rend_descriptor_write_ubo(RendRenderer, RendBuffer ubo, uint32_t binding, uint32_t slot);
+extern void rend_descriptor_write_ssbo(RendRenderer, RendBuffer ssbo, uint32_t binding, uint32_t slot);
+extern void rend_descriptor_write_texture(RendRenderer, RendTexture *texture, uint32_t binding, uint32_t slot); // Writes texture to a slot in the texture array. Does not need to be in the main render loop.
+
+/* Buffers */
+extern RendBuffer rend_buffer_create(RendRenderer renderer, size_t size, RendBufferType type, bool gpu);
+extern void rend_buffer_destroy(RendBuffer *buffer);
+extern void rend_buffer_write(RendRenderer renderer, RendBuffer *buffer, const void *data, size_t size, size_t offset);
+extern void rend_buffer_copy(RendRenderer renderer, RendBuffer *dest, size_t dest_offset, RendBuffer *src, size_t src_offset, size_t bytes);
+extern uint64_t rend_buffer_address(RendBuffer *buffer);
+
+/* Textures */
+extern RendTexture rend_texture_create(RendRenderer renderer, uint32_t width, uint32_t height, uint32_t depth, uint32_t mip_levels, uint32_t layers, RendFormat format); // Create a texture.
+extern RendTexture rend_texture_create_from_data(RendRenderer renderer, const void *data, uint32_t width, uint32_t height, RendFormat format); // Create texture and copy data to it immediately.
+extern void rend_texture_destroy(RendRenderer renderer, RendTexture *texture); // Destroy texture. Does not deallocate it's memory from the bump allocator.
+extern void rend_texture_copy_data(RendRenderer renderer, RendTexture *texture, const void *data, size_t size); // Copy data to texture. MUST be the same size as the format expects (width x height x sizeof format).
+extern void rend_texture_copy_buffer(RendRenderer renderer, RendTexture *texture, RendBuffer *buffer); // Copy buffer to texture.
+
+/* Create, Configure, and Destroy Rendering Pipelines */
+extern RendPipeline rend_pipeline_create_graphics_spirv(RendRenderer renderer, uint8_t *vertex_bytes, size_t vertex_size, uint8_t *frag_bytes, size_t frag_size, const RendVertexBinding *vertex_bindings, uint32_t vertex_binding_count, const RendVertexAttributes *vertex_attributes, uint32_t vertex_attribute_count, const RendPushConstantInfo *push_constants, uint32_t push_constant_count, RendPolygonMode polygon_mode, RendCullMode cull_mode, RendTopology topology, RendFormat color_format, bool depth_test_enable); // Create a pipeline for a renderer using a configuration handle.
+extern RendPipeline rend_pipeline_create_graphics_bindless_spirv(RendRenderer renderer, uint8_t *vertex_bytes, size_t vertex_size, uint8_t *frag_bytes, size_t frag_size, const RendPushConstantInfo *push_constants, uint32_t push_constant_count, RendPolygonMode polygon_mode, RendCullMode cull_mode, RendTopology topology, RendFormat color_format, bool depth_test_enable); // Create a pipeline for a renderer using a configuration handle.
+extern RendPipeline rend_pipeline_create_meshlet_spirv(RendRenderer renderer, uint8_t *meshlet_bytes, size_t meshlet_size, uint8_t *frag_bytes, size_t frag_size, const RendPushConstantInfo *push_constants, uint32_t push_constant_count, RendPolygonMode polygon_mode, RendCullMode cull_mode, bool depth_test_enable); // Creates a meshlet rendering pipeline.
+extern RendPipeline rend_pipeline_create_compute_spirv(RendRenderer renderer, const uint8_t *compute_bytes, size_t compute_size, const RendPushConstantInfo *push_constants, uint32_t push_constant_count); // Create a compute pipeline.
+
+/* Commands */
+extern void rend_cmd_render_begin(RendRenderer renderer, float r, float g, float b, float a); // Begin render pass to default target the window.
+extern void rend_cmd_render_begin_texture(RendRenderer renderer, RendTexture *texture); // Begin render pass with a texture as the target.
+extern void rend_cmd_render_end(RendRenderer renderer); // End render pass.
+extern void rend_cmd_render_end_texture(RendRenderer renderer, RendTexture *texture); // End render pass that targets texture. Transition texture to read optimal format.
+extern void rend_cmd_bind_pipeline(RendPipeline pipeline); // Bind the pipeline to this frame.
+extern void rend_cmd_bind_vertex_buffer(RendPipeline pipeline, uint32_t binding, RendBuffer buffer, size_t offset); // Bind vertex buffer to this graphics pipeline.
+extern void rend_cmd_bind_index_buffer(RendPipeline pipeline, RendBuffer buffer, size_t offset, RendIndexType index_type); // Bind index buffer to this graphics pipeline.
+extern void rend_cmd_push_constants(RendPipeline pipeline, void *push_data, size_t size); // Send push constants to this pipeline / command buffer.
+extern void rend_cmd_dispatch(RendPipeline pipeline, uint32_t x, uint32_t y, uint32_t z); // Dispatch compute commands to group with dimensions x, y, z!
+extern void rend_cmd_draw(RendPipeline pipeline, size_t count, uint32_t instance_count); // Calls draw command on the pipeline.
+extern void rend_cmd_draw_indexed(RendPipeline pipeline, uint32_t index_count, uint32_t first_index, int32_t vertex_offset, uint32_t instance_count); // Draw the pipeline using indexed rendering.
+extern void rend_cmd_blit(RendRenderer renderer, RendTexture *src, RendTexture *dst, uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h, uint32_t dst_x, uint32_t dst_y, uint32_t dst_w, uint32_t dst_h); // Blit a section of one texture onto another texture.
+
+enum RendBackendType_t {
+ REND_BACKEND_AUTO = 0,
+ REND_BACKEND_VULKAN_14,
+ REND_BACKEND_COUNT
+};
+
+enum RendLifetime_t {
+ REND_LIFETIME_FRAME = 0,
+ REND_LIFETIME_PERMANENT
+};
+
+enum RendTopology_t {
+ REND_TOPOLOGY_TRIANGLE_LIST = 0,
+ REND_TOPOLOGY_TRIANGLE_STRIP,
+ REND_TOPOLOGY_LINE_LIST,
+ REND_TOPOLOGY_LINE_STRIP,
+ REND_TOPOLOGY_POINT_LIST,
+};
+
+enum RendCullMode_t {
+ REND_CULL_MODE_NONE = 0,
+ REND_CULL_MODE_FRONT,
+ REND_CULL_MODE_BACK,
+ REND_CULL_MODE_FRONT_AND_BACK,
+};
+
+enum RendPolygonMode_t {
+ REND_POLYGON_MODE_FILL = 0,
+ REND_POLYGON_MODE_LINE,
+ REND_POLYGON_MODE_POINT,
+};
+
+enum RendFormat_t {
+ REND_FORMAT_UNDEFINED = 0,
+ REND_FORMAT_R8_UNORM,
+ REND_FORMAT_R8G8_UNORM,
+ REND_FORMAT_R8G8B8A8_UNORM,
+ REND_FORMAT_B8G8R8A8_UNORM,
+
+ REND_FORMAT_R8G8B8A8_SRGB,
+ REND_FORMAT_B8G8R8A8_SRGB,
+
+ REND_FORMAT_R32_SFLOAT,
+ REND_FORMAT_R32G32_SFLOAT,
+ REND_FORMAT_R32G32B32_SFLOAT,
+ REND_FORMAT_R32G32B32A32_SFLOAT,
+
+ /* useful aliases */
+ REND_FORMAT_1_SFLOAT32 = REND_FORMAT_R32_SFLOAT,
+ REND_FORMAT_2_SFLOAT32 = REND_FORMAT_R32G32_SFLOAT,
+ REND_FORMAT_3_SFLOAT32 = REND_FORMAT_R32G32B32_SFLOAT,
+ REND_FORMAT_4_SFLOAT32 = REND_FORMAT_R32G32B32A32_SFLOAT,
+
+ REND_FORMAT_R16_SFLOAT,
+ REND_FORMAT_R16G16_SFLOAT,
+ REND_FORMAT_R16G16B16A16_SFLOAT,
+
+ REND_FORMAT_R8G8B8A8_UINT,
+ REND_FORMAT_R16G16B16A16_UINT,
+ REND_FORMAT_R32_UINT,
+ REND_FORMAT_R32_SINT,
+ REND_FORMAT_R32G32B32A32_UINT,
+
+ REND_FORMAT_D32_SFLOAT,
+ REND_FORMAT_D24_UNORM_S8_UINT,
+ REND_FORMAT_D32_SFLOAT_S8_UINT,
+
+ REND_FORMAT_COUNT
+};
+
+static size_t rend_format_size[REND_FORMAT_COUNT] = {
+ [REND_FORMAT_UNDEFINED] = 0,
+
+ [REND_FORMAT_R8_UNORM] = 1,
+ [REND_FORMAT_R8G8_UNORM] = 2,
+ [REND_FORMAT_R8G8B8A8_UNORM] = 4,
+ [REND_FORMAT_B8G8R8A8_UNORM] = 4,
+
+ [REND_FORMAT_R8G8B8A8_SRGB] = 4,
+ [REND_FORMAT_B8G8R8A8_SRGB] = 4,
+
+ [REND_FORMAT_R32_SFLOAT] = 4,
+ [REND_FORMAT_R32G32_SFLOAT] = 8,
+ [REND_FORMAT_R32G32B32_SFLOAT] = 12,
+ [REND_FORMAT_R32G32B32A32_SFLOAT] = 16,
+
+ [REND_FORMAT_R16_SFLOAT] = 2,
+ [REND_FORMAT_R16G16_SFLOAT] = 4,
+ [REND_FORMAT_R16G16B16A16_SFLOAT] = 8,
+
+ [REND_FORMAT_R8G8B8A8_UINT] = 4,
+ [REND_FORMAT_R16G16B16A16_UINT] = 8,
+ [REND_FORMAT_R32_UINT] = 4,
+ [REND_FORMAT_R32_SINT] = 4,
+ [REND_FORMAT_R32G32B32A32_UINT] = 16,
+
+ [REND_FORMAT_D32_SFLOAT] = 4,
+ [REND_FORMAT_D24_UNORM_S8_UINT] = 4,
+ [REND_FORMAT_D32_SFLOAT_S8_UINT] = 8, // typically padded to 64-bit alignment by GPU drivers
+};
+
+enum RendBufferType_t {
+ REND_BUFFER_VERTEX ,
+ REND_BUFFER_INDEX ,
+ REND_BUFFER_UNIFORM ,
+ REND_BUFFER_STORAGE ,
+ REND_BUFFER_INDIRECT ,
+ REND_BUFFER_TRANSFER ,
+ REND_BUFFER_COUNT ,
+};
+
+
+
+/* CHANGE LOG
+ * 0.1.0 - @vasco - vulkan instance
+ * 0.1.1 - @vasco - swapchain
+ * 0.1.2 - @vasco - command buffers
+ * 0.2.0 - @vasco - push basic vertex data to the gpu
+ * 0.3.1 - @vasco - Fixed rend_quit not freeing all objects.
+ * 0.4.0 - @vasco - Added resource sets.
+ * 0.4.1 - @vasco - Fixed binding descriptor sets.
+ * 0.4.2 - @vasco - Fixed capped framerate due to FIFO being always enabled and added vsync option.
+ * 0.4.3 - @vasco - Replaced bad fence based synchronization with a single timeline semaphore.
+ * 0.4.4 - @vasco - Deprecated pipeline destruction and clearing because it doesnt make any sense.
+ * 0.4.5 - @vasco - Exposed depth testing.
+ * 0.5.0 - @vasco - Host-visible buffers
+ * 0.5.1 - @vasco - Removed RendMemProperties from public API.
+ * 0.5.2 - @vasco - push_data is now push_vertices_and_draw and pipeline_draw not takes vertex count
+ * 0.6.0 - @vasco - indexed rendering
+ * 0.6.1 - @vasco - cool beans
+ * 0.6.2 - @vasco - Removed RendMemProperties is back.
+ * 0.6.3 - @vasco - push_data removed in favor of making the pipeline more low level. A prebuilt "gfx" pipeline can be added in the future.
+ * 0.6.4 - @vasco - Moved to push constants and bindless descriptors unde the hood.
+ * 0.7.0 - @vasco - Low level buffer creation API if you want device-local data.
+ * 0.7.1 - @vasco - typedefs for ease of use
+ * 0.8.0 - @vasco - bindless resources
+ * 0.8.1 - @vasco - remove old binding code from backend
+ * 0.8.2 - @vasco - nothing works!!!
+ * 0.8.3 - @vasco - pool allocator
+ * 0.8.4 - @vasco - buffers 2.0
+ * 0.8.5 - @vasco - memory 2.0
+ * 0.8.6 - @vasco - arena allocator
+ * 0.8.7 - @vasco - everything works!!!
+ * 0.8.8 - @vasco - fix device selection
+ * 0.8.9 - @vasco - images 2.0
+ * 0.9.0 - @vasco - textures!!!!!
+ * 0.9.1 - @vasco - clear to color
+ * 0.10.0 - @vasco - instanced rendering
+ * 0.10.1 - @vasco - API clean up pt. 1 (remove RendShader in favor of pointers to data)
+ * 0.10.2 - @vasco - API clean up pt. 2 (remove RendPipelineConfig in favor of large functions)
+ * 0.11.0 - @vasco - compute shaders, dispatch command and render pass is now separate
+ * 0.11.1 - @vasco - better descriptor binding, multiple ubo, ssbo and texture arrays
+ * 0.11.2 - @vasco - cool beans
+ * 1.0.0 - @vasco - finished API release
+ * 1.0.1 - @vasco - render pass that targets textures
+ *
+ * 1.0.0 finished API release
+ *
+ * -------------------------------------------
+ *
+ * 1.1.0 shader hot realoading plugin (need to add settings managament and dll loading to Podium)
+ */
+
+
+#endif
diff --git a/rend_internal.h b/rend_internal.h
new file mode 100644
index 0000000..b88f339
--- /dev/null
+++ b/rend_internal.h
@@ -0,0 +1,199 @@
+#ifndef REND_INTERNAL_H
+#define REND_INTERNAL_H
+
+#include "rend_vk_internal.h"
+
+#if defined(REND_DEBUG)
+#define P_LOG_DEBUG_ENABLED 1
+#endif
+
+#if defined(REND_DEBUG_MEMORY)
+#define rmalloc(size) p_debug_malloc_impl((size), __FILE__, __LINE__, __func__)
+#define rrealloc(ptr, size) p_debug_realloc_impl((ptr), (size), __FILE__, __LINE__, __func__)
+#define rfree(ptr) p_debug_free_impl((ptr), __FILE__, __LINE__, __func__)
+#else
+#define rmalloc malloc
+#define rrealloc(ptr, size) realloc((ptr), (size))
+#define rfree free
+#endif
+
+#define REND_TODO \
+ do { \
+ fprintf(stderr, "REND TODO: %s() in %s:%d\n", __func__, __FILE__, __LINE__); \
+ abort(); \
+ } while(0)
+
+#define REND__CRASH(...)\
+ PERROR(__VA_ARGS__);\
+ exit(1);
+
+#define REND__WARN(...) PWARN("[REND] "__VA_ARGS__);
+
+#include "podium.h"
+#include "rend.h"
+
+#include <stdint.h>
+
+
+enum RendPipelineType {
+ REND__PIPELINE_GRAPHICS,
+ REND__PIPELINE_COMPUTE,
+ REND__PIPELINE_MESH,
+};
+
+typedef struct rend_pipeline_config_t {
+
+ const RendVertexBinding *vertex_bindings;
+ const RendVertexAttributes *vertex_attributes;
+ const RendPushConstantInfo *push_constants;
+
+ uint32_t vertex_binding_count;
+ uint32_t vertex_attribute_count;
+ uint32_t push_constant_count;
+
+ uint16_t color_format;
+ uint16_t depth_format;
+
+ uint16_t polygon_mode;
+ uint16_t cull_mode;
+ uint16_t topology;
+
+ uint8_t depth_test_enable;
+} Rend__PipelineConfig;
+
+typedef struct {
+ bool (*renderer_create)(RendRenderer, P_Window *window);
+ void (*renderer_destroy)(RendRenderer);
+
+ bool (*renderer_frame_begin)(RendRenderer);
+ void (*renderer_frame_end)(RendRenderer, float *delta);
+
+ void (*descriptor_write_buffer)(RendRenderer renderer, RendBuffer ubo, uint32_t binding, uint32_t slot, uint32_t offset, uint32_t size, bool is_ubo);
+ void (*descriptor_write_texture)(void *ctx, RendTexture *texture, uint32_t binding, uint32_t slot);
+
+ RendBuffer (*buffer_create_lifetime)(RendRenderer renderer, size_t size, RendBufferType type, bool gpu, int lifetime);
+ void (*buffer_destroy)(RendBuffer *buffer);
+ void (*buffer_copy)(RendRenderer renderer, RendBuffer *dest, size_t dest_offset, RendBuffer *src, size_t src_offset, size_t bytes);
+
+ RendTexture (*texture_create)(RendRenderer renderer, uint32_t width, uint32_t height, uint32_t depth, uint32_t mip_levels, uint32_t layers, RendFormat format);
+ void (*texture_destroy)(RendRenderer renderer, RendTexture *tex);
+ void (*texture_copy_buffer)(RendRenderer renderer, RendTexture *texture, RendBuffer *buffer);
+ void (*texture_blit)(RendRenderer renderer, RendTexture *src, RendTexture *dst, uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h, uint32_t dst_x, uint32_t dst_y, uint32_t dst_w, uint32_t dst_h);
+
+ bool (*pipeline_create)(RendRenderer, RendPipeline, Rend__PipelineConfig, uint8_t type, const uint8_t *shader1, size_t bytes1, const uint8_t *shader2, size_t bytes2, const uint8_t *shader3, size_t bytes3);
+ void (*pipeline_bind)(RendPipeline);
+ void (*pipeline_push_constants)(RendPipeline pipeline, void *push_data, size_t size);
+
+ void (*pipeline_bind_vertex_buffer)(RendPipeline pipeline, uint32_t binding, RendBuffer buffer, size_t offset);
+ void (*pipeline_bind_index_buffer)(RendPipeline pipeline, RendBuffer buffer, size_t offset, RendIndexType index_type);
+
+ void (*pipeline_dispatch)(RendPipeline pipeline, uint32_t x, uint32_t y, uint32_t z);
+ void (*pipeline_draw)(RendPipeline, size_t count, uint32_t instance_count);
+ void (*pipeline_draw_indexed)(RendPipeline pipeline, uint32_t index_count, uint32_t first_index, int32_t vertex_offset, uint32_t instance_count);
+ void (*pipeline_set_blend)(RendPipeline, bool);
+
+ void (*renderer_render_pass_begin)(RendRenderer renderer, float r, float g, float b, float a);
+ void (*renderer_render_pass_begin_texture)(RendRenderer, RendTexture*);
+ void (*renderer_render_pass_end)(RendRenderer renderer);
+ void (*renderer_render_pass_end_texture)(RendRenderer renderer, RendTexture*);
+} RendVTable;
+
+
+struct rend_pipeline_t {
+ struct rend_pipeline_t *next;
+ struct rend_pipeline_t *prev;
+ void *backend_ctx;
+ uint32_t idx; // index into renderers internal array of pipelines
+ uint32_t frame_count;
+ uint8_t backend;
+ uint8_t type;
+};
+
+struct rend_renderer_t {
+ struct rend_renderer_t *next;
+ struct rend_renderer_t *prev;
+
+ struct rend_pipeline_t *pipeline_head;
+ void* context; // backend specific internal data
+ P_Window *window;
+ uint64_t frame_count;
+
+ RendBindingInfo bind_info;
+
+ uint32_t texture_binding;
+ uint32_t texture_count;
+ uint32_t ubo_binding;
+ uint32_t ubo_count;
+
+ uint8_t backend;
+ uint8_t in_frame;
+ uint8_t in_pass;
+ uint8_t vsync;
+};
+
+
+/* NOTE(vasco): Its simpler if backeds all use a uniform struct than
+ * every single one having to basically redefine the same thing
+ */
+
+struct RendMemory {
+ void *host_mapped_memory; // pointer if memory is host visible
+ uint64_t device_memory; // original device memory pointer
+ uint64_t size; // size of the memory allocation AFTER THE OFFSET
+ uint64_t offset; // we must sum the offset to device memory
+ uint32_t heap_index; // heap index where the memory is located
+ uint32_t id; // used by custom allocators
+};
+
+struct RendBuffer {
+ RendMemory memory;
+ void *mapped_memory; // pointer to offset memory
+ void *allocator;
+ void *logical_device;
+ uint64_t handle;
+ uint64_t gpu_address; // addresses must be buffer specific and cannot be generalized into memory because of how vulkan works
+ uint32_t usage;
+ uint32_t size;
+ uint8_t backend;
+};
+
+struct RendTexture {
+
+ RendMemory memory;
+ void *ctx;
+
+ uint64_t handle;
+ uint64_t view;
+ uint64_t sampler;
+
+ uint64_t id;
+
+ uint32_t width;
+ uint32_t height;
+
+ uint32_t format;
+
+ uint32_t depth;
+ uint32_t mip_levels;
+ uint32_t layers;
+
+ uint32_t img_type;
+ uint32_t usage;
+ uint32_t sample_count_flags;
+ uint32_t sharing_mode;
+
+ uint32_t layout;
+
+ uint8_t backend;
+};
+
+struct RendSpecs {
+ bool graphics;
+ bool transfer;
+ bool compute;
+ bool present;
+ bool sampler_anisotropy;
+ bool discrete_gpu;
+};
+
+#endif
diff --git a/rend_vk.c b/rend_vk.c
new file mode 100644
index 0000000..9deca12
--- /dev/null
+++ b/rend_vk.c
@@ -0,0 +1,2131 @@
+#pragma once
+#include "rend.h"
+#include "rend_internal.h"
+#include <stdint.h>
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_core.h>
+
+#include "rend_vk_internal.h"
+#include "rend_vk_allocator.c"
+#include "rend_vk_device.c"
+#include "rend_vk_arena.c"
+#include "rend_vk_image.c"
+
+#define REND_MIN_FRAMES_IN_FLIGHT 2 // double buffering!
+#define REND_MAX_FRAMES_IN_FLIGHT 4 // quadruple buffering!
+
+#define REND_VK_MAX_PIPELINES 100
+
+typedef struct {
+ VkSwapchainKHR handle;
+ VkSurfaceFormatKHR format;
+ VkExtent2D extent;
+
+ uint32_t image_count;
+ VkImage *images;
+ VkImageView *views;
+
+ RendVkImage depth_attachment;
+} RendVkSwapchain;
+
+typedef struct {
+ VkCommandPool command_pool;
+ VkCommandBuffer command_buffer;
+ VkSemaphore image_acquired_semaphore;
+} RendVkFrameResources;
+
+typedef struct {
+ uint8_t push_data[128];
+ uint32_t size;
+} RendVkResourceSet;
+
+typedef struct RendVkPipeline {
+ void *ctx;
+ VkPipeline handle;
+ VkPipelineLayout layout;
+ uint32_t vertex_count;
+ uint32_t vertex_stride;
+ uint32_t push_constants_range;
+
+ bool blend_enable;
+} RendVkPipeline;
+
+
+/*
+ * per renderer context, separate from global vk_ variables
+ * such as the instance, the allocator, the devices etc...
+ */
+typedef struct RendVk14Context {
+
+ P_Window *window;
+ VkSurfaceKHR surface;
+ RendVkPipeline pipelines[REND_VK_MAX_PIPELINES];
+ RendVkFrameResources frame_resources[REND_MAX_FRAMES_IN_FLIGHT];
+ RendVkSwapchain swapchain;
+ VkSemaphore timeline_semaphore;
+ VkSemaphore render_complete_semaphores[REND_MAX_FRAMES_IN_FLIGHT];
+
+ VkCommandPool upload_command_pool;
+ VkCommandPool graphics_command_pool;
+
+ RendVkArenaAllocator arena_persistent; // magical arena allocator for every type of memory
+ RendVkArenaAllocator arena_frame;
+
+ uint64_t frame;
+ uint64_t frame_index;
+ uint64_t signal_value;
+ uint64_t next_signal_value;
+ uint64_t max_frames_in_flight;
+ uint32_t pipeline_count;
+ uint32_t image_index;
+
+ bool require_swapchain_recreation;
+
+ bool vsync;
+ bool in_frame;
+
+ VkDescriptorPool descriptor_pool;
+ VkDescriptorSet desc_set;
+ VkDescriptorSetLayout desc_layout;
+
+} RendVk14Context;
+
+
+/* function declarations */
+extern bool rend_vk14_init();
+extern void rend_vk14_quit();
+
+extern bool rend_vk14_renderer_create(RendRenderer, P_Window *window);
+extern void rend_vk14_renderer_destroy(RendRenderer);
+extern bool rend_vk14_renderer_frame_begin(RendRenderer);
+extern void rend_vk14_renderer_frame_end(RendRenderer, float *delta);
+
+static inline void rend_vk14__renderer_render_pass_begin_internal(RendRenderer renderer, float r, float g, float b, float a, uint64_t view_handle, uint64_t depth_attachment_view_handle, uint32_t offset_x, uint32_t offset_y, uint32_t width, uint32_t height);
+
+extern void rend_vk14_renderer_render_pass_begin(RendRenderer renderer, float r, float g, float b, float a);
+extern void rend_vk14_renderer_render_pass_begin_texture(RendRenderer renderer, RendTexture *texture);
+extern void rend_vk14_renderer_render_pass_end(RendRenderer renderer);
+extern void rend_vk14_renderer_render_pass_end_texture(RendRenderer renderer, RendTexture *texture);
+
+extern void rend_vk14_descriptor_write_buffer(RendRenderer renderer, RendBuffer ubo, uint32_t binding, uint32_t slot, uint32_t offset, uint32_t size, bool is_ubo);
+extern void rend_vk14_descriptor_write_texture(void *ctx, RendTexture *texture, uint32_t binding, uint32_t slot);
+
+extern RendBuffer rend_vk14_buffer_create_lifetime(RendRenderer renderer, size_t size, RendBufferType type, bool gpu, int lifetime);
+extern void rend_vk14_buffer_destroy(RendBuffer *buffer);
+extern void rend_vk14_buffer_copy(RendRenderer renderer, RendBuffer *dest, size_t dest_offset, RendBuffer *src, size_t src_offset, size_t bytes);
+
+extern RendTexture rend_vk14_texture_create(RendRenderer renderer, uint32_t width, uint32_t height, uint32_t depth, uint32_t mip_levels, uint32_t layers, RendFormat format);
+extern void rend_vk14_texture_destroy(RendRenderer renderer, RendTexture *tex);
+extern void rend_vk14_texture_transition_layout(RendRenderer renderer, VkCommandBuffer cmd, RendTexture *texture, VkImageLayout new_layout);
+extern void rend_vk14_texture_transfer_ownership_release(RendRenderer renderer, VkCommandBuffer cmd, RendTexture *texture, uint32_t src_family, uint32_t dst_family, VkImageLayout new_layout);
+extern void rend_vk14_texture_transfer_ownership_acquire(RendRenderer renderer, VkCommandBuffer cmd, RendTexture *texture, uint32_t src_family, uint32_t dst_family, VkImageLayout new_layout);
+extern void rend_vk14_texture_copy_buffer(RendRenderer renderer, RendTexture *texture, RendBuffer *buffer);
+extern void rend_vk14_texture_blit(RendRenderer renderer, RendTexture *src, RendTexture *dst, uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h, uint32_t dst_x, uint32_t dst_y, uint32_t dst_w, uint32_t dst_h);
+
+static VkCommandBuffer rend_vk_cmdbuffer_single_use_begin(VkCommandPool pool);
+static void rend_vk_cmdbuffer_single_use_end(VkCommandPool pool, VkCommandBuffer cmd, VkQueue q);
+
+extern bool rend_vk14_pipeline_create(RendRenderer renderer, RendPipeline pipeline, Rend__PipelineConfig config, uint8_t type, const uint8_t *shader1, size_t bytes1, const uint8_t *shader2, size_t bytes2, const uint8_t *shader3, size_t bytes3);
+extern void rend_vk14_pipeline_bind(RendPipeline);
+extern void rend_vk14_pipeline_push_constants(RendPipeline pipeline, void *push_data, size_t size);
+
+extern void rend_vk14_pipeline_bind_vertex_buffer(RendPipeline pipeline, uint32_t binding, RendBuffer buffer, size_t offset);
+extern void rend_vk14_pipeline_bind_index_buffer(RendPipeline pipeline, RendBuffer buffer, size_t offset, RendIndexType index_type);
+
+extern void rend_vk14_pipeline_dispatch(RendPipeline pipeline, uint32_t x, uint32_t y, uint32_t z);
+extern void rend_vk14_pipeline_draw(RendPipeline, size_t count, uint32_t instance_count);
+extern void rend_vk14_pipeline_draw_indexed(RendPipeline pipeline, uint32_t index_count, uint32_t first_index, int32_t vertex_offset, uint32_t instance_count);
+extern void rend_vk14_pipeline_set_blend(RendPipeline, bool);
+
+VKAPI_ATTR VkBool32 VKAPI_CALL rend_vk_debug_func( VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagsEXT message_types, const VkDebugUtilsMessengerCallbackDataEXT *callback_data, void *user_data);
+static void rend_vk_pipeline_destroy(RendVkPipeline *pipeline);
+static void rend_vk_swapchain_create(RendVk14Context *ctx, RendVkSwapchain *swapchain);
+static void rend_vk_swapchain_destroy(RendVk14Context *ctx, RendVkSwapchain *swapchain);
+
+static VkShaderModule rend_vk_shader_module_create(const void *data, size_t size);
+static uint32_t rend_vk_get_heap_index(uint32_t memory_type_bits, uint32_t preferred_index);
+
+
+extern bool
+rend_vk14_init()
+{
+ if (vk_instance) {
+ return true;
+ }
+
+ /* create instance if does not exist */
+ VkApplicationInfo vk_app_info = {VK_STRUCTURE_TYPE_APPLICATION_INFO};
+ vk_app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+ vk_app_info.pApplicationName = "REND";
+ vk_app_info.applicationVersion = VK_MAKE_VERSION(REND_MAJOR, REND_MINOR, REND_PATCH);
+ vk_app_info.apiVersion = VK_API_VERSION_1_4;
+ vk_app_info.pEngineName = "REND Renderer";
+ vk_app_info.engineVersion = VK_MAKE_VERSION(REND_MAJOR, REND_MINOR, REND_PATCH);
+
+ const char **extensions_darray = (const char **)p_vulkan_get_extensions();
+ p_darray_push(extensions_darray, VK_KHR_SURFACE_EXTENSION_NAME);
+
+#ifdef REND_DEBUG
+ p_darray_push(extensions_darray, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+ // p_darray_push(extensions_darray, VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME);
+ PDEBUG("Vulkan Extensions: ");
+ for (int i = 0; i < p_darray_len(extensions_darray); i++)
+ PDEBUG("%s", extensions_darray[i]);
+#endif
+
+ const char **required_validation_layers = NULL;
+
+#ifdef REND_DEBUG
+ p_darray_push(required_validation_layers, "VK_LAYER_KHRONOS_validation");
+
+ PDEBUG("Required Validation Layers: ");
+ for (uint32_t i = 0; i < p_darray_len(required_validation_layers); i++) {
+ PDEBUG(" - %s", required_validation_layers[i]);
+ }
+
+ VkValidationFeatureEnableEXT enabled_validation_features[] = {
+ // VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT,
+ // VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_RESERVE_BINDING_SLOT_EXT,
+ };
+
+ VkValidationFeaturesEXT validation_features = {
+ .sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT,
+ .pNext = NULL,
+ // .enabledValidationFeatureCount = sizeof(enabled_validation_features) / sizeof(enabled_validation_features[0]),
+ // .pEnabledValidationFeatures = enabled_validation_features,
+ };
+#endif
+
+ VkInstanceCreateInfo vk_create_info = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
+ vk_create_info.pApplicationInfo = &vk_app_info;
+ vk_create_info.ppEnabledExtensionNames = extensions_darray;
+ vk_create_info.enabledExtensionCount = p_darray_len(extensions_darray);
+ vk_create_info.pApplicationInfo = &vk_app_info;
+#ifdef REND_DEBUG
+ vk_create_info.enabledLayerCount = p_darray_len(required_validation_layers);
+ vk_create_info.ppEnabledLayerNames = required_validation_layers;
+ vk_create_info.pNext = &validation_features;
+#else
+ vk_create_info.enabledLayerCount = 0;
+ vk_create_info.ppEnabledLayerNames = NULL;
+#endif
+
+ VkResult res = vkCreateInstance(&vk_create_info, vk_allocator, &vk_instance);
+ CHECK_VK_RESULT(res);
+
+ PDEBUG("Vulkan instance created!");
+ p_darray_destroy(extensions_darray);
+#ifdef REND_DEBUG
+ p_darray_destroy(required_validation_layers);
+#endif
+
+#ifdef REND_DEBUG
+ uint32_t log_severity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
+ VkDebugUtilsMessengerCreateInfoEXT debug_create_info = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
+ debug_create_info.messageSeverity = log_severity;
+ debug_create_info.messageType =
+ VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
+ debug_create_info.pfnUserCallback = rend_vk_debug_func;
+
+ PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( vk_instance, "vkCreateDebugUtilsMessengerEXT");
+ if (!func) {
+ PDEBUG("Failed to create vulkan debug messenger!");
+ return false;
+ }
+ func(vk_instance, &debug_create_info, vk_allocator, &vk_debug_messenger);
+#endif
+ return true;
+}
+
+extern void
+rend_vk14_quit()
+{
+ PDEBUG("[REND_VK14] Destroying device.");
+
+ /* we destroy the device on quit */
+ if (vk_device.logical_device != 0) {
+ vkDeviceWaitIdle(vk_device.logical_device);
+ rend_vk_device_destroy();
+ vk_device.logical_device = 0;
+ }
+
+ if (vk_debug_messenger) {
+ PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( vk_instance, "vkDestroyDebugUtilsMessengerEXT");
+ func(vk_instance, vk_debug_messenger, vk_allocator);
+ vk_debug_messenger = 0;
+ }
+
+ if (vk_device.swapchain_support.format) rfree(vk_device.swapchain_support.format);
+ if (vk_device.swapchain_support.present_modes) rfree(vk_device.swapchain_support.present_modes);
+
+ vkDestroyInstance(vk_instance, vk_allocator);
+ vk_instance = 0;
+}
+
+extern bool
+rend_vk14_renderer_create(RendRenderer renderer, P_Window *window)
+{
+ RendVk14Context *ctx = rmalloc(sizeof(*ctx));
+ memset(ctx, 0, sizeof *ctx);
+ renderer->context = ctx;
+
+ ctx->window = window;
+ ctx->vsync = renderer->vsync;
+
+ /* get surface from window */
+ if (!p_vulkan_create_surface(window, vk_instance, vk_allocator, &ctx->surface)) {
+ PERROR("Failed to create vulkan surface!");
+ return false;
+ }
+
+ RendSpecs specs = {
+ .sampler_anisotropy = true,
+ .graphics = true,
+ .transfer = true,
+ .discrete_gpu = false, // even though its not a requirement, I expect discrete gpu to be picked
+ };
+
+ /* we lazily create the logical device only after creating the first renderer
+ * because we need the surface first */
+ if (vk_device.logical_device == 0) {
+ rend_vk_device_create(ctx->surface, specs, &vk_device, rend_vk_device_score_default);
+ }
+
+ /* we must create arena before swapchain */
+ ctx->arena_persistent = rend_vk_arena_create(
+ vk_device.logical_device,
+ vk_device.physical_device,
+ vk_device.properties.limits,
+ vk_allocator);
+
+ ctx->arena_frame = rend_vk_arena_create(
+ vk_device.logical_device,
+ vk_device.physical_device,
+ vk_device.properties.limits,
+ vk_allocator);
+
+ /* create swapchain */
+ rend_vk_swapchain_create(ctx, &ctx->swapchain);
+
+ /* timeline semaphore */
+ VkSemaphoreTypeCreateInfo timeline_type_info = {VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO};
+ timeline_type_info.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
+ timeline_type_info.initialValue = ctx->max_frames_in_flight;
+
+ VkSemaphoreCreateInfo timeline_semaphore_info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
+ timeline_semaphore_info.pNext = &timeline_type_info;
+
+ if (vkCreateSemaphore(vk_device.logical_device, &timeline_semaphore_info, vk_allocator, &ctx->timeline_semaphore) != VK_SUCCESS) {
+ PERROR("Unable to create the timeline semaphore for the renderer!");
+ return false;
+ }
+
+ /* create per frame semaphores */
+ VkSemaphoreCreateInfo frame_semaphore_info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
+ for (uint32_t u = 0; u < REND_MAX_FRAMES_IN_FLIGHT; ++u) {
+ if (vkCreateSemaphore(
+ vk_device.logical_device,
+ &frame_semaphore_info,
+ vk_allocator,
+ &ctx->frame_resources[u].image_acquired_semaphore) != VK_SUCCESS) {
+ PERROR("Unable to create semaphore for frame #%u!", u);
+ return false;
+ }
+
+ if (vkCreateSemaphore(
+ vk_device.logical_device,
+ &frame_semaphore_info,
+ vk_allocator,
+ &ctx->render_complete_semaphores[u]) != VK_SUCCESS) {
+ PERROR("Unable to create render complete semaphore #%u!", u);
+ return false;
+ }
+
+ VkCommandPoolCreateInfo pool_info = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
+ pool_info.queueFamilyIndex = vk_device.graphics_family_index;
+ if (vkCreateCommandPool(vk_device.logical_device, &pool_info, vk_allocator, &ctx->frame_resources[u].command_pool) != VK_SUCCESS) {
+ PERROR("Unable to create command pool for frame #%u!", u);
+ return false;
+ }
+
+ VkCommandBufferAllocateInfo cmdbuf_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
+ cmdbuf_info.commandPool = ctx->frame_resources[u].command_pool;
+ cmdbuf_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ cmdbuf_info.commandBufferCount = 1;
+ if (vkAllocateCommandBuffers(vk_device.logical_device, &cmdbuf_info, &ctx->frame_resources[u].command_buffer) != VK_SUCCESS) {
+ PERROR("Unable to create command buffer for frame #%u!", u);
+ return false;
+ }
+
+ }
+
+ VkCommandPoolCreateInfo pool_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = vk_device.transfer_family_index,
+ .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT
+ };
+
+ if (vkCreateCommandPool(vk_device.logical_device, &pool_info, vk_allocator, &ctx->upload_command_pool) != VK_SUCCESS) {
+ PERROR("Unable to create upload command pool!");
+ return false;
+ }
+
+ pool_info.queueFamilyIndex = vk_device.graphics_family_index;
+ if (vkCreateCommandPool(vk_device.logical_device, &pool_info, vk_allocator, &ctx->graphics_command_pool) != VK_SUCCESS) {
+ PERROR("Unable to create graphics command pool!");
+ return false;
+ }
+
+ ctx->frame = 0;
+ ctx->frame_index = 0;
+ ctx->next_signal_value = ctx->max_frames_in_flight + 1; /* start at frame zero */
+
+ /*
+ * Descriptor Pool
+ */
+ {
+ RendBindingInfo bind_info = renderer->bind_info;
+
+ uint32_t total_ubos = 0;
+ for (uint32_t i = 0; i < bind_info.ubo_binding_count; ++i) {
+ total_ubos += bind_info.ubo_array_sizes[i];
+ }
+
+ uint32_t total_ssbos = 0;
+ for (uint32_t i = 0; i < bind_info.ssbo_binding_count; ++i) {
+ total_ssbos += bind_info.ssbo_array_sizes[i];
+ }
+
+ uint32_t total_textures = 0;
+ for (uint32_t i = 0; i < bind_info.texture_binding_count; ++i) {
+ total_textures += bind_info.texture_array_sizes[i];
+ }
+
+ const uint32_t pool_count = 3;
+ VkDescriptorPoolSize pool_sizes[3] = {
+ { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = (total_ubos > 0) ? total_ubos : 1 },
+ { .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .descriptorCount = (total_ssbos > 0) ? total_ssbos : 1 },
+ { .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = (total_textures > 0) ? total_textures : 1 }
+ };
+
+ VkDescriptorPoolCreateInfo pool_info = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+ .flags = 0,
+ .maxSets = 1,
+ .poolSizeCount = pool_count,
+ .pPoolSizes = pool_sizes,
+ .pNext = NULL,
+ };
+
+ VkResult result = vkCreateDescriptorPool(vk_device.logical_device, &pool_info, vk_allocator, &ctx->descriptor_pool);
+ if (result != VK_SUCCESS) {
+ REND__CRASH("Failed to create descriptor pool!");
+ return false;
+ }
+
+
+ /*
+ * Descriptor Sets!!!!!!
+ */
+
+ const uint32_t binding_count = bind_info.ubo_binding_count + bind_info.ssbo_binding_count + bind_info.texture_binding_count;
+ VkDescriptorSetLayoutBinding binding_array[binding_count];
+
+ for (uint32_t i = 0; i < bind_info.ubo_binding_count; ++i) {
+ binding_array[i] = (VkDescriptorSetLayoutBinding) {
+ .binding = bind_info.ubo_bindings[i],
+ .descriptorCount = bind_info.ubo_array_sizes[i],
+ .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+ .stageFlags = VK_SHADER_STAGE_ALL,
+ .pImmutableSamplers = 0,
+ };
+ };
+
+ uint32_t offset = bind_info.ubo_binding_count;
+ for (uint32_t i = 0; i < bind_info.ssbo_binding_count; ++i) {
+ binding_array[i + offset] = (VkDescriptorSetLayoutBinding) {
+ .binding = bind_info.ssbo_bindings[i],
+ .descriptorCount = bind_info.ssbo_array_sizes[i],
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .stageFlags = VK_SHADER_STAGE_ALL,
+ .pImmutableSamplers = 0,
+ };
+ };
+
+ offset += bind_info.ssbo_binding_count;
+ for (uint32_t i = 0; i < bind_info.texture_binding_count; ++i) {
+ binding_array[i + offset] = (VkDescriptorSetLayoutBinding) {
+ .binding = bind_info.texture_bindings[i],
+ .descriptorCount = bind_info.texture_array_sizes[i],
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .stageFlags = VK_SHADER_STAGE_ALL,
+ .pImmutableSamplers = 0,
+ };
+ };
+
+ VkDescriptorBindingFlags binding_flags[binding_count];
+ for (uint32_t u = 0; u < binding_count; ++u) {
+ binding_flags[u] = VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT;
+ }
+
+ VkDescriptorSetLayoutBindingFlagsCreateInfo desc_flags_info = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO,
+ .bindingCount = binding_count,
+ .pBindingFlags = binding_flags
+ };
+
+ VkDescriptorSetLayoutCreateInfo desc_layout_info = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .flags = 0,
+ .bindingCount = binding_count,
+ .pBindings = binding_array,
+ .pNext = &desc_flags_info,
+ };
+
+ result = vkCreateDescriptorSetLayout(vk_device.logical_device, &desc_layout_info, vk_allocator, &ctx->desc_layout);
+ if (result != VK_SUCCESS) {
+ REND__CRASH("Failed to create descriptor set layout!");
+ return false;
+ }
+
+ VkDescriptorSetAllocateInfo alloc_info = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+ .descriptorPool = ctx->descriptor_pool,
+ .descriptorSetCount = 1, // ONLY ONE DESCRIPTOR SET
+ .pSetLayouts = &ctx->desc_layout,
+ .pNext = NULL,
+ };
+
+ result = vkAllocateDescriptorSets(vk_device.logical_device, &alloc_info, &ctx->desc_set);
+ if (result != VK_SUCCESS) {
+ REND__CRASH("Failed to allocate descriptors!!!");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+extern void
+rend_vk14_renderer_destroy(RendRenderer renderer)
+{
+ assert(renderer && renderer->context);
+ RendVk14Context *ctx = (RendVk14Context *)renderer->context;
+
+ VkDevice dev = vk_device.logical_device;
+
+ PDEBUG("[REND] Waiting for device...");
+ vkDeviceWaitIdle(dev);
+
+ PDEBUG("[REND] Destroying renderer...");
+ vkDestroySemaphore(dev, ctx->timeline_semaphore, vk_allocator);
+
+ /* destroy per frame semaphores */
+ for (uint32_t u = 0; u < REND_MAX_FRAMES_IN_FLIGHT; ++u) {
+ vkDestroySemaphore(dev, ctx->frame_resources[u].image_acquired_semaphore, vk_allocator);
+ vkDestroySemaphore(dev, ctx->render_complete_semaphores[u], vk_allocator);
+ vkDestroyCommandPool(dev, ctx->frame_resources[u].command_pool, vk_allocator);
+ ctx->frame_resources[u].image_acquired_semaphore = 0;
+ ctx->frame_resources[u].command_pool = 0;
+ ctx->frame_resources[u].command_buffer = 0;
+ }
+
+ vkDestroyCommandPool(vk_device.logical_device, ctx->upload_command_pool, vk_allocator);
+ vkDestroyCommandPool(vk_device.logical_device, ctx->graphics_command_pool, vk_allocator);
+
+ rend_vk_arena_destroy(&ctx->arena_persistent);
+ rend_vk_arena_destroy(&ctx->arena_frame);
+
+
+ vkDestroyDescriptorSetLayout(dev, ctx->desc_layout, vk_allocator);
+ vkDestroyDescriptorPool(dev, ctx->descriptor_pool, vk_allocator);
+
+ PDEBUG("[REND] Destroying pipelines...");
+ for (uint32_t u = 0; u < ctx->pipeline_count; ++u) {
+ rend_vk_pipeline_destroy(&ctx->pipelines[u]);
+ }
+
+ PDEBUG("[REND] Destroying swapchain...");
+ rend_vk_swapchain_destroy(ctx, &ctx->swapchain);
+ ctx->swapchain.handle = 0;
+
+ PDEBUG("[REND] Destroying surface...");
+ assert(vk_instance);
+ vkDestroySurfaceKHR(vk_instance, ctx->surface, vk_allocator);
+ ctx->surface = 0;
+
+ rfree(renderer->context);
+ renderer->context = 0;
+}
+
+extern bool
+rend_vk14_renderer_frame_begin(RendRenderer renderer)
+{
+ assert(renderer && (uintptr_t)renderer != 0xffffffff00000000 && "Possible stack corruption");
+ RendVk14Context *ctx = (RendVk14Context *)renderer->context;
+
+ /* check if swapchain needs recreation */
+ if (ctx->require_swapchain_recreation) {
+ PDEBUG("[REND] Awaiting device...");
+ vkDeviceWaitIdle(vk_device.logical_device);
+ PDEBUG("[REND] Recreating swapchain...");
+ rend_vk_swapchain_destroy(ctx, &ctx->swapchain);
+ rend_vk_swapchain_create(ctx, &ctx->swapchain);
+ ctx->require_swapchain_recreation = false;
+ }
+
+ VkDevice dev = vk_device.logical_device;
+
+ /* wait on timeline semaphore */
+ const uint64_t frame_res_index = ctx->frame % ctx->max_frames_in_flight;
+ ctx->frame_index = frame_res_index;
+
+ const uint64_t signal_value = ctx->next_signal_value++;
+ const uint64_t wait_value = signal_value - ctx->max_frames_in_flight;
+ ctx->signal_value = signal_value;
+
+ VkSemaphoreWaitInfo timeline_wait_info = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO,
+ .semaphoreCount = 1,
+ .pSemaphores = &ctx->timeline_semaphore,
+ .pValues = &wait_value
+ };
+
+ vkWaitSemaphores(vk_device.logical_device, &timeline_wait_info, UINT64_MAX);
+
+ RendVkFrameResources frame_resource = ctx->frame_resources[frame_res_index];
+ vkResetCommandPool(dev, frame_resource.command_pool, 0);
+
+ /* acquire next image */
+ VkResult acquire_image = vkAcquireNextImageKHR(
+ vk_device.logical_device,
+ ctx->swapchain.handle,
+ UINT64_MAX,
+ frame_resource.image_acquired_semaphore,
+ VK_NULL_HANDLE,
+ &ctx->image_index);
+
+ if (acquire_image == VK_ERROR_OUT_OF_DATE_KHR) {
+ /* out of date images, may need recreating */
+ ctx->require_swapchain_recreation = true;
+ ctx->frame++;
+ return false;
+ } else if (acquire_image == VK_SUBOPTIMAL_KHR) {
+ /* requires recreation but CAN CONTINUE RENDERING */
+ ctx->require_swapchain_recreation = true;
+ }
+
+ VkCommandBufferBeginInfo cmd_begin_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
+ };
+
+ vkBeginCommandBuffer(frame_resource.command_buffer, &cmd_begin_info);
+
+ static const size_t NUM_LAYOUT_BARRIERS = 2;
+ VkImageMemoryBarrier2 layout_barriers[NUM_LAYOUT_BARRIERS];
+ layout_barriers[0] = (VkImageMemoryBarrier2) {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
+
+ .srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
+ .srcAccessMask = 0,
+
+ .dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
+ .dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT_KHR,
+
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+
+ .image = ctx->swapchain.images[ctx->image_index],
+
+ .subresourceRange = (VkImageSubresourceRange) {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ },
+
+ // .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ // .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ };
+ layout_barriers[1] = (VkImageMemoryBarrier2) {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
+
+ .srcStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT,
+ .srcAccessMask = 0,
+
+ .dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT,
+ .dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
+
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
+
+ .image = ctx->swapchain.depth_attachment.handle,
+
+ .subresourceRange = (VkImageSubresourceRange) {
+ .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ },
+ };
+
+
+ VkMemoryBarrier2 memory_barrier = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_HOST_BIT,
+ .srcAccessMask = VK_ACCESS_2_HOST_WRITE_BIT,
+ .dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT,
+ .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT,
+ };
+
+ VkDependencyInfo dep_info = {VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
+ dep_info.memoryBarrierCount = 1;
+ dep_info.pMemoryBarriers = &memory_barrier;
+ dep_info.imageMemoryBarrierCount = (uint32_t) NUM_LAYOUT_BARRIERS;
+ dep_info.pImageMemoryBarriers = layout_barriers;
+
+ vkCmdPipelineBarrier2(frame_resource.command_buffer, &dep_info);
+
+ ctx->in_frame = true;
+ return true;
+}
+
+extern void
+rend_vk14_renderer_frame_end(RendRenderer renderer, float *delta)
+{
+ RendVk14Context *ctx = (RendVk14Context *)renderer->context;
+
+ RendVkFrameResources res = ctx->frame_resources[ctx->frame_index];
+
+ VkImageMemoryBarrier2 present_barrier;
+ present_barrier = (VkImageMemoryBarrier2) {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
+
+ .srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
+ .srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
+
+ .dstStageMask = VK_PIPELINE_STAGE_2_NONE,
+ .dstAccessMask = 0,
+
+ .oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+
+ .image = ctx->swapchain.images[ctx->image_index],
+
+ .subresourceRange = (VkImageSubresourceRange) {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ }
+
+ };
+
+ VkDependencyInfo dep_info = {VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
+ dep_info.imageMemoryBarrierCount = 1;
+ dep_info.pImageMemoryBarriers = &present_barrier;
+
+ vkCmdPipelineBarrier2(res.command_buffer, &dep_info);
+ vkEndCommandBuffer(res.command_buffer);
+
+ /* ensure swapchain image is available */
+ VkSemaphoreSubmitInfo image_acquire_await_info = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
+ .semaphore = res.image_acquired_semaphore,
+ .stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT
+ };
+
+ /* signal that the image is presented */
+ VkSemaphoreSubmitInfo semaphore_signals[2] = {
+ [0] = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
+ .semaphore = ctx->render_complete_semaphores[ctx->image_index],
+ .stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT
+ },
+ [1] = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
+ .semaphore = ctx->timeline_semaphore,
+ .value = ctx->signal_value,
+ .stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT
+ },
+ };
+
+ VkCommandBufferSubmitInfo cmd_submit_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
+ .commandBuffer = res.command_buffer,
+ };
+
+ VkSubmitInfo2 submit_info = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
+ .waitSemaphoreInfoCount = 1,
+ .pWaitSemaphoreInfos = &image_acquire_await_info,
+ .commandBufferInfoCount = 1,
+ .pCommandBufferInfos = &cmd_submit_info,
+ .signalSemaphoreInfoCount = 2,
+ .pSignalSemaphoreInfos = semaphore_signals
+ };
+
+
+
+ vkQueueSubmit2(vk_device.graphics_queue, 1, &submit_info, VK_NULL_HANDLE);
+
+ VkPresentInfoKHR present_info = {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &ctx->render_complete_semaphores[ctx->image_index],
+ .swapchainCount = 1,
+ .pSwapchains = &ctx->swapchain.handle,
+ .pImageIndices = &ctx->image_index,
+ .pResults = NULL,
+ };
+
+ vkQueuePresentKHR(vk_device.graphics_queue, &present_info);
+
+ ctx->frame++;
+ ctx->in_frame = false;
+ uint64_t f = ctx->frame_index;
+
+ /* clear host mapped memory */
+ rend_vk_arena_clear_all(&ctx->arena_frame);
+ rend_vk_arena_clear(&ctx->arena_persistent, vk_device.host_index);
+}
+
+static inline void
+rend_vk14__renderer_render_pass_begin_internal(RendRenderer renderer, float r, float g, float b, float a, uint64_t view_handle, uint64_t depth_attachment_view_handle, uint32_t offset_x, uint32_t offset_y, uint32_t width, uint32_t height)
+{
+ RendVk14Context *ctx = (RendVk14Context*) renderer->context;
+ assert(ctx->in_frame && "must begin render pass inside a frame");
+
+ RendVkFrameResources frame_resource = ctx->frame_resources[ctx->frame_index];
+
+ VkRenderingAttachmentInfo color_attachment = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
+ .imageView = (VkImageView) view_handle,
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, // clear image
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE, // store for presentation
+ .clearValue = (VkClearValue) {
+ .color = {r, g, b, a},
+ }
+ };
+
+ VkRenderingAttachmentInfo depth_attachment = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
+ .imageView = (VkImageView) depth_attachment_view_handle,
+ .imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, // clear depth data
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, // don't care after rendering
+ .clearValue = (VkClearValue) {
+ .depthStencil = (VkClearDepthStencilValue) {1.0f, 0},
+ },
+ };
+
+ VkRenderingInfo rendering_info = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
+ .renderArea.offset = (VkOffset2D) {offset_x, offset_y},
+ .renderArea.extent = (VkExtent2D) {width, height},
+ .layerCount = 1,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &color_attachment,
+ .pDepthAttachment = &depth_attachment,
+ };
+
+ vkCmdBeginRendering(frame_resource.command_buffer, &rendering_info);
+
+ VkViewport viewport = {
+ .x = offset_x,
+ .y = offset_y,
+ .width = width,
+ .height = height,
+ .minDepth = 0.0f,
+ .maxDepth = 1.0f
+ };
+
+ vkCmdSetViewport(frame_resource.command_buffer, 0, 1, &viewport);
+
+ VkRect2D scissor = {{offset_x, offset_y}, {width, height}};
+ vkCmdSetScissor(frame_resource.command_buffer, 0, 1, &scissor);
+}
+
+
+extern void
+rend_vk14_renderer_render_pass_begin(RendRenderer renderer, float r, float g, float b, float a)
+{
+
+ RendVk14Context *ctx = (RendVk14Context*) renderer->context;
+
+ rend_vk14__renderer_render_pass_begin_internal(
+ renderer,
+ r, g, b, a,
+ (uint64_t) ctx->swapchain.views[ctx->image_index],
+ (uint64_t) ctx->swapchain.depth_attachment.view,
+ 0, 0,
+ ctx->swapchain.extent.width, ctx->swapchain.extent.height
+ );
+}
+
+extern void
+rend_vk14_renderer_render_pass_begin_texture(RendRenderer renderer, RendTexture *texture)
+{
+
+ RendVk14Context *ctx = (RendVk14Context*) renderer->context;
+
+ RendVkFrameResources frame_resource = ctx->frame_resources[ctx->frame_index];
+
+ rend_vk14_texture_transition_layout(renderer, ctx->frame_resources[ctx->frame_index].command_buffer, texture, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+
+ VkRenderingAttachmentInfo color_attachment = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
+ .imageView = (VkImageView) texture->view,
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, // load image instead of clearing
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE, // store for presentation
+ };
+
+ VkRenderingAttachmentInfo depth_attachment = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
+ .imageView = (VkImageView) ctx->swapchain.depth_attachment.view,
+ .imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, // clear depth data
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, // don't care after rendering
+ .clearValue = (VkClearValue) {
+ .depthStencil = (VkClearDepthStencilValue) {1.0f, 0},
+ },
+ };
+
+ VkRenderingInfo rendering_info = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
+ .renderArea.offset = (VkOffset2D) {0, 0},
+ .renderArea.extent = (VkExtent2D) {texture->width, texture->height},
+ .layerCount = 1,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &color_attachment,
+ .pDepthAttachment = &depth_attachment,
+ };
+
+ vkCmdBeginRendering(frame_resource.command_buffer, &rendering_info);
+
+ VkViewport viewport = {
+ .x = 0,
+ .y = 0,
+ .width = texture->width,
+ .height = texture->height,
+ .minDepth = 0.0f,
+ .maxDepth = 1.0f
+ };
+
+ vkCmdSetViewport(frame_resource.command_buffer, 0, 1, &viewport);
+
+ VkRect2D scissor = {{0, 0}, {texture->width, texture->height}};
+ vkCmdSetScissor(frame_resource.command_buffer, 0, 1, &scissor);
+}
+
+extern void
+rend_vk14_renderer_render_pass_end_texture(RendRenderer renderer, RendTexture *texture)
+{
+
+ RendVk14Context *ctx = (RendVk14Context*) renderer->context;
+ rend_vk14_texture_transition_layout(renderer, ctx->frame_resources[ctx->frame_index].command_buffer, texture, VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL);
+ rend_vk14_renderer_render_pass_end(renderer);
+}
+
+
+extern void
+rend_vk14_renderer_render_pass_end(RendRenderer renderer)
+{
+ RendVk14Context *ctx = (RendVk14Context*) renderer->context;
+ assert(ctx->in_frame && "must end render pass inside a frame");
+ RendVkFrameResources res = ctx->frame_resources[ctx->frame_index];
+ vkCmdEndRendering(res.command_buffer);
+}
+
+extern void
+rend_vk14_descriptor_write_buffer(RendRenderer renderer, RendBuffer ubo, uint32_t binding, uint32_t slot, uint32_t offset, uint32_t size, bool is_ubo)
+{
+ RendVk14Context *ctx = renderer->context;
+
+ VkDescriptorBufferInfo buffer_info = {
+ .buffer = (VkBuffer) ubo.handle,
+ .offset = offset,
+ .range = size,
+ };
+
+ VkWriteDescriptorSet descriptor_write = {
+ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .dstSet = ctx->desc_set,
+ .dstBinding = binding,
+ .dstArrayElement = slot, // write texture to slot
+ .descriptorCount = 1,
+ .descriptorType = (is_ubo) ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .pBufferInfo = &buffer_info,
+ };
+
+ vkUpdateDescriptorSets(vk_device.logical_device, 1, &descriptor_write, 0, NULL);
+}
+
+
+extern RendBuffer
+rend_vk14_buffer_create_lifetime(RendRenderer renderer, size_t size, RendBufferType type, bool gpu, int lifetime)
+{
+ RendVk14Context *ctx = (RendVk14Context *)renderer->context;
+ int32_t index = (gpu) ? vk_device.device_index : vk_device.host_index;
+
+ VkBufferUsageFlags vk_usage = 0;
+ switch (type) {
+ case REND_BUFFER_VERTEX:
+ vk_usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+ break;
+ case REND_BUFFER_INDEX:
+ vk_usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+ break;
+ case REND_BUFFER_UNIFORM:
+ vk_usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+ break;
+ case REND_BUFFER_STORAGE:
+ vk_usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+ break;
+ case REND_BUFFER_TRANSFER:
+ vk_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+ break;
+ default:
+ REND__CRASH("Invalid buffer type!");
+ break;
+ }
+
+ RendBuffer buffer = {0};
+ buffer.usage = vk_usage | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
+
+ bool is_concurrent = (gpu && (vk_device.graphics_family_index != vk_device.transfer_family_index));
+ int32_t family[] = { vk_device.graphics_family_index, vk_device.transfer_family_index };
+
+ VkBufferCreateInfo buffer_info = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = size,
+ .usage = buffer.usage,
+ .sharingMode = is_concurrent ? VK_SHARING_MODE_CONCURRENT : VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = is_concurrent ? 2 : 1,
+ .pQueueFamilyIndices = is_concurrent ? family : NULL,
+ };
+
+ if (vkCreateBuffer(vk_device.logical_device, &buffer_info, vk_allocator, (VkBuffer*) &buffer.handle) != VK_SUCCESS) {
+ REND__CRASH("Failed to create VkBuffer!");
+ }
+
+ VkMemoryRequirements mem_reqs;
+ vkGetBufferMemoryRequirements(vk_device.logical_device, (VkBuffer) buffer.handle, &mem_reqs);
+
+ assert((mem_reqs.memoryTypeBits & (1u << index)) && "Buffer incompatible with chosen memory type!");
+
+ RendVkArenaAllocator *arena = (lifetime == REND_LIFETIME_FRAME) ? &ctx->arena_frame : &ctx->arena_persistent;
+ RendMemory vk_memory = rend_vk_arena_alloc(arena, mem_reqs.size, index);
+
+ if (vkBindBufferMemory(vk_device.logical_device, (VkBuffer) buffer.handle, (VkDeviceMemory) vk_memory.device_memory, vk_memory.offset) != VK_SUCCESS) {
+ REND__CRASH("Failed to bind VkBuffer memory!");
+ }
+
+ buffer.memory = vk_memory;
+
+ VkBufferDeviceAddressInfo address_info = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
+ .buffer = (VkBuffer) buffer.handle,
+ };
+
+ if (!gpu && vk_memory.host_mapped_memory) {
+ buffer.mapped_memory = vk_memory.host_mapped_memory;
+ } else {
+ buffer.mapped_memory = NULL;
+ }
+
+ buffer.gpu_address = vkGetBufferDeviceAddress(vk_device.logical_device, &address_info);
+ buffer.size = size;
+ return buffer;
+}
+
+extern void
+rend_vk14_buffer_destroy(RendBuffer *buffer)
+{
+ // NOTE: this function is meant to be called by the user
+ // when freeing his buffers mid frame, there may be a better
+ // way using fences perhaps? or by checking the timeline semaphore?
+ vkDeviceWaitIdle(vk_device.logical_device);
+ vkDestroyBuffer(vk_device.logical_device, (VkBuffer) buffer->handle, vk_allocator);
+ memset(buffer, 0xC0FFEE, sizeof *buffer); // fill buffer with coffee
+}
+
+extern void
+rend_vk14_buffer_copy(RendRenderer renderer, RendBuffer *dest, size_t dest_offset, RendBuffer *src, size_t src_offset, size_t bytes)
+{
+ assert(src && dest); // check that im not sending null pointers
+ assert(src->usage & VK_BUFFER_USAGE_TRANSFER_SRC_BIT); // source buffer must be marked as transfer src
+ assert(dest->usage & VK_BUFFER_USAGE_TRANSFER_DST_BIT); // dest buffer must be marked as transfer dest
+
+ RendVk14Context *ctx = (RendVk14Context *)renderer->context;
+ VkBuffer src_vk = (VkBuffer)(uintptr_t)src->handle;
+ VkBuffer dest_vk = (VkBuffer)(uintptr_t)dest->handle;
+
+ VkCommandBuffer transfer_cmd = VK_NULL_HANDLE;
+
+ VkCommandBufferAllocateInfo alloc_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandBufferCount = 1,
+ .commandPool = ctx->upload_command_pool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .pNext = NULL,
+ };
+
+ if (vkAllocateCommandBuffers(vk_device.logical_device, &alloc_info, &transfer_cmd) != VK_SUCCESS) {
+ REND__CRASH("Failed to allocate transfer command buffer!");
+ }
+
+ VkCommandBufferBeginInfo begin_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ };
+ vkBeginCommandBuffer(transfer_cmd, &begin_info);
+
+ VkBufferCopy buffer_copy = {
+ .srcOffset = (VkDeviceSize)src_offset,
+ .dstOffset = (VkDeviceSize)dest_offset,
+ .size = (VkDeviceSize)bytes,
+ };
+
+ vkCmdCopyBuffer(transfer_cmd, src_vk, dest_vk, 1, &buffer_copy);
+ vkEndCommandBuffer(transfer_cmd);
+
+ VkCommandBufferSubmitInfo cmd_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
+ .commandBuffer = transfer_cmd,
+ .deviceMask = 0,
+ };
+
+ VkSubmitInfo2 submit_info = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
+ .commandBufferInfoCount = 1,
+ .pCommandBufferInfos = &cmd_info,
+ };
+
+ vkQueueSubmit2(vk_device.transfer_queue, 1, &submit_info, VK_NULL_HANDLE);
+
+ vkQueueWaitIdle(vk_device.transfer_queue);
+ vkFreeCommandBuffers(vk_device.logical_device, ctx->upload_command_pool, 1, &transfer_cmd);
+}
+
+
+extern RendTexture
+rend_vk14_texture_create(RendRenderer renderer, uint32_t width, uint32_t height, uint32_t depth, uint32_t mip_levels, uint32_t layers, RendFormat format)
+{
+ assert(format < REND_FORMAT_COUNT && "Invalid format!");
+
+ RendVk14Context *ctx = (RendVk14Context*) renderer->context;
+ VkFormat vk_format = vk_format_from_rend_format[format];
+
+ uint32_t safe_depth = (depth > 0) ? depth : 1;
+ uint32_t safe_mips = (mip_levels > 0) ? mip_levels : 1;
+ uint32_t safe_layers = (layers > 0) ? layers : 1;
+
+ RendTexture tex = {
+ .handle = 0,
+ .width = width,
+ .height = height,
+ .depth = safe_depth,
+ .mip_levels = safe_mips,
+ .layers = safe_layers,
+ .format = vk_format,
+ .layout = VK_IMAGE_LAYOUT_UNDEFINED,
+ };
+
+ VkImageCreateInfo image_info = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = (safe_depth > 1) ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
+ .extent = { .width = width, .height = height, .depth = safe_depth },
+ .mipLevels = tex.mip_levels,
+ .arrayLayers = tex.layers,
+ .format = vk_format,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .flags = 0,
+ };
+
+ if (vkCreateImage(vk_device.logical_device, &image_info, vk_allocator, (VkImage*)&tex.handle) != VK_SUCCESS) {
+ REND__CRASH("failed to create image!");
+ return tex;
+ }
+
+ VkMemoryRequirements mem_requirements;
+ vkGetImageMemoryRequirements(vk_device.logical_device, (VkImage)tex.handle, &mem_requirements);
+
+ uint32_t index = rend_vk_get_heap_index(mem_requirements.memoryTypeBits, vk_device.device_index);
+ tex.memory = rend_vk_arena_alloc(&ctx->arena_persistent, mem_requirements.size, index);
+
+ vkBindImageMemory(vk_device.logical_device, (VkImage)tex.handle, (VkDeviceMemory)tex.memory.device_memory, (VkDeviceSize)tex.memory.offset);
+
+ // Correct ImageView creation using strict VkImageViewType and derived format
+ VkImageViewCreateInfo view_info = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = (VkImage)tex.handle,
+ .viewType = (safe_depth > 1) ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D,
+ .format = vk_format,
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = tex.mip_levels,
+ .baseArrayLayer = 0,
+ .layerCount = tex.layers,
+ },
+ };
+
+ if (vkCreateImageView(vk_device.logical_device, &view_info, vk_allocator, (VkImageView*)&tex.view) != VK_SUCCESS) {
+ REND__CRASH("failed to create image view!");
+ return tex;
+ }
+
+ /* per-texture sampler */
+ VkSamplerCreateInfo sampler_info = {
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
+ .magFilter = VK_FILTER_LINEAR, // force sharp upscaling
+ .minFilter = VK_FILTER_LINEAR, // TODO: add filter setting to texture
+ .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+
+ .mipmapMode = (tex.mip_levels > 1) ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST,
+ .minLod = 0.0f,
+ .maxLod = (tex.mip_levels > 1) ? (float)tex.mip_levels : 0.0f,
+
+ .anisotropyEnable = (tex.mip_levels > 1 && vk_device.features.samplerAnisotropy) ? VK_TRUE : VK_FALSE,
+ .maxAnisotropy = 8.0f,
+ };
+
+ if (vkCreateSampler(vk_device.logical_device, &sampler_info, vk_allocator, (VkSampler*)&tex.sampler) != VK_SUCCESS) {
+ REND__CRASH("failed to create sampler!");
+ }
+
+ return tex;
+}
+
+extern void
+rend_vk14_texture_destroy(RendRenderer renderer, RendTexture *tex)
+{
+ assert(renderer && tex);
+ RendVk14Context *ctx = (RendVk14Context*)renderer->context;
+
+ if (tex->handle) {
+ vkDestroyImage(vk_device.logical_device, (VkImage) tex->handle, vk_allocator);
+ tex->handle = 0;
+ }
+
+ if (tex->view) {
+ vkDestroyImageView(vk_device.logical_device, (VkImageView) tex->view, vk_allocator);
+ tex->view = 0;
+ }
+
+ if (tex->sampler) {
+ vkDestroySampler(vk_device.logical_device, (VkSampler)tex->sampler, vk_allocator);
+ tex->sampler = 0;
+ }
+
+ // memset(tex, 0xBABE, sizeof *tex);
+}
+
+extern void
+rend_vk14_texture_transition_layout(RendRenderer renderer, VkCommandBuffer cmd, RendTexture *texture, VkImageLayout new_layout)
+{
+ RendVk14Context *ctx = renderer->context;
+ VkCommandPool pool = ctx->upload_command_pool;
+
+ VkImageMemoryBarrier2 barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
+ .oldLayout = texture->layout,
+ .newLayout = new_layout,
+
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+
+ .image = (VkImage) texture->handle,
+
+ .subresourceRange = (VkImageSubresourceRange) {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = texture->mip_levels,
+ .baseArrayLayer = 0,
+ .layerCount = texture->layers,
+ },
+
+ .srcAccessMask = 0, // TODO
+ .dstAccessMask = 0, // TODO
+
+ };
+
+ if (texture->layout == VK_IMAGE_LAYOUT_UNDEFINED && new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
+ barrier.srcAccessMask = 0;
+ barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
+ barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT;
+ barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
+ } else if (texture->layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
+ barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
+ barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT;
+ barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
+ barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
+ } else {
+ barrier.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT;
+ barrier.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT;
+ barrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
+ barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
+ }
+
+ VkDependencyInfo dep = {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .pImageMemoryBarriers = &barrier,
+ .imageMemoryBarrierCount = 1,
+ };
+
+ texture->layout = new_layout;
+ vkCmdPipelineBarrier2(cmd, &dep);
+}
+
+extern void
+rend_vk14_texture_transfer_ownership_release(RendRenderer renderer, VkCommandBuffer cmd, RendTexture *texture, uint32_t src_family, uint32_t dst_family, VkImageLayout new_layout)
+{
+ RendVk14Context *ctx = renderer->context;
+ (void) ctx; // reserved in case you need it for stage/access lookups later
+
+ VkImageMemoryBarrier2 barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
+ .oldLayout = texture->layout,
+ .newLayout = new_layout,
+ .srcQueueFamilyIndex = src_family,
+ .dstQueueFamilyIndex = dst_family,
+ .image = (VkImage) texture->handle,
+ .subresourceRange = (VkImageSubresourceRange) {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = texture->mip_levels,
+ .baseArrayLayer = 0,
+ .layerCount = texture->layers,
+ },
+ };
+
+ if (texture->layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
+ barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
+ barrier.dstAccessMask = 0;
+ barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
+ barrier.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT;
+ } else {
+ barrier.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT;
+ barrier.dstAccessMask = 0;
+ barrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
+ barrier.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT;
+ }
+
+ VkDependencyInfo dep = {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .pImageMemoryBarriers = &barrier,
+ .imageMemoryBarrierCount = 1,
+ };
+
+ texture->layout = new_layout;
+ vkCmdPipelineBarrier2(cmd, &dep);
+}
+
+extern void
+rend_vk14_texture_transfer_ownership_acquire(RendRenderer renderer, VkCommandBuffer cmd, RendTexture *texture, uint32_t src_family, uint32_t dst_family, VkImageLayout new_layout)
+{
+ RendVk14Context *ctx = renderer->context;
+ (void) ctx;
+
+ VkImageMemoryBarrier2 barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
+ .oldLayout = texture->layout,
+ .newLayout = new_layout,
+ .srcQueueFamilyIndex = src_family,
+ .dstQueueFamilyIndex = dst_family,
+ .image = (VkImage) texture->handle,
+
+ .subresourceRange = (VkImageSubresourceRange) {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = texture->mip_levels,
+ .baseArrayLayer = 0,
+ .layerCount = texture->layers,
+ },
+ };
+
+ if (texture->layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
+ barrier.srcAccessMask = 0;
+ barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT;
+ barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT;
+ barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
+ } else {
+ barrier.srcAccessMask = 0;
+ barrier.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT;
+ barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT;
+ barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
+ }
+
+ VkDependencyInfo dep = {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .pImageMemoryBarriers = &barrier,
+ .imageMemoryBarrierCount = 1,
+ };
+
+ texture->layout = new_layout;
+ vkCmdPipelineBarrier2(cmd, &dep);
+}
+
+extern void
+rend_vk14_texture_copy_buffer(RendRenderer renderer, RendTexture *texture, RendBuffer *buffer)
+{
+ RendVk14Context *ctx = renderer->context;
+
+ VkCommandBuffer cmd_transfer = rend_vk_cmdbuffer_single_use_begin(ctx->upload_command_pool); {
+ rend_vk14_texture_transition_layout(renderer, cmd_transfer, texture, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+ VkBufferImageCopy region = {
+ .imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .imageSubresource.layerCount = texture->layers,
+ .imageExtent = (VkExtent3D) { texture->width, texture->height, texture->depth },
+ };
+
+ vkCmdCopyBufferToImage(cmd_transfer, (VkBuffer) buffer->handle, (VkImage) texture->handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
+ rend_vk14_texture_transfer_ownership_release(renderer, cmd_transfer, texture, vk_device.transfer_family_index, vk_device.graphics_family_index, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+ } rend_vk_cmdbuffer_single_use_end(ctx->upload_command_pool, cmd_transfer, vk_device.transfer_queue);
+
+ VkCommandBuffer cmd_graphics = rend_vk_cmdbuffer_single_use_begin(ctx->graphics_command_pool); {
+ rend_vk14_texture_transfer_ownership_acquire(renderer, cmd_graphics, texture, vk_device.transfer_family_index, vk_device.graphics_family_index, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+ } rend_vk_cmdbuffer_single_use_end(ctx->graphics_command_pool, cmd_graphics, vk_device.graphics_queue);
+}
+
+extern void
+rend_vk14_texture_blit(RendRenderer renderer, RendTexture *src, RendTexture *dst, uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h, uint32_t dst_x, uint32_t dst_y, uint32_t dst_w, uint32_t dst_h)
+{
+
+ RendVk14Context *ctx = (RendVk14Context*) renderer->context;
+ VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer;
+
+ rend_vk14_texture_transition_layout(renderer, cmd, src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+ rend_vk14_texture_transition_layout(renderer, cmd, dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+
+ VkImageBlit2 blit_region = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2,
+ .srcSubresource = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .srcOffsets = {
+ { src_x, src_y, 0 },
+ { src_x + src_w, src_y + src_h, 1 }
+ },
+ .dstSubresource = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .dstOffsets = {
+ { dst_x, dst_y, 0 },
+ { dst_x + dst_w, dst_y + dst_h, 1 }
+ }
+ };
+
+ VkBlitImageInfo2 blit_info = {
+ .sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2,
+ .srcImage = (VkImage) src->handle,
+ .srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ .dstImage = (VkImage) dst->handle,
+ .dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .regionCount = 1,
+ .pRegions = &blit_region,
+
+ .filter = VK_FILTER_NEAREST // crispy
+ };
+
+ vkCmdBlitImage2(cmd, &blit_info);
+}
+
+static VkCommandBuffer
+rend_vk_cmdbuffer_single_use_begin(VkCommandPool pool)
+{
+ VkCommandBufferAllocateInfo alloc_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandPool = pool,
+ .commandBufferCount = 1,
+ };
+
+ VkCommandBuffer cmd;
+ vkAllocateCommandBuffers(vk_device.logical_device, &alloc_info, &cmd);
+
+ VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ };
+
+ vkBeginCommandBuffer(cmd, &begin);
+
+ return cmd;
+}
+
+static void
+rend_vk_cmdbuffer_single_use_end(VkCommandPool pool, VkCommandBuffer cmd, VkQueue q)
+{
+ vkEndCommandBuffer(cmd);
+
+ VkSubmitInfo submit_info = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cmd,
+ };
+
+ vkQueueSubmit(q, 1, &submit_info, VK_NULL_HANDLE);
+ vkQueueWaitIdle(q);
+
+ vkFreeCommandBuffers(vk_device.logical_device, pool, 1, &cmd);
+}
+
+extern bool
+rend_vk14_pipeline_create(RendRenderer renderer, RendPipeline pipeline, Rend__PipelineConfig config, uint8_t type, const uint8_t *shader1, size_t bytes1, const uint8_t *shader2, size_t bytes2, const uint8_t *shader3, size_t bytes3)
+{
+
+ RendVk14Context *ctx = (RendVk14Context *)renderer->context;
+ pipeline->idx = ctx->pipeline_count++;
+ pipeline->backend_ctx = ctx; // useful for when we only have RendPipeline as an argument
+
+ RendVkPipeline *vk_pipeline = &ctx->pipelines[pipeline->idx];
+ vk_pipeline->blend_enable = false;
+
+ /*
+ * set color and depth format
+ */
+ VkFormat color_format = (config.color_format != REND_FORMAT_UNDEFINED)
+ ? vk_format_from_rend_format[config.color_format]
+ : ctx->swapchain.format.format;
+
+ VkFormat depth_format = (config.depth_format != REND_FORMAT_UNDEFINED)
+ ? vk_format_from_rend_format[config.depth_format]
+ : vk_device.depth_format;
+
+ /*
+ * Pipeline Layout
+ */
+
+ vk_pipeline->push_constants_range = 0;
+ uint32_t total_size = 0;
+ for (uint32_t u = 0; u < config.push_constant_count; ++u) {
+ total_size += config.push_constants[u].size;
+ }
+
+ vk_pipeline->push_constants_range = total_size;
+
+ VkPushConstantRange pc_range = {
+ .offset = 0,
+ .size = total_size,
+ .stageFlags = VK_SHADER_STAGE_ALL,
+ };
+
+ VkPipelineLayoutCreateInfo layout_info = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pSetLayouts = &ctx->desc_layout,
+ .setLayoutCount = 1,
+ .pPushConstantRanges = (total_size > 0) ? &pc_range : NULL,
+ .pushConstantRangeCount = (total_size > 0) ? 1 : 0,
+ };
+
+ CHECK_VK_RESULT(vkCreatePipelineLayout(vk_device.logical_device, &layout_info, vk_allocator, &vk_pipeline->layout));
+
+ /*
+ * Setup shaders based on pipeline type
+ */
+ VkPipelineShaderStageCreateInfo shader_stages[3] = {0};
+ VkShaderModule shader_modules[3] = {0};
+ uint32_t shader_count = 0;
+
+ if (type == REND__PIPELINE_GRAPHICS) {
+ shader_count = 2;
+ shader_modules[0] = rend_vk_shader_module_create(shader1, bytes1);
+ shader_modules[1] = rend_vk_shader_module_create(shader2, bytes2);
+
+ shader_stages[0] = (VkPipelineShaderStageCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_VERTEX_BIT,
+ .module = shader_modules[0],
+ .pName = "main"
+ };
+
+ shader_stages[1] = (VkPipelineShaderStageCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
+ .module = shader_modules[1],
+ .pName = "main",
+ };
+
+ } else if (type == REND__PIPELINE_MESH) {
+ shader_count = 2;
+ shader_modules[0] = rend_vk_shader_module_create(shader1, bytes1);
+ shader_modules[1] = rend_vk_shader_module_create(shader2, bytes2);
+
+ shader_stages[0] = (VkPipelineShaderStageCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_MESH_BIT_EXT,
+ .module = shader_modules[0],
+ .pName = "main"
+ };
+
+ shader_stages[1] = (VkPipelineShaderStageCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
+ .module = shader_modules[1],
+ .pName = "main",
+ };
+
+ } else if (type == REND__PIPELINE_COMPUTE) {
+ shader_count = 1;
+ shader_modules[0] = rend_vk_shader_module_create(shader1, bytes1);
+
+ shader_stages[0] = (VkPipelineShaderStageCreateInfo) {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_COMPUTE_BIT,
+ .module = shader_modules[0],
+ .pName = "main"
+ };
+ } else {
+ REND__CRASH("Invalid pipeline type");
+ }
+
+ /*
+ * Compute Pipeline Branch
+ */
+ if (type == REND__PIPELINE_COMPUTE) {
+ VkComputePipelineCreateInfo compute_pipeline_info = {
+ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
+ .stage = shader_stages[0],
+ .layout = vk_pipeline->layout,
+ .basePipelineHandle = VK_NULL_HANDLE,
+ .basePipelineIndex = -1,
+ };
+
+ CHECK_VK_RESULT(vkCreateComputePipelines(vk_device.logical_device, VK_NULL_HANDLE, 1, &compute_pipeline_info, vk_allocator, &vk_pipeline->handle));
+
+ PINFO("Successfully created compute pipeline!");
+ }
+ /*
+ * Graphics / Mesh Pipeline Branch
+ */
+ else {
+ VkPipelineRenderingCreateInfo rendering_info = {VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO};
+ rendering_info.colorAttachmentCount = 1;
+ rendering_info.pColorAttachmentFormats = &color_format;
+ rendering_info.depthAttachmentFormat = depth_format;
+
+ VkPipelineViewportStateCreateInfo viewport_state = {VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
+ viewport_state.viewportCount = 1;
+ viewport_state.scissorCount = 1;
+
+ VkPipelineRasterizationStateCreateInfo rasterizer = {VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
+ rasterizer.depthClampEnable = VK_FALSE;
+ rasterizer.rasterizerDiscardEnable = VK_FALSE;
+ rasterizer.polygonMode = vk_polymode[config.polygon_mode];
+ rasterizer.lineWidth = 1.0f;
+ rasterizer.cullMode = vk_cullflags[config.cull_mode];
+ rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
+ rasterizer.depthBiasEnable = VK_FALSE;
+
+ VkPipelineMultisampleStateCreateInfo multisampling = {VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
+ multisampling.sampleShadingEnable = VK_FALSE;
+ multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+
+ VkPipelineColorBlendAttachmentState color_blend_attachment = {0};
+ color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+ color_blend_attachment.blendEnable = VK_FALSE;
+
+ VkPipelineColorBlendStateCreateInfo color_blending = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
+ color_blending.logicOpEnable = VK_FALSE;
+ color_blending.logicOp = VK_LOGIC_OP_COPY;
+ color_blending.attachmentCount = 1;
+ color_blending.pAttachments = &color_blend_attachment;
+
+ VkPipelineDepthStencilStateCreateInfo depth_stencil = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
+ depth_stencil.depthTestEnable = config.depth_test_enable;
+ depth_stencil.depthWriteEnable = VK_TRUE;
+ depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS;
+ depth_stencil.depthBoundsTestEnable = VK_FALSE;
+ depth_stencil.stencilTestEnable = VK_FALSE;
+
+ VkDynamicState dynamic_states[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+ VkPipelineDynamicStateCreateInfo dynamic_state = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO};
+ dynamic_state.dynamicStateCount = 2;
+ dynamic_state.pDynamicStates = dynamic_states;
+
+ VkPipelineVertexInputStateCreateInfo vertex_input_info = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
+ VkPipelineInputAssemblyStateCreateInfo input_assembly = {VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
+ VkVertexInputBindingDescription* vk_bindings = NULL;
+ VkVertexInputAttributeDescription* vk_attributes = NULL;
+
+ if (type == REND__PIPELINE_GRAPHICS) {
+ uint32_t binding_count = config.vertex_binding_count;
+ uint32_t attribute_count = config.vertex_attribute_count;
+
+ /*
+ * Bind Vertex Attributes
+ */
+ if (binding_count > 0) {
+ vk_bindings = rmalloc(sizeof(VkVertexInputBindingDescription) * binding_count);
+ for (uint32_t i = 0; i < binding_count; i++) {
+ RendVertexBinding rb = config.vertex_bindings[i];
+ vk_bindings[i].binding = rb.binding;
+ vk_bindings[i].stride = rb.stride;
+ vk_bindings[i].inputRate = (rb.input_rate == REND_INPUT_RATE_INSTANCE) ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
+ }
+ }
+
+ if (attribute_count > 0) {
+ vk_attributes = rmalloc(sizeof(VkVertexInputAttributeDescription) * attribute_count);
+ for (uint32_t i = 0; i < attribute_count; i++) {
+ RendVertexAttributes ra = config.vertex_attributes[i];
+ vk_attributes[i].binding = ra.binding;
+ vk_attributes[i].location = ra.location;
+ vk_attributes[i].offset = ra.offset;
+ vk_attributes[i].format = vk_format_from_rend_format[ra.format];
+ }
+ }
+
+ vertex_input_info.vertexBindingDescriptionCount = binding_count;
+ vertex_input_info.pVertexBindingDescriptions = vk_bindings;
+ vertex_input_info.vertexAttributeDescriptionCount = attribute_count;
+ vertex_input_info.pVertexAttributeDescriptions = vk_attributes;
+
+ input_assembly.topology = vk_topology[config.topology];
+ input_assembly.primitiveRestartEnable = VK_FALSE;
+ }
+
+ VkGraphicsPipelineCreateInfo pipeline_info = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
+ pipeline_info.pNext = &rendering_info;
+ pipeline_info.stageCount = shader_count;
+ pipeline_info.pStages = shader_stages;
+ pipeline_info.pVertexInputState = &vertex_input_info;
+ pipeline_info.pInputAssemblyState = &input_assembly;
+ pipeline_info.pViewportState = &viewport_state;
+ pipeline_info.pRasterizationState = &rasterizer;
+ pipeline_info.pMultisampleState = &multisampling;
+ pipeline_info.pColorBlendState = &color_blending;
+ pipeline_info.pDepthStencilState = &depth_stencil;
+ pipeline_info.pDynamicState = &dynamic_state;
+ pipeline_info.layout = vk_pipeline->layout;
+ pipeline_info.renderPass = VK_NULL_HANDLE;
+
+ CHECK_VK_RESULT(vkCreateGraphicsPipelines(vk_device.logical_device, VK_NULL_HANDLE, 1, &pipeline_info, vk_allocator, &vk_pipeline->handle));
+
+ if (vk_bindings) rfree(vk_bindings);
+ if (vk_attributes) rfree(vk_attributes);
+
+ }
+
+ /* destroy shader modules */
+ for (uint32_t i = 0; i < shader_count; ++i) {
+ if (shader_modules[i] != VK_NULL_HANDLE) {
+ vkDestroyShaderModule(vk_device.logical_device, shader_modules[i], vk_allocator);
+ }
+ }
+
+ return true;
+}
+
+
+extern void
+rend_vk14_pipeline_bind(RendPipeline pipeline)
+{
+ RendVk14Context *ctx = pipeline->backend_ctx;
+ RendVkPipeline vk_pipeline = ctx->pipelines[pipeline->idx];
+ VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer;
+ VkPipelineBindPoint bind_point = (pipeline->type == REND__PIPELINE_COMPUTE) ? VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS;
+ vkCmdBindPipeline(cmd, bind_point, vk_pipeline.handle);
+ vkCmdBindDescriptorSets(cmd, bind_point, vk_pipeline.layout, 0, 1, &ctx->desc_set, 0, NULL);
+}
+
+extern void
+rend_vk14_pipeline_push_constants(RendPipeline pipeline, void *push_data, size_t size)
+{
+ assert(pipeline && push_data);
+
+ RendVk14Context *ctx = pipeline->backend_ctx;
+ RendVkPipeline p = ctx->pipelines[pipeline->idx];
+ assert(size <= p.push_constants_range && "Size exceeds bound push constant range!");
+ vkCmdPushConstants(ctx->frame_resources[ctx->frame_index].command_buffer, p.layout, VK_SHADER_STAGE_ALL, 0, size, push_data);
+}
+
+extern void
+rend_vk14_pipeline_bind_vertex_buffer(RendPipeline pipeline, uint32_t binding, RendBuffer buffer, size_t offset)
+{
+ RendVk14Context *ctx = pipeline->backend_ctx;
+ VkBuffer buf = (VkBuffer)(uintptr_t)buffer.handle;
+ VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer;
+
+ VkDeviceSize vk_offset = (VkDeviceSize)offset;
+ vkCmdBindVertexBuffers(cmd, binding, 1, &buf, &vk_offset);
+}
+
+extern void
+rend_vk14_pipeline_bind_index_buffer(RendPipeline pipeline, RendBuffer buffer, size_t offset, RendIndexType index_type)
+{
+ RendVk14Context *ctx = pipeline->backend_ctx;
+ VkBuffer buf = (VkBuffer)(uintptr_t)buffer.handle;
+ VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer;
+
+ VkIndexType vk_index_type = (index_type == REND_INDEX_UINT16) ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32;
+
+ vkCmdBindIndexBuffer(cmd, buf, (VkDeviceSize)offset, vk_index_type);
+}
+
+extern void
+rend_vk14_descriptor_write_texture(void *ctx, RendTexture *texture, uint32_t binding, uint32_t slot)
+{
+ RendVk14Context *vk_ctx = (RendVk14Context*) ctx;
+
+ VkDescriptorImageInfo image_info = {
+ .sampler = (VkSampler) texture->sampler,
+ .imageView = (VkImageView) texture->view,
+ .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+ };
+
+ VkWriteDescriptorSet descriptor_write = {
+ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .dstSet = vk_ctx->desc_set,
+ .dstBinding = binding,
+ .dstArrayElement = slot, // write texture to slot
+ .descriptorCount = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .pImageInfo = &image_info,
+ };
+
+ vkUpdateDescriptorSets(vk_device.logical_device, 1, &descriptor_write, 0, NULL);
+}
+
+extern void
+rend_vk14_pipeline_dispatch(RendPipeline pipeline, uint32_t x, uint32_t y, uint32_t z)
+{
+ RendVk14Context *ctx = pipeline->backend_ctx;
+ VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer;
+ vkCmdDispatch(cmd, x, y, z);
+}
+
+extern void
+rend_vk14_pipeline_draw(RendPipeline pipeline, size_t count, uint32_t instance_count)
+{
+ RendVk14Context *ctx = pipeline->backend_ctx;
+ VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer;
+ vkCmdDraw(cmd, count, instance_count, 0, 0);
+}
+
+extern void
+rend_vk14_pipeline_draw_indexed(RendPipeline pipeline, uint32_t index_count, uint32_t first_index, int32_t vertex_offset, uint32_t instance_count)
+{
+ RendVk14Context *ctx = pipeline->backend_ctx;
+ VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer;
+ vkCmdDrawIndexed(cmd, index_count, instance_count, first_index, vertex_offset, 0);
+}
+
+extern void
+rend_vk14_pipeline_set_blend(RendPipeline pipeline, bool blend)
+{
+ RendVk14Context *ctx = pipeline->backend_ctx;
+ RendVkPipeline *vk_pipeline = &ctx->pipelines[pipeline->idx];
+ vk_pipeline->blend_enable = blend;
+}
+
+extern void
+rend_vk14_pipeline_draw_instanced(RendPipeline pipeline, uint32_t index_count, uint32_t instance_count)
+{
+ RendVk14Context *ctx = pipeline->backend_ctx;
+ VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer;
+ vkCmdDraw(cmd, index_count, instance_count, 0, 0);
+}
+
+extern VKAPI_ATTR VkBool32 VKAPI_CALL
+rend_vk_debug_func(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagsEXT message_types, const VkDebugUtilsMessengerCallbackDataEXT *callback_data, void *user_data)
+{
+ switch (message_severity) {
+ default:
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
+ PERROR(callback_data->pMessage);
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
+ PWARN(callback_data->pMessage);
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
+ PINFO(callback_data->pMessage);
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
+ PTRACE(callback_data->pMessage);
+ break;
+ }
+ return VK_FALSE;
+}
+
+
+
+static void
+rend_vk_pipeline_destroy(RendVkPipeline *pipeline)
+{
+ VkDevice dev = vk_device.logical_device;
+
+ if (pipeline->handle != VK_NULL_HANDLE) {
+ vkDestroyPipeline(dev, pipeline->handle, vk_allocator);
+ }
+
+ if (pipeline->layout != VK_NULL_HANDLE) {
+ vkDestroyPipelineLayout(dev, pipeline->layout, vk_allocator);
+ }
+}
+
+static void
+rend_vk_swapchain_create(RendVk14Context *ctx, RendVkSwapchain *swapchain)
+{
+ VkSurfaceCapabilitiesKHR surface_caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk_device.physical_device, ctx->surface, &surface_caps);
+ uint32_t width = surface_caps.currentExtent.width;
+ uint32_t height = surface_caps.currentExtent.height;
+
+ VkExtent2D swapchain_extent = {width, height};
+
+ ctx->max_frames_in_flight = REND_MIN_FRAMES_IN_FLIGHT;
+
+ if (surface_caps.minImageCount > ctx->max_frames_in_flight) {
+ ctx->max_frames_in_flight = surface_caps.minImageCount;
+
+ }
+
+ if (surface_caps.maxImageCount < ctx->max_frames_in_flight) {
+ ctx->max_frames_in_flight = surface_caps.maxImageCount;
+ }
+
+ bool found = false;
+ for (uint32_t i = 0; i < vk_device.swapchain_support.format_count; i++) {
+ VkSurfaceFormatKHR format = vk_device.swapchain_support.format[i];
+
+ /* preferred format */
+ if (format.format == VK_FORMAT_B8G8R8A8_UNORM &&
+ format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+ swapchain->format = format;
+ found = true;
+ break;
+ }
+ }
+
+ /* default format */
+ if (!found) {
+ swapchain->format = vk_device.swapchain_support.format[0];
+ }
+
+ /* NOTE: mailbox is probably the best for most applications
+ * but I may want the ability to pick a different mode in
+ * very niche circumstances.
+ *
+ * We also may want immediate mode if we want to disable VSYNC.
+ */
+
+ /* default present mode */
+ VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR;
+ for (uint32_t i = 0; i < vk_device.swapchain_support.present_mode_count; i++) {
+ VkPresentModeKHR pres = vk_device.swapchain_support.present_modes[i];
+ /* preferred format is mailbox */
+ if (ctx->vsync) {
+ if (pres == VK_PRESENT_MODE_MAILBOX_KHR) {
+ present_mode = pres;
+ break;
+ }
+ } else {
+ if (pres == VK_PRESENT_MODE_IMMEDIATE_KHR) {
+ present_mode = pres;
+ break;
+ }
+ }
+ }
+
+#ifdef REND_DEBUG
+ static const char* present_mode_names[] = {
+ [VK_PRESENT_MODE_IMMEDIATE_KHR] = "IMMEDIATE",
+ [VK_PRESENT_MODE_MAILBOX_KHR] = "MAILBOX",
+ [VK_PRESENT_MODE_FIFO_KHR] = "FIFO",
+ [VK_PRESENT_MODE_FIFO_RELAXED_KHR] = "FIFO_RELAXED"
+ };
+ PDEBUG("Present mode %s was chosen!", present_mode_names[present_mode]);
+#endif
+
+ VkExtent2D min = surface_caps.minImageExtent;
+ VkExtent2D max = surface_caps.maxImageExtent;
+ swapchain_extent.width = (swapchain_extent.width < min.width) ? min.width : swapchain_extent.width;
+ swapchain_extent.width = (swapchain_extent.width > max.width) ? max.width : swapchain_extent.width;
+ swapchain_extent.height = (swapchain_extent.height < min.height) ? min.height : swapchain_extent.height;
+ swapchain_extent.height = (swapchain_extent.height > max.height) ? max.height : swapchain_extent.height;
+
+ uint32_t img_count = surface_caps.minImageCount + 1;
+ if (surface_caps.maxImageCount > 0 && img_count > surface_caps.maxImageCount) {
+ img_count = surface_caps.maxImageCount;
+ }
+
+ VkSwapchainCreateInfoKHR swapchain_create_info = { VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
+ swapchain_create_info.surface = ctx->surface;
+ swapchain_create_info.minImageCount = img_count;
+ swapchain_create_info.imageFormat = swapchain->format.format;
+ swapchain_create_info.imageColorSpace = swapchain->format.colorSpace;
+ swapchain_create_info.imageExtent = swapchain_extent;
+ swapchain_create_info.imageArrayLayers = 1;
+ swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+
+ /* index sharing */
+ if (vk_device.graphics_family_index != vk_device.present_family_index) {
+ uint32_t queueFamilyIndices[] = {vk_device.graphics_family_index, vk_device.present_family_index};
+ swapchain_create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
+ swapchain_create_info.queueFamilyIndexCount = 2;
+ swapchain_create_info.pQueueFamilyIndices = queueFamilyIndices;
+ } else {
+ swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ swapchain_create_info.queueFamilyIndexCount = 0;
+ swapchain_create_info.pQueueFamilyIndices = 0;
+ }
+
+ swapchain_create_info.preTransform = surface_caps.currentTransform;
+ swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+ swapchain_create_info.presentMode = present_mode;
+ swapchain_create_info.clipped = VK_TRUE;
+ swapchain_create_info.oldSwapchain = 0;
+
+ if (vkCreateSwapchainKHR(vk_device.logical_device, &swapchain_create_info, vk_allocator, &swapchain->handle) != VK_SUCCESS) {
+ REND__CRASH("Swapchain creation failed, you are probably trying to create two renderers for the same surface!");
+ }
+
+ swapchain->image_count = 0;
+ CHECK_VK_RESULT(vkGetSwapchainImagesKHR(vk_device.logical_device, swapchain->handle, &swapchain->image_count, 0));
+
+ if (!swapchain->images) {
+ swapchain->images = rmalloc(swapchain->image_count * sizeof *swapchain->images);
+ }
+ if (!swapchain->views) {
+ swapchain->views = rmalloc(swapchain->image_count * sizeof *swapchain->views);
+ }
+
+ CHECK_VK_RESULT( vkGetSwapchainImagesKHR(vk_device.logical_device, swapchain->handle, &swapchain->image_count, swapchain->images));
+
+ for (uint32_t i = 0; i < swapchain->image_count; i++) {
+
+ VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
+ view_info.image = swapchain->images[i];
+ view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ view_info.format = swapchain->format.format;
+ view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ view_info.subresourceRange.baseMipLevel = 0;
+ view_info.subresourceRange.levelCount = 1;
+ view_info.subresourceRange.baseArrayLayer = 0;
+ view_info.subresourceRange.layerCount = 1;
+
+ CHECK_VK_RESULT(vkCreateImageView(vk_device.logical_device, &view_info, vk_allocator, &swapchain->views[i]));
+ }
+
+ /* depth resources */
+ if (!rend_vk_device_detect_depth_format(&vk_device)) {
+ vk_device.depth_format = VK_FORMAT_UNDEFINED;
+ PFATAL("Failed to find a supported depth buffer format!");
+ }
+
+ swapchain->depth_attachment = rend_vk_image_create(
+ vk_device.logical_device,
+ VK_IMAGE_TYPE_2D,
+ swapchain_extent.width, swapchain_extent.height,
+ vk_device.depth_format,
+ VK_IMAGE_TILING_OPTIMAL,
+ VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+ 1, // depth
+ 1, // mip level
+ 1, // layers
+ VK_SAMPLE_COUNT_1_BIT,
+ VK_SHARING_MODE_EXCLUSIVE
+ );
+
+ uint32_t mem_type = rend_vk_image_required_memory_type(&swapchain->depth_attachment);
+ uint32_t depth_index = rend_vk_get_heap_index(mem_type, vk_device.device_index);
+
+ assert(ctx);
+ RendMemory depth_mem = rend_vk_arena_alloc(&ctx->arena_persistent, swapchain->depth_attachment.requirements.size, depth_index);
+
+ rend_vk_image_bind_memory(&swapchain->depth_attachment, &depth_mem);
+
+ rend_vk_image_view_create(
+ &swapchain->depth_attachment,
+ VK_IMAGE_VIEW_TYPE_2D,
+ VK_IMAGE_ASPECT_DEPTH_BIT
+ );
+
+ swapchain->extent = swapchain_extent;
+
+}
+
+static void
+rend_vk_swapchain_destroy(RendVk14Context *ctx, RendVkSwapchain *swapchain)
+{
+ if (!swapchain || swapchain->handle == VK_NULL_HANDLE) return;
+ assert(vk_device.logical_device);
+
+ rend_vk_image_destroy(&swapchain->depth_attachment);
+
+ if (swapchain->views) {
+ for (uint32_t i = 0; i < swapchain->image_count; i++) {
+ vkDestroyImageView(vk_device.logical_device, swapchain->views[i], vk_allocator);
+ }
+ rfree(swapchain->views);
+ swapchain->views = 0;
+ }
+
+ if (swapchain->images) {
+ rfree(swapchain->images);
+ swapchain->images = 0;
+ }
+
+ swapchain->image_count = 0;
+ vkDestroySwapchainKHR(vk_device.logical_device, swapchain->handle, vk_allocator);
+ swapchain->handle = VK_NULL_HANDLE;
+}
+
+static VkShaderModule
+rend_vk_shader_module_create(const void *data, size_t size)
+{
+
+ VkShaderModuleCreateInfo create_info = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
+ create_info.codeSize = size;
+ create_info.pCode = (const uint32_t *)data;
+
+ VkShaderModule module;
+ VkResult res = vkCreateShaderModule(vk_device.logical_device, &create_info, vk_allocator, &module);
+
+ if (res != VK_SUCCESS) {
+ PWARN("Failed to create shader module for %p", data);
+ return VK_NULL_HANDLE;
+ }
+
+ return module;
+}
+
+static uint32_t
+rend_vk_get_heap_index(uint32_t memory_type_bits, uint32_t preferred_index)
+{
+ if (memory_type_bits & (1u << preferred_index)) {
+ return preferred_index;
+ }
+
+ for (uint32_t i = 0; i < 32; i++) {
+ if (memory_type_bits & (1u << i)) {
+ return i;
+ }
+ }
+
+ return UINT32_MAX;
+}
diff --git a/rend_vk_allocator.c b/rend_vk_allocator.c
new file mode 100644
index 0000000..737e566
--- /dev/null
+++ b/rend_vk_allocator.c
@@ -0,0 +1,118 @@
+#pragma once
+#include "rend_internal.h"
+#include <stdint.h>
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_core.h>
+
+typedef struct RendVkAllocatorState {
+ const char *name;
+} RendVkAllocatorState;
+
+typedef struct RendVkAllocatorHeader {
+ uint32_t offset;
+} RendVkAllocatorHeader;
+
+static bool rend_vk_allocator_is_power_of_two(uintptr_t x);
+static void *rend_vk_allocator_alloc(void *pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocationScope);
+static void *rend_vk_allocator_realloc(void *pUserData, void *pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocationScope);
+static void rend_vk_allocator_free(void *pUserData, void *pMemory);
+static void rend_vk_allocator_internal_notification(void *pUserData, size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope);
+static void rend_vk_allocator_free_notification(void *pUserData, size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope);
+
+static VkAllocationCallbacks rend_vk_allocator = {
+ .pfnAllocation = rend_vk_allocator_alloc,
+ .pfnReallocation = rend_vk_allocator_realloc,
+ .pfnFree = rend_vk_allocator_free,
+ .pfnInternalAllocation = rend_vk_allocator_internal_notification,
+ .pfnInternalFree = rend_vk_allocator_free_notification,
+};
+
+static const char* rend_vk_allocator_scope_name[] = {
+ [VK_SYSTEM_ALLOCATION_SCOPE_CACHE] = "Cache",
+ [VK_SYSTEM_ALLOCATION_SCOPE_COMMAND] = "Command",
+ [VK_SYSTEM_ALLOCATION_SCOPE_DEVICE] = "Device",
+ [VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE] = "Instance",
+ [VK_SYSTEM_ALLOCATION_SCOPE_OBJECT] = "Object",
+};
+
+
+static bool
+rend_vk_allocator_is_power_of_two(uintptr_t x)
+{
+ return (x & (x-1)) == 0;
+}
+
+static uintptr_t
+rend_vk_allocator_align_forward(uintptr_t ptr, size_t align)
+{
+ uintptr_t p, a, modulo;
+
+ assert(rend_vk_allocator_is_power_of_two(align));
+
+ p = ptr;
+ a = (uintptr_t)align;
+ modulo = p & (a-1);
+
+ if (modulo != 0) {
+ p += a - modulo;
+ }
+ return p;
+}
+
+static void*
+rend_vk_allocator_alloc(void *pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
+{
+ size_t total_size = size + alignment + sizeof (RendVkAllocatorHeader);
+ uint8_t *raw_ptr = malloc(total_size);
+ uintptr_t unaligned_addr = (uintptr_t)(raw_ptr + sizeof (RendVkAllocatorHeader));
+
+ uint8_t *aligned_ptr = (uint8_t*)rend_vk_allocator_align_forward(unaligned_addr, alignment);
+ RendVkAllocatorHeader *header = (RendVkAllocatorHeader*) aligned_ptr - 1;
+ header->offset = aligned_ptr - raw_ptr;
+
+ PDEBUG("[VK_ALLOC] %p - bytes %lu with alignment %lu - scope %s",
+ aligned_ptr, size, alignment, rend_vk_allocator_scope_name[allocationScope]);
+
+ return (void*) aligned_ptr;
+}
+
+static void*
+rend_vk_allocator_realloc(void *pUserData, void *pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocationScope)
+{
+ if (!pOriginal) return rend_vk_allocator_alloc(pUserData, size, alignment, allocationScope);
+ if (size == 0) {
+ rend_vk_allocator_free(pUserData, pOriginal);
+ return NULL;
+ }
+ void *new_ptr = rend_vk_allocator_alloc(pUserData, size, alignment, allocationScope);
+ if (!new_ptr) return NULL;
+ memcpy(new_ptr, pOriginal, size);
+ rend_vk_allocator_free(pUserData, pOriginal);
+ return new_ptr;
+}
+
+static void
+rend_vk_allocator_free(void *pUserData, void *pMemory)
+{
+ RendVkAllocatorHeader *ptr = pMemory;
+ uint8_t *raw_ptr = pMemory;
+ raw_ptr -= (ptr-1)->offset;
+
+ PDEBUG("[VK_FREE] %p", ptr);
+ free(raw_ptr);
+}
+
+static void
+rend_vk_allocator_internal_notification(void *pUserData, size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope)
+{
+ PDEBUG("[VK_ALLOC_INTERNAL] bytes %lu - scope %s",
+ size, rend_vk_allocator_scope_name[allocationScope]);
+
+}
+
+static void
+rend_vk_allocator_free_notification(void *pUserData, size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope)
+{
+ PDEBUG("[VK_FREE_INTERNAL] bytes %lu - scope %s",
+ size, rend_vk_allocator_scope_name[allocationScope]);
+}
diff --git a/rend_vk_arena.c b/rend_vk_arena.c
new file mode 100644
index 0000000..b936be4
--- /dev/null
+++ b/rend_vk_arena.c
@@ -0,0 +1,264 @@
+#pragma once
+#include "rend_internal.h"
+#include "rend_vk_internal.h"
+#include <vulkan/vulkan_core.h>
+
+/* arenas need to be subdivided into blocks
+ * just like pools because we must respect page size */
+typedef struct RendVkPage {
+ RendMemory memory;
+ size_t head;
+ int reserved;
+} RendVkPage;
+
+typedef struct RendVkPagedArena {
+ RendVkPage *page_darr;
+ uint32_t capacity;
+ uint32_t elements;
+} RendVkPagedArena;
+
+struct RendVkArenaAllocator {
+ VkAllocationCallbacks *allocator;
+ size_t *heap_idx_alloc_sizes; // allocation size per type of memory available on the gpu
+ RendVkPagedArena *mem_arenas; // memory arena per type of memory available on the gpu
+ VkDevice logical_device; // virtual device
+ VkPhysicalDevice physical_device; // physical device we are allocating memory from
+ VkDeviceSize gpu_alignment; // allocations must respect the physical limitations of the gpu
+ VkDeviceSize block_min_size; // minimum size per block of memory
+ uint64_t total_allocations;
+ uint32_t heap_index_count;
+ VkPhysicalDeviceMemoryProperties properties; // useful for getting properties per heap index
+};
+
+static RendVkArenaAllocator
+rend_vk_arena_create(VkDevice logical_device, VkPhysicalDevice physical_device, VkPhysicalDeviceLimits device_limits, VkAllocationCallbacks *allocator)
+{
+ RendVkArenaAllocator arena = {
+ .allocator = allocator,
+ .heap_idx_alloc_sizes = NULL,
+ .logical_device = logical_device,
+ .physical_device = physical_device,
+ .gpu_alignment = 0,
+ .block_min_size = 0,
+ .total_allocations = 0,
+ .heap_index_count = 0,
+ .mem_arenas = 0,
+ };
+
+ VkPhysicalDeviceMemoryProperties mem_properties;
+ vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_properties);
+ arena.properties = mem_properties;
+
+ uint32_t count = mem_properties.memoryTypeCount;
+ arena.heap_index_count = count;
+
+ arena.heap_idx_alloc_sizes = rmalloc(count * sizeof *arena.heap_idx_alloc_sizes);
+ memset(arena.heap_idx_alloc_sizes, 0, count * sizeof *arena.heap_idx_alloc_sizes);
+
+ arena.mem_arenas = rmalloc(count * sizeof *arena.mem_arenas);
+ for (uint32_t u = 0; u < count; ++u) {
+ arena.mem_arenas[u].capacity = 2;
+ arena.mem_arenas[u].elements = 0;
+ arena.mem_arenas[u].page_darr = rmalloc(2 * sizeof *arena.mem_arenas[u].page_darr);
+ memset(arena.mem_arenas[u].page_darr, 0, 2 * sizeof *arena.mem_arenas[u].page_darr);
+ }
+
+ VkDeviceSize alignment = device_limits.bufferImageGranularity;
+ if (device_limits.nonCoherentAtomSize > alignment) {
+ alignment = device_limits.nonCoherentAtomSize;
+ }
+
+ arena.gpu_alignment = alignment;
+ arena.block_min_size = arena.gpu_alignment * 10;
+
+ return arena;
+}
+
+static uint32_t
+rend_vk_arena_add_page(RendVkArenaAllocator *arena, VkDeviceSize size, uint32_t heap_index, bool fit_to_alloc)
+{
+ VkDeviceSize new_arena_size = fit_to_alloc ? size : (size * 2);
+ new_arena_size = (new_arena_size < arena->block_min_size) ? arena->block_min_size : new_arena_size;
+
+ VkMemoryPropertyFlags properties = arena->properties.memoryTypes[heap_index].propertyFlags;
+
+ RendVkPage page = (RendVkPage) {0};
+ page.head = 0;
+
+ VkMemoryAllocateFlagsInfo flags_info = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO,
+ .pNext = NULL,
+ .flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT,
+ .deviceMask = 0
+ };
+
+ VkMemoryAllocateInfo alloc_info = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = new_arena_size,
+ .pNext = &flags_info,
+ .memoryTypeIndex = heap_index
+ };
+
+ page.memory = (RendMemory) {0};
+ page.memory.offset = 0;
+ page.memory.size = new_arena_size;
+
+ VkResult res = vkAllocateMemory(arena->logical_device, &alloc_info, arena->allocator, (VkDeviceMemory*) &page.memory.device_memory);
+ if (res != VK_SUCCESS) {
+ return UINT32_MAX;
+ }
+
+ if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
+ vkMapMemory(
+ arena->logical_device,
+ (VkDeviceMemory) page.memory.device_memory,
+ 0, // Offset inside VkDeviceMemory!!!1
+ new_arena_size, // MUST match alloc_info.allocationSize and NOT raw size!!!
+ 0,
+ &page.memory.host_mapped_memory
+ );
+ }
+
+ RendVkPagedArena *mem_arena = &arena->mem_arenas[heap_index];
+
+ if (mem_arena->elements + 1 >= mem_arena->capacity) {
+ uint32_t new_capacity = mem_arena->capacity * 2;
+ void *new_darr = rrealloc(mem_arena->page_darr, new_capacity * sizeof *mem_arena->page_darr);
+ if (!new_darr) {
+ vkFreeMemory(arena->logical_device, (VkDeviceMemory) page.memory.device_memory, arena->allocator);
+ return UINT32_MAX;
+ }
+ mem_arena->page_darr = new_darr;
+ mem_arena->capacity = new_capacity;
+ }
+
+ uint32_t page_index = mem_arena->elements;
+ mem_arena->page_darr[mem_arena->elements++] = page;
+
+ arena->total_allocations++;
+
+ return page_index;
+}
+
+static RendMemory
+rend_vk_arena_alloc(RendVkArenaAllocator *arena, VkDeviceSize size, uint32_t heap_index)
+{
+ assert(heap_index < 32 && "Unusual heap index. Did you pass the memory type instead?");
+ RendVkPagedArena *mem_arena = &arena->mem_arenas[heap_index];
+
+ VkMemoryPropertyFlags properties = arena->properties.memoryTypes[heap_index].propertyFlags;
+
+ /* align to allocation to fit page size */
+ VkDeviceSize align = arena->gpu_alignment;
+ VkDeviceSize aligned_size = (size + align - 1) & ~(align - 1);
+
+ /* check if fits in previous blocks */
+ int whole_page = !(properties & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+ uint32_t page_idx = UINT32_MAX;
+ for (uint32_t u = 0; u < mem_arena->elements; ++u) {
+ RendVkPage page = mem_arena->page_darr[u];
+ int valid = (whole_page) ? (page.head == 0) : 1;
+ if ((page.head + aligned_size <= page.memory.size) && valid) {
+ page_idx = u;
+ break;
+ }
+ }
+
+ /* page not found, add new page */
+ if (page_idx == UINT32_MAX) {
+ page_idx = rend_vk_arena_add_page(arena, aligned_size, heap_index, whole_page);
+ if (page_idx == UINT32_MAX) {
+ REND__CRASH("[REND_VK] Arena page allocation failed!");
+ }
+ }
+
+ mem_arena->page_darr[page_idx].reserved = whole_page;
+
+ RendMemory memory = {
+ .device_memory = mem_arena->page_darr[page_idx].memory.device_memory,
+ .size = aligned_size,
+ .offset = mem_arena->page_darr[page_idx].head,
+ .host_mapped_memory = 0,
+ .heap_index = heap_index,
+ .id = page_idx,
+ };
+
+ if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
+ memory.host_mapped_memory = mem_arena->page_darr[page_idx].memory.host_mapped_memory + memory.offset;
+ }
+
+ /* move head */
+ arena->heap_idx_alloc_sizes[heap_index] += aligned_size;
+ mem_arena->page_darr[page_idx].head += aligned_size;
+ return memory;
+}
+
+
+static void
+rend_vk_arena_clear(RendVkArenaAllocator *arena, uint32_t heap_index)
+{
+ assert(heap_index < 32 && "Unusual heap index. Did you pass the memory type instead?");
+ RendVkPagedArena mem_arena = arena->mem_arenas[heap_index];
+ if (mem_arena.page_darr) {
+ for (uint32_t u = 0; u < mem_arena.elements; ++u) {
+ mem_arena.page_darr[u].head = 0;
+ mem_arena.page_darr[u].reserved = 0;
+ }
+ }
+}
+
+static void
+rend_vk_arena_clear_all(RendVkArenaAllocator *arena)
+{
+ for (uint32_t u = 0; u < arena->properties.memoryTypeCount; ++u) {
+ rend_vk_arena_clear(arena, u);
+ }
+}
+
+static void
+rend_vk_arena_shrink(RendVkArenaAllocator *arena, uint32_t heap_index)
+{
+ assert(heap_index < 32 && "Unusual heap index. Did you pass the memory type instead?");
+ RendVkPagedArena *mem_arena = &arena->mem_arenas[heap_index];
+ if (mem_arena->capacity > 4) {
+ uint32_t new_capacity = mem_arena->capacity / 2;
+ void *new_darr = rrealloc(mem_arena->page_darr, new_capacity * sizeof *mem_arena->page_darr);
+ if (!new_darr) return;
+ mem_arena->page_darr = new_darr;
+ mem_arena->capacity = new_capacity;
+ }
+}
+
+static void
+rend_vk_arena_destroy(RendVkArenaAllocator *arena)
+{
+ if (arena->mem_arenas) {
+ for (size_t u = 0; u < arena->heap_index_count; ++u) {
+ RendVkPagedArena *mem_arena = &arena->mem_arenas[u];
+ for (uint32_t p = 0; p < mem_arena->elements; ++p) {
+
+ RendMemory memory = mem_arena->page_darr[p].memory ;
+ if (memory.offset == 0) {
+ if (memory.host_mapped_memory) {
+ vkUnmapMemory(arena->logical_device, (VkDeviceMemory) memory.device_memory);
+ }
+ vkFreeMemory(arena->logical_device, (VkDeviceMemory) memory.device_memory, arena->allocator);
+ } else {
+ PWARN("[REND_VK] Attempted to free memory with an offset!");
+ }
+
+ }
+ if (mem_arena->page_darr) {
+ rfree(mem_arena->page_darr);
+ mem_arena->page_darr = 0;
+ }
+ }
+ rfree(arena->mem_arenas);
+ arena->mem_arenas = 0;
+ }
+ if (arena->heap_idx_alloc_sizes) {
+ rfree(arena->heap_idx_alloc_sizes);
+ arena->heap_idx_alloc_sizes = 0;
+ }
+ memset(arena, 0, sizeof *arena);
+}
diff --git a/rend_vk_device.c b/rend_vk_device.c
new file mode 100644
index 0000000..8daeded
--- /dev/null
+++ b/rend_vk_device.c
@@ -0,0 +1,395 @@
+#pragma once
+#include "rend_internal.h"
+#include "rend_vk_internal.h"
+#include <vulkan/vulkan_core.h>
+
+// struct RendDevice {
+// bool anisotropy_support;
+// bool bindless_support;
+// };
+
+
+static bool
+rend_vk_device_create(VkSurfaceKHR surface, RendSpecs specs, RendVkDevice *out_device, uint32_t (*score_devices)(RendVkDevice *, RendSpecs, char **))
+{
+ assert(out_device);
+
+ uint32_t device_count = 0;
+ CHECK_VK_RESULT(vkEnumeratePhysicalDevices(vk_instance, &device_count, VK_NULL_HANDLE));
+ if (device_count == 0) {
+ PFATAL("No GPU with Vulkan support found!");
+ return false;
+ }
+
+ VkPhysicalDevice physical_devices[device_count];
+ CHECK_VK_RESULT(vkEnumeratePhysicalDevices(vk_instance, &device_count, physical_devices));
+
+ char **extension_names_darray = NULL;
+ p_darray_push(extension_names_darray, VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+
+ uint32_t best_score = 1; // 1 so that devices that do not meet specs get ignored
+ RendVkDevice best_device = {0};
+
+ PDEBUG("DEVICE SCORE");
+ for (uint32_t i = 0; i < device_count; i++) {
+
+ /* populate device struct */
+ RendVkDevice scoring = {0};
+ scoring.surface = surface;
+ scoring.physical_device = physical_devices[i];
+ vkGetPhysicalDeviceProperties(physical_devices[i], &scoring.properties);
+ vkGetPhysicalDeviceFeatures(physical_devices[i], &scoring.features);
+ vkGetPhysicalDeviceMemoryProperties(physical_devices[i], &scoring.memory);
+
+ /* score device */
+ uint32_t dev_score = score_devices(&scoring, specs, extension_names_darray);
+ PDEBUG("%-20.20s %5d", scoring.properties.deviceName, dev_score);
+ if (dev_score >= best_score) {
+ if (best_device.swapchain_support.format) {
+ rfree(best_device.swapchain_support.format);
+ }
+ if (best_device.swapchain_support.present_modes) {
+ rfree(best_device.swapchain_support.present_modes);
+ }
+ best_score = dev_score;
+ best_device = scoring;
+ } else {
+ if (scoring.swapchain_support.format) {
+ rfree(scoring.swapchain_support.format);
+ }
+ if (scoring.swapchain_support.present_modes) {
+ rfree(scoring.swapchain_support.present_modes);
+ }
+ }
+
+ }
+
+ /* free extensions array after scoring */
+ p_darray_destroy(extension_names_darray);
+
+ if (best_score > 1) {
+ PDEBUG("Driver version %d.%d.%d", VK_VERSION_MAJOR(best_device.properties.driverVersion), VK_VERSION_MINOR(best_device.properties.driverVersion), VK_VERSION_PATCH(best_device.properties.driverVersion));
+ PDEBUG("Vulkan API version %d.%d.%d", VK_VERSION_MAJOR(best_device.properties.apiVersion), VK_VERSION_MINOR(best_device.properties.apiVersion), VK_VERSION_PATCH(best_device.properties.apiVersion));
+ }
+
+ if (!best_device.physical_device) {
+ PERROR("No physical devices were found that meet specs!");
+ return false;
+ }
+
+ /* update out device to use best selected device */
+ *out_device = best_device;
+
+ PDEBUG("Graphics Family Index: %u", out_device->graphics_family_index);
+ PDEBUG("Present Family Index: %u", out_device->present_family_index);
+ PDEBUG("Compute Family Index: %u", out_device->compute_family_index);
+ PDEBUG("Transfer Family Index: %u", out_device->transfer_family_index);
+
+ bool present_shares_graphics_q = out_device->present_family_index == out_device->graphics_family_index;
+ bool transfer_shares_graphics_q = out_device->transfer_family_index == out_device->graphics_family_index;
+
+ uint32_t index_count = 1;
+ if (!present_shares_graphics_q) index_count++;
+ if (!transfer_shares_graphics_q) index_count++;
+
+ uint32_t indices[index_count];
+ uint8_t index = 0;
+
+ indices[index++] = out_device->graphics_family_index;
+ if (!present_shares_graphics_q) {
+ indices[index++] = out_device->present_family_index;
+ }
+ if (!transfer_shares_graphics_q) {
+ indices[index++] = out_device->transfer_family_index;
+ }
+
+ VkDeviceQueueCreateInfo q_create_info[index_count];
+
+ f32 queue_priority[2] = {1.0f, 1.0f};
+ for (uint32_t i = 0; i < index_count; i++) {
+ q_create_info[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ q_create_info[i].queueFamilyIndex = indices[i];
+ // TODO: request 2 queues when possible
+ // q_create_info[i].queueCount = (indices[i] == out_device->graphics_family_index) ? 2 : 1;
+ q_create_info[i].queueCount = 1;
+ q_create_info[i].flags = 0;
+ q_create_info[i].pNext = 0;
+ q_create_info[i].pQueuePriorities = queue_priority;
+ }
+
+ // TODO: driven by the same config as the check_specs function
+ // used earlier
+
+ VkPhysicalDeviceFeatures device_features = {0};
+ device_features.samplerAnisotropy = specs.sampler_anisotropy;
+ device_features.fillModeNonSolid = VK_TRUE;
+ device_features.shaderInt64 = VK_TRUE;
+
+ // vulkan 1.3 features (dynamic rendering + sync2)
+ VkPhysicalDeviceVulkan13Features vk13_features = {0};
+ vk13_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
+ vk13_features.dynamicRendering = VK_TRUE;
+ vk13_features.synchronization2 = VK_TRUE;
+
+ // vulkan 1.2 features (timeline semaphores)
+ VkPhysicalDeviceVulkan12Features vk12_features = {0};
+ vk12_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
+ vk12_features.timelineSemaphore = VK_TRUE;
+
+ // vk12_features.descriptorIndexing = VK_TRUE;
+ // vk12_features.descriptorBindingSampledImageUpdateAfterBind = VK_TRUE;
+ vk12_features.descriptorBindingPartiallyBound = VK_TRUE;
+ // vk12_features.descriptorBindingUniformBufferUpdateAfterBind = VK_TRUE;
+ // vk12_features.descriptorBindingVariableDescriptorCount = VK_TRUE;
+ // vk12_features.runtimeDescriptorArray = VK_TRUE;
+
+ vk12_features.bufferDeviceAddress = VK_TRUE;
+ vk12_features.scalarBlockLayout = VK_TRUE;
+ vk12_features.pNext = &vk13_features;
+
+ // vulkan 1.1 features (draw parameters)
+ VkPhysicalDeviceVulkan11Features vk11_features = {0};
+ vk11_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
+ vk11_features.shaderDrawParameters = VK_TRUE;
+ vk11_features.pNext = &vk12_features;
+
+ VkDeviceCreateInfo device_create_info = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
+ device_create_info.pNext = &vk11_features;
+ device_create_info.queueCreateInfoCount = index_count;
+ device_create_info.pQueueCreateInfos = q_create_info;
+ device_create_info.pEnabledFeatures = &device_features;
+ device_create_info.enabledExtensionCount = 1;
+
+ const char *extention_names = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
+ device_create_info.ppEnabledExtensionNames = &extention_names;
+
+ /* deprecated and ignored */
+ device_create_info.enabledLayerCount = 0;
+ device_create_info.ppEnabledLayerNames = 0;
+
+ CHECK_VK_RESULT(vkCreateDevice(out_device->physical_device, &device_create_info, vk_allocator, &out_device->logical_device));
+
+ vkGetDeviceQueue(out_device->logical_device, out_device->graphics_family_index, 0, &out_device->graphics_queue);
+ vkGetDeviceQueue(out_device->logical_device, out_device->present_family_index, 0, &out_device->present_queue);
+ vkGetDeviceQueue(out_device->logical_device, out_device->transfer_family_index, 0, &out_device->transfer_queue);
+
+ PDEBUG("GRAPHICS | PRESENT | COMPUTE | TRANSFER | DEVICE");
+ PDEBUG(" %02d | %02d | %02d | %02d | %s",
+ out_device->graphics_family_index != UINT32_MAX,
+ out_device->present_family_index != UINT32_MAX,
+ out_device->compute_family_index != UINT32_MAX,
+ out_device->transfer_family_index != UINT32_MAX,
+ out_device->properties.deviceName);
+
+ VkPhysicalDeviceMemoryProperties mem_props = out_device->memory;
+ out_device->device_index = UINT32_MAX;
+ for (uint32_t i = 0; i < mem_props.memoryTypeCount; i++) {
+ if ((mem_props.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) == VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
+ out_device->device_index = i;
+ break;
+ }
+ }
+
+ VkMemoryPropertyFlags host_flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+ out_device->host_index = UINT32_MAX;
+ for (uint32_t i = 0; i < mem_props.memoryTypeCount; i++) {
+ if ((mem_props.memoryTypes[i].propertyFlags & host_flags) == host_flags) {
+ out_device->host_index = i;
+ break;
+ }
+ }
+
+ PDEBUG("Device local heap: %2u", out_device->device_index);
+ PDEBUG("Host mapped heap: %2u", out_device->host_index);
+ return true;
+}
+
+static void
+rend_vk_device_destroy(void)
+{
+ vk_device.graphics_queue = 0;
+ vk_device.transfer_queue = 0;
+ vk_device.present_queue = 0;
+
+ if (vk_device.logical_device) {
+ vkDestroyDevice(vk_device.logical_device, vk_allocator);
+ vk_device.logical_device = 0;
+ }
+
+ if (vk_device.swapchain_support.format) {
+ rfree(vk_device.swapchain_support.format);
+ vk_device.swapchain_support.format = 0;
+ vk_device.swapchain_support.format_count = 0;
+ }
+
+ if (vk_device.swapchain_support.present_modes) {
+ rfree(vk_device.swapchain_support.present_modes);
+ vk_device.swapchain_support.present_modes = 0;
+ vk_device.swapchain_support.present_mode_count = 0;
+ }
+
+ vk_device = (RendVkDevice){0};
+}
+
+static void
+rend_vk_physical_device_query_swapchain_support(RendVkDevice *device)
+{
+ /* surface capabilities */
+ CHECK_VK_RESULT(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device->physical_device, device->surface, &device->swapchain_support.capabilities));
+
+ /* surface formats */
+ CHECK_VK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(device->physical_device, device->surface, &device->swapchain_support.format_count, VK_NULL_HANDLE));
+ if (device->swapchain_support.format_count != 0) {
+ if (!device->swapchain_support.format) {
+ device->swapchain_support.format = rmalloc(device->swapchain_support.format_count * sizeof(*device->swapchain_support.format));
+ }
+ CHECK_VK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(device->physical_device, device->surface, &device->swapchain_support.format_count, device->swapchain_support.format));
+ }
+
+ /* present modes */
+ CHECK_VK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(device->physical_device, device->surface, &device->swapchain_support.present_mode_count, VK_NULL_HANDLE));
+ if (device->swapchain_support.present_mode_count != 0) {
+ if (!device->swapchain_support.present_modes) {
+ device->swapchain_support.present_modes = rmalloc(device->swapchain_support.present_mode_count * sizeof(*device->swapchain_support.present_modes));
+ }
+ CHECK_VK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(device->physical_device, device->surface, &device->swapchain_support.present_mode_count, device->swapchain_support.present_modes));
+ }
+}
+
+static bool
+rend_vk_device_detect_depth_format(RendVkDevice *device)
+{
+ const uint64_t candidate_count = 3;
+ VkFormat candidates[] = {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT};
+
+ uint32_t flags = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ for (uint32_t i = 0; i < candidate_count; i++) {
+ VkFormatProperties properties;
+ vkGetPhysicalDeviceFormatProperties(device->physical_device, candidates[i], &properties);
+ if ((properties.linearTilingFeatures & flags) == flags) {
+ device->depth_format = candidates[i];
+ return true;
+ } else if ((properties.optimalTilingFeatures & flags) == flags) {
+ device->depth_format = candidates[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static uint32_t
+rend_vk_device_score_default(RendVkDevice *device, RendSpecs minimum_specs, char **required_extensions)
+{
+ /* NOTE: When we score the device we will also check for specs. Not meeting a spec
+ * leads to 0 score and continue. We will assume device already contains some information
+ * about properties when this function is called. */
+
+ uint32_t score = 0;
+
+ device->graphics_family_index = UINT32_MAX;
+ device->present_family_index = UINT32_MAX;
+ device->compute_family_index = UINT32_MAX;
+ device->transfer_family_index = UINT32_MAX;
+
+ if (minimum_specs.discrete_gpu) {
+ if (device->properties.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
+ return 0;
+ }
+ }
+
+ uint32_t q_family_count = 0;
+ vkGetPhysicalDeviceQueueFamilyProperties(device->physical_device, &q_family_count, VK_NULL_HANDLE);
+
+ VkQueueFamilyProperties q_family[q_family_count];
+ vkGetPhysicalDeviceQueueFamilyProperties(device->physical_device, &q_family_count, q_family);
+
+ uint8_t min_transfer_score = 255;
+ for (uint32_t i = 0; i < q_family_count; i++) {
+
+ uint8_t transfer_score = 0;
+ if (q_family[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+ device->graphics_family_index = i;
+ transfer_score++;
+ }
+
+ if (q_family[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
+ device->compute_family_index = i;
+ transfer_score++;
+ }
+
+ if (q_family[i].queueFlags & VK_QUEUE_TRANSFER_BIT) {
+ // take the index if the transfer score is minimal
+ if (transfer_score <= min_transfer_score) {
+ min_transfer_score = transfer_score;
+ device->transfer_family_index = i;
+ }
+ }
+
+ VkBool32 supports_present = VK_FALSE;
+ CHECK_VK_RESULT(vkGetPhysicalDeviceSurfaceSupportKHR(device->physical_device, i, device->surface, &supports_present));
+ if (supports_present) {
+ device->present_family_index = i;
+ }
+ }
+
+ if ( (minimum_specs.graphics && device->graphics_family_index == UINT32_MAX) ||
+ (minimum_specs.present && device->present_family_index == UINT32_MAX) ||
+ (minimum_specs.compute && device->compute_family_index == UINT32_MAX) ||
+ (minimum_specs.transfer && device->transfer_family_index == UINT32_MAX)) {
+ return 0;
+ }
+
+ // swapchain support
+ rend_vk_physical_device_query_swapchain_support(device);
+
+ if (device->swapchain_support.format_count < 1 || device->swapchain_support.present_mode_count < 1) {
+ return 0;
+ }
+
+ // check for extension specs
+ if (required_extensions) {
+ uint32_t available_extentions_count = 0;
+ VkExtensionProperties *available_extentions = NULL;
+ CHECK_VK_RESULT(vkEnumerateDeviceExtensionProperties(device->physical_device, VK_NULL_HANDLE, &available_extentions_count, VK_NULL_HANDLE));
+
+ if (available_extentions_count != 0) {
+
+ available_extentions = rmalloc(available_extentions_count * sizeof(*available_extentions));
+ CHECK_VK_RESULT(vkEnumerateDeviceExtensionProperties(device->physical_device, VK_NULL_HANDLE, &available_extentions_count, available_extentions));
+
+ uint32_t required_extention_count = p_darray_len(required_extensions);
+
+ bool overall_found = true;
+ for (uint32_t i = 0; i < required_extention_count; ++i) {
+ bool found = false;
+ for (uint32_t j = 0; j < available_extentions_count; ++j) {
+ if (strcmp(required_extensions[i], available_extentions[j].extensionName) == 0) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ overall_found = false;
+ break;
+ }
+ }
+
+ rfree(available_extentions);
+ if (!overall_found) {
+ return 0;
+ }
+ }
+ }
+
+ if (minimum_specs.sampler_anisotropy && !device->features.samplerAnisotropy) {
+ return 0;
+ }
+
+ score = 10;
+ if (device->properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
+ score += 1000;
+ }
+
+ return score;
+}
diff --git a/rend_vk_image.c b/rend_vk_image.c
new file mode 100644
index 0000000..e842bf0
--- /dev/null
+++ b/rend_vk_image.c
@@ -0,0 +1,125 @@
+#pragma once
+#include "rend_internal.h"
+#include "rend_vk_internal.h"
+#include <vulkan/vulkan_core.h>
+
+struct RendVkImage {
+ VkDevice logical_device;
+ VkImage handle;
+ VkImageView view;
+
+ VkMemoryRequirements requirements;
+
+ VkImageType img_type;
+ uint32_t width, height;
+ VkFormat format;
+ VkImageTiling tiling;
+ VkImageUsageFlags usage;
+ uint32_t depth;
+ uint32_t mip_levels;
+ uint32_t layers;
+ VkSampleCountFlags sample_count_flags;
+ VkSharingMode sharing_mode;
+
+ RendMemory *memory;
+};
+
+static RendVkImage
+rend_vk_image_create(VkDevice logical_device, VkImageType img_type, uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling,
+ VkImageUsageFlags usage, VkMemoryPropertyFlags memory_flags, uint32_t depth, uint32_t mip_levels, uint32_t layers,
+ VkSampleCountFlags sample_count_flags, VkSharingMode sharing_mode)
+{
+ RendVkImage image = {
+ .handle = VK_NULL_HANDLE,
+ .memory = VK_NULL_HANDLE,
+ .logical_device = logical_device,
+ .img_type = img_type,
+ .width = width,
+ .height = height,
+ .format = format,
+ .tiling = tiling,
+ .usage = usage,
+ .depth = depth,
+ .mip_levels = mip_levels,
+ .layers = layers,
+ .sample_count_flags = sample_count_flags,
+ .sharing_mode = sharing_mode
+ };
+
+ VkImageCreateInfo img_create_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
+ img_create_info.imageType = img_type;
+ img_create_info.extent.width = width;
+ img_create_info.extent.height = height;
+ img_create_info.extent.depth = depth;
+ img_create_info.mipLevels = mip_levels;
+ img_create_info.arrayLayers = layers;
+ img_create_info.format = format;
+ img_create_info.tiling = tiling;
+ img_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ img_create_info.usage = usage;
+ img_create_info.samples = sample_count_flags;
+ img_create_info.sharingMode = sharing_mode;
+
+
+ if (vkCreateImage(logical_device, &img_create_info, vk_allocator, &image.handle) != VK_SUCCESS) {
+ return image;
+ }
+
+ return image;
+}
+
+static uint32_t
+rend_vk_image_required_memory_type(RendVkImage *img)
+{
+ assert(img->memory == NULL && "Image already bound to memory");
+ VkMemoryRequirements memory_requirements = {0};
+ vkGetImageMemoryRequirements(img->logical_device, img->handle, &memory_requirements);
+ img->requirements = memory_requirements;
+ return memory_requirements.memoryTypeBits;
+}
+
+static void
+rend_vk_image_bind_memory(RendVkImage *img, RendMemory *memory)
+{
+ assert(img->memory == NULL && "Image already bound to memory");
+ vkBindImageMemory(img->logical_device, img->handle, (VkDeviceMemory) memory->device_memory, memory->offset);
+}
+
+static void
+rend_vk_image_destroy(RendVkImage *img)
+{
+ if (img->view) {
+ vkDestroyImageView(img->logical_device, img->view, vk_allocator);
+ img->view = 0;
+ }
+
+ if (img->handle) {
+ vkDestroyImage(img->logical_device, img->handle, vk_allocator);
+ img->handle = 0;
+ }
+}
+
+static void
+rend_vk_image_view_create(RendVkImage *image, VkImageViewType view_type, VkImageAspectFlags view_aspect_flags)
+{
+ VkImageViewCreateInfo view_create_info = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = image->handle,
+ .format = image->format,
+ .viewType = view_type,
+ .subresourceRange.aspectMask = view_aspect_flags,
+ .subresourceRange.baseMipLevel = 0, // offset starts at 0
+ .subresourceRange.levelCount = image->mip_levels, // number of mips
+ .subresourceRange.baseArrayLayer = 0, // offset starts at 0
+ .subresourceRange.layerCount = image->layers, // number of layers
+ };
+
+ vkCreateImageView(image->logical_device, &view_create_info, vk_allocator, &image->view);
+}
+
+static uint64_t
+rend_vk_image_address(RendVkImage *image)
+{
+ assert(image->memory == NULL && "Image already bound to memory");
+ return (uint64_t) image->memory->device_memory + image->memory->offset;
+}
diff --git a/rend_vk_internal.h b/rend_vk_internal.h
new file mode 100644
index 0000000..e85a6dc
--- /dev/null
+++ b/rend_vk_internal.h
@@ -0,0 +1,177 @@
+#ifndef REND_VK_INTERNAL_H
+#define REND_VK_INTERNAL_H
+
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_core.h>
+#include <assert.h>
+
+#define CHECK_VK_RESULT(r) \
+{ \
+ assert(r == VK_SUCCESS && __LINE__); \
+}
+
+
+typedef struct RendVkImage RendVkImage;
+typedef struct RendVkArenaAllocator RendVkArenaAllocator;
+typedef struct RendVkSbta RendVkSbta;
+
+typedef struct RendVkPendingCmd RendVkPendingCmd;
+typedef struct RendVkDeferredCmds RendVkDeferredCmds;
+
+typedef struct {
+ const char **extention_names_darray;
+ bool sampler_anisotropy;
+ bool discrete_gpu;
+ bool graphics;
+ bool present;
+ bool compute;
+ bool transfer;
+} RendVkDevicespecs;
+
+typedef struct {
+ uint32_t graphics_family_index;
+ uint32_t present_family_index;
+ uint32_t compute_family_index;
+ uint32_t transfer_family_index;
+} RendVkQueueFamily;
+
+typedef struct {
+ VkSurfaceCapabilitiesKHR capabilities;
+ VkSurfaceFormatKHR *format;
+ VkPresentModeKHR *present_modes;
+ uint32_t format_count;
+ uint32_t present_mode_count;
+} RendVkSwapchainSupport;
+
+typedef struct {
+ /* logical device handle */
+ VkDevice logical_device;
+
+ /* physical device limitation */
+ VkPhysicalDeviceProperties properties;
+ VkPhysicalDeviceMemoryProperties memory;
+ VkPhysicalDevice physical_device;
+ VkPhysicalDeviceFeatures features;
+ VkPhysicalDeviceDescriptorBufferPropertiesEXT desc_props;
+
+ /* swapchain support */
+ VkSurfaceKHR surface;
+ RendVkSwapchainSupport swapchain_support;
+ VkFormat depth_format;
+
+ /* queues */
+ VkQueue graphics_queue;
+ VkQueue present_queue;
+ VkQueue compute_queue;
+ VkQueue transfer_queue;
+ uint32_t graphics_family_index;
+ uint32_t present_family_index;
+ uint32_t compute_family_index;
+ uint32_t transfer_family_index;
+
+ /* memory heap indices */
+ uint32_t host_index;
+ uint32_t device_index;
+} RendVkDevice;
+
+static VkInstance vk_instance = 0;
+static VkDebugUtilsMessengerEXT vk_debug_messenger = 0;
+static RendVkDevice vk_device = {0};
+
+/* helper functions */
+static int32_t rend_vk_memory_find_index(VkPhysicalDevice device, uint32_t type_filter, VkMemoryPropertyFlags property_flags);
+
+/* rend_vk_device.c */
+static bool rend_vk_device_create(VkSurfaceKHR surface, RendSpecs specs, RendVkDevice *out_device, uint32_t (*score_devices)(RendVkDevice *, RendSpecs, char **));
+static uint32_t rend_vk_device_score_default(RendVkDevice *device, RendSpecs minimum_specs, char **required_extensions);
+static void rend_vk_physical_device_query_swapchain_support(RendVkDevice *device);
+static bool rend_vk_device_detect_depth_format(RendVkDevice *device);
+static void rend_vk_device_destroy(void);
+
+/* rend_vk_arena.c */
+static RendVkArenaAllocator rend_vk_arena_create(VkDevice logical_device, VkPhysicalDevice physical_device, VkPhysicalDeviceLimits device_limits, VkAllocationCallbacks *allocator);
+static uint32_t rend_vk_arena_add_page(RendVkArenaAllocator *arena, VkDeviceSize size, uint32_t heap_index, bool fit_to_alloc);
+static RendMemory rend_vk_arena_alloc(RendVkArenaAllocator *arena, VkDeviceSize size, uint32_t heap_index);
+static void rend_vk_arena_clear(RendVkArenaAllocator *arena, uint32_t memory_type);
+static void rend_vk_arena_clear_all(RendVkArenaAllocator *arena);
+static void rend_vk_arena_shrink(RendVkArenaAllocator *arena, uint32_t memory_type);
+static void rend_vk_arena_destroy(RendVkArenaAllocator *arena);
+
+/* rend_vk_command_queue.c */
+static inline RendVkDeferredCmds rend_vk_cmdbuf_deferred_create(void);
+static inline void rend_vk_cmdbuf_deferred_destroy(RendVkDeferredCmds *tracker);
+static inline uint64_t rend_vk_cmdbuf_deferred_submit(RendVkDeferredCmds *tracker, VkCommandPool pool, VkCommandBuffer cmd, VkQueue queue, uint64_t wait_value, VkPipelineStageFlags2 wait_stage, VkPipelineStageFlags2 signal_stage);
+static inline void rend_vk_cmdbuf_deferred_push(RendVkDeferredCmds *tracker, VkCommandPool pool, VkCommandBuffer cmd, uint64_t wait_value);
+static inline void rend_vk_cmdbuf_deferred_flush(RendVkDeferredCmds *tracker);
+
+/* rend_vk_image.c */
+static RendVkImage rend_vk_image_create(VkDevice logical_device, VkImageType img_type, uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags memory_flags, uint32_t depth, uint32_t mip_levels, uint32_t layers, VkSampleCountFlags sample_count_flags, VkSharingMode sharing_mode);
+static uint32_t rend_vk_image_required_memory_type(RendVkImage *img);
+static void rend_vk_image_bind_memory(RendVkImage *image, RendMemory *memory);
+static void rend_vk_image_destroy(RendVkImage *img);
+static void rend_vk_image_view_create(RendVkImage *image, VkImageViewType view_type, VkImageAspectFlags view_aspect_flags);
+static uint64_t rend_vk_image_address(RendVkImage *image);
+
+
+static VkAllocationCallbacks *vk_allocator = 0; // we are only ever going to use one, either the default one or the custom one
+
+static VkFormat vk_format_from_rend_format[] = {
+ [REND_FORMAT_R8_UNORM] = VK_FORMAT_R8_UNORM,
+ [REND_FORMAT_R8G8_UNORM] = VK_FORMAT_R8G8_UNORM,
+ [REND_FORMAT_R8G8B8A8_UNORM] = VK_FORMAT_R8G8B8A8_UNORM,
+ [REND_FORMAT_B8G8R8A8_UNORM] = VK_FORMAT_B8G8R8A8_UNORM,
+ [REND_FORMAT_R8G8B8A8_SRGB] = VK_FORMAT_R8G8B8A8_SRGB,
+ [REND_FORMAT_B8G8R8A8_SRGB] = VK_FORMAT_B8G8R8A8_SRGB,
+ [REND_FORMAT_R32_SFLOAT] = VK_FORMAT_R32_SFLOAT,
+ [REND_FORMAT_R32G32_SFLOAT] = VK_FORMAT_R32G32_SFLOAT,
+ [REND_FORMAT_R32G32B32_SFLOAT] = VK_FORMAT_R32G32B32_SFLOAT,
+ [REND_FORMAT_R32G32B32A32_SFLOAT]= VK_FORMAT_R32G32B32A32_SFLOAT,
+ [REND_FORMAT_R16_SFLOAT] = VK_FORMAT_R16_SFLOAT,
+ [REND_FORMAT_R16G16_SFLOAT] = VK_FORMAT_R16G16_SFLOAT,
+ [REND_FORMAT_R16G16B16A16_SFLOAT]= VK_FORMAT_R16G16B16A16_SFLOAT,
+ [REND_FORMAT_R32_UINT] = VK_FORMAT_R32_UINT,
+ [REND_FORMAT_R32_SINT] = VK_FORMAT_R32_SINT,
+ [REND_FORMAT_R32G32B32A32_UINT] = VK_FORMAT_R32G32B32A32_UINT,
+ [REND_FORMAT_R16G16B16A16_UINT] = VK_FORMAT_R16G16B16A16_UINT,
+ [REND_FORMAT_R8G8B8A8_UINT] = VK_FORMAT_R8G8B8A8_UINT,
+ [REND_FORMAT_D32_SFLOAT] = VK_FORMAT_D32_SFLOAT,
+ [REND_FORMAT_D24_UNORM_S8_UINT] = VK_FORMAT_D24_UNORM_S8_UINT,
+ [REND_FORMAT_D32_SFLOAT_S8_UINT] = VK_FORMAT_D32_SFLOAT_S8_UINT,
+};
+
+static VkPrimitiveTopology vk_topology[] = {
+ [REND_TOPOLOGY_TRIANGLE_LIST] = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ [REND_TOPOLOGY_TRIANGLE_STRIP] = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
+ [REND_TOPOLOGY_LINE_LIST] = VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
+ [REND_TOPOLOGY_LINE_STRIP] = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP,
+ [REND_TOPOLOGY_POINT_LIST] = VK_PRIMITIVE_TOPOLOGY_POINT_LIST
+};
+
+static VkPolygonMode vk_polymode[] = {
+ [REND_POLYGON_MODE_FILL] = VK_POLYGON_MODE_FILL,
+ [REND_POLYGON_MODE_LINE] = VK_POLYGON_MODE_LINE,
+ [REND_POLYGON_MODE_POINT] = VK_POLYGON_MODE_POINT,
+};
+
+static VkCullModeFlags vk_cullflags[] = {
+ [REND_CULL_MODE_NONE] = VK_CULL_MODE_NONE,
+ [REND_CULL_MODE_FRONT] = VK_CULL_MODE_FRONT_BIT,
+ [REND_CULL_MODE_BACK] = VK_CULL_MODE_BACK_BIT,
+ [REND_CULL_MODE_FRONT_AND_BACK] = VK_CULL_MODE_FRONT_AND_BACK,
+};
+
+static int32_t
+rend_vk_memory_find_index(VkPhysicalDevice device, uint32_t type_filter, VkMemoryPropertyFlags property_flags)
+{
+ VkPhysicalDeviceMemoryProperties memory_properties;
+ vkGetPhysicalDeviceMemoryProperties(device, &memory_properties);
+ for (uint32_t i = 0; i < memory_properties.memoryTypeCount; i++) {
+ if (type_filter & (1 << i) && (memory_properties.memoryTypes[i].propertyFlags & property_flags) == property_flags) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+#endif
diff --git a/unused code/rend_vk_buffer.c b/unused code/rend_vk_buffer.c
new file mode 100644
index 0000000..37f3e06
--- /dev/null
+++ b/unused code/rend_vk_buffer.c
@@ -0,0 +1,116 @@
+#pragma once
+#include "rend_internal.h"
+#include "rend_vk_internal.h"
+#include <vulkan/vulkan_core.h>
+
+#if 0
+static RendBuffer
+rend_vk_buffer_create(VkDevice logical_device, VkAllocationCallbacks *allocator, VkDeviceSize size, VkBufferUsageFlags usage, uint32_t *family_indices, uint32_t family_indices_count)
+{
+ RendBuffer buffer = {
+ .handle = 0,
+ .logical_device = logical_device,
+ .memory = NULL,
+ .allocator = allocator,
+ .usage = usage | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
+ };
+
+ VkBufferCreateInfo buffer_info = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = size,
+ .usage = usage,
+ .sharingMode = (family_indices_count > 1) ? VK_SHARING_MODE_CONCURRENT : VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = family_indices_count,
+ .pQueueFamilyIndices = family_indices
+ };
+
+ vkCreateBuffer(logical_device, &buffer_info, allocator, &buffer.handle);
+ return buffer;
+}
+
+static uint32_t
+rend_vk_buffer_required_memory_type(RendBuffer *buffer)
+{
+ assert(buffer->memory == NULL && "Buffer already bound to memory");
+ VkMemoryRequirements mem_requirements;
+ vkGetBufferMemoryRequirements(buffer->logical_device, buffer->handle, &mem_requirements);
+ return mem_requirements.memoryTypeBits;
+}
+
+static void
+rend_vk_buffer_bind_memory(RendBuffer *buffer, RendVkMemory *memory)
+{
+ assert(buffer->memory == NULL && "Buffer already bound to memory");
+ vkBindBufferMemory(buffer->logical_device, buffer->handle, memory->device_memory, 0);
+ buffer->memory = memory;
+}
+
+static void
+rend_vk_buffer_destroy(RendBuffer *buffer)
+{
+ assert(buffer && buffer->handle != 0);
+ vkDestroyBuffer(buffer->logical_device, buffer->handle, buffer->allocator);
+ buffer->handle = 0;
+ buffer->memory = 0;
+}
+
+static void
+rend_vk_buffer_copy_device(VkQueue queue, VkCommandPool pool, RendBuffer *dest, size_t dest_offset, size_t bytes, RendBuffer *src, size_t src_offset, VkFence fence)
+{
+ assert(src && dest); // check that im not sending null pointers
+ assert(src->usage & VK_BUFFER_USAGE_TRANSFER_SRC_BIT); // source buffer must be marked as transfer src
+ assert(dest->usage & VK_BUFFER_USAGE_TRANSFER_DST_BIT); // dest buffer must be marked as transfer dest
+
+ VkCommandBuffer transfer_cmd = VK_NULL_HANDLE;
+
+ VkCommandBufferAllocateInfo alloc_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandBufferCount = 1,
+ .commandPool = pool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .pNext = NULL,
+ };
+
+ vkAllocateCommandBuffers(dest->logical_device, &alloc_info, &transfer_cmd);
+
+ VkCommandBufferBeginInfo begin_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ };
+ vkBeginCommandBuffer(transfer_cmd, &begin_info);
+
+ VkBufferCopy buffer_copy = {
+ .srcOffset = (VkDeviceSize)src_offset,
+ .dstOffset = (VkDeviceSize)dest_offset,
+ .size = (VkDeviceSize)bytes,
+ };
+
+ vkCmdCopyBuffer(transfer_cmd, src->handle, dest->handle, 1, &buffer_copy);
+ vkEndCommandBuffer(transfer_cmd);
+
+ VkCommandBufferSubmitInfo cmd_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
+ .commandBuffer = transfer_cmd,
+ .deviceMask = 0,
+ };
+
+ VkSubmitInfo2 submit_info = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
+ .commandBufferInfoCount = 1,
+ .pCommandBufferInfos = &cmd_info,
+ };
+
+ vkQueueSubmit2(queue, 1, &submit_info, fence);
+ vkQueueWaitIdle(queue);
+ vkFreeCommandBuffers(dest->logical_device, pool, 1, &transfer_cmd);
+}
+
+static uint64_t
+rend_vk_buffer_address(RendBuffer *buffer, VkDevice device)
+{
+ VkBufferDeviceAddressInfoKHR address_info = {VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR};
+ address_info.buffer = buffer->handle;
+ VkDeviceAddress address = vkGetBufferDeviceAddress(device, &address_info);
+ return (uint64_t) address;
+}
+#endif
diff --git a/unused code/rend_vk_command_queue.c b/unused code/rend_vk_command_queue.c
new file mode 100644
index 0000000..1ddd780
--- /dev/null
+++ b/unused code/rend_vk_command_queue.c
@@ -0,0 +1,153 @@
+#pragma once
+#include "rend_internal.h"
+#include "rend_vk_internal.h"
+#include <vulkan/vulkan_core.h>
+#include <stdatomic.h>
+#include <stdlib.h>
+
+/* DEFERRED COMMANDS:
+ * Tracks single-use command buffers submitted for async GPU work,
+ * so callers don't have to block (vkQueueWaitIdle) to know when it's
+ * safe to free/reset them. One shared timeline semaphore; every
+ * submission claims the next monotonic value.
+ */
+
+struct RendVkPendingCmd {
+ VkCommandPool pool;
+ VkCommandBuffer cmd;
+ uint64_t wait_value;
+};
+
+struct RendVkDeferredCmds {
+ VkSemaphore timeline;
+ RendVkPendingCmd *darray;
+ size_t count;
+ size_t capacity;
+};
+
+#define RENDER_VK_DEFERRED_CMDS_INITIAL_CAPACITY 16
+
+static inline RendVkDeferredCmds
+rend_vk_cmdbuf_deferred_create(void)
+{
+ RendVkDeferredCmds tracker = {0};
+
+ VkSemaphoreTypeCreateInfo type_info = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO,
+ .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE,
+ .initialValue = 0,
+ };
+ VkSemaphoreCreateInfo sem_info = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ .pNext = &type_info,
+ };
+ vkCreateSemaphore(vk_device.logical_device, &sem_info, vk_allocator, &tracker.timeline);
+
+ tracker.capacity = RENDER_VK_DEFERRED_CMDS_INITIAL_CAPACITY;
+ tracker.darray = rmalloc(sizeof(RendVkPendingCmd) * tracker.capacity);
+ tracker.count = 0;
+
+ return tracker;
+}
+
+static inline void
+rend_vk_cmdbuf_deferred_destroy(RendVkDeferredCmds *tracker)
+{
+ vkDestroySemaphore(vk_device.logical_device, tracker->timeline, vk_allocator);
+ rfree(tracker->darray);
+ *tracker = (RendVkDeferredCmds) {0};
+}
+
+static inline void rend_vk_cmdbuf_deferred_lock(RendVkDeferredCmds *t) { (void)t; }
+static inline void rend_vk_cmdbuf_deferred_unlock(RendVkDeferredCmds *t) { (void)t; }
+
+static inline VkCommandBuffer
+rend_vk_cmdbuf_deferred_begin(VkCommandPool pool)
+{
+ VkCommandBufferAllocateInfo alloc_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandPool = pool,
+ .commandBufferCount = 1,
+ };
+
+ VkCommandBuffer cmd;
+ vkAllocateCommandBuffers(vk_device.logical_device, &alloc_info, &cmd);
+
+ VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ };
+
+ vkBeginCommandBuffer(cmd, &begin);
+ return cmd;
+}
+
+static inline void
+rend_vk_cmdbuf_deferred_push(RendVkDeferredCmds *tracker, VkCommandPool pool, VkCommandBuffer cmd, uint64_t wait_value)
+{
+ rend_vk_cmdbuf_deferred_lock(tracker);
+ if (tracker->count == tracker->capacity) {
+ tracker->capacity *= 2;
+ tracker->darray = realloc(tracker->darray, sizeof(RendVkPendingCmd) * tracker->capacity);
+ }
+ tracker->darray[tracker->count++] = (RendVkPendingCmd) {
+ .pool = pool,
+ .cmd = cmd,
+ .wait_value = wait_value,
+ };
+ rend_vk_cmdbuf_deferred_unlock(tracker);
+}
+
+static inline uint64_t
+rend_vk_cmdbuf_deferred_submit(RendVkDeferredCmds *tracker, VkCommandPool pool, VkCommandBuffer cmd, VkQueue queue, uint64_t wait_value, VkPipelineStageFlags2 wait_stage, VkPipelineStageFlags2 signal_stage)
+{
+ uint64_t signal_value = wait_value + 1;
+
+ VkCommandBufferSubmitInfo cmd_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
+ .commandBuffer = cmd,
+ };
+ VkSemaphoreSubmitInfo wait_info = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
+ .semaphore = tracker->timeline,
+ .value = wait_value,
+ .stageMask = wait_stage,
+ };
+ VkSemaphoreSubmitInfo signal_info = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
+ .semaphore = tracker->timeline,
+ .value = signal_value,
+ .stageMask = signal_stage,
+ };
+ VkSubmitInfo2 submit = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
+ .waitSemaphoreInfoCount = wait_value ? 1 : 0,
+ .pWaitSemaphoreInfos = &wait_info,
+ .commandBufferInfoCount = 1,
+ .pCommandBufferInfos = &cmd_info,
+ .signalSemaphoreInfoCount = 1,
+ .pSignalSemaphoreInfos = &signal_info,
+ };
+
+ vkQueueSubmit2(queue, 1, &submit, VK_NULL_HANDLE);
+
+ rend_vk_cmdbuf_deferred_push(tracker, pool, cmd, signal_value);
+ return signal_value;
+}
+
+static inline void
+rend_vk_cmdbuf_deferred_flush(RendVkDeferredCmds *tracker)
+{
+ uint64_t completed;
+ vkGetSemaphoreCounterValue(vk_device.logical_device, tracker->timeline, &completed);
+
+ for (size_t i = 0; i < tracker->count; ) {
+ if (completed >= tracker->darray[i].wait_value) {
+ vkFreeCommandBuffers(vk_device.logical_device, tracker->darray[i].pool, 1, &tracker->darray[i].cmd);
+ tracker->darray[i] = tracker->darray[--tracker->count];
+ } else {
+ i++;
+ }
+ }
+}
diff --git a/unused code/rend_vk_memory.c b/unused code/rend_vk_memory.c
new file mode 100644
index 0000000..4782b46
--- /dev/null
+++ b/unused code/rend_vk_memory.c
@@ -0,0 +1,100 @@
+#pragma once
+
+#include "rend_internal.h"
+#include "rend_vk_internal.h"
+#include <vulkan/vulkan_core.h>
+
+
+#if 0
+static RendVkMemory
+rend_vk_memory_malloc(size_t size, VkDevice logical_device, VkPhysicalDevice physical_device, uint32_t heap_index, VkAllocationCallbacks *allocator)
+{
+ RendVkMemory memory = {
+ .host_mapped_memory = NULL,
+ .device_memory = 0,
+ .logical_device = logical_device,
+ .heap_index = heap_index
+ };
+
+ VkMemoryAllocateFlagsInfo flags_info = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO,
+ .pNext = NULL,
+ .flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT, /* REQUIRED for BDA buffers */
+ .deviceMask = 0
+ };
+
+ VkMemoryAllocateInfo alloc_info = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = size,
+ .pNext = &flags_info,
+ .memoryTypeIndex = heap_index
+ };
+
+ vkAllocateMemory(logical_device, &alloc_info, allocator, &memory.device_memory);
+ return memory;
+}
+
+static void
+rend_vk_memory_free(RendVkMemory *memory)
+{
+ if (memory->host_mapped_memory) {
+ rend_vk_memory_unmap(memory);
+ }
+ if (memory->offset == 0) {
+ vkFreeMemory(memory->logical_device, memory->device_memory, memory->allocator);
+ memset(memory, 0, sizeof *memory);
+ } else {
+ PWARN("[REND_VK] Attempted to free memory with an offset!");
+ }
+}
+
+static void*
+rend_vk_memory_map(RendVkMemory *memory)
+{
+ // SPEC: memory must have been created with a memory type that reports VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+ // vkMapMemory will fail if the implementation is unable to allocate an appropriately sized contiguous virtual address range,
+ // e.g. due to virtual address space fragmentation or platform limits.
+ // In such cases, vkMapMemory must return VK_ERROR_MEMORY_MAP_FAILED.
+ // The application can improve the likelihood of success by reducing the size of the mapped range and/or removing unneeded mappings using vkUnmapMemory.
+
+ vkMapMemory(memory->logical_device, memory->device_memory, memory->offset, memory->size, 0, &memory->host_mapped_memory);
+ return memory->host_mapped_memory;
+}
+
+static void
+rend_vk_memory_unmap(RendVkMemory *memory)
+{
+ if (memory->offset == 0) {
+ vkUnmapMemory(memory->logical_device, memory->device_memory);
+ memory->host_mapped_memory = 0;
+ } else {
+ PWARN("[REND_VK] Attempted to unmap memory with an offset!");
+ }
+}
+
+static void
+rend_vk_memory_copy(RendVkMemory *memory, size_t offset, const void *data, size_t size)
+{
+ /* bounds check */
+ if (offset + size > memory->size) {
+ REND__WARN("Memory write out of bounds!");
+ return;
+ }
+
+ /* important to cast this */
+ uint8_t *dest = memory->host_mapped_memory;
+ memcpy(dest + offset, data, size);
+
+ /* flush host writes if memory is non-coherent */
+ // VkMappedMemoryRange range = {
+ // .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+ // .memory = memory->device_memory,
+ // .offset = offset,
+ // .size = size
+ // };
+ //
+ // vkFlushMappedMemoryRanges(memory->logical_device, 1, &range);
+
+}
+
+#endif
diff --git a/unused code/rend_vk_pool.c b/unused code/rend_vk_pool.c
new file mode 100644
index 0000000..e527354
--- /dev/null
+++ b/unused code/rend_vk_pool.c
@@ -0,0 +1,221 @@
+#pragma once
+#include "rend_internal.h"
+#include "rend_vk_internal.h"
+#include <vulkan/vulkan_core.h>
+
+/* NOTE(vasco):
+ * Check out https://kylehalladay.com/blog/tutorial/2017/12/13/Custom-Allocators-Vulkan.html
+ * for a basic grasp on what it entails to actually write a memory allocator for vulkan.
+ *
+ * This allocator isnt anything particularly amazing but it works so I'm keeping this code.
+ * Based on the framework presented here we could implement other more efficient allocators.
+ * Which is what I did for the arena allocator.
+ */
+
+#if 0
+typedef struct RendVkAddress {
+ VkDeviceMemory handle;
+ uint32_t type;
+ uint32_t id;
+ VkDeviceSize size;
+ VkDeviceSize offset;
+} RendVkAddress;
+
+typedef struct RendVkPoolLayout {
+ uint64_t offset, size;
+} RendVkPoolLayout;
+
+typedef struct RendVKPoolBlock {
+ RendVkAddress address;
+ RendVkPoolLayout *layout_darr;
+ uint8_t reserved;
+} RendVKPoolBlock;
+
+typedef struct RendVkPoolMemory {
+ RendVKPoolBlock *block_darr;
+} RendVkPoolMemory;
+
+struct RendVkPoolAllocator {
+ VkAllocationCallbacks *allocator;
+ size_t *mem_type_alloc_sizes; // allocation size per type of memory available on the gpu
+ RendVkPoolMemory *mem_pools; // memory pools per type of memory available on the gpu
+ VkDevice logical_device; // virtual device
+ VkPhysicalDevice physical_device; // physical device we are allocating memory from
+ VkDeviceSize page_size; // allocations must respect the physical limitations of the gpu
+ VkDeviceSize block_min_size; // minimum size per block of memory
+ uint64_t total_allocations;
+ uint32_t memory_type_count;
+};
+
+static RendVkPoolAllocator
+rend_vk_pool_create(VkDevice logical_device, VkPhysicalDevice physical_device, VkPhysicalDeviceLimits device_limits, VkAllocationCallbacks *allocator)
+{
+ RendVkPoolAllocator pool = {
+ .allocator = allocator,
+ .mem_type_alloc_sizes = NULL,
+ .mem_pools = NULL,
+ .logical_device = logical_device,
+ .physical_device = physical_device,
+ .page_size = 0,
+ .block_min_size = 0,
+ .total_allocations = 0,
+ .memory_type_count = 0,
+ };
+
+ VkPhysicalDeviceMemoryProperties mem_properties;
+ vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_properties);
+
+ pool.mem_type_alloc_sizes = malloc(mem_properties.memoryTypeCount * sizeof *pool.mem_type_alloc_sizes);
+ memset(pool.mem_type_alloc_sizes, 0, mem_properties.memoryTypeCount * sizeof *pool.mem_type_alloc_sizes);
+
+ pool.mem_pools = malloc(mem_properties.memoryTypeCount * sizeof *pool.mem_pools);
+ memset(pool.mem_pools, 0, mem_properties.memoryTypeCount * sizeof *pool.mem_pools);
+
+ pool.memory_type_count = mem_properties.memoryTypeCount;
+ pool.page_size = device_limits.bufferImageGranularity;
+ pool.block_min_size = pool.page_size * 10;
+
+ return pool;
+}
+
+static uint32_t
+rend_vk_pool_add_block(RendVkPoolAllocator *pool, VkDeviceSize size, uint32_t memory_type, VkMemoryPropertyFlags properties, bool fit_to_alloc)
+{
+ VkDeviceSize new_pool_size = size * 2;
+ new_pool_size = (new_pool_size < pool->block_min_size) ? pool->block_min_size : new_pool_size;
+
+ VkMemoryAllocateInfo alloc_info = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = new_pool_size,
+ .memoryTypeIndex = rend_vk_memory_find_index(pool->physical_device, memory_type, properties)
+ };
+
+ RendVKPoolBlock block = {0};
+ VkResult res = vkAllocateMemory(pool->logical_device, &alloc_info, pool->allocator, &block.address.handle);
+ block.address.type = memory_type;
+ block.address.size = new_pool_size;
+
+ RendVkPoolMemory *mem_pool = &pool->mem_pools[memory_type];
+ p_darray_push(mem_pool->block_darr, block);
+
+ RendVkPoolLayout layout = {
+ .offset = 0,
+ .size = new_pool_size
+ };
+ p_darray_push(mem_pool->block_darr[pool_size - 1].layout_darr, layout);
+
+ pool->total_allocations++;
+
+ size_t pool_size = p_darray_len(mem_pool->block_darr);
+ return pool_size - 1;
+}
+
+static RendVkMemory
+rend_vk_pool_alloc(RendVkPoolAllocator *pool, VkDeviceSize size, uint32_t usage, uint32_t memory_type, VkMemoryPropertyFlags properties)
+{
+ RendVkPoolMemory *mem_pool = &pool->mem_pools[memory_type];
+
+ VkDeviceSize requested_alloc_size = ((size / pool->page_size) + 1) * pool->page_size;
+ pool->mem_type_alloc_sizes[memory_type] += requested_alloc_size;
+
+ /* find free chunk for allocation
+ * TODO: free list?
+ */
+ int whole_page = usage != VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+ uint64_t block_idx = UINT64_MAX;
+ uint64_t span_idx = UINT64_MAX;
+ for (uint32_t i = 0; i < p_darray_len(mem_pool->block_darr); ++i) {
+ for (uint32_t j = 0; j < p_darray_len(mem_pool->block_darr[i].layout_darr); ++j) {
+ int valid = (whole_page) ? mem_pool->block_darr[i].layout_darr[j].offset == 0 : 1;
+ if (mem_pool->block_darr[i].layout_darr[j].size >= size && valid) {
+ block_idx = i;
+ span_idx = j;
+ }
+ }
+ }
+
+ if (block_idx == UINT64_MAX || span_idx == UINT64_MAX) {
+ block_idx = rend_vk_pool_add_block(pool, size, memory_type, properites, whole_page);
+ span_idx = 0;
+ }
+
+ mem_pool->block_darr[block_idx].reserved = whole_page;
+
+ RendVkMemory memory = {
+ .device_memory = mem_pool->block_darr[block_idx].address.handle,
+ .size = size,
+ .offset = mem_pool->block_darr[block_idx].layout_darr[span_idx].offset,
+ .type = memory_type,
+
+ .logical_device = pool->logical_device,
+ .allocator = pool->allocator,
+ .host_mapped_memory = 0,
+ .physical_device = pool->physical_device,
+ .properties = properties,
+
+ .id = block_idx,
+ };
+
+ /* mark chunck */
+ mem_pool->block_darr[block_idx].layout_darr[span_idx].offset += size;
+ mem_pool->block_darr[block_idx].layout_darr[span_idx].size -= size;
+ return memory;
+}
+
+
+static void
+rend_vk_pool_free(RendVkPoolAllocator *pool, RendVkMemory *memory)
+{
+ VkDeviceSize requested = ((memory->size / pool->page_size) + 1) * pool->page_size;
+
+ RendVkPoolMemory *mem_pool = &pool->mem_pools[memory->type];
+
+ pool->blocks_[allocation.id].pageReserved = false;
+
+ mem_pool->block_darr[block_idx].layout_darr[span_idx].offset += size;
+ mem_pool->block_darr[block_idx].layout_darr[span_idx].size -= size;
+
+ OffsetSize span = {allocation.offset, requestedAllocSize };
+ bool found = false;
+
+ uint32_t numLayoutMems = pool.blocks[allocation.id].layout.size();
+ for (uint32_t j = 0; j < numLayoutMems; ++j)
+ {
+ if (pool.blocks[allocation.id].layout[j].offset == requestedAllocSize +allocation.offset)
+ {
+ pool.blocks[allocation.id].layout[j].offset = allocation.offset;
+ pool.blocks[allocation.id].layout[j].size += requestedAllocSize;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ state.memPools[allocation.type].blocks[allocation.id].layout.push_back(span);
+ state.memTypeAllocSizes[allocation.type] -= requestedAllocSize;
+ }
+}
+
+static void
+rend_vk_pool_destroy(RendVkPoolAllocator *pool)
+{
+ if (pool->mem_pools) {
+ for (size_t u = 0; u < pool->memory_type_count; ++u) {
+ RendVkPoolMemory mem_pool = pool->mem_pools[u];
+ for (size_t b = 0; b < p_darray_len(mem_pool.block_darr); ++b) {
+ RendVKPoolBlock block = mem_pool.block_darr[b];
+ p_darray_destroy(block.layout_darr);
+ }
+ p_darray_destroy(mem_pool.block_darr);
+ }
+ free(pool->mem_pools);
+ pool->mem_pools = 0;
+ }
+ if (pool->mem_type_alloc_sizes) {
+ free(pool->mem_type_alloc_sizes);
+ pool->mem_type_alloc_sizes = 0;
+ }
+ memset(pool, 0, sizeof *pool);
+}
+#endif
diff --git a/unused code/rend_vk_sbta.c b/unused code/rend_vk_sbta.c
new file mode 100644
index 0000000..7bc51c4
--- /dev/null
+++ b/unused code/rend_vk_sbta.c
@@ -0,0 +1,331 @@
+#pragma once
+#include "rend_internal.h"
+#include "rend_vk_internal.h"
+#include <vulkan/vulkan_core.h>
+
+/*
+ * Sparse Bindless Texture Array (SBTA)
+ *
+ */
+
+struct RendVkSbta {
+ VkDevice logical_device;
+ RendVkImage image; /* single 2D array image, arrayLayers = max_layers */
+ VkImageView *views; /* per-layer VkImageView array */
+ VkFormat format;
+ VkExtent2D extent;
+ uint32_t max_layers;
+ uint32_t mip_levels;
+ uint64_t *bitmap; /* 1 bit per layer slot */
+ uint32_t bitmap_word_count;
+ uint32_t allocated_count;
+ RendMemory memory;
+};
+
+static inline uint32_t
+rend_vk_sbta_mip_count(uint32_t w, uint32_t h)
+{
+ uint32_t v = (w > h) ? w : h;
+ uint32_t levels = 1;
+ while (v >>= 1) { levels++; }
+ return levels;
+}
+
+static void
+rend_vk_sbta_create(RendVkSbta *sbta, VkDevice logical_device, VkExtent2D extent, uint32_t layers)
+{
+ memset(sbta, 0, sizeof *sbta);
+ sbta->logical_device = logical_device;
+ sbta->format = VK_FORMAT_R8G8B8A8_SRGB;
+ sbta->extent = extent;
+ sbta->max_layers = layers;
+ sbta->mip_levels = rend_vk_sbta_mip_count(extent.width, extent.height);
+
+ /* create image */
+ sbta->image = rend_vk_image_create(
+ logical_device,
+ VK_IMAGE_TYPE_2D,
+ extent.width, extent.height,
+ sbta->format,
+ VK_IMAGE_TILING_OPTIMAL,
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+ 1, /* depth */
+ sbta->mip_levels,
+ layers,
+ VK_SAMPLE_COUNT_1_BIT,
+ VK_SHARING_MODE_EXCLUSIVE
+ );
+
+ /* allocate per-layer views array */
+ sbta->views = rmalloc(layers * sizeof *sbta->views);
+ memset(sbta->views, 0, layers * sizeof *sbta->views);
+
+ /* bitmap: ceil(layers / 64) words */
+ sbta->bitmap_word_count = (layers + 63) / 64;
+ sbta->bitmap = rmalloc(sbta->bitmap_word_count * sizeof *sbta->bitmap);
+ memset(sbta->bitmap, 0, sbta->bitmap_word_count * sizeof *sbta->bitmap);
+ sbta->allocated_count = 0;
+}
+
+static void
+rend_vk_sbta_create_views(RendVkSbta *sbta)
+{
+ for (uint32_t i = 0; i < sbta->max_layers; ++i) {
+ VkImageViewCreateInfo view_info = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = sbta->image.handle,
+ .format = sbta->format,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .subresourceRange.baseMipLevel = 0,
+ .subresourceRange.levelCount = sbta->mip_levels,
+ .subresourceRange.baseArrayLayer = i,
+ .subresourceRange.layerCount = 1,
+ };
+ vkCreateImageView(sbta->logical_device, &view_info, vk_allocator, &sbta->views[i]);
+ }
+}
+
+static void
+rend_vk_sbta_destroy(RendVkSbta *sbta)
+{
+ for (uint32_t i = 0; i < sbta->max_layers; ++i) {
+ if (sbta->views[i]) {
+ vkDestroyImageView(sbta->logical_device, sbta->views[i], vk_allocator);
+ }
+ }
+
+ rend_vk_image_destroy(&sbta->image);
+
+ rfree(sbta->views);
+ rfree(sbta->bitmap);
+ memset(sbta, 0, sizeof *sbta);
+}
+
+static uint64_t
+rend_vk_sbta_alloc(RendVkSbta *sbta)
+{
+ for (uint32_t w = 0; w < sbta->bitmap_word_count; ++w) {
+ if (sbta->bitmap[w] == ~(uint64_t)0) continue; /* word full */
+
+ /* find first zero bit */
+ uint64_t word = sbta->bitmap[w];
+ uint64_t bit = ~word & (word + 1); /* isolate lowest zero bit */
+ uint32_t bit_index = 0;
+ uint64_t tmp = bit;
+ while (tmp >>= 1) { bit_index++; }
+
+ uint64_t slot = (uint64_t)w * 64 + bit_index;
+ if (slot >= sbta->max_layers) return UINT64_MAX; /* past capacity */
+
+ sbta->bitmap[w] |= bit;
+ sbta->allocated_count++;
+ return slot;
+ }
+ return UINT64_MAX; /* full */
+}
+
+static void
+rend_vk_sbta_free(RendVkSbta *sbta, uint64_t idx)
+{
+ assert(idx < sbta->max_layers && "SBTA free: index out of range");
+
+ uint32_t word = (uint32_t)(idx / 64);
+ uint32_t bit = (uint32_t)(idx % 64);
+ assert((sbta->bitmap[word] & (1ULL << bit)) && "SBTA free: slot not allocated (double free?)");
+
+ sbta->bitmap[word] &= ~(1ULL << bit);
+ sbta->allocated_count--;
+}
+
+static void
+rend_vk_sbta_upload(RendVkSbta *sbta, VkCommandBuffer cmd, RendVkArenaAllocator *staging_arena, uint64_t slot, void *pixels, uint64_t size)
+{
+ assert(slot < sbta->max_layers);
+ assert(pixels && size > 0);
+
+ /* ---- staging buffer ---- */
+ VkBufferCreateInfo buf_info = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = size,
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+ };
+
+ VkBuffer staging_buf;
+ vkCreateBuffer(sbta->logical_device, &buf_info, vk_allocator, &staging_buf);
+
+ VkMemoryRequirements staging_reqs;
+ vkGetBufferMemoryRequirements(sbta->logical_device, staging_buf, &staging_reqs);
+
+ uint32_t host_index = vk_device.host_index;
+ RendMemory staging_mem = rend_vk_arena_alloc(staging_arena, staging_reqs.size, host_index);
+ vkBindBufferMemory(sbta->logical_device, staging_buf, (VkDeviceMemory) staging_mem.device_memory, staging_mem.offset);
+
+ /* copy pixels into staging */
+ memcpy(staging_mem.host_mapped_memory, pixels, size);
+
+ /* ---- transition layer mip 0: UNDEFINED -> TRANSFER_DST ---- */
+ VkImageMemoryBarrier2 barrier_to_dst = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_NONE,
+ .srcAccessMask = VK_ACCESS_2_NONE,
+ .dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
+ .dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .image = sbta->image.handle,
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = sbta->mip_levels,
+ .baseArrayLayer = (uint32_t)slot,
+ .layerCount = 1,
+ },
+ };
+
+ VkDependencyInfo dep_to_dst = {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .imageMemoryBarrierCount = 1,
+ .pImageMemoryBarriers = &barrier_to_dst,
+ };
+ vkCmdPipelineBarrier2(cmd, &dep_to_dst);
+
+ /* ---- copy staging -> image mip 0 ---- */
+ VkBufferImageCopy copy_region = {
+ .bufferOffset = 0,
+ .imageSubresource = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = (uint32_t)slot,
+ .layerCount = 1,
+ },
+ .imageExtent = { sbta->extent.width, sbta->extent.height, 1 },
+ };
+
+ vkCmdCopyBufferToImage(cmd, staging_buf, sbta->image.handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy_region);
+
+ /* ---- generate mipmaps via blit chain ---- */
+ int32_t mip_w = (int32_t)sbta->extent.width;
+ int32_t mip_h = (int32_t)sbta->extent.height;
+
+ for (uint32_t mip = 1; mip < sbta->mip_levels; ++mip) {
+ /* transition previous mip: TRANSFER_DST -> TRANSFER_SRC */
+ VkImageMemoryBarrier2 barrier_src = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
+ .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
+ .dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
+ .dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ .image = sbta->image.handle,
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = mip - 1,
+ .levelCount = 1,
+ .baseArrayLayer = (uint32_t)slot,
+ .layerCount = 1,
+ },
+ };
+
+ VkDependencyInfo dep_src = {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .imageMemoryBarrierCount = 1,
+ .pImageMemoryBarriers = &barrier_src,
+ };
+ vkCmdPipelineBarrier2(cmd, &dep_src);
+
+ /* blit from mip-1 to mip */
+ int32_t next_w = (mip_w > 1) ? mip_w / 2 : 1;
+ int32_t next_h = (mip_h > 1) ? mip_h / 2 : 1;
+
+ VkImageBlit2 blit = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2,
+ .srcSubresource = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = mip - 1,
+ .baseArrayLayer = (uint32_t)slot,
+ .layerCount = 1,
+ },
+ .srcOffsets = { {0, 0, 0}, {mip_w, mip_h, 1} },
+ .dstSubresource = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = mip,
+ .baseArrayLayer = (uint32_t)slot,
+ .layerCount = 1,
+ },
+ .dstOffsets = { {0, 0, 0}, {next_w, next_h, 1} },
+ };
+
+ VkBlitImageInfo2 blit_info = {
+ .sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2,
+ .srcImage = sbta->image.handle,
+ .srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ .dstImage = sbta->image.handle,
+ .dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .regionCount = 1,
+ .pRegions = &blit,
+ .filter = VK_FILTER_LINEAR,
+ };
+ vkCmdBlitImage2(cmd, &blit_info);
+
+ mip_w = next_w;
+ mip_h = next_h;
+ }
+
+ /* ---- final transition: all mips -> SHADER_READ_ONLY ---- */
+ /* last mip is still TRANSFER_DST, all others are TRANSFER_SRC */
+
+ /* transition last mip: TRANSFER_DST -> SHADER_READ_ONLY */
+ VkImageMemoryBarrier2 barriers_final[2] = {
+ /* mips 0..N-2: TRANSFER_SRC -> SHADER_READ_ONLY */
+ {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
+ .srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT,
+ .dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
+ .dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+ .image = sbta->image.handle,
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = (sbta->mip_levels > 1) ? sbta->mip_levels - 1 : 1,
+ .baseArrayLayer = (uint32_t)slot,
+ .layerCount = 1,
+ },
+ },
+ /* last mip: TRANSFER_DST -> SHADER_READ_ONLY */
+ {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
+ .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
+ .dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
+ .dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+ .image = sbta->image.handle,
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = sbta->mip_levels - 1,
+ .levelCount = 1,
+ .baseArrayLayer = (uint32_t)slot,
+ .layerCount = 1,
+ },
+ },
+ };
+
+ uint32_t barrier_count = (sbta->mip_levels > 1) ? 2 : 1;
+
+ /* if only 1 mip, use second barrier (TRANSFER_DST path) */
+ VkImageMemoryBarrier2 *barrier_ptr = (sbta->mip_levels > 1) ? barriers_final : &barriers_final[1];
+
+ VkDependencyInfo dep_final = {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .imageMemoryBarrierCount = barrier_count,
+ .pImageMemoryBarriers = barrier_ptr,
+ };
+ vkCmdPipelineBarrier2(cmd, &dep_final);
+}