summaryrefslogtreecommitdiff
path: root/src/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'src/renderer')
-rw-r--r--src/renderer/backends/backend_opengl.c23
-rw-r--r--src/renderer/backends/backend_vulkan.c1980
-rw-r--r--src/renderer/backends/vulkan/vulkan_glossary.md18
-rw-r--r--src/renderer/backends/vulkan_helpers.h166
-rw-r--r--src/renderer/cleanroom/README.md1
-rw-r--r--src/renderer/cleanroom/types.h200
-rw-r--r--src/renderer/render.c39
-rw-r--r--src/renderer/render.h27
-rw-r--r--src/renderer/render_backend.h19
-rw-r--r--src/renderer/render_types.h10
10 files changed, 2439 insertions, 44 deletions
diff --git a/src/renderer/backends/backend_opengl.c b/src/renderer/backends/backend_opengl.c
index ea6cb00..e3a4fb6 100644
--- a/src/renderer/backends/backend_opengl.c
+++ b/src/renderer/backends/backend_opengl.c
@@ -60,6 +60,29 @@ void clear_screen(vec3 colour) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
+void texture_data_upload(texture *tex) {
+ printf("Texture name %s\n", tex->name);
+ TRACE("Upload texture data");
+ u32 texture_id;
+ glGenTextures(1, &texture_id);
+ glBindTexture(GL_TEXTURE_2D, texture_id);
+ tex->texture_id = texture_id;
+
+ // set the texture wrapping parameters
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
+ GL_REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method)
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ // set texture filtering parameters
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tex->width, tex->height, 0, tex->channel_type,
+ GL_UNSIGNED_BYTE, tex->image_data);
+ glGenerateMipmap(GL_TEXTURE_2D);
+ DEBUG("Freeing texture image data after uploading to GPU");
+ // stbi_image_free(tex->image_data); // data is on gpu now so we dont need it around
+}
+
void bind_texture(shader s, texture *tex, u32 slot) {
// printf("bind texture slot %d with texture id %d \n", slot, tex->texture_id);
glActiveTexture(GL_TEXTURE0 + slot);
diff --git a/src/renderer/backends/backend_vulkan.c b/src/renderer/backends/backend_vulkan.c
index 6347e27..e0b0e4e 100644
--- a/src/renderer/backends/backend_vulkan.c
+++ b/src/renderer/backends/backend_vulkan.c
@@ -1 +1,1979 @@
-// #FUTURE \ No newline at end of file
+#include "camera.h"
+#include "primitives.h"
+#define CDEBUG
+// #define CEL_PLATFORM_LINUX
+#if CEL_REND_BACKEND_VULKAN
+// ^ Temporary
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <vulkan/vk_platform.h>
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_core.h>
+#include "colours.h"
+#include "str.h"
+
+#include "darray.h"
+#include "defines.h"
+#include "file.h"
+#include "log.h"
+#include "maths.h"
+#include "maths_types.h"
+#include "render_backend.h"
+#include "render_types.h"
+#include "vulkan_helpers.h"
+
+#include <stdlib.h>
+
+#define SCR_WIDTH 1000
+#define SCR_HEIGHT 1000
+
+#include <glad/glad.h>
+
+#include <glfw3.h>
+
+KITC_DECL_TYPED_ARRAY(VkLayerProperties)
+
+typedef struct vulkan_device {
+ VkPhysicalDevice physical_device;
+ VkDevice logical_device;
+ vulkan_swapchain_support_info swapchain_support;
+ i32 graphics_queue_index;
+ i32 present_queue_index;
+ i32 compute_queue_index;
+ i32 transfer_queue_index;
+ VkQueue graphics_queue;
+ VkQueue present_queue;
+ VkQueue compute_queue;
+ VkQueue transfer_queue;
+ VkCommandPool gfx_command_pool;
+ VkPhysicalDeviceProperties properties;
+ VkPhysicalDeviceFeatures features;
+ VkPhysicalDeviceMemoryProperties memory;
+ VkFormat depth_format;
+} vulkan_device;
+
+typedef struct vulkan_image {
+ VkImage handle;
+ VkDeviceMemory memory;
+ VkImageView view;
+ u32 width;
+ u32 height;
+} vulkan_image;
+
+typedef struct vulkan_texture_data {
+ vulkan_image image;
+ VkSampler sampler;
+} vulkan_texture_data;
+
+typedef enum vulkan_renderpass_state {
+ READY,
+ RECORDING,
+ IN_RENDER_PASS,
+ RECORDING_ENDING,
+ SUBMITTED,
+ NOT_ALLOCATED
+} vulkan_renderpass_state;
+
+typedef struct vulkan_renderpass {
+ VkRenderPass handle;
+ vec4 render_area;
+ vec4 clear_colour;
+ f32 depth;
+ u32 stencil;
+ vulkan_renderpass_state state;
+} vulkan_renderpass;
+
+typedef struct vulkan_framebuffer {
+ VkFramebuffer handle;
+ u32 attachment_count;
+ VkImageView* attachments;
+ vulkan_renderpass* renderpass;
+} vulkan_framebuffer;
+
+KITC_DECL_TYPED_ARRAY(vulkan_framebuffer)
+
+typedef struct vulkan_swapchain {
+ VkSurfaceFormatKHR image_format;
+ u8 max_frames_in_flight;
+ VkSwapchainKHR handle;
+ u32 image_count;
+ VkImage* images;
+ VkImageView* views;
+ vulkan_image depth_attachment;
+ vulkan_framebuffer_darray* framebuffers;
+} vulkan_swapchain;
+
+// overengineered
+typedef enum vulkan_command_buffer_state {
+ COMMAND_BUFFER_STATE_READY,
+ COMMAND_BUFFER_STATE_IN_RENDER_PASS,
+ COMMAND_BUFFER_STATE_RECORDING,
+ COMMAND_BUFFER_STATE_RECORDING_ENDED,
+ COMMAND_BUFFER_STATE_SUBMITTED,
+ COMMAND_BUFFER_STATE_NOT_ALLOCATED,
+} vulkan_command_buffer_state;
+
+typedef struct vulkan_command_buffer {
+ VkCommandBuffer handle;
+ vulkan_command_buffer_state state;
+} vulkan_command_buffer;
+
+KITC_DECL_TYPED_ARRAY(vulkan_command_buffer)
+
+typedef struct vulkan_fence {
+ VkFence handle;
+ bool is_signaled;
+} vulkan_fence;
+
+typedef struct vulkan_shader_stage {
+ VkShaderModuleCreateInfo create_info;
+ VkShaderModule handle;
+ VkPipelineShaderStageCreateInfo stage_create_info;
+} vulkan_shader_stage;
+
+typedef struct vulkan_pipeline {
+ VkPipeline handle;
+ VkPipelineLayout layout;
+} vulkan_pipeline;
+
+typedef struct global_object_uniform {
+ mat4 projection; // 64 bytes
+ mat4 view; // 64 bytes
+ f32 padding[32];
+} global_object_uniform;
+
+typedef struct object_uniform {
+ vec4 diffuse_colour;
+ vec4 v_reserved0;
+ vec4 v_reserved1;
+ vec4 v_reserved2;
+} object_uniform;
+
+#define MAX_OBJECT_COUNT 1024
+#define VULKAN_OBJECT_SHADER_DESCRIPTOR_COUNT 1
+
+typedef struct geometry_render_data {
+ u32 id;
+ mat4 model;
+ texture* textures[16];
+} geometry_render_data;
+
+typedef struct vulkan_buffer {
+ u64 total_size;
+ VkBuffer handle;
+ VkBufferUsageFlagBits usage;
+ bool is_locked;
+ VkDeviceMemory memory;
+ i32 memory_index;
+ u32 memory_property_flags;
+} vulkan_buffer;
+
+#define SHADER_STAGE_COUNT 2
+
+typedef struct vulkan_shader {
+ // vertex, fragment
+ vulkan_shader_stage stages[SHADER_STAGE_COUNT];
+ vulkan_pipeline pipeline;
+
+ // descriptors
+ VkDescriptorPool descriptor_pool;
+ VkDescriptorSetLayout descriptor_set_layout;
+ VkDescriptorSet descriptor_sets[3]; // one for each in-flight frame
+
+ vulkan_buffer global_uniforms_buffer;
+
+ // Data that's global for all objects drawn
+ global_object_uniform global_ubo;
+ object_uniform object_ubo;
+ vulkan_texture_data* texture_data;
+} vulkan_shader;
+
+typedef struct vulkan_context {
+ VkInstance instance;
+ VkAllocationCallbacks* allocator;
+ VkSurfaceKHR surface;
+ vulkan_device device;
+ u32 framebuffer_width;
+ u32 framebuffer_height;
+ vulkan_swapchain swapchain;
+ vulkan_renderpass main_renderpass;
+ vulkan_buffer object_vertex_buffer;
+ vulkan_buffer object_index_buffer;
+ u64 geometry_vertex_offset;
+ u64 geometry_index_offset;
+
+ vulkan_command_buffer_darray* gfx_command_buffers;
+
+ VkSemaphore* image_available_semaphores;
+ VkSemaphore* queue_complete_semaphores;
+ u32 in_flight_fence_count;
+ vulkan_fence* in_flight_fences;
+ vulkan_fence** images_in_flight;
+
+ u32 image_index;
+ u32 current_frame;
+
+ vulkan_shader object_shader;
+
+ // TODO: swapchain recreation
+
+#if defined(DEBUG)
+ VkDebugUtilsMessengerEXT vk_debugger;
+#endif
+} vulkan_context;
+
+static vulkan_context context;
+
+static i32 find_memory_index(vulkan_context* context, u32 type_filter, u32 property_flags) {
+ VkPhysicalDeviceMemoryProperties memory_properties;
+ vkGetPhysicalDeviceMemoryProperties(context->device.physical_device, &memory_properties);
+
+ for (u32 i = 0; i < memory_properties.memoryTypeCount; ++i) {
+ // Check each memory type to see if its bit is set to 1.
+ if (type_filter & (1 << i) &&
+ (memory_properties.memoryTypes[i].propertyFlags & property_flags) == property_flags) {
+ return i;
+ }
+ }
+
+ WARN("Unable to find suitable memory type!");
+ return -1;
+}
+
+/** @brief Internal backend state */
+typedef struct vulkan_state {
+} vulkan_state;
+
+typedef struct vertex_pos {
+ vec3 pos;
+ vec3 normal;
+} vertex_pos;
+
+// pipeline stuff
+bool vulkan_graphics_pipeline_create(vulkan_context* context, vulkan_renderpass* renderpass,
+ u32 attribute_count,
+ VkVertexInputAttributeDescription* attributes,
+ u32 descriptor_set_layout_count,
+ VkDescriptorSetLayout* descriptor_set_layouts, u32 stage_count,
+ VkPipelineShaderStageCreateInfo* stages, VkViewport viewport,
+ VkRect2D scissor, bool is_wireframe,
+ vulkan_pipeline* out_pipeline) {
+ VkPipelineViewportStateCreateInfo viewport_state = {
+ VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO
+ };
+ viewport_state.viewportCount = 1;
+ viewport_state.pViewports = &viewport;
+ viewport_state.scissorCount = 1;
+ viewport_state.pScissors = &scissor;
+
+ // Rasterizer
+ VkPipelineRasterizationStateCreateInfo rasterizer_create_info = {
+ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO
+ };
+ rasterizer_create_info.depthClampEnable = VK_FALSE;
+ rasterizer_create_info.rasterizerDiscardEnable = VK_FALSE;
+ rasterizer_create_info.polygonMode = is_wireframe ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL;
+ rasterizer_create_info.lineWidth = 1.0f;
+ rasterizer_create_info.cullMode = VK_CULL_MODE_BACK_BIT;
+ rasterizer_create_info.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+ rasterizer_create_info.depthBiasEnable = VK_FALSE;
+ rasterizer_create_info.depthBiasConstantFactor = 0.0;
+ rasterizer_create_info.depthBiasClamp = 0.0;
+ rasterizer_create_info.depthBiasSlopeFactor = 0.0;
+
+ // Multisampling
+ VkPipelineMultisampleStateCreateInfo ms_create_info = {
+ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO
+ };
+ ms_create_info.sampleShadingEnable = VK_FALSE;
+ ms_create_info.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+ ms_create_info.minSampleShading = 1.0;
+ ms_create_info.pSampleMask = 0;
+ ms_create_info.alphaToCoverageEnable = VK_FALSE;
+ ms_create_info.alphaToOneEnable = VK_FALSE;
+
+ // Depth and stencil testing
+ VkPipelineDepthStencilStateCreateInfo depth_stencil = {
+ VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO
+ };
+ depth_stencil.depthTestEnable = VK_TRUE;
+ depth_stencil.depthWriteEnable = VK_TRUE;
+ depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS;
+ depth_stencil.depthBoundsTestEnable = VK_FALSE;
+ depth_stencil.stencilTestEnable = VK_FALSE;
+ depth_stencil.pNext = 0;
+
+ VkPipelineColorBlendAttachmentState color_blend_attachment_state;
+ color_blend_attachment_state.blendEnable = VK_TRUE;
+ color_blend_attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+ color_blend_attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ color_blend_attachment_state.colorBlendOp = VK_BLEND_OP_ADD;
+ color_blend_attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+ color_blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ color_blend_attachment_state.alphaBlendOp = VK_BLEND_OP_ADD;
+ color_blend_attachment_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
+ VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+
+ VkPipelineColorBlendStateCreateInfo color_blend = {
+ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO
+ };
+ color_blend.logicOpEnable = VK_FALSE;
+ color_blend.logicOp = VK_LOGIC_OP_COPY;
+ color_blend.attachmentCount = 1;
+ color_blend.pAttachments = &color_blend_attachment_state;
+
+ const u32 dynamic_state_count = 3;
+ VkDynamicState dynamic_states[3] = {
+ VK_DYNAMIC_STATE_VIEWPORT,
+ VK_DYNAMIC_STATE_SCISSOR,
+ VK_DYNAMIC_STATE_LINE_WIDTH,
+ };
+
+ VkPipelineDynamicStateCreateInfo dynamic_state = {
+ VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO
+ };
+ dynamic_state.dynamicStateCount = dynamic_state_count;
+ dynamic_state.pDynamicStates = dynamic_states;
+
+ // Vertex input
+ VkVertexInputBindingDescription binding_desc;
+ binding_desc.binding = 0;
+ binding_desc.stride = sizeof(vertex);
+ binding_desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+ VkPipelineVertexInputStateCreateInfo vertex_input_info = {
+ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
+ };
+ vertex_input_info.vertexBindingDescriptionCount = 1;
+ vertex_input_info.pVertexBindingDescriptions = &binding_desc;
+ vertex_input_info.vertexAttributeDescriptionCount = attribute_count;
+ vertex_input_info.pVertexAttributeDescriptions = attributes;
+
+ VkPipelineInputAssemblyStateCreateInfo input_assembly = {
+ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO
+ };
+ input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+ input_assembly.primitiveRestartEnable = VK_FALSE;
+
+ VkPipelineLayoutCreateInfo pipeline_layout_create_info = {
+ VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO
+ };
+
+ // Pushconstants
+ VkPushConstantRange push_constant;
+ push_constant.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+ push_constant.offset = sizeof(mat4) * 0;
+ push_constant.size = sizeof(mat4) * 2;
+
+ pipeline_layout_create_info.pushConstantRangeCount = 1;
+ pipeline_layout_create_info.pPushConstantRanges = &push_constant;
+
+ pipeline_layout_create_info.setLayoutCount = descriptor_set_layout_count;
+ pipeline_layout_create_info.pSetLayouts = descriptor_set_layouts;
+
+ vkCreatePipelineLayout(context->device.logical_device, &pipeline_layout_create_info,
+ context->allocator, &out_pipeline->layout);
+
+ VkGraphicsPipelineCreateInfo pipeline_create_info = {
+ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO
+ };
+ pipeline_create_info.stageCount = stage_count;
+ pipeline_create_info.pStages = stages;
+ pipeline_create_info.pVertexInputState = &vertex_input_info;
+ pipeline_create_info.pInputAssemblyState = &input_assembly;
+
+ pipeline_create_info.pViewportState = &viewport_state;
+ pipeline_create_info.pRasterizationState = &rasterizer_create_info;
+ pipeline_create_info.pMultisampleState = &ms_create_info;
+ pipeline_create_info.pDepthStencilState = &depth_stencil;
+ pipeline_create_info.pColorBlendState = &color_blend;
+ pipeline_create_info.pDynamicState = &dynamic_state;
+ pipeline_create_info.pTessellationState = 0;
+
+ pipeline_create_info.layout = out_pipeline->layout;
+
+ pipeline_create_info.renderPass = renderpass->handle;
+ pipeline_create_info.subpass = 0;
+ pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE;
+ pipeline_create_info.basePipelineIndex = -1;
+
+ VkResult result =
+ vkCreateGraphicsPipelines(context->device.logical_device, VK_NULL_HANDLE, 1,
+ &pipeline_create_info, context->allocator, &out_pipeline->handle);
+ if (result != VK_SUCCESS) {
+ FATAL("graphics pipeline creation failed. its fked mate");
+ ERROR_EXIT("Doomed");
+ }
+
+ return true;
+}
+
+void vulkan_pipeline_bind(vulkan_command_buffer* command_buffer, VkPipelineBindPoint bind_point,
+ vulkan_pipeline* pipeline) {
+ vkCmdBindPipeline(command_buffer->handle, bind_point, pipeline->handle);
+}
+
+void vulkan_buffer_bind(vulkan_context* context, vulkan_buffer* buffer, u64 offset) {
+ vkBindBufferMemory(context->device.logical_device, buffer->handle, buffer->memory, offset);
+}
+
+bool vulkan_buffer_create(vulkan_context* context, u64 size, VkBufferUsageFlagBits usage,
+ u32 memory_property_flags, bool bind_on_create,
+ vulkan_buffer* out_buffer) {
+ memset(out_buffer, 0, sizeof(vulkan_buffer));
+ out_buffer->total_size = size;
+ out_buffer->usage = usage;
+ out_buffer->memory_property_flags = memory_property_flags;
+
+ VkBufferCreateInfo buffer_info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
+ buffer_info.size = size;
+ buffer_info.usage = usage;
+ buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+ vkCreateBuffer(context->device.logical_device, &buffer_info, context->allocator,
+ &out_buffer->handle);
+
+ VkMemoryRequirements requirements;
+ vkGetBufferMemoryRequirements(context->device.logical_device, out_buffer->handle, &requirements);
+ out_buffer->memory_index =
+ find_memory_index(context, requirements.memoryTypeBits, out_buffer->memory_property_flags);
+
+ // Allocate
+ VkMemoryAllocateInfo allocate_info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
+ allocate_info.allocationSize = requirements.size;
+ allocate_info.memoryTypeIndex = (u32)out_buffer->memory_index;
+
+ vkAllocateMemory(context->device.logical_device, &allocate_info, context->allocator,
+ &out_buffer->memory);
+
+ if (bind_on_create) {
+ vulkan_buffer_bind(context, out_buffer, 0);
+ }
+
+ DEBUG("Created buffer.");
+
+ return true;
+}
+
+// lock and unlock?
+
+void* vulkan_buffer_lock_memory(vulkan_context* context, vulkan_buffer* buffer, u64 offset,
+ u64 size, u32 flags) {
+ void* data;
+ vkMapMemory(context->device.logical_device, buffer->memory, offset, size, flags, &data);
+ return data;
+}
+void* vulkan_buffer_unlock_memory(vulkan_context* context, vulkan_buffer* buffer) {
+ vkUnmapMemory(context->device.logical_device, buffer->memory);
+}
+
+void vulkan_buffer_load_data(vulkan_context* context, vulkan_buffer* buffer, u64 offset, u64 size,
+ u32 flags, const void* data) {
+ void* data_ptr = 0;
+ VK_CHECK(
+ vkMapMemory(context->device.logical_device, buffer->memory, offset, size, flags, &data_ptr));
+ memcpy(data_ptr, data, size);
+ vkUnmapMemory(context->device.logical_device, buffer->memory);
+}
+
+// TODO: destroy
+
+bool create_shader_module(vulkan_context* context, const char* filename, const char* type_str,
+ VkShaderStageFlagBits flag, u32 stage_index,
+ vulkan_shader_stage* shader_stages) {
+ memset(&shader_stages[stage_index].create_info, 0, sizeof(VkShaderModuleCreateInfo));
+ memset(&shader_stages[stage_index].stage_create_info, 0, sizeof(VkPipelineShaderStageCreateInfo));
+
+ shader_stages[stage_index].create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+
+ // todo: file input
+ FileData file_contents = load_spv_file(filename);
+
+ shader_stages[stage_index].create_info.codeSize = file_contents.size;
+ shader_stages[stage_index].create_info.pCode = (u32*)file_contents.data;
+
+ vkCreateShaderModule(context->device.logical_device, &shader_stages[stage_index].create_info,
+ context->allocator, &shader_stages[stage_index].handle);
+
+ shader_stages[stage_index].stage_create_info.sType =
+ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ shader_stages[stage_index].stage_create_info.stage = flag;
+ shader_stages[stage_index].stage_create_info.module = shader_stages[stage_index].handle;
+ shader_stages[stage_index].stage_create_info.pName = "main";
+
+ free(file_contents.data);
+
+ // TODO: Descriptors
+
+ return true;
+}
+
+bool vulkan_object_shader_create(vulkan_context* context, vulkan_shader* out_shader) {
+ char stage_type_strs[SHADER_STAGE_COUNT][5] = { "vert", "frag" };
+ char stage_filenames[SHADER_STAGE_COUNT][256] = { "build/linux/x86_64/debug/object.vert.spv",
+ "build/linux/x86_64/debug/object.frag.spv" };
+ VkShaderStageFlagBits stage_types[SHADER_STAGE_COUNT] = { VK_SHADER_STAGE_VERTEX_BIT,
+ VK_SHADER_STAGE_FRAGMENT_BIT };
+ for (u8 i = 0; i < SHADER_STAGE_COUNT; i++) {
+ DEBUG("Loading %s", stage_filenames[i]);
+ create_shader_module(context, stage_filenames[i], stage_type_strs[i], stage_types[i], i,
+ out_shader->stages);
+ }
+
+ // descriptors
+ VkDescriptorSetLayoutBinding global_ubo_layout_binding;
+ global_ubo_layout_binding.binding = 0;
+ global_ubo_layout_binding.descriptorCount = 1;
+ global_ubo_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ global_ubo_layout_binding.pImmutableSamplers = 0;
+ global_ubo_layout_binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+
+ VkDescriptorSetLayoutBinding sampler_layout_binding;
+ sampler_layout_binding.binding = 1;
+ sampler_layout_binding.descriptorCount = 1;
+ sampler_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ sampler_layout_binding.pImmutableSamplers = 0;
+ sampler_layout_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+
+ VkDescriptorSetLayoutCreateInfo global_layout_info = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO
+ };
+
+ VkDescriptorSetLayoutBinding bindings[2] = { global_ubo_layout_binding, sampler_layout_binding };
+
+ global_layout_info.bindingCount = 2;
+ global_layout_info.pBindings = bindings;
+
+ VK_CHECK(vkCreateDescriptorSetLayout(context->device.logical_device, &global_layout_info,
+ context->allocator, &out_shader->descriptor_set_layout));
+
+ VkDescriptorPoolSize global_pool_size;
+ global_pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ global_pool_size.descriptorCount = 3;
+
+ VkDescriptorPoolSize sampler_pool_size;
+ sampler_pool_size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ sampler_pool_size.descriptorCount = 3;
+
+ VkDescriptorPoolSize pool_sizes[2] = { global_pool_size, sampler_pool_size };
+
+ VkDescriptorPoolCreateInfo pool_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
+ pool_info.poolSizeCount = 2;
+ pool_info.pPoolSizes = pool_sizes;
+ pool_info.maxSets = 3;
+
+ VK_CHECK(vkCreateDescriptorPool(context->device.logical_device, &pool_info, context->allocator,
+ &out_shader->descriptor_pool));
+
+ // Pipeline creation
+ VkViewport viewport;
+ viewport.x = 0;
+ viewport.y = 0;
+ viewport.width = (f32)context->framebuffer_width;
+ viewport.height = (f32)context->framebuffer_height;
+ viewport.minDepth = 0.0;
+ viewport.maxDepth = 1.0;
+
+ VkRect2D scissor;
+ scissor.offset.x = scissor.offset.y = 0;
+ scissor.extent.width = context->framebuffer_width;
+ scissor.extent.height = context->framebuffer_height;
+
+ // Attributes
+ u32 offset = 0;
+ const i32 attribute_count = 3;
+ VkVertexInputAttributeDescription attribute_descs[3];
+ // Position
+ VkFormat formats[3] = { VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT,
+ VK_FORMAT_R32G32_SFLOAT };
+
+ u64 sizes[3] = { sizeof(vec3), sizeof(vec3), sizeof(vec2) };
+
+ for (u32 i = 0; i < attribute_count; i++) {
+ attribute_descs[i].binding = 0;
+ attribute_descs[i].location = i;
+ attribute_descs[i].format = formats[i];
+ attribute_descs[i].offset = offset;
+ offset += sizes[i];
+ }
+
+ // Descriptor set layouts
+ VkDescriptorSetLayout layouts[1] = { out_shader->descriptor_set_layout };
+
+ // Stages
+ VkPipelineShaderStageCreateInfo stage_create_infos[SHADER_STAGE_COUNT];
+ memset(stage_create_infos, 0, sizeof(stage_create_infos));
+ for (u32 i = 0; i < SHADER_STAGE_COUNT; i++) {
+ stage_create_infos[i].sType = out_shader->stages[i].stage_create_info.sType;
+ stage_create_infos[i] = out_shader->stages[i].stage_create_info;
+ }
+
+ vulkan_graphics_pipeline_create(
+ context, &context->main_renderpass, attribute_count, attribute_descs, 1, layouts,
+ SHADER_STAGE_COUNT, stage_create_infos, viewport, scissor, false, &out_shader->pipeline);
+ INFO("Graphics pipeline created!");
+
+ // Uniform buffer
+ if (!vulkan_buffer_create(context, sizeof(global_object_uniform),
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+ true, &out_shader->global_uniforms_buffer)) {
+ ERROR("Couldnt create uniforms buffer");
+ return false;
+ }
+
+ VkDescriptorSetLayout global_layouts[3] = {
+ out_shader->descriptor_set_layout,
+ out_shader->descriptor_set_layout,
+ out_shader->descriptor_set_layout,
+ };
+
+ VkDescriptorSetAllocateInfo alloc_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO };
+ alloc_info.descriptorPool = out_shader->descriptor_pool;
+ alloc_info.descriptorSetCount = 3;
+ alloc_info.pSetLayouts = global_layouts;
+ VK_CHECK(vkAllocateDescriptorSets(context->device.logical_device, &alloc_info,
+ out_shader->descriptor_sets));
+
+ return true;
+}
+void vulkan_object_shader_destroy(vulkan_context* context, vulkan_shader* shader) {}
+void vulkan_object_shader_use(vulkan_context* context, vulkan_shader* shader) {
+ u32 image_index = context->image_index;
+ vulkan_pipeline_bind(&context->gfx_command_buffers->data[image_index],
+ VK_PIPELINE_BIND_POINT_GRAPHICS, &shader->pipeline);
+}
+void vulkan_object_shader_update_global_state(vulkan_context* context, vulkan_shader* shader) {
+ u32 image_index = context->image_index;
+ VkCommandBuffer cmd_buffer = context->gfx_command_buffers->data[image_index].handle;
+ VkDescriptorSet global_descriptors = shader->descriptor_sets[image_index];
+
+ u32 range = sizeof(global_object_uniform);
+ u64 offset = 0;
+
+ // copy data to buffer
+ vulkan_buffer_load_data(context, &shader->global_uniforms_buffer, offset, range, 0,
+ &shader->global_ubo);
+
+ VkDescriptorBufferInfo buffer_info;
+ buffer_info.buffer = shader->global_uniforms_buffer.handle;
+ buffer_info.offset = offset;
+ buffer_info.range = range;
+
+ VkDescriptorImageInfo image_info;
+ image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ image_info.imageView = shader->texture_data->image.view;
+ image_info.sampler = shader->texture_data->sampler;
+
+ VkWriteDescriptorSet uniform_write = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
+ uniform_write.dstSet = shader->descriptor_sets[image_index];
+ uniform_write.dstBinding = 0;
+ uniform_write.dstArrayElement = 0;
+ uniform_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ uniform_write.descriptorCount = 1;
+ uniform_write.pBufferInfo = &buffer_info;
+
+ VkWriteDescriptorSet texture_write = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
+ texture_write.dstSet = shader->descriptor_sets[image_index];
+ texture_write.dstBinding = 1;
+ texture_write.dstArrayElement = 0;
+ texture_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ texture_write.descriptorCount = 1;
+ texture_write.pImageInfo = &image_info;
+
+ VkWriteDescriptorSet writes[2] = { uniform_write, texture_write };
+
+ vkUpdateDescriptorSets(context->device.logical_device, 2, writes, 0, 0);
+
+ vkCmdBindDescriptorSets(cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, shader->pipeline.layout, 0,
+ 1, &global_descriptors, 0, 0);
+}
+
+void vulkan_object_shader_update_object(vulkan_context* context, vulkan_shader* shader,
+ mat4 model) {
+ u32 image_index = context->image_index;
+ VkCommandBuffer cmd_buffer = context->gfx_command_buffers->data[image_index].handle;
+ // vulkan_command_buffer* cmd_buffer = &context->gfx_command_buffers->data[context.image_index];
+
+ vkCmdPushConstants(cmd_buffer, shader->pipeline.layout, VK_SHADER_STAGE_VERTEX_BIT, 0,
+ sizeof(mat4), &model);
+
+ // vulkan_object_shader_use(context, &context->object_shader);
+ VkDeviceSize offsets[1] = { 0 };
+ vkCmdBindVertexBuffers(cmd_buffer, 0, 1, &context->object_vertex_buffer.handle,
+ (VkDeviceSize*)offsets);
+
+ vkCmdBindIndexBuffer(cmd_buffer, context->object_index_buffer.handle, 0, VK_INDEX_TYPE_UINT32);
+
+ vkCmdDrawIndexed(cmd_buffer, 36, 1, 0, 0, 0);
+ // vkCmdDraw(cmd_buffer, 36, 1, 0, 0);
+}
+
+bool select_physical_device(vulkan_context* ctx) {
+ u32 physical_device_count = 0;
+ VK_CHECK(vkEnumeratePhysicalDevices(ctx->instance, &physical_device_count, 0));
+ if (physical_device_count == 0) {
+ FATAL("No devices that support vulkan were found");
+ return false;
+ }
+ TRACE("Number of devices found %d", physical_device_count);
+
+ VkPhysicalDevice physical_devices[physical_device_count];
+ VK_CHECK(vkEnumeratePhysicalDevices(ctx->instance, &physical_device_count, physical_devices));
+
+ for (u32 i = 0; i < physical_device_count; i++) {
+ VkPhysicalDeviceProperties properties;
+ vkGetPhysicalDeviceProperties(physical_devices[i], &properties);
+
+ VkPhysicalDeviceFeatures features;
+ vkGetPhysicalDeviceFeatures(physical_devices[i], &features);
+
+ VkPhysicalDeviceMemoryProperties memory;
+ vkGetPhysicalDeviceMemoryProperties(physical_devices[i], &memory);
+
+ vulkan_physical_device_requirements requirements = {};
+ requirements.graphics = true;
+ requirements.present = true;
+ requirements.compute = true;
+ requirements.transfer = true;
+
+ requirements.sampler_anistropy = true;
+ requirements.discrete_gpu = true;
+ requirements.device_ext_names[0] = str8lit(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+ requirements.device_ext_name_count = 1;
+
+ vulkan_physical_device_queue_family_info queue_info = {};
+
+ bool result = physical_device_meets_requirements(physical_devices[i], ctx->surface, &properties,
+ &features, &requirements, &queue_info,
+ &ctx->device.swapchain_support);
+
+ if (result) {
+ INFO("GPU Driver version: %d.%d.%d", VK_VERSION_MAJOR(properties.driverVersion),
+ VK_VERSION_MINOR(properties.driverVersion), VK_VERSION_PATCH(properties.driverVersion));
+
+ INFO("Vulkan API version: %d.%d.%d", VK_VERSION_MAJOR(properties.apiVersion),
+ VK_VERSION_MINOR(properties.apiVersion), VK_VERSION_PATCH(properties.apiVersion));
+
+ // TODO: print gpu memory information -
+ // https://youtu.be/6Kj3O2Ov1RU?si=pXfP5NvXXcXjJsrG&t=2439
+
+ ctx->device.physical_device = physical_devices[i];
+ ctx->device.graphics_queue_index = queue_info.graphics_family_index;
+ ctx->device.present_queue_index = queue_info.present_family_index;
+ ctx->device.compute_queue_index = queue_info.compute_family_index;
+ ctx->device.transfer_queue_index = queue_info.transfer_family_index;
+ ctx->device.properties = properties;
+ ctx->device.features = features;
+ ctx->device.memory = memory;
+ break;
+ }
+ }
+
+ if (!ctx->device.physical_device) {
+ ERROR("No suitable physical devices were found :(");
+ return false;
+ }
+
+ INFO("Physical device selected: %s\n", ctx->device.properties.deviceName);
+ return true;
+}
+
+bool vulkan_device_create(vulkan_context* context) {
+ // Physical device - NOTE: mutates the context directly
+ if (!select_physical_device(context)) {
+ return false;
+ }
+
+// Logical device - NOTE: mutates the context directly
+
+// queues
+#define VULKAN_QUEUES_COUNT 2
+ const char* queue_names[VULKAN_QUEUES_COUNT] = {
+ "GRAPHICS",
+ "TRANSFER",
+ };
+ i32 indices[VULKAN_QUEUES_COUNT] = {
+ context->device.graphics_queue_index,
+ context->device.transfer_queue_index,
+ };
+ f32 prio_one = 1.0;
+ VkDeviceQueueCreateInfo queue_create_info[2]; // HACK: make 2 queues, graphics and transfer
+ // graphics
+ queue_create_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queue_create_info[0].queueFamilyIndex = 0;
+ queue_create_info[0].queueCount = 1;
+ queue_create_info[0].flags = 0;
+ queue_create_info[0].pNext = 0;
+ queue_create_info[0].pQueuePriorities = &prio_one;
+ // transfer
+ queue_create_info[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queue_create_info[1].queueFamilyIndex = 1;
+ queue_create_info[1].queueCount = 1;
+ queue_create_info[1].flags = 0;
+ queue_create_info[1].pNext = 0;
+ queue_create_info[1].pQueuePriorities = &prio_one;
+
+ // for (int i = 0; i < 2; i++) {
+ // TRACE("Configure %s queue", queue_names[i]);
+ // queue_create_info[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ // queue_create_info[i].queueFamilyIndex = indices[i];
+ // queue_create_info[i].queueCount = 1; // make just one of them
+ // queue_create_info[i].flags = 0;
+ // queue_create_info[i].pNext = 0;
+ // f32 priority = 1.0;
+ // queue_create_info[i].pQueuePriorities = &priority;
+ // }
+
+ // features
+ VkPhysicalDeviceFeatures device_features = {};
+ device_features.samplerAnisotropy = VK_TRUE; // request anistrophy
+
+ // device itself
+ VkDeviceCreateInfo device_create_info = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
+ device_create_info.queueCreateInfoCount = VULKAN_QUEUES_COUNT;
+ device_create_info.pQueueCreateInfos = queue_create_info;
+ device_create_info.pEnabledFeatures = &device_features;
+ device_create_info.enabledExtensionCount = 1;
+ const char* extension_names = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
+ device_create_info.ppEnabledExtensionNames = &extension_names;
+
+ // deprecated
+ device_create_info.enabledLayerCount = 0;
+ device_create_info.ppEnabledLayerNames = 0;
+
+ VkResult result = vkCreateDevice(context->device.physical_device, &device_create_info,
+ context->allocator, &context->device.logical_device);
+ if (result != VK_SUCCESS) {
+ printf("error creating logical device with status %u\n", result);
+ ERROR_EXIT("Bye bye");
+ }
+ INFO("Logical device created");
+
+ // get queues
+ vkGetDeviceQueue(context->device.logical_device, context->device.graphics_queue_index, 0,
+ &context->device.graphics_queue);
+ // vkGetDeviceQueue(context->device.logical_device, context->device.present_queue_index, 0,
+ // &context->device.present_queue);
+ // vkGetDeviceQueue(context->device.logical_device, context->device.compute_queue_index, 0,
+ // &context->device.compute_queue);
+ vkGetDeviceQueue(context->device.logical_device, context->device.transfer_queue_index, 0,
+ &context->device.transfer_queue);
+
+ // create command pool for graphics queue
+ VkCommandPoolCreateInfo pool_create_info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
+ pool_create_info.queueFamilyIndex = context->device.graphics_queue_index;
+ pool_create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+ vkCreateCommandPool(context->device.logical_device, &pool_create_info, context->allocator,
+ &context->device.gfx_command_pool);
+ INFO("Created Command Pool")
+
+ return true;
+}
+void vulkan_device_destroy(vulkan_context* context) {
+ context->device.physical_device = 0; // release
+ // TODO: reset other memory
+}
+
+bool vulkan_device_detect_depth_format(vulkan_device* device) {
+ const size_t n_candidates = 3;
+ VkFormat candidates[3] = { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT,
+ VK_FORMAT_D24_UNORM_S8_UINT };
+ u32 flags = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ for (u64 i = 0; i < n_candidates; i++) {
+ VkFormatProperties properties;
+ vkGetPhysicalDeviceFormatProperties(device->physical_device, candidates[i], &properties);
+
+ if ((properties.linearTilingFeatures & flags) == flags) {
+ device->depth_format = candidates[i];
+ return true;
+ }
+ if ((properties.optimalTilingFeatures & flags) == flags) {
+ device->depth_format = candidates[i];
+ return true;
+ }
+ }
+ return false;
+}
+
+void vulkan_image_view_create(vulkan_context* context, VkFormat format, vulkan_image* image,
+ VkImageAspectFlags aspect_flags) {
+ VkImageViewCreateInfo view_create_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
+ view_create_info.image = image->handle;
+ view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ view_create_info.format = format;
+ view_create_info.subresourceRange.aspectMask = aspect_flags;
+
+ view_create_info.subresourceRange.baseMipLevel = 0;
+ view_create_info.subresourceRange.levelCount = 1;
+ view_create_info.subresourceRange.baseArrayLayer = 0;
+ view_create_info.subresourceRange.layerCount = 1;
+
+ vkCreateImageView(context->device.logical_device, &view_create_info, context->allocator,
+ &image->view);
+}
+
+void vulkan_image_transition_layout(vulkan_context* context, vulkan_command_buffer* command_buffer,
+ vulkan_image* image, VkFormat format, VkImageLayout old_layout,
+ VkImageLayout new_layout) {
+ VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
+ barrier.oldLayout = old_layout;
+ barrier.newLayout = new_layout;
+ barrier.srcQueueFamilyIndex = context->device.graphics_queue_index;
+ barrier.dstQueueFamilyIndex = context->device.graphics_queue_index;
+ barrier.image = image->handle;
+ barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ barrier.subresourceRange.baseMipLevel = 0;
+ barrier.subresourceRange.levelCount = 1;
+ barrier.subresourceRange.baseArrayLayer = 0;
+ barrier.subresourceRange.layerCount = 1;
+ barrier.srcAccessMask = 0;
+ barrier.dstAccessMask = 0;
+
+ VkPipelineStageFlags source_stage;
+ VkPipelineStageFlags dest_stage;
+
+ if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED &&
+ new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
+ barrier.srcAccessMask = 0;
+ barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+
+ source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+ dest_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+
+ } else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL &&
+ new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
+ barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+ source_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+ dest_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+ } else {
+ FATAL("Unsupported image layout transition");
+ return;
+ }
+
+ vkCmdPipelineBarrier(command_buffer->handle, source_stage, dest_stage, 0, 0, 0, 0, 0, 1,
+ &barrier);
+}
+
+void vulkan_image_copy_from_buffer(vulkan_image* image, VkBuffer buffer,
+ vulkan_command_buffer* command_buffer) {
+ VkBufferImageCopy region;
+ region.bufferOffset = 0;
+ region.bufferRowLength = 0;
+ region.bufferImageHeight = 0;
+ region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.imageSubresource.mipLevel = 0;
+ region.imageSubresource.baseArrayLayer = 0;
+ region.imageSubresource.layerCount = 1;
+ printf("Image details width: %d height %d\n", image->width, image->height);
+ region.imageOffset.x = 0;
+ region.imageOffset.y = 0;
+ region.imageOffset.z = 0;
+ region.imageExtent.width = image->width;
+ region.imageExtent.height = image->height;
+ region.imageExtent.depth = 1;
+
+ vkCmdCopyBufferToImage(command_buffer->handle, buffer, image->handle,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
+}
+
+void vulkan_image_create(vulkan_context* context, VkImageType image_type, u32 width, u32 height,
+ VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage,
+ VkMemoryPropertyFlags memory_flags, bool create_view,
+ VkImageAspectFlags aspect_flags, vulkan_image* out_image) {
+ // copy params
+ out_image->width = width;
+ out_image->height = height;
+
+ // create info
+ VkImageCreateInfo image_create_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
+ image_create_info.imageType = image_type;
+ image_create_info.extent.width = width;
+ image_create_info.extent.height = height;
+ image_create_info.extent.depth = 1;
+ image_create_info.mipLevels = 1;
+ image_create_info.arrayLayers = 1;
+ image_create_info.format = format;
+ image_create_info.tiling = tiling;
+ image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ image_create_info.usage = usage;
+ image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
+ image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+ VK_CHECK(vkCreateImage(context->device.logical_device, &image_create_info, context->allocator,
+ &out_image->handle));
+
+ VkMemoryRequirements memory_reqs;
+ vkGetImageMemoryRequirements(context->device.logical_device, out_image->handle, &memory_reqs);
+
+ i32 memory_type = -1;
+ VkPhysicalDeviceMemoryProperties memory_properties;
+ vkGetPhysicalDeviceMemoryProperties(context->device.physical_device, &memory_properties);
+
+ for (u32 i = 0; i < memory_properties.memoryTypeCount; i++) {
+ // typefilter = memoryTypeBits , prop filter = memory_flags
+ if (memory_reqs.memoryTypeBits & (1 << i) &&
+ (memory_properties.memoryTypes[i].propertyFlags & memory_flags)) {
+ memory_type = i;
+ break;
+ }
+ }
+
+ if (memory_type < 0) {
+ ERROR_EXIT("couldnt find a suitable memory type for the image");
+ }
+
+ // allocate memory
+ VkMemoryAllocateInfo memory_allocate_info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
+ memory_allocate_info.allocationSize = memory_reqs.size;
+ memory_allocate_info.memoryTypeIndex = memory_type;
+ vkAllocateMemory(context->device.logical_device, &memory_allocate_info, context->allocator,
+ &out_image->memory);
+
+ // bind memory
+ // TODO: maybe bind context->device.logical_device to device at the top of the functions?
+ vkBindImageMemory(context->device.logical_device, out_image->handle, out_image->memory, 0);
+
+ if (create_view) {
+ out_image->view = 0;
+ vulkan_image_view_create(context, format, out_image, aspect_flags);
+ }
+}
+
+// TODO: vulkan_image_destroy
+
+void vulkan_framebuffer_create(vulkan_context* context, vulkan_renderpass* renderpass, u32 width,
+ u32 height, u32 attachment_count, VkImageView* attachments,
+ vulkan_framebuffer* out_framebuffer) {
+ out_framebuffer->attachments = malloc(sizeof(VkImageView) * attachment_count);
+ for (u32 i = 0; i < attachment_count; i++) {
+ out_framebuffer->attachments[i] = attachments[i];
+ }
+ out_framebuffer->attachment_count = attachment_count;
+ out_framebuffer->renderpass = renderpass;
+
+ VkFramebufferCreateInfo framebuffer_create_info = {
+ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO
+ }; // TODO
+
+ framebuffer_create_info.renderPass = renderpass->handle;
+ framebuffer_create_info.attachmentCount = attachment_count;
+ framebuffer_create_info.pAttachments = out_framebuffer->attachments;
+ framebuffer_create_info.width = width;
+ framebuffer_create_info.height = height;
+ framebuffer_create_info.layers = 1;
+
+ vkCreateFramebuffer(context->device.logical_device, &framebuffer_create_info, context->allocator,
+ &out_framebuffer->handle);
+}
+
+// TODO: vulkan_framebuffer_destroy
+
+void vulkan_command_buffer_allocate(vulkan_context* context, VkCommandPool pool, bool is_primary,
+ vulkan_command_buffer* out_command_buffer) {
+ VkCommandBufferAllocateInfo allocate_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
+ allocate_info.commandPool = pool;
+ allocate_info.level =
+ is_primary ? VK_COMMAND_BUFFER_LEVEL_PRIMARY : VK_COMMAND_BUFFER_LEVEL_SECONDARY;
+ allocate_info.commandBufferCount = 1;
+ allocate_info.pNext = 0;
+
+ out_command_buffer->state = COMMAND_BUFFER_STATE_NOT_ALLOCATED;
+ vkAllocateCommandBuffers(context->device.logical_device, &allocate_info,
+ &out_command_buffer->handle);
+ out_command_buffer->state = COMMAND_BUFFER_STATE_READY;
+}
+
+void vulkan_command_buffer_free(vulkan_context* context, VkCommandPool pool,
+ vulkan_command_buffer* out_command_buffer) {
+ // TODO: implement freeing
+}
+
+void vulkan_command_buffer_begin(vulkan_command_buffer* command_buffer, bool is_single_use,
+ bool is_renderpass_continue, bool is_simultaneous_use) {
+ VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
+ begin_info.flags = 0;
+ if (is_single_use) {
+ begin_info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+ }
+ // TODO: RENDER_PASS_CONTINUE_BIT & SIMULTANEOUS_USE_BIT
+
+ begin_info.pNext = 0;
+ begin_info.pInheritanceInfo = 0;
+ vkBeginCommandBuffer(command_buffer->handle, &begin_info);
+
+ command_buffer->state = COMMAND_BUFFER_STATE_RECORDING;
+}
+
+void vulkan_command_buffer_end(vulkan_command_buffer* command_buffer) {
+ VK_CHECK(vkEndCommandBuffer(command_buffer->handle));
+ command_buffer->state = COMMAND_BUFFER_STATE_RECORDING_ENDED;
+}
+void vulkan_command_buffer_update_submitted(vulkan_command_buffer* command_buffer) {
+ command_buffer->state = COMMAND_BUFFER_STATE_SUBMITTED;
+}
+void vulkan_command_buffer_reset(vulkan_command_buffer* command_buffer) {
+ command_buffer->state = COMMAND_BUFFER_STATE_READY;
+}
+
+void vulkan_command_buffer_allocate_and_begin_oneshot(vulkan_context* context, VkCommandPool pool,
+ vulkan_command_buffer* out_command_buffer) {
+ vulkan_command_buffer_allocate(context, pool, true, out_command_buffer);
+ vulkan_command_buffer_begin(out_command_buffer, true, false, false);
+}
+
+void vulkan_command_buffer_end_oneshot(vulkan_context* context, VkCommandPool pool,
+ vulkan_command_buffer* command_buffer, VkQueue queue) {
+ vulkan_command_buffer_end(command_buffer);
+
+ // submit to queue
+ VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
+ submit_info.commandBufferCount = 1;
+ submit_info.pCommandBuffers = &command_buffer->handle;
+ VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, 0));
+ // wait for it to finish
+ VK_CHECK(vkQueueWaitIdle(queue));
+
+ vulkan_command_buffer_free(context, pool, command_buffer);
+}
+
+void vulkan_buffer_copy_to(vulkan_context* context, VkCommandPool pool, VkFence fence,
+ VkQueue queue, VkBuffer source, u64 source_offset, VkBuffer dest,
+ u64 dest_offset, u64 size) {
+ vkQueueWaitIdle(queue);
+
+ vulkan_command_buffer temp_cmd_buf;
+ vulkan_command_buffer_allocate_and_begin_oneshot(context, pool, &temp_cmd_buf);
+
+ VkBufferCopy copy_region;
+ copy_region.srcOffset = source_offset;
+ copy_region.dstOffset = dest_offset;
+ copy_region.size = size;
+
+ vkCmdCopyBuffer(temp_cmd_buf.handle, source, dest, 1, &copy_region);
+
+ vulkan_command_buffer_end_oneshot(context, pool, &temp_cmd_buf, queue);
+}
+
+void vulkan_swapchain_create(vulkan_context* context, u32 width, u32 height,
+ vulkan_swapchain* out_swapchain) {
+ VkExtent2D swapchain_extent = { width, height };
+ out_swapchain->max_frames_in_flight = 2; // support triple buffering
+
+ // find a format
+ bool found;
+ for (u32 i = 0; i < context->device.swapchain_support.format_count; i++) {
+ VkSurfaceFormatKHR format = context->device.swapchain_support.formats[i];
+ if (format.format == VK_FORMAT_B8G8R8A8_UNORM &&
+ format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+ out_swapchain->image_format = format;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ out_swapchain->image_format = context->device.swapchain_support.formats[0];
+ }
+
+ VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR; // guaranteed to be implemented
+ // TODO: look for mailbox - https://youtu.be/jWKVb_QdSNM?si=bHcd3sEf-M0x3QwH&t=1687
+
+ // TODO: requery swapchain support
+
+ u32 image_count = context->device.swapchain_support.capabilities.minImageCount;
+
+ VkSwapchainCreateInfoKHR swapchain_create_info = { VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR };
+ swapchain_create_info.surface = context->surface;
+ swapchain_create_info.minImageCount = image_count;
+ swapchain_create_info.imageFormat = out_swapchain->image_format.format;
+ swapchain_create_info.imageColorSpace = out_swapchain->image_format.colorSpace;
+ DEBUG("Image extent %d %d\n", swapchain_extent.width, swapchain_extent.height);
+ swapchain_create_info.imageExtent = swapchain_extent;
+ swapchain_create_info.imageArrayLayers = 1;
+ swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ swapchain_create_info.queueFamilyIndexCount = 0;
+ swapchain_create_info.pQueueFamilyIndices = 0;
+
+ swapchain_create_info.preTransform =
+ context->device.swapchain_support.capabilities.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;
+
+ TRACE("Create swapchain");
+ VK_CHECK(vkCreateSwapchainKHR(context->device.logical_device, &swapchain_create_info,
+ context->allocator, &out_swapchain->handle));
+
+ context->current_frame = 0;
+
+ // images
+ out_swapchain->image_count = 0;
+ vkGetSwapchainImagesKHR(context->device.logical_device, out_swapchain->handle,
+ &out_swapchain->image_count, 0);
+
+ if (!out_swapchain->images) {
+ out_swapchain->images = (VkImage*)malloc(sizeof(VkImage) * out_swapchain->image_count);
+ }
+ if (!out_swapchain->views) {
+ out_swapchain->views = (VkImageView*)malloc(sizeof(VkImage) * out_swapchain->image_count);
+ }
+ VK_CHECK(vkGetSwapchainImagesKHR(context->device.logical_device, out_swapchain->handle,
+ &out_swapchain->image_count, out_swapchain->images));
+
+ // views
+ for (int i = 0; i < out_swapchain->image_count; i++) {
+ VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
+ view_info.image = out_swapchain->images[i];
+ view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ view_info.format = out_swapchain->image_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;
+
+ VK_CHECK(vkCreateImageView(context->device.logical_device, &view_info, context->allocator,
+ &out_swapchain->views[i]));
+ }
+
+ // depth attachment
+ if (!vulkan_device_detect_depth_format(&context->device)) {
+ ERROR_EXIT("Failed to find a supported depth format");
+ }
+ vulkan_image_create(context, VK_IMAGE_TYPE_2D, swapchain_extent.width, swapchain_extent.height,
+ context->device.depth_format, VK_IMAGE_TILING_OPTIMAL,
+ VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, true, VK_IMAGE_ASPECT_DEPTH_BIT,
+ &out_swapchain->depth_attachment);
+ INFO("Depth attachment created");
+
+ INFO("Swapchain created successfully");
+}
+
+// TODO: swapchain destroy
+void vulkan_swapchain_recreate(vulkan_context* context, u32 width, u32 height,
+ vulkan_swapchain* swapchain) {
+ // TODO
+}
+bool vulkan_swapchain_acquire_next_image_index(vulkan_context* context, vulkan_swapchain* swapchain,
+ u64 timeout_ns,
+ VkSemaphore image_available_semaphore, VkFence fence,
+ u32* out_image_index) {
+ VkResult result =
+ vkAcquireNextImageKHR(context->device.logical_device, swapchain->handle, timeout_ns,
+ image_available_semaphore, fence, out_image_index);
+
+ if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
+ FATAL("Failed to acquire swapchain image");
+ return false;
+ }
+
+ return true;
+}
+
+void vulkan_swapchain_present(vulkan_context* context, vulkan_swapchain* swapchain,
+ VkQueue graphics_queue, VkQueue present_queue,
+ VkSemaphore render_complete_semaphore, u32 present_image_index) {
+ // return image to swapchain for presentation
+ VkPresentInfoKHR present_info = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR };
+ present_info.waitSemaphoreCount = 1;
+ present_info.pWaitSemaphores = &render_complete_semaphore;
+ present_info.swapchainCount = 1;
+ present_info.pSwapchains = &swapchain->handle;
+ present_info.pImageIndices = &present_image_index;
+ present_info.pResults = 0;
+
+ VkResult result = vkQueuePresentKHR(present_queue, &present_info);
+ if (result != VK_SUCCESS) {
+ if (result == VK_SUBOPTIMAL_KHR) {
+ // WARN("Swapchain suboptimal - maybe resize needed?");
+ } else {
+ FATAL("Failed to present swapchain iamge");
+ }
+ }
+
+ // advance the current frame
+ context->current_frame = (context->current_frame + 1) % swapchain->max_frames_in_flight;
+}
+
+void vulkan_renderpass_create(vulkan_context* context, vulkan_renderpass* out_renderpass,
+ vec4 render_area, vec4 clear_colour, f32 depth, u32 stencil) {
+ out_renderpass->render_area = render_area;
+ out_renderpass->clear_colour = clear_colour;
+ out_renderpass->depth = depth;
+ out_renderpass->stencil = stencil;
+
+ // main subpass
+ VkSubpassDescription subpass = {};
+ subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+
+ // attachments
+ u32 attachment_desc_count = 2;
+ VkAttachmentDescription attachment_descriptions[2];
+
+ // Colour attachment
+ VkAttachmentDescription color_attachment;
+ color_attachment.format = context->swapchain.image_format.format;
+ color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+ color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+ color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ color_attachment.flags = 0;
+
+ attachment_descriptions[0] = color_attachment;
+
+ VkAttachmentReference color_attachment_reference;
+ color_attachment_reference.attachment = 0;
+ color_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+ subpass.colorAttachmentCount = 1;
+ subpass.pColorAttachments = &color_attachment_reference;
+
+ // Depth attachment
+ VkAttachmentDescription depth_attachment;
+ depth_attachment.format = context->device.depth_format;
+ depth_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+ depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ depth_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ depth_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ depth_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ depth_attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ depth_attachment.flags = 0;
+
+ attachment_descriptions[1] = depth_attachment;
+
+ VkAttachmentReference depth_attachment_reference;
+ depth_attachment_reference.attachment = 1;
+ depth_attachment_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+ subpass.pDepthStencilAttachment = &depth_attachment_reference;
+
+ // TODO: other attachment styles
+
+ subpass.inputAttachmentCount = 0;
+ subpass.pInputAttachments = 0;
+ subpass.pResolveAttachments = 0;
+ subpass.preserveAttachmentCount = 0;
+ subpass.preserveAttachmentCount = 0;
+
+ // renderpass dependencies
+ VkSubpassDependency dependency;
+ dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
+ dependency.dstSubpass = 0;
+ dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ dependency.srcAccessMask = 0;
+ dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ dependency.dstAccessMask =
+ VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ dependency.dependencyFlags = 0;
+
+ VkRenderPassCreateInfo render_pass_create_info = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
+ render_pass_create_info.attachmentCount = attachment_desc_count;
+ render_pass_create_info.pAttachments = attachment_descriptions;
+ render_pass_create_info.subpassCount = 1;
+ render_pass_create_info.pSubpasses = &subpass;
+ render_pass_create_info.dependencyCount = 1;
+ render_pass_create_info.pDependencies = &dependency;
+ render_pass_create_info.pNext = 0;
+ render_pass_create_info.flags = 0;
+
+ VK_CHECK(vkCreateRenderPass(context->device.logical_device, &render_pass_create_info,
+ context->allocator, &out_renderpass->handle));
+}
+
+// TODO: renderpass destroy
+
+void vulkan_renderpass_begin(vulkan_command_buffer* command_buffer, vulkan_renderpass* renderpass,
+ VkFramebuffer framebuffer) {
+ VkRenderPassBeginInfo begin_info = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
+ begin_info.renderPass = renderpass->handle;
+ begin_info.framebuffer = framebuffer;
+ begin_info.renderArea.offset.x = renderpass->render_area.x;
+ begin_info.renderArea.offset.y = renderpass->render_area.y;
+ begin_info.renderArea.extent.width = renderpass->render_area.z;
+ begin_info.renderArea.extent.height = renderpass->render_area.w;
+
+ VkClearValue clear_values[2];
+ memset(&clear_values, 0, sizeof(VkClearValue) * 2);
+ clear_values[0].color.float32[0] = renderpass->clear_colour.x;
+ clear_values[0].color.float32[1] = renderpass->clear_colour.y;
+ clear_values[0].color.float32[2] = renderpass->clear_colour.z;
+ clear_values[0].color.float32[3] = renderpass->clear_colour.w;
+ clear_values[1].depthStencil.depth = renderpass->depth;
+ clear_values[1].depthStencil.stencil = renderpass->stencil;
+
+ begin_info.clearValueCount = 2;
+ begin_info.pClearValues = clear_values;
+
+ vkCmdBeginRenderPass(command_buffer->handle, &begin_info, VK_SUBPASS_CONTENTS_INLINE);
+ command_buffer->state = COMMAND_BUFFER_STATE_IN_RENDER_PASS;
+}
+
+void vulkan_renderpass_end(vulkan_command_buffer* command_buffer, vulkan_renderpass* renderpass) {
+ vkCmdEndRenderPass(command_buffer->handle);
+ command_buffer->state = COMMAND_BUFFER_STATE_RECORDING;
+}
+
+bool create_buffers(vulkan_context* context) {
+ VkMemoryPropertyFlagBits mem_prop_flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+
+ const u64 vertex_buffer_size = sizeof(vertex_pos) * 1024 * 1024;
+ if (!vulkan_buffer_create(context, vertex_buffer_size,
+ VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ mem_prop_flags, true, &context->object_vertex_buffer)) {
+ ERROR("couldnt create vertex buffer");
+ return false;
+ }
+
+ context->geometry_vertex_offset = 0;
+
+ const u64 index1_buffer_size = sizeof(u32) * 1024 * 1024;
+ if (!vulkan_buffer_create(context, index1_buffer_size,
+ VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ mem_prop_flags, true, &context->object_index_buffer)) {
+ ERROR("couldnt create vertex buffer");
+ return false;
+ }
+ context->geometry_index_offset = 0;
+
+ return true;
+}
+
+void create_command_buffers(renderer* ren) {
+ if (!context.gfx_command_buffers) {
+ context.gfx_command_buffers = vulkan_command_buffer_darray_new(context.swapchain.image_count);
+ }
+
+ for (u32 i = 0; i < context.swapchain.image_count; i++) {
+ vulkan_command_buffer_allocate(&context, context.device.gfx_command_pool, true,
+ &context.gfx_command_buffers->data[i]);
+ }
+}
+
+void upload_data_range(vulkan_context* context, VkCommandPool pool, VkFence fence, VkQueue queue,
+ vulkan_buffer* buffer, u64 offset, u64 size, void* data) {
+ VkBufferUsageFlags flags =
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+ vulkan_buffer staging;
+ vulkan_buffer_create(context, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, flags, true, &staging);
+ // load data into staging buffer
+ printf("Size: %ld\n", size);
+ vulkan_buffer_load_data(context, &staging, 0, size, 0, data);
+
+ // copy
+ vulkan_buffer_copy_to(context, pool, fence, queue, staging.handle, 0, buffer->handle, offset,
+ size);
+
+ vkDestroyBuffer(context->device.logical_device, staging.handle, context->allocator);
+}
+
+void regenerate_framebuffers(renderer* ren, vulkan_swapchain* swapchain,
+ vulkan_renderpass* renderpass) {
+ for (u32 i = 0; i < swapchain->image_count; i++) {
+ u32 attachment_count = 2; // one for depth, one for colour
+
+ VkImageView attachments[2] = { swapchain->views[i], swapchain->depth_attachment.view };
+
+ vulkan_framebuffer_create(&context, renderpass, context.framebuffer_width,
+ context.framebuffer_height, 2, attachments,
+ &swapchain->framebuffers->data[i]);
+ }
+}
+
+void vulkan_fence_create(vulkan_context* context, bool create_signaled, vulkan_fence* out_fence) {
+ out_fence->is_signaled = create_signaled;
+ VkFenceCreateInfo fence_create_info = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
+ if (out_fence->is_signaled) {
+ fence_create_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+ }
+
+ vkCreateFence(context->device.logical_device, &fence_create_info, context->allocator,
+ &out_fence->handle);
+}
+
+// TODO: vulkan_fence_destroy
+
+bool vulkan_fence_wait(vulkan_context* context, vulkan_fence* fence, u64 timeout_ns) {
+ if (!fence->is_signaled) {
+ VkResult result =
+ vkWaitForFences(context->device.logical_device, 1, &fence->handle, true, timeout_ns);
+ switch (result) {
+ case VK_SUCCESS:
+ fence->is_signaled = true;
+ return true;
+ case VK_TIMEOUT:
+ WARN("vk_fence_wait - Timed out");
+ break;
+ default:
+ ERROR("vk_fence_wait - Unhanlded error type");
+ break;
+ }
+ } else {
+ return true;
+ }
+
+ return false;
+}
+void vulkan_fence_reset(vulkan_context* context, vulkan_fence* fence) {
+ if (fence->is_signaled) {
+ vkResetFences(context->device.logical_device, 1, &fence->handle);
+ fence->is_signaled = false;
+ }
+}
+
+bool gfx_backend_init(renderer* ren) {
+ INFO("loading Vulkan backend");
+
+ vulkan_state* internal = malloc(sizeof(vulkan_state));
+ ren->backend_state = (void*)internal;
+
+ context.allocator = 0; // TODO: custom allocator
+
+ context.framebuffer_width = SCR_WIDTH;
+ context.framebuffer_height = SCR_HEIGHT;
+
+ // Setup Vulkan instance
+ VkApplicationInfo app_info = { VK_STRUCTURE_TYPE_APPLICATION_INFO };
+ app_info.apiVersion = VK_API_VERSION_1_3;
+ app_info.pApplicationName = ren->config.window_name;
+ app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
+ app_info.pEngineName = "Celeritas Engine";
+ app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
+
+ VkInstanceCreateInfo create_info = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO };
+ create_info.pApplicationInfo = &app_info;
+
+ cstr_darray* required_extensions = cstr_darray_new(2);
+ cstr_darray_push(required_extensions, VK_KHR_SURFACE_EXTENSION_NAME);
+
+ plat_get_required_extension_names(required_extensions);
+
+#if defined(CDEBUG)
+ cstr_darray_push(required_extensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+
+ DEBUG("Required extensions:");
+ for (u32 i = 0; i < cstr_darray_len(required_extensions); i++) {
+ DEBUG(" %s", required_extensions->data[i]);
+ }
+#endif
+
+ create_info.enabledExtensionCount = cstr_darray_len(required_extensions);
+ create_info.ppEnabledExtensionNames = required_extensions->data;
+
+ // Validation layers
+ create_info.enabledLayerCount = 0;
+ create_info.ppEnabledLayerNames = 0;
+#if defined(CDEBUG)
+ INFO("Validation layers enabled");
+ cstr_darray* desired_validation_layers = cstr_darray_new(1);
+ cstr_darray_push(desired_validation_layers, "VK_LAYER_KHRONOS_validation");
+
+ u32 n_available_layers = 0;
+ VK_CHECK(vkEnumerateInstanceLayerProperties(&n_available_layers, 0));
+ TRACE("%d available layers", n_available_layers);
+ VkLayerProperties_darray* available_layers = VkLayerProperties_darray_new(n_available_layers);
+ VK_CHECK(vkEnumerateInstanceLayerProperties(&n_available_layers, available_layers->data));
+
+ for (int i = 0; i < cstr_darray_len(desired_validation_layers); i++) {
+ // look through layers to make sure we can find the ones we want
+ bool found = false;
+ for (int j = 0; j < n_available_layers; j++) {
+ if (str8_equals(str8_cstr_view(desired_validation_layers->data[i]),
+ str8_cstr_view(available_layers->data[j].layerName))) {
+ found = true;
+ TRACE("Found layer %s", desired_validation_layers->data[i]);
+ break;
+ }
+ }
+
+ if (!found) {
+ FATAL("Required validation is missing %s", desired_validation_layers->data[i]);
+ return false;
+ }
+ }
+ INFO("All validation layers are present");
+ create_info.enabledLayerCount = cstr_darray_len(desired_validation_layers);
+ create_info.ppEnabledLayerNames = desired_validation_layers->data;
+#endif
+
+ VkResult result = vkCreateInstance(&create_info, NULL, &context.instance);
+ if (result != VK_SUCCESS) {
+ ERROR("vkCreateInstance failed with result: %u", result);
+ return false;
+ }
+
+ // Debugger
+#if defined(CDEBUG)
+ DEBUG("Creating Vulkan debugger")
+ u32 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 = vk_debug_callback;
+
+ PFN_vkCreateDebugUtilsMessengerEXT func =
+ (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(context.instance,
+ "vkCreateDebugUtilsMessengerEXT");
+ assert(func);
+ VK_CHECK(func(context.instance, &debug_create_info, context.allocator, &context.vk_debugger));
+ DEBUG("Vulkan debugger created");
+
+#endif
+
+ // Surface creation
+ DEBUG("Create SurfaceKHR")
+ VkSurfaceKHR surface;
+ VK_CHECK(glfwCreateWindowSurface(context.instance, ren->window, NULL, &surface));
+ context.surface = surface;
+ DEBUG("Vulkan surface created")
+
+ // Device creation
+ if (!vulkan_device_create(&context)) {
+ FATAL("device creation failed");
+ return false;
+ }
+
+ // Swapchain creation
+ vulkan_swapchain_create(&context, SCR_WIDTH, SCR_HEIGHT, &context.swapchain);
+
+ // Renderpass creation
+ vulkan_renderpass_create(&context, &context.main_renderpass,
+ vec4(0, 0, context.framebuffer_width, context.framebuffer_height),
+ rgba_to_vec4(COLOUR_SEA_GREEN), 1.0, 0);
+
+ // Framebiffers creation
+ context.swapchain.framebuffers = vulkan_framebuffer_darray_new(context.swapchain.image_count);
+ regenerate_framebuffers(ren, &context.swapchain, &context.main_renderpass);
+ INFO("Framebuffers created");
+
+ // Command buffers creation
+ create_command_buffers(ren);
+ INFO("Command buffers created");
+
+ // Sync objects
+ context.image_available_semaphores =
+ calloc(context.swapchain.max_frames_in_flight, sizeof(VkSemaphore));
+ context.queue_complete_semaphores =
+ calloc(context.swapchain.max_frames_in_flight, sizeof(VkSemaphore));
+ context.in_flight_fences = calloc(context.swapchain.max_frames_in_flight, sizeof(vulkan_fence));
+
+ for (u8 i = 0; i < context.swapchain.max_frames_in_flight; i++) {
+ VkSemaphoreCreateInfo semaphore_create_info = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
+ vkCreateSemaphore(context.device.logical_device, &semaphore_create_info, context.allocator,
+ &context.image_available_semaphores[i]);
+ vkCreateSemaphore(context.device.logical_device, &semaphore_create_info, context.allocator,
+ &context.queue_complete_semaphores[i]);
+
+ // create the fence in a signaled state
+ vulkan_fence_create(&context, true, &context.in_flight_fences[i]);
+ }
+
+ context.images_in_flight = malloc(sizeof(vulkan_fence*) * context.swapchain.max_frames_in_flight);
+ for (u8 i = 0; i < context.swapchain.max_frames_in_flight; i++) {
+ context.images_in_flight[i] = 0;
+ }
+ INFO("Sync objects created");
+
+ // Shader modules
+ vulkan_object_shader_create(&context, &context.object_shader);
+ INFO("Compiled shader modules")
+
+ create_buffers(&context);
+ INFO("Created buffers");
+
+ // TODO: temporary test code
+
+ mesh cube = prim_cube_mesh_create();
+
+ vertex* verts = malloc(sizeof(vertex) * cube.vertices->len);
+
+ f32 scale = 3.0;
+ for (size_t i = 0; i < cube.vertices->len; i++) {
+ verts[i].position = vec3_mult(cube.vertices->data[i].position, scale);
+ verts[i].normal = cube.vertices->data[i].normal;
+ verts[i].uv = cube.vertices->data[i].uv;
+ }
+
+ // const f32 s = 1.0;
+ // const u32 vert_count = 4;
+ // vertex_pos verts[4] = { 0 };
+
+ // verts[0].pos.x = -0.5 * s;
+ // verts[0].pos.y = -0.5 * s;
+
+ // verts[1].pos.x = 0.5 * s;
+ // verts[1].pos.y = 0.5 * s;
+
+ // verts[2].pos.x = -0.5 * s;
+ // verts[2].pos.y = 0.5 * s;
+
+ // verts[3].pos.x = 0.5 * s;
+ // verts[3].pos.y = -0.5 * s;
+
+ // const u32 index_count = 6;
+ // u32 indices[6] = { 0, 1, 2, 0, 3, 1 };
+
+ upload_data_range(&context, context.device.gfx_command_pool, 0, context.device.graphics_queue,
+ &context.object_vertex_buffer, 0, sizeof(vertex) * cube.vertices->len, verts);
+ TRACE("Uploaded vertex data");
+ upload_data_range(&context, context.device.gfx_command_pool, 0, context.device.graphics_queue,
+ &context.object_index_buffer, 0, sizeof(u32) * cube.indices_len, cube.indices);
+ TRACE("Uploaded index data");
+ vertex_darray_free(cube.vertices);
+ free(cube.indices);
+
+ // upload texture
+
+ // --- End test code
+
+ INFO("Vulkan renderer initialisation succeeded");
+ return true;
+}
+
+void gfx_backend_shutdown(renderer* ren) {
+ DEBUG("Destroying Vulkan debugger");
+ if (context.vk_debugger) {
+ PFN_vkDestroyDebugUtilsMessengerEXT func =
+ (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
+ context.instance, "vkDestroyDebugUtilsMessengerEXT");
+ func(context.instance, context.vk_debugger, context.allocator);
+ }
+
+ DEBUG("Destroying Vulkan instance...");
+ vkDestroyInstance(context.instance, context.allocator);
+}
+
+void backend_begin_frame(renderer* ren, f32 delta_time) {
+ vulkan_device* device = &context.device;
+
+ // TODO: resize gubbins
+
+ if (!vulkan_fence_wait(&context, &context.in_flight_fences[context.current_frame], UINT64_MAX)) {
+ WARN("In-flight fence wait failure");
+ }
+
+ if (!vulkan_swapchain_acquire_next_image_index(
+ &context, &context.swapchain, UINT64_MAX,
+ context.image_available_semaphores[context.current_frame], 0, &context.image_index)) {
+ WARN("couldnt acquire swapchain next image");
+ }
+
+ vulkan_command_buffer* command_buffer = &context.gfx_command_buffers->data[context.image_index];
+ vulkan_command_buffer_reset(command_buffer);
+ vulkan_command_buffer_begin(command_buffer, false, false, false);
+
+ VkViewport viewport;
+ viewport.x = 0.0;
+ viewport.y = 0;
+ viewport.width = (f32)context.framebuffer_width;
+ viewport.height = (f32)context.framebuffer_height;
+ viewport.minDepth = 0.0;
+ viewport.maxDepth = 1.0;
+
+ VkRect2D scissor;
+ scissor.offset.x = scissor.offset.y = 0;
+ scissor.extent.width = context.framebuffer_width;
+ scissor.extent.height = context.framebuffer_height;
+
+ vkCmdSetViewport(command_buffer->handle, 0, 1, &viewport);
+ vkCmdSetScissor(command_buffer->handle, 0, 1, &scissor);
+
+ context.main_renderpass.render_area.z = context.framebuffer_width;
+ context.main_renderpass.render_area.w = context.framebuffer_height;
+
+ vulkan_renderpass_begin(command_buffer, &context.main_renderpass,
+ context.swapchain.framebuffers->data[context.image_index].handle);
+}
+
+void texture_data_upload(texture* tex) {
+ printf("Texture name %s\n", tex->name);
+ tex->backend_data = malloc(sizeof(vulkan_texture_data));
+ vulkan_texture_data* data = (vulkan_texture_data*)tex->backend_data;
+ printf("Texture (%s) details: \n width %d\n height %d\n channel count %d\n", tex->name,
+ tex->width, tex->height, tex->channel_count);
+ VkDeviceSize image_size = tex->width * tex->height * max(tex->channel_count, 4);
+
+ TRACE("Creating buffer of size %ld", image_size);
+
+ VkFormat image_format = VK_FORMAT_R8G8B8A8_SRGB;
+
+ VkBufferUsageFlags usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+ VkMemoryPropertyFlags memory_prop_flags =
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+ vulkan_buffer staging;
+ vulkan_buffer_create(&context, image_size, usage, memory_prop_flags, true, &staging);
+ DEBUG("Uploading image data");
+ vulkan_buffer_load_data(&context, &staging, 0, image_size, 0, tex->image_data);
+ INFO("Loaded iamge data!");
+
+ vulkan_image_create(
+ &context, VK_IMAGE_TYPE_2D, tex->width, tex->height, image_format, VK_IMAGE_TILING_OPTIMAL,
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, true, VK_IMAGE_ASPECT_COLOR_BIT, &data->image);
+
+ vulkan_command_buffer temp_buffer;
+ vulkan_command_buffer_allocate_and_begin_oneshot(&context, context.device.gfx_command_pool,
+ &temp_buffer);
+
+ vulkan_image_transition_layout(&context, &temp_buffer, &data->image, image_format,
+ VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+
+ vulkan_image_copy_from_buffer(&data->image, staging.handle, &temp_buffer);
+
+ vulkan_image_transition_layout(&context, &temp_buffer, &data->image, image_format,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+ vulkan_command_buffer_end_oneshot(&context, context.device.gfx_command_pool, &temp_buffer,
+ context.device.graphics_queue);
+
+ VkSamplerCreateInfo sampler_info = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
+ sampler_info.magFilter = VK_FILTER_LINEAR;
+ sampler_info.minFilter = VK_FILTER_LINEAR;
+ sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+ sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+ sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+ sampler_info.anisotropyEnable = VK_TRUE;
+ sampler_info.maxAnisotropy = 16;
+ sampler_info.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
+ sampler_info.unnormalizedCoordinates = VK_FALSE;
+ sampler_info.compareEnable = VK_FALSE;
+ sampler_info.compareOp = VK_COMPARE_OP_ALWAYS;
+ sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+ sampler_info.mipLodBias = 0.0;
+ sampler_info.minLod = 0.0;
+ sampler_info.maxLod = 0.0;
+
+ VkResult res = vkCreateSampler(context.device.logical_device, &sampler_info, context.allocator,
+ &data->sampler);
+ if (res != VK_SUCCESS) {
+ ERROR("Error creating texture sampler for image %s", tex->name);
+ return;
+ }
+
+ tex->image_data = (void*)data;
+}
+
+// TODO: destroy texture
+
+void backend_end_frame(renderer* ren, f32 delta_time) {
+ vulkan_command_buffer* command_buffer = &context.gfx_command_buffers->data[context.image_index];
+
+ vulkan_renderpass_end(command_buffer, &context.main_renderpass);
+
+ vulkan_command_buffer_end(command_buffer);
+
+ // TODO: wait on fence - https://youtu.be/hRL71D1f3pU?si=nLJx-ZsemDBeQiQ1&t=1037
+
+ context.images_in_flight[context.image_index] = &context.in_flight_fences[context.current_frame];
+
+ vulkan_fence_reset(&context, &context.in_flight_fences[context.current_frame]);
+
+ VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
+ submit_info.commandBufferCount = 1;
+ submit_info.pCommandBuffers = &command_buffer->handle;
+ submit_info.signalSemaphoreCount = 1;
+ submit_info.pSignalSemaphores = &context.queue_complete_semaphores[context.current_frame];
+ submit_info.waitSemaphoreCount = 1;
+ submit_info.pWaitSemaphores = &context.image_available_semaphores[context.current_frame];
+
+ VkPipelineStageFlags flags[1] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
+ submit_info.pWaitDstStageMask = flags;
+
+ VkResult result = vkQueueSubmit(context.device.graphics_queue, 1, &submit_info,
+ context.in_flight_fences[context.current_frame].handle);
+
+ if (result != VK_SUCCESS) {
+ ERROR("queue submission failed. fark.");
+ }
+
+ vulkan_command_buffer_update_submitted(command_buffer);
+
+ vulkan_swapchain_present(
+ &context, &context.swapchain, context.device.graphics_queue, context.device.graphics_queue,
+ context.queue_complete_semaphores[context.current_frame], context.image_index);
+}
+
+void gfx_backend_draw_frame(renderer* ren, camera* cam, mat4 model, texture* tex) {
+ backend_begin_frame(ren, 16.0);
+
+ mat4 proj;
+ mat4 view;
+
+ camera_view_projection(cam, SCR_HEIGHT, SCR_WIDTH, &view, &proj);
+
+ context.object_shader.texture_data = (vulkan_texture_data*)tex->image_data;
+ gfx_backend_update_global_state(proj, view, cam->position, vec4(1.0, 1.0, 1.0, 1.0), 0);
+
+ vulkan_object_shader_update_object(&context, &context.object_shader, model);
+
+ backend_end_frame(ren, 16.0);
+}
+
+void gfx_backend_update_global_state(mat4 projection, mat4 view, vec3 view_pos, vec4 ambient_colour,
+ i32 mode) {
+ vulkan_object_shader_use(&context, &context.object_shader);
+
+ vulkan_object_shader_update_global_state(&context, &context.object_shader);
+ context.object_shader.global_ubo.projection = projection;
+ context.object_shader.global_ubo.view = view;
+ // TODO: other UBO properties
+}
+
+void clear_screen(vec3 colour) {}
+
+void bind_texture(shader s, texture* tex, u32 slot) {}
+void bind_mesh_vertex_buffer(void* backend, mesh* mesh) {}
+void draw_primitives(cel_primitive_topology primitive, u32 start_index, u32 count) {}
+
+shader shader_create_separate(const char* vert_shader, const char* frag_shader) {}
+void set_shader(shader s) {}
+
+void uniform_vec3f(u32 program_id, const char* uniform_name, vec3* value) {}
+void uniform_f32(u32 program_id, const char* uniform_name, f32 value) {}
+void uniform_i32(u32 program_id, const char* uniform_name, i32 value) {}
+void uniform_mat4f(u32 program_id, const char* uniform_name, mat4* value) {}
+
+VKAPI_ATTR VkBool32 VKAPI_CALL vk_debug_callback(
+ VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT flags,
+ const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) {
+ switch (severity) {
+ default:
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
+ ERROR("%s", callback_data->pMessage);
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
+ WARN("%s", callback_data->pMessage);
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
+ INFO("%s", callback_data->pMessage);
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
+ TRACE("%s", callback_data->pMessage);
+ break;
+ }
+ return VK_FALSE;
+}
+
+#endif \ No newline at end of file
diff --git a/src/renderer/backends/vulkan/vulkan_glossary.md b/src/renderer/backends/vulkan/vulkan_glossary.md
new file mode 100644
index 0000000..4214f9d
--- /dev/null
+++ b/src/renderer/backends/vulkan/vulkan_glossary.md
@@ -0,0 +1,18 @@
+# Vulkan Glossary
+
+*from https://vkguide.dev/docs/introduction/vulkan_execution/*
+
+- **VkInstance**: The Vulkan context, used to access drivers.
+- **VkPhysicalDevice**: A GPU. Used to query physical GPU details, like features, capabilities, memory size, etc.
+- **VkDevice**: The “logical” GPU context that you actually execute things on.
+- **VkBuffer**: A chunk of GPU visible memory.
+- **VkImage**: A texture you can write to and read from.
+- **VkPipeline**: Holds the state of the gpu needed to draw. For example: shaders, rasterization options, depth settings.
+- **VkRenderPass**: Holds information about the images you are rendering into. All drawing commands have to be done inside a renderpass. Only used in legacy vkguide.
+- **VkFrameBuffer**: Holds the target images for a renderpass. Only used in legacy vkguide.
+- **VkCommandBuffer**: Encodes GPU commands. All execution that is performed on the GPU itself (not in the driver) has to be encoded in a VkCommandBuffer.
+- **VkQueue**: Execution “port” for commands. GPUs will have a set of queues with different properties. Some allow only graphics commands, others only allow memory commands, etc. Command buffers are executed by submitting them into a queue, which will copy the rendering commands onto the GPU for execution.
+- **VkDescriptorSet**: Holds the binding information that connects shader inputs to data such as VkBuffer resources and VkImage textures. Think of it as a set of gpu-side pointers that you bind once.
+- **VkSwapchainKHR**: Holds the images for the screen. It allows you to render things into a visible window. The KHR suffix shows that it comes from an extension, which in this case is VK_KHR_swapchain.
+- **VkSemaphore**: Synchronizes GPU to GPU execution of commands. Used for syncing multiple command buffer submissions one after another.
+- **VkFence**: Synchronizes GPU to CPU execution of commands. Used to know if a command buffer has finished being executed on the GPU.
diff --git a/src/renderer/backends/vulkan_helpers.h b/src/renderer/backends/vulkan_helpers.h
new file mode 100644
index 0000000..3465aed
--- /dev/null
+++ b/src/renderer/backends/vulkan_helpers.h
@@ -0,0 +1,166 @@
+#pragma once
+
+#include <assert.h>
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_core.h>
+
+#include "darray.h"
+#include "defines.h"
+#include "log.h"
+#include "str.h"
+
+#define VULKAN_PHYS_DEVICE_MAX_EXTENSION_NAMES 36
+
+DECL_TYPED_ARRAY(const char*, cstr)
+
+static void plat_get_required_extension_names(cstr_darray* extensions) {
+#ifdef CEL_PLATFORM_LINUX
+ cstr_darray_push(extensions, "VK_KHR_xcb_surface");
+#endif
+}
+
+// TODO(omni): port to using internal assert functions
+#define VK_CHECK(vulkan_expr) \
+ { assert(vulkan_expr == VK_SUCCESS); }
+
+// TODO: typedef struct vk_debugger {} vk_debugger;
+
+typedef struct vulkan_physical_device_requirements {
+ bool graphics;
+ bool present;
+ bool compute;
+ bool transfer;
+ str8 device_ext_names[VULKAN_PHYS_DEVICE_MAX_EXTENSION_NAMES];
+ size_t device_ext_name_count;
+ bool sampler_anistropy;
+ bool discrete_gpu;
+} vulkan_physical_device_requirements;
+
+typedef struct vulkan_physical_device_queue_family_info {
+ u32 graphics_family_index;
+ u32 present_family_index;
+ u32 compute_family_index;
+ u32 transfer_family_index;
+} vulkan_physical_device_queue_family_info;
+
+#define VULKAN_MAX_DEFAULT 32
+
+typedef struct vulkan_swapchain_support_info {
+ VkSurfaceCapabilitiesKHR capabilities;
+ VkSurfaceFormatKHR formats[VULKAN_MAX_DEFAULT];
+ u32 format_count;
+ VkPresentModeKHR present_modes[VULKAN_MAX_DEFAULT];
+ u32 mode_count;
+} vulkan_swapchain_support_info;
+
+VKAPI_ATTR VkBool32 VKAPI_CALL vk_debug_callback(
+ VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT flags,
+ const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data);
+
+void vulkan_device_query_swapchain_support(VkPhysicalDevice device, VkSurfaceKHR surface,
+ vulkan_swapchain_support_info* out_support_info) {
+ // TODO: add VK_CHECK to these calls!
+
+ // Surface capabilities
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &out_support_info->capabilities);
+
+ // Surface formats
+ vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &out_support_info->format_count,
+ 0); // Get number of formats
+ if (out_support_info->format_count > 0) {
+ vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &out_support_info->format_count,
+ out_support_info->formats);
+ }
+
+ // Present Modes
+ vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &out_support_info->mode_count,
+ 0); // Get number of formats
+ if (out_support_info->mode_count > 0) {
+ vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &out_support_info->mode_count,
+ out_support_info->present_modes);
+ }
+}
+
+static bool physical_device_meets_requirements(
+ VkPhysicalDevice device, VkSurfaceKHR surface, const VkPhysicalDeviceProperties* properties,
+ const VkPhysicalDeviceFeatures* features,
+ const vulkan_physical_device_requirements* requirements,
+ vulkan_physical_device_queue_family_info* out_queue_info,
+ vulkan_swapchain_support_info* out_swapchain_support) {
+ // TODO: pass in an arena
+
+ out_queue_info->graphics_family_index = -1;
+ out_queue_info->present_family_index = -1;
+ out_queue_info->compute_family_index = -1;
+ out_queue_info->transfer_family_index = -1;
+
+ if (requirements->discrete_gpu) {
+ if (properties->deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
+ TRACE("Device is not a physical GPU. Skipping.");
+ return false;
+ }
+ }
+
+ u32 queue_family_count = 0;
+ vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, 0);
+ VkQueueFamilyProperties queue_families[queue_family_count];
+ vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families);
+
+ INFO("Graphics | Present | Compute | Transfer | Name");
+ u8 min_transfer_score = 255;
+ for (u32 i = 0; i < queue_family_count; i++) {
+ u8 current_transfer_score = 0;
+
+ // Graphics queue
+ if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+ out_queue_info->graphics_family_index = i;
+ current_transfer_score++;
+ }
+
+ // Compute queue
+ if (queue_families[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
+ out_queue_info->compute_family_index = i;
+ current_transfer_score++;
+ }
+
+ // Transfer queue
+ if (queue_families[i].queueFlags & VK_QUEUE_TRANSFER_BIT) {
+ // always take the lowest score transfer index
+ if (current_transfer_score <= min_transfer_score) {
+ min_transfer_score = current_transfer_score;
+ out_queue_info->transfer_family_index = i;
+ }
+ }
+
+ // Present Queue
+ VkBool32 supports_present = VK_FALSE;
+ vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &supports_present);
+ if (supports_present) {
+ out_queue_info->present_family_index = i;
+ }
+ }
+
+ INFO(" %d | %d | %d | %d | %s",
+ out_queue_info->graphics_family_index != -1, out_queue_info->present_family_index != -1,
+ out_queue_info->compute_family_index != -1, out_queue_info->transfer_family_index != -1,
+ properties->deviceName);
+ TRACE("Graphics Family queue index: %d", out_queue_info->graphics_family_index);
+ TRACE("Present Family queue index: %d", out_queue_info->present_family_index);
+ TRACE("Compute Family queue index: %d", out_queue_info->compute_family_index);
+ TRACE("Transfer Family queue index: %d", out_queue_info->transfer_family_index);
+
+ if ((!requirements->graphics ||
+ (requirements->graphics && out_queue_info->graphics_family_index != -1))) {
+ INFO("Physical device meets our requirements! Proceed.");
+
+ vulkan_device_query_swapchain_support(
+ device, surface, out_swapchain_support
+
+ // TODO: error handling i.e. format count = 0 or present mode = 0
+
+ );
+ return true;
+ }
+
+ return false;
+} \ No newline at end of file
diff --git a/src/renderer/cleanroom/README.md b/src/renderer/cleanroom/README.md
new file mode 100644
index 0000000..d510f16
--- /dev/null
+++ b/src/renderer/cleanroom/README.md
@@ -0,0 +1 @@
+# Cleanroom / Re-jig of the renderer structure \ No newline at end of file
diff --git a/src/renderer/cleanroom/types.h b/src/renderer/cleanroom/types.h
new file mode 100644
index 0000000..3f62cab
--- /dev/null
+++ b/src/renderer/cleanroom/types.h
@@ -0,0 +1,200 @@
+#pragma once
+#include "darray.h"
+#include "defines.h"
+#include "maths_types.h"
+#include "str.h"
+
+typedef int texture_handle;
+typedef int buffer_handle;
+typedef int model_handle;
+
+/** @brief Texture Description - used by texture creation functions */
+typedef struct texture_desc {
+ // gpu_texture_type tex_type;
+ // gpu_texture_format format;
+ // u32x2 extents;
+} texture_desc;
+
+/*
+ - render_types.h
+ - ral_types.h
+ - ral.h
+ - render.h ?
+*/
+
+// gpu types
+typedef enum gpu_primitive_topology {
+ CEL_PRIMITIVE_TOPOLOGY_POINT,
+ CEL_PRIMITIVE_TOPOLOGY_LINE,
+ CEL_PRIMITIVE_TOPOLOGY_LINE_STRIP,
+ CEL_PRIMITIVE_TOPOLOGY_TRIANGLE,
+ CEL_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
+ CEL_PRIMITIVE_TOPOLOGY_COUNT
+} cel_primitive_topology;
+
+typedef enum gpu_texture_type {
+ CEL_TEXTURE_TYPE_2D,
+ CEL_TEXTURE_TYPE_3D,
+ CEL_TEXTURE_TYPE_2D_ARRAY,
+ CEL_TEXTURE_TYPE_CUBE_MAP,
+ CEL_TEXTURE_TYPE_COUNT
+} gpu_texture_type;
+
+typedef enum gpu_texture_format {
+ CEL_TEXTURE_FORMAT_8_8_8_8_RGBA_UNORM,
+ CEL_TEXTURE_FORMAT_DEPTH_DEFAULT,
+ CEL_TEXTURE_FORMAT_COUNT
+} gpu_texture_format;
+
+/* render_types */
+typedef struct mesh mesh;
+typedef struct model model;
+typedef struct model pbr_material;
+typedef struct model bp_material; // blinn-phong
+
+#include "maths_types.h"
+
+typedef enum vertex_format { VERTEX_STATIC_3D, VERTEX_SPRITE, VERTEX_COUNT } vertex_format;
+
+typedef union vertex {
+ struct {
+ vec3 position;
+ vec4 colour;
+ vec2 tex_coords;
+ vec3 normal;
+ } static_3d; /** @brief standard vertex format for static geometry in 3D */
+
+ struct {
+ vec2 position;
+ vec4 colour;
+ vec2 tex_coords;
+ } sprite;
+
+ struct {
+ vec3 position;
+ vec4 colour;
+ vec2 tex_coords;
+ vec3 normal;
+ vec4i bone_ids; // Integer vector for bone IDs
+ vec4 bone_weights; // Weight of each bone's influence
+ } animated_3d; /** @brief vertex format for skeletal (animated) geometry in 3D */
+} vertex;
+
+KITC_DECL_TYPED_ARRAY(vertex)
+KITC_DECL_TYPED_ARRAY(u32)
+
+typedef struct geometry_data {
+ vertex_format format;
+ vertex_darray vertices;
+ u32_darray indices;
+} geometry_data;
+
+typedef struct mesh {
+ buffer_handle vertex_buffer;
+ buffer_handle index_buffer;
+ u32 index_count;
+ bool has_indices;
+ geometry_data* vertices; // NULL means it has been freed
+} mesh;
+
+/* Hot reloading:
+C side - reload_model():
+ - load model from disk using existing loader
+ - remove from transform graph so it isnt tried to be drawn
+ -
+
+*/
+
+typedef struct model {
+ str8 debug_name;
+ mesh* meshes;
+ u32 mesh_count;
+} model;
+
+// ? How to tie together materials and shaders
+
+// Three registers
+// 1. low level graphics api calls "ral"
+// 2. higher level render calls
+// 3. simplified immediate mode API
+
+// 3 - you don't need to know how the renderer works at all
+// 2 - you need to know how the overall renderer is designed
+// 1 - you need to understand graphics API specifics
+
+/* render.h */
+// frontend -- these can be called from say a loop in an example, or via FFI
+texture_handle texture_create(const char* debug_name, texture_desc description, const u8* data);
+
+void texture_data_upload(texture_handle texture);
+buffer_handle buffer_create(const char* debug_name, u64 size);
+bool buffer_destroy(buffer_handle buffer);
+
+// models and meshes are implemented **in terms of the above**
+mesh mesh_create(geometry_data* geometry);
+model_handle model_load(const char* debug_name, const char* filepath);
+
+/* ral.h */
+
+enum pipeline_type {
+ GRAPHICS,
+ COMPUTE,
+} pipeline_type;
+
+// backend -- these are not seen by the higher-level code
+typedef struct gpu_swapchain gpu_swapchain;
+typedef struct gpu_device gpu_device;
+typedef struct gpu_pipeline gpu_pipeline;
+typedef struct gpu_cmd_encoder gpu_cmd_encoder;
+typedef struct gpu_cmd_buffer gpu_cmd_buffer; // Ready for submission
+
+void gpu_cmd_encoder_begin();
+void gpu_cmd_encoder_begin_render();
+void gpu_cmd_encoder_begin_compute();
+
+/* Actual commands that we can encode */
+void encode_buffer_copy(gpu_cmd_encoder* encoder, buffer_handle src, u64 src_offset,
+ buffer_handle dst, u64 dst_offset, u64 copy_size);
+void encode_clear_buffer(gpu_cmd_encoder* encoder, buffer_handle buf);
+// render pass
+void encode_set_vertex_buffer(gpu_cmd_encoder* encoder, buffer_handle buf);
+void encode_set_index_buffer(gpu_cmd_encoder* encoder, buffer_handle buf);
+void encode_draw_indexed(gpu_cmd_encoder* encoder, u64 index_count, u64* indices);
+
+// FUTURE: compute passes
+
+/** @brief Finish recording and return a command buffer that can be submitted to a queue */
+gpu_cmd_buffer gpu_cmd_encoder_finish(gpu_cmd_encoder* encoder);
+
+void gpu_queue_submit(gpu_cmd_buffer* buffer);
+
+// Buffers
+void gpu_buffer_create(u64 size);
+void gpu_buffer_destroy(buffer_handle buffer);
+void gpu_buffer_upload();
+void gpu_buffer_bind(buffer_handle buffer);
+
+// Textures
+void gpu_texture_create();
+void gpu_texture_destroy();
+void gpu_texture_upload();
+
+// Samplers
+void gpu_sampler_create();
+
+// command buffer gubbins
+
+// 3. SIMA (simplified immediate mode api) / render.h
+// - dont need to worry about uploading mesh data
+// - very useful for debugging
+void imm_draw_cuboid();
+void imm_draw_sphere(vec3 pos, f32 radius, vec4 colour);
+void imm_draw_camera_frustum();
+static void imm_draw_model(
+ const char* model_filepath); // tracks internally whether the model is loaded
+
+static void imm_draw_model(const char* model_filepath) {
+ // check that model is loaded
+ // if not loaded, load model and upload to gpu - LRU cache for models
+ // else submit draw call
+} \ No newline at end of file
diff --git a/src/renderer/render.c b/src/renderer/render.c
index 9d250c6..2993225 100644
--- a/src/renderer/render.c
+++ b/src/renderer/render.c
@@ -17,8 +17,8 @@
// FIXME: get rid of these and store dynamic screen realestate
// in renderer
-#define SCR_WIDTH 1080
-#define SCR_HEIGHT 800
+#define SCR_WIDTH 1000
+#define SCR_HEIGHT 1000
material DEFAULT_MATERIAL = { 0 };
@@ -28,17 +28,20 @@ bool renderer_init(renderer* ren) {
// NOTE: all platforms use GLFW at the moment but thats subject to change
glfwInit();
- DEBUG("init graphics api (OpenGL) backend");
+#if defined(CEL_REND_BACKEND_OPENGL)
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+#elif defined(CEL_REND_BACKEND_VULKAN)
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+#endif
// glfw window creation
GLFWwindow* window = glfwCreateWindow(ren->config.scr_width, ren->config.scr_height,
ren->config.window_name, NULL, NULL);
if (window == NULL) {
- printf("Failed to create GLFW window\n");
+ ERROR("Failed to create GLFW window\n");
glfwTerminate();
return false;
}
@@ -46,6 +49,7 @@ bool renderer_init(renderer* ren) {
glfwMakeContextCurrent(ren->window);
+ DEBUG("init graphics api backend");
if (!gfx_backend_init(ren)) {
FATAL("Couldnt load graphics api backend");
return false;
@@ -59,6 +63,8 @@ bool renderer_init(renderer* ren) {
return true;
}
+void renderer_shutdown(renderer* ren) {}
+
void render_frame_begin(renderer* ren) {
vec3 color = ren->config.clear_colour;
clear_screen(color);
@@ -220,7 +226,7 @@ texture texture_data_load(const char* path, bool invert_y) {
stbi_set_flip_vertically_on_load(invert_y);
#pragma GCC diagnostic ignored "-Wpointer-sign"
- char* data = stbi_load(path, &width, &height, &num_channels, 0);
+ char* data = stbi_load(path, &width, &height, &num_channels, STBI_rgb_alpha);
if (data) {
DEBUG("loaded texture: %s", path);
} else {
@@ -243,29 +249,6 @@ texture texture_data_load(const char* path, bool invert_y) {
.image_data = data };
}
-void texture_data_upload(texture* tex) {
- printf("Texture name %s\n", tex->name);
- TRACE("Upload texture data");
- u32 texture_id;
- glGenTextures(1, &texture_id);
- glBindTexture(GL_TEXTURE_2D, texture_id);
- tex->texture_id = texture_id;
-
- // set the texture wrapping parameters
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
- GL_REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method)
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
- // set texture filtering parameters
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tex->width, tex->height, 0, tex->channel_type,
- GL_UNSIGNED_BYTE, tex->image_data);
- glGenerateMipmap(GL_TEXTURE_2D);
- DEBUG("Freeing texture image data after uploading to GPU");
- // stbi_image_free(tex->image_data); // data is on gpu now so we dont need it around
-}
-
void dir_light_upload_uniforms(shader shader, directional_light* light) {
uniform_vec3f(shader.program_id, "dirLight.direction", &light->direction);
uniform_vec3f(shader.program_id, "dirLight.ambient", &light->ambient);
diff --git a/src/renderer/render.h b/src/renderer/render.h
index 10702e3..b6dad00 100644
--- a/src/renderer/render.h
+++ b/src/renderer/render.h
@@ -1,3 +1,13 @@
+/**
+ * @file render.h
+ * @author your name (you@domain.com)
+ * @brief Renderer frontend
+ * @version 0.1
+ * @date 2024-03-21
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
#pragma once
#include "camera.h"
@@ -10,10 +20,15 @@ bool renderer_init(renderer* ren);
/** @brief shutdown the render system frontend */
void renderer_shutdown(renderer* ren);
+void renderer_on_resize(renderer* ren);
+
+struct render_packet;
+
// --- Frame
void render_frame_begin(renderer* ren);
void render_frame_end(renderer* ren);
+void render_frame_draw(renderer* ren);
// --- models meshes
void model_upload_meshes(renderer* ren, model* model);
@@ -22,15 +37,3 @@ void draw_mesh(renderer* ren, mesh* mesh, transform tf, material* mat, mat4* vie
// ---
texture texture_data_load(const char* path, bool invert_y); // #frontend
-void texture_data_upload(texture* tex); // #backend
-
-// --- Uniforms
-
-/** @brief upload a vec3 of f32 to a uniform */
-void uniform_vec3f(u32 program_id, const char* uniform_name, vec3* value);
-/** @brief upload a single f32 to a uniform */
-void uniform_f32(u32 program_id, const char* uniform_name, f32 value);
-/** @brief upload a integer to a uniform */
-void uniform_i32(u32 program_id, const char* uniform_name, i32 value);
-/** @brief upload a mat4 of f32 to a uniform */
-void uniform_mat4f(u32 program_id, const char* uniform_name, mat4* value); \ No newline at end of file
diff --git a/src/renderer/render_backend.h b/src/renderer/render_backend.h
index 7e1c16c..da30bcc 100644
--- a/src/renderer/render_backend.h
+++ b/src/renderer/render_backend.h
@@ -1,17 +1,25 @@
/**
- * @brief
+ * @brief Renderer backend
*/
#pragma once
+#include "camera.h"
#include "maths_types.h"
#include "render_types.h"
/// --- Lifecycle
+
+/** @brief Initialise the graphics API backend */
bool gfx_backend_init(renderer* ren);
void gfx_backend_shutdown(renderer* ren);
+void gfx_backend_draw_frame(renderer* ren, camera* camera, mat4 model, texture* tex);
+void gfx_backend_update_global_state(mat4 projection, mat4 view, vec3 view_pos, vec4 ambient_colour,
+ i32 mode);
+
void clear_screen(vec3 colour);
+void texture_data_upload(texture* tex);
void bind_texture(shader s, texture* tex, u32 slot);
void bind_mesh_vertex_buffer(void* backend, mesh* mesh);
void draw_primitives(cel_primitive_topology primitive, u32 start_index, u32 count);
@@ -20,3 +28,12 @@ shader shader_create_separate(const char* vert_shader, const char* frag_shader);
void set_shader(shader s);
// --- Uniforms
+
+/** @brief upload a vec3 of f32 to a uniform */
+void uniform_vec3f(u32 program_id, const char* uniform_name, vec3* value);
+/** @brief upload a single f32 to a uniform */
+void uniform_f32(u32 program_id, const char* uniform_name, f32 value);
+/** @brief upload a integer to a uniform */
+void uniform_i32(u32 program_id, const char* uniform_name, i32 value);
+/** @brief upload a mat4 of f32 to a uniform */
+void uniform_mat4f(u32 program_id, const char* uniform_name, mat4* value); \ No newline at end of file
diff --git a/src/renderer/render_types.h b/src/renderer/render_types.h
index 483e392..80e1372 100644
--- a/src/renderer/render_types.h
+++ b/src/renderer/render_types.h
@@ -8,6 +8,7 @@
#pragma once
#include "darray.h"
+#include "maths.h"
#include "maths_types.h"
#include "str.h"
@@ -34,6 +35,10 @@ typedef struct renderer_config {
vec3 clear_colour; /** colour that the screen gets cleared to every frame */
} renderer_config;
+typedef struct frame_stats {
+ u64 last_time;
+} frame_stats;
+
typedef struct renderer {
struct GLFWwindow *window; /** Currently all platforms use GLFW*/
void *backend_state; /** Graphics API-specific state */
@@ -47,7 +52,8 @@ typedef struct renderer {
typedef struct texture {
u32 texture_id;
char name[MAX_TEXTURE_NAME_LEN];
- void *image_data;
+ void* image_data;
+ void* backend_data;
u32 width;
u32 height;
u8 channel_count;
@@ -174,4 +180,4 @@ typedef enum gpu_texture_format {
TEXTURE_FORMAT_8_8_8_8_RGBA_UNORM,
TEXTURE_FORMAT_DEPTH_DEFAULT,
TEXTURE_FORMAT_COUNT
-} gpu_texture_format;
+} gpu_texture_format; \ No newline at end of file