diff options
| author | Vasco <[email protected]> | 2026-08-12 17:47:50 +0100 |
|---|---|---|
| committer | Vasco <[email protected]> | 2026-08-12 17:47:50 +0100 |
| commit | 95e3e76fd9c789cbf2d4409ef49456addc71643f (patch) | |
| tree | 34161614f5f0ae909f58eb5e58a2ffd26c2f0e9f | |
| -rw-r--r-- | nogl.h | 141 | ||||
| -rw-r--r-- | rend.c | 604 | ||||
| -rw-r--r-- | rend.h | 290 | ||||
| -rw-r--r-- | rend_internal.h | 199 | ||||
| -rw-r--r-- | rend_vk.c | 2131 | ||||
| -rw-r--r-- | rend_vk_allocator.c | 118 | ||||
| -rw-r--r-- | rend_vk_arena.c | 264 | ||||
| -rw-r--r-- | rend_vk_device.c | 395 | ||||
| -rw-r--r-- | rend_vk_image.c | 125 | ||||
| -rw-r--r-- | rend_vk_internal.h | 177 | ||||
| -rw-r--r-- | unused code/rend_vk_buffer.c | 116 | ||||
| -rw-r--r-- | unused code/rend_vk_command_queue.c | 153 | ||||
| -rw-r--r-- | unused code/rend_vk_memory.c | 100 | ||||
| -rw-r--r-- | unused code/rend_vk_pool.c | 221 | ||||
| -rw-r--r-- | unused code/rend_vk_sbta.c | 331 |
15 files changed, 5365 insertions, 0 deletions
@@ -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); @@ -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); +} @@ -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, ®ion); + 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, ©_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); +} |
