diff options
Diffstat (limited to 'src/renderer')
26 files changed, 5660 insertions, 417 deletions
diff --git a/src/renderer/archive/old_backend_vulkan.c b/src/renderer/archive/old_backend_vulkan.c new file mode 100644 index 0000000..a18ca70 --- /dev/null +++ b/src/renderer/archive/old_backend_vulkan.c @@ -0,0 +1,1990 @@ +#include "camera.h" +#include "primitives.h" +#define CDEBUG +#define CEL_REND_BACKEND_VULKAN 1 +#if CEL_REND_BACKEND_VULKAN +// ^ Temporary + +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <vk_platform.h> +#include <vulkan.h> +#include <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 }; + 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, ®ion); + } + + 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, ©_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/archive/render.c b/src/renderer/archive/render.c new file mode 100644 index 0000000..b1e2a46 --- /dev/null +++ b/src/renderer/archive/render.c @@ -0,0 +1,413 @@ +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include "animation.h" +#include "maths_types.h" +#include "mem.h" +#include "transform_hierarchy.h" +#define STB_IMAGE_IMPLEMENTATION +#include <stb_image.h> + +#define STB_TRUETYPE_IMPLEMENTATION +#include <stb_truetype.h> + +#include "render.h" +#include "render_types.h" + +#include <glad/glad.h> +#include <glfw3.h> + +#include "defines.h" +#include "log.h" +#include "maths.h" +#include "render_backend.h" + +// FIXME: get rid of these and store dynamic screen realestate +// in renderer +#define SCR_WIDTH 1000 +#define SCR_HEIGHT 1000 + +material DEFAULT_MATERIAL = { 0 }; + +bool renderer_init(renderer* ren) { + INFO("Renderer init"); + + // NOTE: all platforms use GLFW at the moment but thats subject to change + glfwInit(); + +#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) { + ERROR("Failed to create GLFW window\n"); + glfwTerminate(); + return false; + } + ren->window = window; + + glfwMakeContextCurrent(ren->window); + + DEBUG("init graphics api backend"); + if (!gfx_backend_init(ren)) { + FATAL("Couldnt load graphics api backend"); + return false; + } + + ren->blinn_phong = + shader_create_separate("assets/shaders/blinn_phong.vert", "assets/shaders/blinn_phong.frag"); + + ren->skinned = + shader_create_separate("assets/shaders/skinned.vert", "assets/shaders/blinn_phong.frag"); + + default_material_init(); + + return true; +} + +void renderer_shutdown(renderer* ren) {} + +void render_frame_begin(renderer* ren) { + vec3 color = ren->config.clear_colour; + clear_screen(color); +} +void render_frame_end(renderer* ren) { + // present frame + glfwSwapBuffers(ren->window); + glfwPollEvents(); +} + +void default_material_init() { + INFO("Load default material") + DEFAULT_MATERIAL.ambient_colour = (vec3){ 0.5, 0.5, 0.5 }; + DEFAULT_MATERIAL.diffuse = (vec3){ 0.8, 0.8, 0.8 }; + DEFAULT_MATERIAL.specular = (vec3){ 1.0, 1.0, 1.0 }; + DEFAULT_MATERIAL.diffuse_texture = texture_data_load("assets/textures/white1x1.png", false); + DEFAULT_MATERIAL.specular_texture = texture_data_load("assets/textures/black1x1.png", false); + DEFAULT_MATERIAL.spec_exponent = 32.0; + strcpy(DEFAULT_MATERIAL.name, "Default"); + texture_data_upload(&DEFAULT_MATERIAL.diffuse_texture); + texture_data_upload(&DEFAULT_MATERIAL.specular_texture); +} + +void model_destroy(model* model) { + TRACE("Freeing all data for model %s", model->name); + arena_free_all(&model->animation_data_arena); + arena_free_storage(&model->animation_data_arena); + mesh_darray_free(model->meshes); + material_darray_free(model->materials); + if (model->is_uploaded) { + // Delete gpu buffer data + for (u32 i = 0; i < mesh_darray_len(model->meshes); i++) { + // FIXME: dont leak Opengl + glDeleteBuffers(1, &model->meshes->data[i].vbo); + glDeleteVertexArrays(1, &model->meshes->data[i].vao); + } + } +} + +typedef struct draw_ctx { + model_darray* models; + renderer* ren; + camera* cam; + scene* scene; +} draw_ctx; +bool draw_scene_node(transform_node* node, void* ctx_data) { + if (!node || !node->parent) return true; + draw_ctx* ctx = ctx_data; + model* m = &ctx->models->data[node->model.raw]; + draw_model(ctx->ren, ctx->cam, m, &node->world_matrix_tf, ctx->scene); + return true; +} + +void draw_scene(arena* frame, model_darray* models, renderer* ren, camera* camera, + transform_hierarchy* tfh, scene* scene) { + draw_ctx* ctx = arena_alloc(frame, sizeof(draw_ctx)); + ctx->models = models; + ctx->ren = ren; + ctx->cam = camera; + ctx->scene = scene; + transform_hierarchy_dfs(transform_hierarchy_root_node(tfh), draw_scene_node, true, ctx); +} + +void draw_model(renderer* ren, camera* camera, model* model, mat4* model_tf, scene* scene) { + // TRACE("Drawing model: %s", model->name); + mat4 view; + mat4 proj; + camera_view_projection(camera, SCR_HEIGHT, SCR_WIDTH, &view, &proj); + + set_shader(ren->blinn_phong); + + // set camera uniform + uniform_vec3f(ren->blinn_phong.program_id, "viewPos", &camera->position); + // set light uniforms + dir_light_upload_uniforms(ren->blinn_phong, &scene->dir_light); + for (int i = 0; i < scene->n_point_lights; i++) { + point_light_upload_uniforms(ren->blinn_phong, &scene->point_lights[i], '0' + i); + } + + for (size_t i = 0; i < mesh_darray_len(model->meshes); i++) { + mesh* m = &model->meshes->data[i]; + if (vertex_darray_len(m->vertices) == 0) { + continue; + } + // TRACE("Drawing mesh %d", i); + material* mat = &model->materials->data[m->material_index]; + draw_mesh(ren, m, model_tf, mat, &view, &proj); + } +} + +void draw_mesh(renderer* ren, mesh* mesh, mat4* model_tf, material* mat, mat4* view, mat4* proj) { + shader lighting_shader = ren->blinn_phong; + + // bind buffer + bind_mesh_vertex_buffer(ren->backend_state, mesh); + + // bind textures + bind_texture(lighting_shader, &mat->diffuse_texture, 0); // bind to slot 0 + bind_texture(lighting_shader, &mat->specular_texture, 1); // bind to slot 1 + uniform_f32(lighting_shader.program_id, "material.shininess", 32.); + + // upload model, view, and projection matrices + uniform_mat4f(lighting_shader.program_id, "model", model_tf); + uniform_mat4f(lighting_shader.program_id, "view", view); + uniform_mat4f(lighting_shader.program_id, "projection", proj); + + // draw triangles + u32 num_vertices = vertex_darray_len(mesh->vertices); + draw_primitives(CEL_PRIMITIVE_TOPOLOGY_TRIANGLE, 0, num_vertices); +} + +void draw_skinned_mesh(renderer* ren, mesh* mesh, transform tf, material* mat, mat4* view, + mat4* proj) { + shader lighting_shader = ren->skinned; + + // bind buffer + bind_mesh_vertex_buffer(ren->backend_state, mesh); + + // bind textures + bind_texture(lighting_shader, &mat->diffuse_texture, 0); // bind to slot 0 + bind_texture(lighting_shader, &mat->specular_texture, 1); // bind to slot 1 + + // Uniforms + uniform_f32(lighting_shader.program_id, "material.shininess", 32.); + mat4 trans = mat4_translation(tf.position); + mat4 rot = mat4_rotation(tf.rotation); + mat4 scale = mat4_scale(tf.scale); + mat4 model_tf = mat4_mult(trans, mat4_mult(rot, scale)); + uniform_mat4f(lighting_shader.program_id, "model", &model_tf); + uniform_mat4f(lighting_shader.program_id, "view", view); + uniform_mat4f(lighting_shader.program_id, "projection", proj); + + // bone transforms + size_t n_bones = mesh->bones->len; + + // for now assume correct ordering + mat4* bone_transforms = malloc(n_bones * sizeof(mat4)); + mat4 parent = mat4_ident(); + for (int bone_i = 0; bone_i < n_bones; bone_i++) { + joint j = mesh->bones->data[bone_i]; + transform tf = mesh->bones->data[bone_i].transform_components; + tf.position.y = -tf.position.y; + mat4 local = transform_to_mat(&tf); + mat4 inverse = j.inverse_bind_matrix; + inverse.data[13] = -inverse.data[13]; + mat4 intemediate = mat4_mult(local, inverse); + + bone_transforms[bone_i] = intemediate; + parent = bone_transforms[bone_i]; + } + + // premultiply the inverses + // for (int bone_i = 0; bone_i < n_bones; bone_i++) { + // joint j = mesh->bones->data[bone_i]; + // // bone_transforms[bone_i] = mat4_mult(bone_transforms[bone_i], j.inverse_bind_matrix); + // bone_transforms[bone_i] = mat4_mult(bone_transforms[bone_i], j.inverse_bind_matrix); + // } + + glUniformMatrix4fv(glGetUniformLocation(lighting_shader.program_id, "boneMatrices"), n_bones, + GL_FALSE, &bone_transforms->data[0]); + + free(bone_transforms); + + // draw triangles + u32 num_vertices = vertex_darray_len(mesh->vertices); + draw_primitives(CEL_PRIMITIVE_TOPOLOGY_TRIANGLE, 0, num_vertices); +} + +void draw_skinned_model(renderer* ren, camera* cam, model* model, transform tf, scene* scene) { + mat4 view; + mat4 proj; + camera_view_projection(cam, SCR_HEIGHT, SCR_WIDTH, &view, &proj); + + set_shader(ren->skinned); + + // set camera uniform + uniform_vec3f(ren->skinned.program_id, "viewPos", &cam->position); + // set light uniforms + dir_light_upload_uniforms(ren->skinned, &scene->dir_light); + for (int i = 0; i < scene->n_point_lights; i++) { + point_light_upload_uniforms(ren->skinned, &scene->point_lights[i], '0' + i); + } + + for (size_t i = 0; i < mesh_darray_len(model->meshes); i++) { + mesh* m = &model->meshes->data[i]; + if (vertex_darray_len(m->vertices) == 0) { + continue; + } + // material* mat = &model->materials->data[m->material_index]; + material* mat = &DEFAULT_MATERIAL; + draw_skinned_mesh(ren, m, tf, mat, &view, &proj); + } +} + +void model_upload_meshes(renderer* ren, model* model) { + INFO("Upload mesh vertex data to GPU for model %s", model->name); + + size_t num_meshes = mesh_darray_len(model->meshes); + u32 VBOs[num_meshes]; + u32 VAOs[num_meshes]; + glGenBuffers(num_meshes, VBOs); + glGenVertexArrays(num_meshes, VAOs); + + u64 total_verts = 0; + + TRACE("num meshes %d", num_meshes); + + // upload each mesh to the GPU + for (int mesh_i = 0; mesh_i < num_meshes; mesh_i++) { + mesh mesh = model->meshes->data[mesh_i]; + model->meshes->data[mesh_i].vao = VAOs[mesh_i]; + model->meshes->data[mesh_i].vbo = VBOs[mesh_i]; + // 3. bind buffers + glBindBuffer(GL_ARRAY_BUFFER, VBOs[mesh_i]); + + size_t num_vertices = vertex_darray_len(model->meshes->data[mesh_i].vertices); + // TRACE("Uploading vertex array data: %d verts", num_vertices); + total_verts += num_vertices; + + size_t static_vertex_size = 2 * sizeof(vec3) + sizeof(vec2); + size_t skinned_vertex_size = 2 * sizeof(vec3) + sizeof(vec2) + 4 * sizeof(u32) + sizeof(vec4); + size_t vertex_size = mesh.is_skinned ? skinned_vertex_size : static_vertex_size; + + // TRACE("sizeof(vertex) -> %ld, vertex_size -> %ld\n", sizeof(vertex), vertex_size); + if (mesh.is_skinned) { + assert(vertex_size == (12 + 12 + 8 + 16 + 16)); + } else { + assert(vertex_size == sizeof(vertex)); + assert(vertex_size == 8 * sizeof(float)); + } + + size_t buffer_size = vertex_size * num_vertices; + u8* bytes = malloc(buffer_size); + + for (int i = 0; i < num_vertices; i++) { + u8* p = bytes + vertex_size * i; + memcpy(p, &mesh.vertices->data[i], static_vertex_size); + if (mesh.is_skinned) { + u8* bone_data_offset = p + static_vertex_size; + memcpy(bone_data_offset, &mesh.vertex_bone_data->data[i], sizeof(vertex_bone_data)); + } + } + + // 4. upload data + glBufferData(GL_ARRAY_BUFFER, buffer_size, bytes, GL_STATIC_DRAW); + + // 5. cont. set mesh vertex layout + glBindVertexArray(model->meshes->data[mesh_i].vao); + // position attribute + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertex_size, (void*)0); + glEnableVertexAttribArray(0); + // normal vector attribute + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, vertex_size, (void*)(3 * sizeof(float))); + glEnableVertexAttribArray(1); + // tex coords + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, vertex_size, (void*)(6 * sizeof(float))); + glEnableVertexAttribArray(2); + + // skinning (optional) + if (mesh.is_skinned) { + glEnableVertexAttribArray(3); + glVertexAttribIPointer(3, 4, GL_INT, vertex_size, (void*)(8 * sizeof(float))); + + glEnableVertexAttribArray(4); + glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, vertex_size, (void*)(12 * sizeof(float))); + } + } + + INFO("Uploaded %d submeshes with a total of %d vertices\n", num_meshes, total_verts); + + // 6. reset buffer + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +texture texture_data_load(const char* path, bool invert_y) { + TRACE("Load texture %s", path); + + // load the file data + // texture loading + int width, height, num_channels; + stbi_set_flip_vertically_on_load(invert_y); + +#pragma GCC diagnostic ignored "-Wpointer-sign" + char* data = stbi_load(path, &width, &height, &num_channels, 0); // STBI_rgb_alpha); + if (data) { + DEBUG("loaded texture: %s", path); + } else { + WARN("failed to load texture"); + } + + unsigned int channel_type; + if (num_channels == 4) { + channel_type = GL_RGBA; + } else { + channel_type = GL_RGB; + } + + return (texture){ .texture_id = 0, + .width = width, + .height = height, + .channel_count = num_channels, + .channel_type = channel_type, + .name = "TODO: Texture names", + .image_data = data }; +} + +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); + uniform_vec3f(shader.program_id, "dirLight.diffuse", &light->diffuse); + uniform_vec3f(shader.program_id, "dirLight.specular", &light->specular); +} + +void point_light_upload_uniforms(shader shader, point_light* light, char index) { + char position_str[] = "pointLights[x].position"; + position_str[12] = (char)index; + char ambient_str[] = "pointLights[x].ambient"; + ambient_str[12] = (char)index; + char diffuse_str[] = "pointLights[x].diffuse"; + diffuse_str[12] = (char)index; + char specular_str[] = "pointLights[x].specular"; + specular_str[12] = (char)index; + char constant_str[] = "pointLights[x].constant"; + constant_str[12] = (char)index; + char linear_str[] = "pointLights[x].linear"; + linear_str[12] = (char)index; + char quadratic_str[] = "pointLights[x].quadratic"; + quadratic_str[12] = (char)index; + uniform_vec3f(shader.program_id, position_str, &light->position); + uniform_vec3f(shader.program_id, ambient_str, &light->ambient); + uniform_vec3f(shader.program_id, diffuse_str, &light->diffuse); + uniform_vec3f(shader.program_id, specular_str, &light->specular); + uniform_f32(shader.program_id, constant_str, light->constant); + uniform_f32(shader.program_id, linear_str, light->linear); + uniform_f32(shader.program_id, quadratic_str, light->quadratic); +}
\ No newline at end of file diff --git a/src/renderer/archive/render.h b/src/renderer/archive/render.h new file mode 100644 index 0000000..31cf3b0 --- /dev/null +++ b/src/renderer/archive/render.h @@ -0,0 +1,46 @@ +/** + * @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" +#include "loaders.h" +#include "render_types.h" +#include "transform_hierarchy.h" + +// --- Lifecycle +/** @brief initialise the render system frontend */ +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); +void draw_model(renderer* ren, camera* camera, model* model, mat4* tf, scene* scene); +void draw_mesh(renderer* ren, mesh* mesh, mat4* tf, material* mat, mat4* view, mat4* proj); +void draw_scene(arena* frame, model_darray* models, renderer* ren, camera* camera, + transform_hierarchy* tfh, scene* scene); + +void draw_skinned_model(renderer* ren, camera* cam, model* model, transform tf, scene* scene); + +void model_destroy(model* model); + +// --- +texture texture_data_load(const char* path, bool invert_y); // #frontend diff --git a/src/renderer/archive/render_backend.h b/src/renderer/archive/render_backend.h new file mode 100644 index 0000000..da30bcc --- /dev/null +++ b/src/renderer/archive/render_backend.h @@ -0,0 +1,39 @@ +/** + * @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); + +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/archive/render_types.h b/src/renderer/archive/render_types.h new file mode 100644 index 0000000..f5ea986 --- /dev/null +++ b/src/renderer/archive/render_types.h @@ -0,0 +1,210 @@ +// /** +// * @file render_types.h +// * @author Omniscient +// * @brief Type definitions for the majority of data required by the renderer system +// * @date 2024-02-24 +// * +// */ +// #pragma once + +#include "animation.h" +#include "darray.h" +#include "maths.h" +#include "maths_types.h" +#include "str.h" + +struct GLFWwindow; + +#define MAX_MATERIAL_NAME_LEN 256 +#define MAX_TEXTURE_NAME_LEN 256 + +// #ifndef RESOURCE_HANDLE_DEFS +// // CORE_DEFINE_HANDLE(model_handle); +// #define ABSENT_MODEL_HANDLE 999999999 +// // CORE_DEFINE_HANDLE(texture_handle); +// #define RESOURCE_HANDLE_DEFS +// #endif + +/* @brief Opaque wrapper around a shader program */ +typedef struct shader { + u32 program_id; +} shader; + +/** @brief configuration passed to the renderer at init time */ +typedef struct renderer_config { + char window_name[256]; + u32 scr_width, scr_height; + 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 */ + renderer_config config; + // shaders + shader blinn_phong; + shader skinned; +} renderer; + +// // --- Lighting & Materials + +typedef struct texture { + u32 texture_id; + char name[MAX_TEXTURE_NAME_LEN]; + void *image_data; + void *backend_data; + u32 width; + u32 height; + u8 channel_count; + u32 channel_type; +} texture; + +typedef struct blinn_phong_material { + char name[MAX_MATERIAL_NAME_LEN]; + texture diffuse_texture; + char diffuse_tex_path[256]; + texture specular_texture; + char specular_tex_path[256]; + vec3 ambient_colour; + vec3 diffuse; + vec3 specular; + f32 spec_exponent; + bool is_loaded; + bool is_uploaded; +} blinn_phong_material; +typedef blinn_phong_material material; // when we start using PBR, this will no longer be the +// case + +// // the default blinn-phong material. MUST be initialised with the function below +// extern material DEFAULT_MATERIAL; +// void default_material_init(); + +#ifndef TYPED_MATERIAL_ARRAY +KITC_DECL_TYPED_ARRAY(material) // creates "material_darray" +#define TYPED_MATERIAL_ARRAY +#endif + +#ifndef TYPED_ANIMATION_CLIP_ARRAY +KITC_DECL_TYPED_ARRAY(animation_clip) // creates "material_darray" +#define TYPED_ANIMATION_CLIP_ARRAY +#endif + +// // lights +typedef struct point_light { + vec3 position; + f32 constant, linear, quadratic; + vec3 ambient; + vec3 diffuse; + vec3 specular; +} point_light; + +typedef struct directional_light { + vec3 direction; + vec3 ambient; + vec3 diffuse; + vec3 specular; +} directional_light; + +// void point_light_upload_uniforms(shader shader, point_light *light, char index); +// void dir_light_upload_uniforms(shader shader, directional_light *light); + +// // --- Models & Meshes + +// /** @brief Vertex format for a static mesh */ +typedef struct vertex { + vec3 position; + vec3 normal; + vec2 uv; +} vertex; + +typedef struct vertex_bone_data { + vec4u joints; /** @brief 4 indices of joints that influence vectors position */ + vec4 weights; /** @brief weight (0,1) of each joint */ +} vertex_bone_data; + +#include "animation.h" +#ifndef TYPED_VERTEX_ARRAY +KITC_DECL_TYPED_ARRAY(vertex) // creates "vertex_darray" +KITC_DECL_TYPED_ARRAY(vertex_bone_data) // creates "skinned_vertex_darray" +KITC_DECL_TYPED_ARRAY(joint) +#define TYPED_VERTEX_ARRAY +#endif + +typedef struct mesh { + vertex_darray *vertices; + vertex_bone_data_darray *vertex_bone_data; // only used if model needs it + joint_darray *bones; + bool is_skinned; + u32 vertex_size; /** size in bytes of each vertex including necessary padding */ + bool has_indices; + u32 *indices; + u32 indices_len; + size_t material_index; + u32 vbo, vao; /** OpenGL data. TODO: dont leak OpenGL details */ +} mesh; + +// #ifndef TYPED_MESH_ARRAY +// KITC_DECL_TYPED_ARRAY(mesh) // creates "mesh_darray" +// #define TYPED_MESH_ARRAY +// #endif + +typedef struct model { + str8 name; + mesh_darray *meshes; + aabb_3d bbox; + material_darray *materials; + animation_clip_darray *animations; + arena animation_data_arena; + bool is_loaded; + bool is_uploaded; +} model; + +// #ifndef TYPED_MODEL_ARRAY +// KITC_DECL_TYPED_ARRAY(model) // creates "model_darray" +// #define TYPED_MODEL_ARRAY +// #endif + +// // --- Scene + +// // NOTE: This struct won't stay like this for a long time. It's somewhat temporary +// // in order to get a basic scene working without putting burden on the caller of +// // draw_model() +// typedef struct scene { +// directional_light dir_light; +// point_light point_lights[4]; +// size_t n_point_lights; +// } scene; + +// // --- Graphics API related + +// // typedef enum cel_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 { +// // TEXTURE_TYPE_2D, +// // TEXTURE_TYPE_3D, +// // TEXTURE_TYPE_2D_ARRAY, +// // TEXTURE_TYPE_CUBE_MAP, +// // TEXTURE_TYPE_COUNT +// // } gpu_texture_type; + +// // typedef enum gpu_texture_format { +// // TEXTURE_FORMAT_8_8_8_8_RGBA_UNORM, +// // TEXTURE_FORMAT_DEPTH_DEFAULT, +// // TEXTURE_FORMAT_COUNT +// // } gpu_texture_format; + +// // typedef enum pipeline_kind { +// // GRAPHICS, +// // COMPUTE, +// // } pipeline_kind; diff --git a/src/renderer/backends/backend_dx11.c b/src/renderer/backends/backend_dx11.c new file mode 100644 index 0000000..7e48853 --- /dev/null +++ b/src/renderer/backends/backend_dx11.c @@ -0,0 +1,5 @@ +#if defined(CEL_REND_BACKEND_DX11) +#include <d3d11.h> +#include <d3dcompiler.h> + +#endif
\ No newline at end of file diff --git a/src/renderer/backends/backend_dx11.h b/src/renderer/backends/backend_dx11.h new file mode 100644 index 0000000..53738aa --- /dev/null +++ b/src/renderer/backends/backend_dx11.h @@ -0,0 +1,29 @@ +#pragma once +#include <d3d11.h> +#include <d3dcompiler.h> + +#include "ral.h" + +#define GPU_SWAPCHAIN_IMG_COUNT 2 + +// typedef struct gpu_swapchain gpu_swapchain; +typedef struct gpu_device { + // VkPhysicalDevice physical_device; + // VkDevice logical_device; + // VkPhysicalDeviceProperties properties; + // VkPhysicalDeviceFeatures features; + // VkPhysicalDeviceMemoryProperties memory; + // VkCommandPool pool; +} gpu_device; +typedef struct gpu_pipeline { +} gpu_pipeline; + +typedef struct gpu_renderpass { + // VkRenderPass vk_handle; + // VkFramebuffer framebuffers[GPU_SWAPCHAIN_IMG_COUNT]; + // u32 +} gpu_renderpass; + +typedef struct gpu_cmd_encoder { + // VkCommandBuffer cmd_buffer; +} gpu_cmd_encoder;
\ No newline at end of file diff --git a/src/renderer/backends/backend_opengl.c b/src/renderer/backends/backend_opengl.c index ea6cb00..4cd97b5 100644 --- a/src/renderer/backends/backend_opengl.c +++ b/src/renderer/backends/backend_opengl.c @@ -1,11 +1,12 @@ #include <stdlib.h> +#include "camera.h" #define CEL_PLATFORM_LINUX #include "defines.h" #include "file.h" #include "log.h" #include "maths_types.h" -#include "render_types.h" +#include "ral.h" #if CEL_REND_BACKEND_OPENGL @@ -36,10 +37,13 @@ bool gfx_backend_init(renderer *ren) { glEnable(GL_DEPTH_TEST); opengl_state *internal = malloc(sizeof(opengl_state)); - ren->backend_state = (void *)internal; + ren->backend_context = (void *)internal; return true; } + +void gfx_backend_draw_frame(renderer *ren, camera *cam, mat4 model, texture *tex) {} + void gfx_backend_shutdown(renderer *ren) {} void uniform_vec3f(u32 program_id, const char *uniform_name, vec3 *value) { @@ -60,6 +64,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..ef266e0 100644 --- a/src/renderer/backends/backend_vulkan.c +++ b/src/renderer/backends/backend_vulkan.c @@ -1 +1,1689 @@ -// #FUTURE
\ No newline at end of file +#include <assert.h> +#include <glfw3.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <vulkan/vk_platform.h> +#include <vulkan/vulkan.h> +#include <vulkan/vulkan_core.h> + +#include "backend_vulkan.h" +#include "buf.h" +#include "darray.h" +#include "maths_types.h" +#include "mem.h" +#include "ral_types.h" +#include "str.h" +#include "vulkan_helpers.h" + +#include "defines.h" +#include "file.h" +#include "log.h" +#include "ral.h" +#include "utils.h" + +// TEMP +#define SCREEN_WIDTH 1000 +#define SCREEN_HEIGHT 1000 +#define VULKAN_QUEUES_COUNT 2 +#define MAX_DESCRIPTOR_SETS 10 + +const char* queue_names[VULKAN_QUEUES_COUNT] = { "GRAPHICS", "TRANSFER" }; + +KITC_DECL_TYPED_ARRAY(VkDescriptorSet) + +typedef struct vulkan_context { + VkInstance instance; + VkAllocationCallbacks* allocator; + VkSurfaceKHR surface; + vulkan_swapchain_support_info swapchain_support; + + arena temp_arena; + gpu_device* device; + gpu_swapchain* swapchain; + u32 framebuffer_count; + VkFramebuffer* + swapchain_framebuffers; // TODO: Move this data into the swapchain as its own struct + + u32 current_img_index; + u32 current_frame; // super important + gpu_cmd_encoder main_cmd_bufs[MAX_FRAMES_IN_FLIGHT]; + VkSemaphore image_available_semaphores[MAX_FRAMES_IN_FLIGHT]; + VkSemaphore render_finished_semaphores[MAX_FRAMES_IN_FLIGHT]; + VkFence in_flight_fences[MAX_FRAMES_IN_FLIGHT]; + + // HACK + VkRenderPass main_renderpass; + + u32 screen_width; + u32 screen_height; + bool is_resizing; + GLFWwindow* window; + + // Storage + gpu_buffer buffers[1024]; + size_t buffer_count; + VkDescriptorSet_darray* free_set_queue; + struct resource_pools* resource_pools; + + VkDebugUtilsMessengerEXT vk_debugger; +} vulkan_context; + +static vulkan_context context; + +// --- Function forward declarations + +/** @brief Enumerates and selects the most appropriate graphics device */ +bool select_physical_device(gpu_device* out_device); + +bool is_physical_device_suitable(VkPhysicalDevice device); + +queue_family_indices find_queue_families(VkPhysicalDevice device); + +bool create_logical_device(gpu_device* out_device); +void create_swapchain_framebuffers(); +void create_sync_objects(); +void create_descriptor_pools(); +size_t vertex_attrib_size(vertex_attrib_type attr); + +VkShaderModule create_shader_module(str8 spirv); + +/** @brief Helper function for creating array of all extensions we want */ +cstr_darray* get_all_extensions(); + +VkImage vulkan_image_create(u32x2 dimensions, VkImageType image_type, VkFormat format, + VkImageUsageFlags usage); +void vulkan_transition_image_layout(gpu_texture* texture, VkFormat format, VkImageLayout old_layout, + VkImageLayout new_layout); + +// --- Handy macros +#define BUFFER_GET(h) (buffer_pool_get(&context.resource_pools->buffers, h)) +#define TEXTURE_GET(h) (texture_pool_get(&context.resource_pools->textures, h)) + +bool gpu_backend_init(const char* window_name, GLFWwindow* window) { + memset(&context, 0, sizeof(vulkan_context)); + context.allocator = 0; // TODO: use an allocator + context.screen_width = SCREEN_WIDTH; + context.screen_height = SCREEN_HEIGHT; + context.window = window; + context.current_img_index = 0; + context.current_frame = 0; + context.free_set_queue = VkDescriptorSet_darray_new(100); + + // Create an allocator + size_t temp_arena_size = 1024 * 1024; + context.temp_arena = arena_create(malloc(temp_arena_size), temp_arena_size); + + // Setup Vulkan instance + VkApplicationInfo app_info = { VK_STRUCTURE_TYPE_APPLICATION_INFO }; + app_info.apiVersion = VK_API_VERSION_1_2; + app_info.pApplicationName = 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; + + // Extensions + cstr_darray* required_extensions = cstr_darray_new(2); + // cstr_darray_push(required_extensions, VK_KHR_SURFACE_EXTENSION_NAME); + + uint32_t count; + const char** extensions = glfwGetRequiredInstanceExtensions(&count); + for (u32 i = 0; i < count; i++) { + cstr_darray_push(required_extensions, extensions[i]); + } + + 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]); + } + + create_info.enabledExtensionCount = cstr_darray_len(required_extensions); + create_info.ppEnabledExtensionNames = required_extensions->data; + + // TODO: Validation layers + create_info.enabledLayerCount = 0; + create_info.ppEnabledLayerNames = NULL; + + 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* available_layers = + arena_alloc(&context.temp_arena, n_available_layers * sizeof(VkLayerProperties)); + VK_CHECK(vkEnumerateInstanceLayerProperties(&n_available_layers, available_layers)); + + 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[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; + + VkResult result = vkCreateInstance(&create_info, NULL, &context.instance); + if (result != VK_SUCCESS) { + ERROR("vkCreateInstance failed with result: %u", result); + return false; + } + TRACE("Vulkan Instance created"); + + 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"); + + // Surface creation + VkSurfaceKHR surface; + VK_CHECK(glfwCreateWindowSurface(context.instance, window, NULL, &surface)); + context.surface = surface; + TRACE("Vulkan Surface created"); + + return true; +} + +void gpu_backend_shutdown() { + gpu_swapchain_destroy(context.swapchain); + + vkDestroySurfaceKHR(context.instance, context.surface, context.allocator); + vkDestroyInstance(context.instance, context.allocator); + arena_free_storage(&context.temp_arena); +} + +bool gpu_device_create(gpu_device* out_device) { + // First things first store this poitner from the renderer + context.device = out_device; + + arena_save savept = arena_savepoint(&context.temp_arena); + // Physical device + if (!select_physical_device(out_device)) { + return false; + } + TRACE("Physical device selected"); + + // Logical device & Queues + create_logical_device(out_device); + + // Create the command pool + VkCommandPoolCreateInfo pool_create_info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; + pool_create_info.queueFamilyIndex = out_device->queue_family_indicies.graphics_family_index; + pool_create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + vkCreateCommandPool(out_device->logical_device, &pool_create_info, context.allocator, + &out_device->pool); + TRACE("Command Pool created"); + + // Synchronisation objects + create_sync_objects(); + TRACE("Synchronisation primitives created"); + + arena_rewind(savept); // Free any temp data + return true; +} + +bool gpu_swapchain_create(gpu_swapchain* out_swapchain) { + context.swapchain = out_swapchain; + + out_swapchain->swapchain_arena = arena_create(malloc(1024), 1024); + + vulkan_device_query_swapchain_support(context.device->physical_device, context.surface, + &context.swapchain_support); + vulkan_swapchain_support_info swapchain_support = context.swapchain_support; + + // TODO: custom swapchain extents VkExtent2D swapchain_extent = { width, height }; + + VkSurfaceFormatKHR image_format = choose_swapchain_format(&swapchain_support); + out_swapchain->image_format = image_format; + VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR; // guaranteed to be implemented + out_swapchain->present_mode = present_mode; + + u32 image_count = swapchain_support.capabilities.minImageCount + 1; + out_swapchain->image_count = image_count; + + 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 = image_format.format; + swapchain_create_info.imageColorSpace = image_format.colorSpace; + swapchain_create_info.imageExtent = swapchain_support.capabilities.currentExtent; + 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 = NULL; + + swapchain_create_info.preTransform = 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 = VK_NULL_HANDLE; + + out_swapchain->extent = swapchain_support.capabilities.currentExtent; + + VK_CHECK(vkCreateSwapchainKHR(context.device->logical_device, &swapchain_create_info, + context.allocator, &out_swapchain->handle)); + TRACE("Vulkan Swapchain created"); + + // Retrieve Images + // out_swapchain->images = + // arena_alloc(&out_swapchain->swapchain_arena, image_count * sizeof(VkImage)); + out_swapchain->images = malloc(image_count * sizeof(VkImage)); + VK_CHECK(vkGetSwapchainImagesKHR(context.device->logical_device, out_swapchain->handle, + &image_count, out_swapchain->images)); + + // Create ImageViews + // TODO: Move this to a separate function + out_swapchain->image_views = malloc(image_count * sizeof(VkImageView)); + // arena_alloc(&out_swapchain->swapchain_arena, image_count * sizeof(VkImageView)); + for (u32 i = 0; i < image_count; i++) { + VkImageViewCreateInfo view_create_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + view_create_info.image = out_swapchain->images[i]; + view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_create_info.format = image_format.format; + view_create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + view_create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + view_create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + view_create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + 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, + &out_swapchain->image_views[i]); + } + + return true; +} + +void gpu_swapchain_destroy(gpu_swapchain* swapchain) { + // Destroy Framebuffers + DEBUG("Image count %d", swapchain->image_count); + for (u32 i = 0; i < swapchain->image_count; i++) { + DEBUG("Framebuffer handle %d", context.swapchain_framebuffers[i]); + vkDestroyFramebuffer(context.device->logical_device, context.swapchain_framebuffers[i], + context.allocator); + } + for (u32 i = 0; i < swapchain->image_count; i++) { + vkDestroyImageView(context.device->logical_device, swapchain->image_views[i], + context.allocator); + } + arena_free_all(&swapchain->swapchain_arena); + vkDestroySwapchainKHR(context.device->logical_device, swapchain->handle, context.allocator); + TRACE("Vulkan Swapchain destroyed"); +} + +static void recreate_swapchain(gpu_swapchain* swapchain) { + int width = 0, height = 0; + glfwGetFramebufferSize(context.window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(context.window, &width, &height); + glfwWaitEvents(); + } + DEBUG("Recreating swapchain..."); + vkDeviceWaitIdle(context.device->logical_device); + + gpu_swapchain_destroy(swapchain); + gpu_swapchain_create(swapchain); + create_swapchain_framebuffers(); +} + +VkFormat format_from_vertex_attr(vertex_attrib_type attr) { + switch (attr) { + case ATTR_F32: + return VK_FORMAT_R32_SFLOAT; + case ATTR_U32: + return VK_FORMAT_R32_UINT; + case ATTR_I32: + return VK_FORMAT_R32_SINT; + case ATTR_F32x2: + return VK_FORMAT_R32G32_SFLOAT; + case ATTR_U32x2: + return VK_FORMAT_R32G32_UINT; + case ATTR_I32x2: + return VK_FORMAT_R32G32_UINT; + case ATTR_F32x3: + return VK_FORMAT_R32G32B32_SFLOAT; + case ATTR_U32x3: + return VK_FORMAT_R32G32B32_UINT; + case ATTR_I32x3: + return VK_FORMAT_R32G32B32_SINT; + case ATTR_F32x4: + return VK_FORMAT_R32G32B32A32_SFLOAT; + case ATTR_U32x4: + return VK_FORMAT_R32G32B32A32_UINT; + case ATTR_I32x4: + return VK_FORMAT_R32G32B32A32_SINT; + } +} + +gpu_pipeline* gpu_graphics_pipeline_create(struct graphics_pipeline_desc description) { + // Allocate + gpu_pipeline_layout* layout = malloc(sizeof(gpu_pipeline_layout)); + gpu_pipeline* pipeline = malloc(sizeof(gpu_pipeline)); + + // Shaders + printf("Vertex shader: %s\n", description.vs.filepath.buf); + printf("Fragment shader: %s\n", description.fs.filepath.buf); + VkShaderModule vertex_shader = create_shader_module(description.vs.code); + VkShaderModule fragment_shader = create_shader_module(description.fs.code); + + // Vertex + VkPipelineShaderStageCreateInfo vert_shader_stage_info = { + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO + }; + vert_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; + vert_shader_stage_info.module = vertex_shader; + vert_shader_stage_info.pName = "main"; + // Fragment + VkPipelineShaderStageCreateInfo frag_shader_stage_info = { + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO + }; + frag_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + frag_shader_stage_info.module = fragment_shader; + frag_shader_stage_info.pName = "main"; + + VkPipelineShaderStageCreateInfo shader_stages[2] = { vert_shader_stage_info, + frag_shader_stage_info }; + + // Attributes + u32 attr_count = description.vertex_desc.attributes_count; + printf("N attributes %d\n", attr_count); + VkVertexInputAttributeDescription attribute_descs[attr_count]; + memset(attribute_descs, 0, attr_count * sizeof(VkVertexInputAttributeDescription)); + u32 offset = 0; + for (u32 i = 0; i < description.vertex_desc.attributes_count; i++) { + attribute_descs[i].binding = 0; + attribute_descs[i].location = i; + attribute_descs[i].format = format_from_vertex_attr(description.vertex_desc.attributes[i]); + attribute_descs[i].offset = offset; + size_t this_offset = vertex_attrib_size(description.vertex_desc.attributes[i]); + printf("offset total %d this attr %ld\n", offset, this_offset); + printf("sizeof vertex %ld\n", sizeof(vertex)); + offset += this_offset; + } + + // Vertex input + // TODO: Generate this from descroiption now + VkVertexInputBindingDescription binding_desc; + binding_desc.binding = 0; + binding_desc.stride = description.vertex_desc.use_full_vertex_size + ? sizeof(vertex) + : description.vertex_desc.stride; + 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 = + attr_count; // description.vertex_desc.attributes_count; + vertex_input_info.pVertexAttributeDescriptions = attribute_descs; + + // Input Assembly + 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; + + // Viewport + VkViewport viewport = { .x = 0, + .y = 0, + .width = (f32)context.swapchain->extent.width, + .height = (f32)context.swapchain->extent.height, + .minDepth = 0.0, + .maxDepth = 1.0 }; + VkRect2D scissor = { .offset = { .x = 0, .y = 0 }, .extent = context.swapchain->extent }; + 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 = + description.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.frontFace = VK_FRONT_FACE_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; + + // TODO: Depth and stencil testing + // VkPipelineDepthStencilStateCreateInfo depth_stencil = { + // VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO + // }; + // depth_stencil.depthTestEnable = description.depth_test ? VK_TRUE : VK_FALSE; + // depth_stencil.depthWriteEnable = description.depth_test ? VK_TRUE : VK_FALSE; + // depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS; + // depth_stencil.depthBoundsTestEnable = VK_FALSE; + // depth_stencil.stencilTestEnable = VK_FALSE; + // depth_stencil.pNext = 0; + + // Blending + VkPipelineColorBlendAttachmentState color_blend_attachment_state; + color_blend_attachment_state.blendEnable = VK_FALSE; + 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; + +// Dynamic state +#define DYNAMIC_STATE_COUNT 2 + VkDynamicState dynamic_states[DYNAMIC_STATE_COUNT] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + }; + + VkPipelineDynamicStateCreateInfo dynamic_state = { + VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO + }; + dynamic_state.dynamicStateCount = DYNAMIC_STATE_COUNT; + dynamic_state.pDynamicStates = dynamic_states; + + // Descriptor Set layouts + + VkDescriptorSetLayout* desc_set_layouts = + malloc(description.data_layouts_count * sizeof(VkDescriptorSetLayout)); + pipeline->desc_set_layouts = desc_set_layouts; + pipeline->desc_set_layouts_count = description.data_layouts_count; + if (description.data_layouts_count > 0) { + pipeline->uniform_pointers = + malloc(description.data_layouts_count * sizeof(desc_set_uniform_buffer)); + } else { + pipeline->uniform_pointers = NULL; + } + + // assert(description.data_layouts_count == 1); + printf("data layouts %d\n", description.data_layouts_count); + for (u32 layout_i = 0; layout_i < description.data_layouts_count; layout_i++) { + shader_data_layout sdl = description.data_layouts[layout_i].shader_data_get_layout(NULL); + TRACE("Got shader data layout %d's bindings! . found %d", layout_i, sdl.bindings_count); + + VkDescriptorSetLayoutBinding desc_set_bindings[sdl.bindings_count]; + + // Bindings + assert(sdl.bindings_count == 2); + for (u32 binding_j = 0; binding_j < sdl.bindings_count; binding_j++) { + desc_set_bindings[binding_j].binding = binding_j; + desc_set_bindings[binding_j].descriptorCount = 1; + switch (sdl.bindings[binding_j].type) { + case SHADER_BINDING_BUFFER: + case SHADER_BINDING_BYTES: + desc_set_bindings[binding_j].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + desc_set_bindings[binding_j].stageFlags = + VK_SHADER_STAGE_VERTEX_BIT; // FIXME: dont hardcode + + u64 buffer_size = sdl.bindings[binding_j].data.bytes.size; + VkDeviceSize uniform_buf_size = buffer_size; + // TODO: Create backing buffer + + VkBuffer buffers[MAX_FRAMES_IN_FLIGHT]; + VkDeviceMemory uniform_buf_memorys[MAX_FRAMES_IN_FLIGHT]; + void* uniform_buf_mem_mappings[MAX_FRAMES_IN_FLIGHT]; + // void* s? + for (size_t frame_i = 0; frame_i < MAX_FRAMES_IN_FLIGHT; frame_i++) { + buffer_handle uniform_buf_handle = + gpu_buffer_create(buffer_size, CEL_BUFFER_UNIFORM, CEL_BUFFER_FLAG_CPU, NULL); + + gpu_buffer* created_gpu_buffer = + BUFFER_GET(uniform_buf_handle); // context.buffers[uniform_buf_handle.raw]; + buffers[frame_i] = created_gpu_buffer->handle; + uniform_buf_memorys[frame_i] = created_gpu_buffer->memory; + vkMapMemory(context.device->logical_device, uniform_buf_memorys[frame_i], 0, + uniform_buf_size, 0, &uniform_buf_mem_mappings[frame_i]); + // now we have a pointer in unifrom_buf_mem_mappings we can write to + } + + desc_set_uniform_buffer uniform_data; + memcpy(&uniform_data.buffers, &buffers, sizeof(buffers)); + memcpy(&uniform_data.uniform_buf_memorys, &uniform_buf_memorys, + sizeof(uniform_buf_memorys)); + memcpy(&uniform_data.uniform_buf_mem_mappings, &uniform_buf_mem_mappings, + sizeof(uniform_buf_mem_mappings)); + uniform_data.size = buffer_size; + + pipeline->uniform_pointers[binding_j] = uniform_data; + + break; + case SHADER_BINDING_TEXTURE: + desc_set_bindings[binding_j].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + desc_set_bindings[binding_j].stageFlags = + VK_SHADER_STAGE_FRAGMENT_BIT; // FIXME: dont hardcode + desc_set_bindings[binding_j].pImmutableSamplers = NULL; + + break; + default: + ERROR_EXIT("Unimplemented binding type!! in backend_vulkan"); + } + switch (sdl.bindings[binding_j].vis) { + case VISIBILITY_VERTEX: + desc_set_bindings[binding_j].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + break; + case VISIBILITY_FRAGMENT: + desc_set_bindings[binding_j].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + break; + case VISIBILITY_COMPUTE: + WARN("Compute is not implemented yet"); + break; + } + } + + VkDescriptorSetLayoutCreateInfo desc_set_layout_info = { + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO + }; + desc_set_layout_info.bindingCount = sdl.bindings_count; + desc_set_layout_info.pBindings = desc_set_bindings; + + VK_CHECK(vkCreateDescriptorSetLayout(context.device->logical_device, &desc_set_layout_info, + context.allocator, &desc_set_layouts[layout_i])); + } + printf("Descriptor set layouts\n"); + + // Layout + VkPipelineLayoutCreateInfo pipeline_layout_create_info = { + VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO + }; + pipeline_layout_create_info.setLayoutCount = description.data_layouts_count; + pipeline_layout_create_info.pSetLayouts = desc_set_layouts; + pipeline_layout_create_info.pushConstantRangeCount = 0; + pipeline_layout_create_info.pPushConstantRanges = NULL; + VK_CHECK(vkCreatePipelineLayout(context.device->logical_device, &pipeline_layout_create_info, + context.allocator, &layout->handle)); + pipeline->layout_handle = layout->handle; // keep a copy of the layout on the pipeline object + + VkGraphicsPipelineCreateInfo pipeline_create_info = { + VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO + }; + + pipeline_create_info.stageCount = 2; + pipeline_create_info.pStages = shader_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 = NULL; // &depth_stencil; + pipeline_create_info.pColorBlendState = &color_blend; + pipeline_create_info.pDynamicState = &dynamic_state; + pipeline_create_info.pTessellationState = 0; + + pipeline_create_info.layout = layout->handle; + + pipeline_create_info.renderPass = description.renderpass->handle; + pipeline_create_info.subpass = 0; + pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE; + pipeline_create_info.basePipelineIndex = -1; + + printf("About to create graphics pipeline\n"); + + VkResult result = + vkCreateGraphicsPipelines(context.device->logical_device, VK_NULL_HANDLE, 1, + &pipeline_create_info, context.allocator, &pipeline->handle); + if (result != VK_SUCCESS) { + FATAL("graphics pipeline creation failed. its fked mate"); + ERROR_EXIT("Doomed"); + } + TRACE("Vulkan Graphics pipeline created"); + + // once the pipeline has been created we can destroy these + vkDestroyShaderModule(context.device->logical_device, vertex_shader, context.allocator); + vkDestroyShaderModule(context.device->logical_device, fragment_shader, context.allocator); + + // Framebuffers + create_swapchain_framebuffers(); + TRACE("Swapchain Framebuffers created"); + + for (u32 frame_i = 0; frame_i < MAX_FRAMES_IN_FLIGHT; frame_i++) { + context.main_cmd_bufs[frame_i] = gpu_cmd_encoder_create(); + } + TRACE("main Command Buffer created"); + + TRACE("Graphics pipeline created"); + return pipeline; +} + +void gpu_pipeline_destroy(gpu_pipeline* pipeline) { + vkDestroyPipeline(context.device->logical_device, pipeline->handle, context.allocator); + vkDestroyPipelineLayout(context.device->logical_device, pipeline->layout_handle, + context.allocator); +} + +gpu_cmd_encoder* gpu_get_default_cmd_encoder() { + return &context.main_cmd_bufs[context.current_frame]; +} + +gpu_renderpass* gpu_renderpass_create(const gpu_renderpass_desc* description) { + // TEMP: allocate with malloc. in the future we will have a pool allocator on the context + gpu_renderpass* renderpass = malloc(sizeof(gpu_renderpass)); + + // 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; + + // Depth attachment + u32x2 ext = { .x = context.swapchain_support.capabilities.currentExtent.width, + .y = context.swapchain_support.capabilities.currentExtent.height }; + texture_desc depth_desc = { .extents = ext, + .format = CEL_TEXTURE_FORMAT_DEPTH_DEFAULT, + .tex_type = CEL_TEXTURE_TYPE_2D }; + texture_handle depth_texture_handle = gpu_texture_create(depth_desc, true, NULL); + gpu_texture* depth = TEXTURE_GET(depth_texture_handle); + + VkAttachmentDescription depth_attachment; + depth_attachment.format = // TODO: 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; + + // main subpass + VkSubpassDescription subpass = { 0 }; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment_reference; + + // sets everything up + // 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; + + // Finally, create the RenderPass + VkRenderPassCreateInfo render_pass_create_info = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO }; + render_pass_create_info.attachmentCount = 1; + render_pass_create_info.pAttachments = &color_attachment; + 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.flags = 0; + render_pass_create_info.pNext = 0; + + VK_CHECK(vkCreateRenderPass(context.device->logical_device, &render_pass_create_info, + context.allocator, &renderpass->handle)); + + // HACK + context.main_renderpass = renderpass->handle; + + return renderpass; +} + +gpu_cmd_encoder gpu_cmd_encoder_create() { + // gpu_cmd_encoder* encoder = malloc(sizeof(gpu_cmd_encoder)); // TODO: fix leaking mem + gpu_cmd_encoder encoder = { 0 }; + + VkCommandBufferAllocateInfo allocate_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; + allocate_info.commandPool = context.device->pool; + allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocate_info.commandBufferCount = 1; + allocate_info.pNext = NULL; + + VK_CHECK(vkAllocateCommandBuffers(context.device->logical_device, &allocate_info, + &encoder.cmd_buffer);); + + VkDescriptorPoolSize pool_sizes[2]; + // Uniforms pool + pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + pool_sizes[0].descriptorCount = MAX_FRAMES_IN_FLIGHT * MAX_DESCRIPTOR_SETS; + // Samplers pool + pool_sizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + pool_sizes[1].descriptorCount = MAX_FRAMES_IN_FLIGHT * MAX_DESCRIPTOR_SETS; + + VkDescriptorPoolCreateInfo pool_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; + pool_info.poolSizeCount = 2; + pool_info.pPoolSizes = pool_sizes; + pool_info.maxSets = 100; + + VK_CHECK(vkCreateDescriptorPool(context.device->logical_device, &pool_info, context.allocator, + &encoder.descriptor_pool)); + + return encoder; +} +void gpu_cmd_encoder_destroy(gpu_cmd_encoder* encoder) { + vkFreeCommandBuffers(context.device->logical_device, context.device->pool, 1, + &encoder->cmd_buffer); +} + +void gpu_cmd_encoder_begin(gpu_cmd_encoder encoder) { + VK_CHECK(vkResetDescriptorPool(context.device->logical_device, encoder.descriptor_pool, 0)); + + VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + VK_CHECK(vkBeginCommandBuffer(encoder.cmd_buffer, &begin_info)); +} + +void gpu_cmd_encoder_begin_render(gpu_cmd_encoder* encoder, gpu_renderpass* renderpass) { + VkRenderPassBeginInfo begin_info = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; + begin_info.renderPass = renderpass->handle; + /* printf("Current img: %d Current frame %d\n", context.current_img_index, context.current_frame); + */ + begin_info.framebuffer = context.swapchain_framebuffers[context.current_img_index]; + begin_info.renderArea.offset = (VkOffset2D){ 0, 0 }; + begin_info.renderArea.extent = context.swapchain->extent; + + // VkClearValue clear_values[2]; + VkClearValue clear_color = { { { 0.02f, 0.02f, 0.02f, 1.0f } } }; + // clear_values[1].depthStencil.depth = renderpass->depth; + // clear_values[1].depthStencil.stencil = renderpass->stencil; + + begin_info.clearValueCount = 1; + begin_info.pClearValues = &clear_color; + + vkCmdBeginRenderPass(encoder->cmd_buffer, &begin_info, VK_SUBPASS_CONTENTS_INLINE); + // command_buffer->state = COMMAND_BUFFER_STATE_IN_RENDER_PASS; +} + +void gpu_cmd_encoder_end_render(gpu_cmd_encoder* encoder) { + vkCmdEndRenderPass(encoder->cmd_buffer); +} + +gpu_cmd_buffer gpu_cmd_encoder_finish(gpu_cmd_encoder* encoder) { + vkEndCommandBuffer(encoder->cmd_buffer); + + // TEMP: submit + return (gpu_cmd_buffer){ .cmd_buffer = encoder->cmd_buffer }; +} + +// --- Binding +void encode_bind_pipeline(gpu_cmd_encoder* encoder, pipeline_kind kind, gpu_pipeline* pipeline) { + vkCmdBindPipeline(encoder->cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->handle); + encoder->pipeline = pipeline; +} + +void encode_bind_shader_data(gpu_cmd_encoder* encoder, u32 group, shader_data* data) { + arena tmp = arena_create(malloc(1024), 1024); + + assert(data->data != NULL); + + // Update the local buffer + desc_set_uniform_buffer ubo = encoder->pipeline->uniform_pointers[group]; + memcpy(ubo.uniform_buf_mem_mappings[context.current_frame], data->data, ubo.size); + + VkDescriptorSetAllocateInfo alloc_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; + alloc_info.descriptorPool = encoder->descriptor_pool; + alloc_info.descriptorSetCount = 1; + alloc_info.pSetLayouts = &encoder->pipeline->desc_set_layouts[group]; + + shader_data_layout sdl = data->shader_data_get_layout(data->data); + size_t binding_count = sdl.bindings_count; + assert(binding_count == 2); + + VkDescriptorSet sets[0]; + VK_CHECK(vkAllocateDescriptorSets(context.device->logical_device, &alloc_info, sets)); + // FIXME: hardcoded + VkDescriptorSet_darray_push(context.free_set_queue, sets[0]); + /* VkDescriptorSet_darray_push(context.free_set_queue, sets[1]); */ + + VkWriteDescriptorSet write_sets[binding_count]; + memset(&write_sets, 0, binding_count * sizeof(VkWriteDescriptorSet)); + + for (u32 i = 0; i < sdl.bindings_count; i++) { + shader_binding binding = sdl.bindings[i]; + + if (binding.type == SHADER_BINDING_BUFFER || binding.type == SHADER_BINDING_BYTES) { + VkDescriptorBufferInfo* buffer_info = arena_alloc(&tmp, sizeof(VkDescriptorBufferInfo)); + buffer_info->buffer = ubo.buffers[context.current_frame]; + buffer_info->offset = 0; + buffer_info->range = binding.data.bytes.size; + + write_sets[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_sets[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write_sets[i].descriptorCount = 1; + write_sets[i].dstSet = sets[0]; + write_sets[i].dstBinding = i; + write_sets[i].dstArrayElement = 0; + write_sets[i].pBufferInfo = buffer_info; + } else if (binding.type == SHADER_BINDING_TEXTURE) { + gpu_texture* texture = TEXTURE_GET(binding.data.texture.handle); + VkDescriptorImageInfo* image_info = arena_alloc(&tmp, sizeof(VkDescriptorImageInfo)); + image_info->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + image_info->imageView = texture->view; + image_info->sampler = texture->sampler; + + write_sets[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_sets[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write_sets[i].descriptorCount = 1; + write_sets[i].dstSet = sets[0]; + write_sets[i].dstBinding = i; + write_sets[i].dstArrayElement = 0; + write_sets[i].pImageInfo = image_info; + } else { + WARN("Unknown binding"); + } + } + + // Update + vkUpdateDescriptorSets(context.device->logical_device, binding_count, write_sets, 0, NULL); + + // Bind + vkCmdBindDescriptorSets(encoder->cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + encoder->pipeline->layout_handle, 0, 1, sets, 0, NULL); + + arena_free_storage(&tmp); +} + +void encode_set_vertex_buffer(gpu_cmd_encoder* encoder, buffer_handle buf) { + gpu_buffer* buffer = BUFFER_GET(buf); // context.buffers[buf.raw]; + VkBuffer vbs[] = { buffer->handle }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(encoder->cmd_buffer, 0, 1, vbs, offsets); +} + +void encode_set_index_buffer(gpu_cmd_encoder* encoder, buffer_handle buf) { + gpu_buffer* buffer = BUFFER_GET(buf); // context.buffers[buf.raw]; + vkCmdBindIndexBuffer(encoder->cmd_buffer, buffer->handle, 0, VK_INDEX_TYPE_UINT32); +} + +// TEMP +void encode_set_default_settings(gpu_cmd_encoder* encoder) { + VkViewport viewport = { 0 }; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = context.swapchain->extent.width; + viewport.height = context.swapchain->extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(encoder->cmd_buffer, 0, 1, &viewport); + + VkRect2D scissor = { 0 }; + scissor.offset = (VkOffset2D){ 0, 0 }; + scissor.extent = context.swapchain->extent; + vkCmdSetScissor(encoder->cmd_buffer, 0, 1, &scissor); +} + +// --- Drawing + +bool gpu_backend_begin_frame() { + u32 current_frame = context.current_frame; + vkWaitForFences(context.device->logical_device, 1, &context.in_flight_fences[current_frame], + VK_TRUE, UINT64_MAX); + + u32 image_index; + VkResult result = vkAcquireNextImageKHR( + context.device->logical_device, context.swapchain->handle, UINT64_MAX, + context.image_available_semaphores[current_frame], VK_NULL_HANDLE, &image_index); + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || context.is_resizing) { + ERROR("Acquire next image failure. recreate swapchain"); + context.is_resizing = false; + recreate_swapchain(context.swapchain); + return false; + } else if (result != VK_SUCCESS) { + ERROR_EXIT("failed to acquire swapchain image"); + } + + vkResetFences(context.device->logical_device, 1, &context.in_flight_fences[current_frame]); + + context.current_img_index = image_index; + VK_CHECK(vkResetCommandBuffer(context.main_cmd_bufs[current_frame].cmd_buffer, 0)); + return true; +} + +void gpu_temp_draw(size_t n_indices) { + gpu_cmd_encoder* encoder = gpu_get_default_cmd_encoder(); // &context.main_cmd_buf; + /* vkCmdDraw(encoder->cmd_buffer, n_verts, 1, 0, 0); */ + vkCmdDrawIndexed(encoder->cmd_buffer, n_indices, 1, 0, 0, 0); +} + +void gpu_backend_end_frame() { + VkPresentInfoKHR present_info = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = &context.render_finished_semaphores[context.current_frame]; + + VkSwapchainKHR swapchains[] = { context.swapchain->handle }; + present_info.swapchainCount = 1; + present_info.pSwapchains = swapchains; + present_info.pImageIndices = &context.current_img_index; + + VkResult result = vkQueuePresentKHR(context.device->present_queue, &present_info); + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + ERROR("Queue present error. recreate swapchain"); + recreate_swapchain(context.swapchain); + return; + } else if (result != VK_SUCCESS) { + ERROR_EXIT("failed to present swapchain image"); + } + context.current_frame = (context.current_frame + 1) % MAX_FRAMES_IN_FLIGHT; + + /* vkDeviceWaitIdle(context.device->logical_device); */ +} + +// TODO: Move into better order in file +void gpu_queue_submit(gpu_cmd_buffer* buffer) { + VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; + + // Specify semaphore to wait on + VkSemaphore wait_semaphores[] = { context.image_available_semaphores[context.current_frame] }; + VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = wait_semaphores; + submit_info.pWaitDstStageMask = wait_stages; + + // Specify semaphore to signal when finished executing buffer + VkSemaphore signal_semaphores[] = { context.render_finished_semaphores[context.current_frame] }; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = signal_semaphores; + + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &buffer->cmd_buffer; + + VK_CHECK(vkQueueSubmit(context.device->graphics_queue, 1, &submit_info, + context.in_flight_fences[context.current_frame])); +} + +inline void encode_draw_indexed(gpu_cmd_encoder* encoder, u64 index_count) { + vkCmdDrawIndexed(encoder->cmd_buffer, index_count, 1, 0, 0, 0); +} + +bool select_physical_device(gpu_device* out_device) { + u32 physical_device_count = 0; + VK_CHECK(vkEnumeratePhysicalDevices(context.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 = + arena_alloc(&context.temp_arena, physical_device_count * sizeof(VkPhysicalDevice)); + VK_CHECK(vkEnumeratePhysicalDevices(context.instance, &physical_device_count, physical_devices)); + + bool found = false; + for (u32 device_i = 0; device_i < physical_device_count; device_i++) { + if (is_physical_device_suitable(physical_devices[device_i])) { + out_device->physical_device = physical_devices[device_i]; + found = true; + break; + } + } + + if (!found) { + FATAL("Couldn't find a suitable physical device"); + return false; + } + + vkGetPhysicalDeviceProperties(out_device->physical_device, &out_device->properties); + vkGetPhysicalDeviceFeatures(out_device->physical_device, &out_device->features); + vkGetPhysicalDeviceMemoryProperties(out_device->physical_device, &out_device->memory); + + return true; +} + +bool is_physical_device_suitable(VkPhysicalDevice device) { + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(device, &properties); + + VkPhysicalDeviceFeatures features; + vkGetPhysicalDeviceFeatures(device, &features); + + VkPhysicalDeviceMemoryProperties memory; + vkGetPhysicalDeviceMemoryProperties(device, &memory); + + // TODO: Check against these device properties + + queue_family_indices indices = find_queue_families(device); + + vulkan_device_query_swapchain_support(device, context.surface, &context.swapchain_support); + + return indices.has_graphics && indices.has_present && context.swapchain_support.mode_count > 0 && + context.swapchain_support.format_count > 0; +} + +queue_family_indices find_queue_families(VkPhysicalDevice device) { + queue_family_indices indices = { 0 }; + + u32 queue_family_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, 0); + + VkQueueFamilyProperties* queue_families = + arena_alloc(&context.temp_arena, queue_family_count * sizeof(VkQueueFamilyProperties)); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families); + + for (u32 q_fam_i = 0; q_fam_i < queue_family_count; q_fam_i++) { + // Graphics queue + if (queue_families[q_fam_i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphics_family_index = q_fam_i; + indices.has_graphics = true; + } + + VkBool32 present_support = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, q_fam_i, context.surface, &present_support); + if (present_support && !indices.has_present) { + indices.present_family_index = q_fam_i; + indices.has_present = true; + } + } + + return indices; +} + +bool create_logical_device(gpu_device* out_device) { + queue_family_indices indices = find_queue_families(out_device->physical_device); + INFO(" %s | %s | %s | %s | %s", bool_str(indices.has_graphics), bool_str(indices.has_present), + bool_str(indices.has_compute), bool_str(indices.has_transfer), + out_device->properties.deviceName); + TRACE("Graphics Family queue index: %d", indices.graphics_family_index); + TRACE("Present Family queue index: %d", indices.present_family_index); + TRACE("Compute Family queue index: %d", indices.compute_family_index); + TRACE("Transfer Family queue index: %d", indices.transfer_family_index); + + // Queues + f32 prio_one = 1.0; + VkDeviceQueueCreateInfo queue_create_infos[1] = { 0 }; + queue_create_infos[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_infos[0].queueFamilyIndex = indices.graphics_family_index; + queue_create_infos[0].queueCount = 1; + queue_create_infos[0].pQueuePriorities = &prio_one; + queue_create_infos[0].flags = 0; + queue_create_infos[0].pNext = 0; + + // queue_create_infos[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + // queue_create_infos[1].queueFamilyIndex = indices.present_family_index; + // queue_create_infos[1].queueCount = 1; + // queue_create_infos[1].pQueuePriorities = &prio_one; + // queue_create_infos[1].flags = 0; + // queue_create_infos[1].pNext = 0; + + // Features + VkPhysicalDeviceFeatures device_features = { 0 }; + device_features.samplerAnisotropy = VK_TRUE; // request anistrophy + + // Device itself + VkDeviceCreateInfo device_create_info = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; + device_create_info.queueCreateInfoCount = 1; + device_create_info.pQueueCreateInfos = queue_create_infos; + 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("Unable to create vulkan logical device. Exiting.."); + } + TRACE("Logical device created"); + + context.device->queue_family_indicies = indices; + + // Retrieve queue handles + vkGetDeviceQueue(context.device->logical_device, indices.graphics_family_index, 0, + &context.device->graphics_queue); + vkGetDeviceQueue(context.device->logical_device, indices.present_family_index, 0, + &context.device->present_queue); + + return true; +} + +VkShaderModule create_shader_module(str8 spirv) { + VkShaderModuleCreateInfo create_info = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; + create_info.codeSize = spirv.len; + create_info.pCode = (uint32_t*)spirv.buf; + + VkShaderModule shader_module; + VK_CHECK(vkCreateShaderModule(context.device->logical_device, &create_info, context.allocator, + &shader_module)); + + return shader_module; +} + +void create_descriptor_pools() {} + +void create_swapchain_framebuffers() { + WARN("Recreating framebuffers..."); + u32 image_count = context.swapchain->image_count; + context.swapchain_framebuffers = + arena_alloc(&context.swapchain->swapchain_arena, image_count * sizeof(VkFramebuffer)); + for (u32 i = 0; i < image_count; i++) { + VkImageView attachments[1] = { context.swapchain->image_views[i] }; + + VkFramebufferCreateInfo framebuffer_create_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO }; + framebuffer_create_info.attachmentCount = 1; + framebuffer_create_info.pAttachments = attachments; + + framebuffer_create_info.renderPass = + context.main_renderpass; // TODO: description.renderpass->handle; + framebuffer_create_info.width = context.swapchain->extent.width; + framebuffer_create_info.height = context.swapchain->extent.height; + framebuffer_create_info.layers = 1; + + VK_CHECK(vkCreateFramebuffer(context.device->logical_device, &framebuffer_create_info, + context.allocator, &context.swapchain_framebuffers[i])); + } +} + +void create_sync_objects() { + VkSemaphoreCreateInfo semaphore_info = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; + VkFenceCreateInfo fence_info = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; + fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VK_CHECK(vkCreateSemaphore(context.device->logical_device, &semaphore_info, context.allocator, + &context.image_available_semaphores[i]);); + VK_CHECK(vkCreateSemaphore(context.device->logical_device, &semaphore_info, context.allocator, + &context.render_finished_semaphores[i]);); + + VK_CHECK(vkCreateFence(context.device->logical_device, &fence_info, context.allocator, + &context.in_flight_fences[i])); + } +} + +static i32 find_memory_index(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; +} + +buffer_handle gpu_buffer_create(u64 size, gpu_buffer_type buf_type, gpu_buffer_flags flags, + const void* data) { + VkBufferCreateInfo buffer_info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + buffer_info.size = size; + buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + switch (buf_type) { + case CEL_BUFFER_DEFAULT: + buffer_info.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + break; + case CEL_BUFFER_VERTEX: + buffer_info.usage |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + break; + case CEL_BUFFER_INDEX: + buffer_info.usage |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + break; + case CEL_BUFFER_UNIFORM: + buffer_info.usage |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + break; + case CEL_BUFFER_COUNT: + WARN("Incorrect gpu_buffer_type provided. using default"); + break; + } + + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + // "allocating" the cpu-side buffer struct + /* gpu_buffer buffer; */ + /* buffer.size = size; */ + buffer_handle handle; + gpu_buffer* buffer = buffer_pool_alloc(&context.resource_pools->buffers, &handle); + buffer->size = size; + + VK_CHECK(vkCreateBuffer(context.device->logical_device, &buffer_info, context.allocator, + &buffer->handle)); + + VkMemoryRequirements requirements; + vkGetBufferMemoryRequirements(context.device->logical_device, buffer->handle, &requirements); + + // Just make them always need all of them for now + i32 memory_index = + find_memory_index(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + // Allocate the actual VRAM + VkMemoryAllocateInfo allocate_info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + allocate_info.allocationSize = requirements.size; + allocate_info.memoryTypeIndex = (u32)memory_index; + + vkAllocateMemory(context.device->logical_device, &allocate_info, context.allocator, + &buffer->memory); + vkBindBufferMemory(context.device->logical_device, buffer->handle, buffer->memory, 0); + + /* Now there are two options: + * 1. create CPU-accessible memory -> map memory -> memcpy -> unmap + * 2. use a staging buffer thats CPU-accessible and copy its contents to a + * GPU-only buffer + */ + + /* context.buffers[context.buffer_count] = buffer; */ + /* context.buffer_count++; */ + + if (data) { + TRACE("Upload data as part of buffer creation"); + if (flags & CEL_BUFFER_FLAG_CPU) { + // map memory -> copy data in -> unmap memory + buffer_upload_bytes(handle, (bytebuffer){ .buf = (u8*)data, .size = size }, 0, size); + } else if (flags & CEL_BUFFER_FLAG_GPU) { + TRACE("Uploading data to buffer using staging buffer"); + // Create a staging buffer + buffer_handle staging = gpu_buffer_create(size, buf_type, CEL_BUFFER_FLAG_CPU, NULL); + + // Copy data into it + buffer_upload_bytes(staging, (bytebuffer){ .buf = (u8*)data, .size = size }, 0, size); + + // Enqueue a copy from the staging buffer into the DEVICE_LOCAL buffer + gpu_cmd_encoder temp_encoder = gpu_cmd_encoder_create(); + gpu_cmd_encoder_begin(temp_encoder); + encode_buffer_copy(&temp_encoder, staging, 0, handle, 0, size); + gpu_cmd_buffer copy_cmd_buffer = gpu_cmd_encoder_finish(&temp_encoder); + + VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &temp_encoder.cmd_buffer; + vkQueueSubmit(context.device->graphics_queue, 1, &submit_info, VK_NULL_HANDLE); + + // Cleanup + vkQueueWaitIdle(context.device->graphics_queue); + gpu_cmd_encoder_destroy(&temp_encoder); + gpu_buffer_destroy(staging); + } + } + + return handle; +} + +void gpu_buffer_destroy(buffer_handle buffer) { + gpu_buffer* b = buffer_pool_get(&context.resource_pools->buffers, buffer); + vkDestroyBuffer(context.device->logical_device, b->handle, context.allocator); + vkFreeMemory(context.device->logical_device, b->memory, context.allocator); + buffer_pool_dealloc(&context.resource_pools->buffers, buffer); +} + +// Upload data to a +void buffer_upload_bytes(buffer_handle gpu_buf, bytebuffer cpu_buf, u64 offset, u64 size) { + gpu_buffer* buffer = buffer_pool_get(&context.resource_pools->buffers, gpu_buf); + void* data_ptr; + vkMapMemory(context.device->logical_device, buffer->memory, 0, size, 0, &data_ptr); + DEBUG("Uploading %d bytes to buffer", size); + memcpy(data_ptr, cpu_buf.buf, size); + vkUnmapMemory(context.device->logical_device, buffer->memory); +} + +void encode_buffer_copy(gpu_cmd_encoder* encoder, buffer_handle src, u64 src_offset, + buffer_handle dst, u64 dst_offset, u64 copy_size) { + VkBufferCopy copy_region; + copy_region.srcOffset = src_offset; + copy_region.dstOffset = dst_offset; + copy_region.size = copy_size; + + gpu_buffer* src_buf = buffer_pool_get(&context.resource_pools->buffers, src); + gpu_buffer* dst_buf = buffer_pool_get(&context.resource_pools->buffers, dst); + vkCmdCopyBuffer(encoder->cmd_buffer, src_buf->handle, dst_buf->handle, 1, ©_region); +} + +// one-shot command buffers +VkCommandBuffer vulkan_command_buffer_create_oneshot() { + VkCommandBufferAllocateInfo alloc_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; + alloc_info.commandPool = context.device->pool; + alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + alloc_info.commandBufferCount = 1; + alloc_info.pNext = 0; + + VkCommandBuffer cmd_buffer; + vkAllocateCommandBuffers(context.device->logical_device, &alloc_info, &cmd_buffer); + + VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(cmd_buffer, &begin_info); + + return cmd_buffer; +} + +void vulkan_command_buffer_finish_oneshot(VkCommandBuffer cmd_buffer) { + VK_CHECK(vkEndCommandBuffer(cmd_buffer)); + + // submit to queue + VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &cmd_buffer; + VK_CHECK(vkQueueSubmit(context.device->graphics_queue, 1, &submit_info, 0)); + VK_CHECK(vkQueueWaitIdle(context.device->graphics_queue)); + + vkFreeCommandBuffers(context.device->logical_device, context.device->pool, 1, &cmd_buffer); +} + +void copy_buffer_to_buffer_oneshot(buffer_handle src, u64 src_offset, buffer_handle dst, + u64 dst_offset, u64 copy_size) { + VkBufferCopy copy_region; + copy_region.srcOffset = src_offset; + copy_region.dstOffset = dst_offset; + copy_region.size = copy_size; + + gpu_buffer* src_buf = buffer_pool_get(&context.resource_pools->buffers, src); + gpu_buffer* dst_buf = buffer_pool_get(&context.resource_pools->buffers, dst); + VkCommandBuffer temp_cmd_buffer = vulkan_command_buffer_create_oneshot(); + vkCmdCopyBuffer(temp_cmd_buffer, src_buf->handle, dst_buf->handle, 1, ©_region); + vulkan_command_buffer_finish_oneshot(temp_cmd_buffer); +} + +void copy_buffer_to_image_oneshot(buffer_handle src, texture_handle dst) { + gpu_buffer* src_buf = buffer_pool_get(&context.resource_pools->buffers, src); + gpu_texture* dst_tex = texture_pool_get(&context.resource_pools->textures, dst); + + VkCommandBuffer temp_cmd_buffer = vulkan_command_buffer_create_oneshot(); + + 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", dst_tex->desc.extents.x, dst_tex->desc.extents.y); + region.imageOffset.x = 0; + region.imageOffset.y = 0; + region.imageOffset.z = 0; + region.imageExtent.width = dst_tex->desc.extents.x; + region.imageExtent.height = dst_tex->desc.extents.y; + region.imageExtent.depth = 1; + + vkCmdCopyBufferToImage(temp_cmd_buffer, src_buf->handle, dst_tex->handle, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + vulkan_command_buffer_finish_oneshot(temp_cmd_buffer); +} + +VkImage vulkan_image_create(u32x2 dimensions, VkImageType image_type, VkFormat format, + VkImageUsageFlags usage) { + VkImage image; + + VkImageCreateInfo image_create_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + image_create_info.imageType = VK_IMAGE_TYPE_2D; + image_create_info.extent.width = dimensions.x; + image_create_info.extent.height = dimensions.y; + 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 = VK_IMAGE_TILING_OPTIMAL; + image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_create_info.usage = usage; // VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + image_create_info.samples = VK_SAMPLE_COUNT_1_BIT; + + VK_CHECK( + vkCreateImage(context.device->logical_device, &image_create_info, context.allocator, &image)); + + return image; +} + +texture_handle gpu_texture_create(texture_desc desc, bool create_view, const void* data) { + VkDeviceSize image_size = desc.extents.x * desc.extents.y * 4; + // FIXME: handle this properly + VkFormat format = desc.format == CEL_TEXTURE_FORMAT_8_8_8_8_RGBA_UNORM ? VK_FORMAT_R8G8B8A8_SRGB + : VK_FORMAT_D32_SFLOAT; + + VkImage image; // vulkan_image_create(desc.extents, VK_IMAGE_TYPE_2D, format, + // VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); + VkDeviceMemory image_memory; + + VkImageCreateInfo image_create_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + image_create_info.imageType = VK_IMAGE_TYPE_2D; + image_create_info.extent.width = desc.extents.x; + image_create_info.extent.height = desc.extents.y; + 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 = VK_IMAGE_TILING_OPTIMAL; + image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + if (format == VK_FORMAT_D32_SFLOAT) { + image_create_info.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + } + image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + image_create_info.samples = VK_SAMPLE_COUNT_1_BIT; + + VK_CHECK( + vkCreateImage(context.device->logical_device, &image_create_info, context.allocator, &image)); + + VkMemoryRequirements memory_reqs; + vkGetImageMemoryRequirements(context.device->logical_device, image, &memory_reqs); + + VkMemoryAllocateInfo alloc_info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + alloc_info.allocationSize = memory_reqs.size; + alloc_info.memoryTypeIndex = + find_memory_index(memory_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + vkAllocateMemory(context.device->logical_device, &alloc_info, context.allocator, &image_memory); + + vkBindImageMemory(context.device->logical_device, image, image_memory, 0); + + texture_handle handle; + gpu_texture* texture = texture_pool_alloc(&context.resource_pools->textures, &handle); + DEBUG("Allocated texture with handle %d", handle.raw); + texture->handle = image; + texture->debug_label = "Test Texture"; + texture->desc = desc; + texture->memory = image_memory; + texture->size = image_size; + + if (data) { + TRACE("Uploading pixel data to texture using staging buffer"); + // Create a staging buffer + buffer_handle staging = + gpu_buffer_create(image_size, CEL_BUFFER_DEFAULT, CEL_BUFFER_FLAG_CPU, NULL); + // Copy data into it + vulkan_transition_image_layout(texture, format, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + buffer_upload_bytes(staging, (bytebuffer){ .buf = (u8*)data, .size = image_size }, 0, + image_size); + copy_buffer_to_image_oneshot(staging, handle); + vulkan_transition_image_layout(texture, format, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + gpu_buffer_destroy(staging); + } + + // Texture View + if (create_view) { + VkImageViewCreateInfo view_create_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + view_create_info.image = image; + view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_create_info.format = format; + view_create_info.subresourceRange.aspectMask = + format == VK_FORMAT_D32_SFLOAT ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; + + view_create_info.subresourceRange.baseMipLevel = 0; + view_create_info.subresourceRange.levelCount = 1; + view_create_info.subresourceRange.baseArrayLayer = 0; + view_create_info.subresourceRange.layerCount = 1; + + VK_CHECK(vkCreateImageView(context.device->logical_device, &view_create_info, context.allocator, + &texture->view)); + } + + // Sampler + 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, + &texture->sampler); + if (res != VK_SUCCESS) { + ERROR("Error creating texture sampler for image %s", texture->debug_label); + return; + } + + return handle; +} + +void vulkan_transition_image_layout(gpu_texture* texture, VkFormat format, VkImageLayout old_layout, + VkImageLayout new_layout) { + VkCommandBuffer temp_cmd_buffer = vulkan_command_buffer_create_oneshot(); + + VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + barrier.oldLayout = old_layout; + barrier.newLayout = new_layout; + barrier.srcQueueFamilyIndex = context.device->queue_family_indicies.graphics_family_index; + barrier.dstQueueFamilyIndex = context.device->queue_family_indicies.graphics_family_index; + barrier.image = texture->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; // TODO + barrier.dstAccessMask = 0; // TODO + + 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(temp_cmd_buffer, source_stage, dest_stage, 0, 0, 0, 0, 0, 1, &barrier); + + vulkan_command_buffer_finish_oneshot(temp_cmd_buffer); +} + +/* TYPED_POOL(gpu_buffer, buffer); */ +/* TYPED_POOL(gpu_texture, texture); */ + +void resource_pools_init(arena* a, struct resource_pools* res_pools) { + buffer_pool buf_pool = buffer_pool_create(a, MAX_BUFFERS, sizeof(gpu_buffer)); + res_pools->buffers = buf_pool; + texture_pool tex_pool = texture_pool_create(a, MAX_TEXTURES, sizeof(gpu_texture)); + res_pools->textures = tex_pool; + + context.resource_pools = res_pools; +} diff --git a/src/renderer/backends/backend_vulkan.h b/src/renderer/backends/backend_vulkan.h new file mode 100644 index 0000000..dc0f7bd --- /dev/null +++ b/src/renderer/backends/backend_vulkan.h @@ -0,0 +1,115 @@ +#pragma once +#include <vulkan/vk_platform.h> +#include <vulkan/vulkan.h> +#include <vulkan/vulkan_core.h> + +#include "defines.h" +#include "mem.h" +#include "ral.h" +#include "ral_types.h" + +#define MAX_FRAMES_IN_FLIGHT 2 +#define GPU_SWAPCHAIN_IMG_COUNT 2 + +/* +Conventions: + - Place the 'handle' as the first field of a struct + - Vulkan specific data goes at the top, followed by our internal data +*/ + +typedef struct queue_family_indices { + u32 graphics_family_index; + u32 present_family_index; + u32 compute_family_index; + u32 transfer_family_index; + bool has_graphics; + bool has_present; + bool has_compute; + bool has_transfer; +} queue_family_indices; + +// typedef struct vulkan_framebuffer { +// } vulkan_framebuffer; + +typedef struct gpu_swapchain { + VkSwapchainKHR handle; + arena swapchain_arena; + VkExtent2D extent; + VkSurfaceFormatKHR image_format; + VkPresentModeKHR present_mode; + u32 image_count; + VkImage* images; + VkImageView* image_views; +} gpu_swapchain; + +typedef struct gpu_device { + // In Vulkan we store both physical and logical device here + VkPhysicalDevice physical_device; + VkDevice logical_device; + VkPhysicalDeviceProperties properties; + VkPhysicalDeviceFeatures features; + VkPhysicalDeviceMemoryProperties memory; + queue_family_indices queue_family_indicies; + VkQueue graphics_queue; + VkQueue present_queue; + VkQueue compute_queue; + VkQueue transfer_queue; + VkCommandPool pool; +} gpu_device; + +typedef struct gpu_pipeline_layout { + VkPipelineLayout handle; +} gpu_pipeline_layout; + +typedef struct desc_set_uniform_buffer { + VkBuffer buffers[MAX_FRAMES_IN_FLIGHT]; + VkDeviceMemory uniform_buf_memorys[MAX_FRAMES_IN_FLIGHT]; + void* uniform_buf_mem_mappings[MAX_FRAMES_IN_FLIGHT]; + size_t size; +} desc_set_uniform_buffer; + +typedef struct gpu_pipeline { + VkPipeline handle; + VkPipelineLayout layout_handle; + + // Descriptor gubbins + shader_data data_layouts[MAX_SHADER_DATA_LAYOUTS]; + u32 data_layouts_count; + + VkDescriptorSetLayout* desc_set_layouts; + // Based on group, we know which data to load + desc_set_uniform_buffer* uniform_pointers; + u32 desc_set_layouts_count; + +} gpu_pipeline; + +typedef struct gpu_renderpass { + VkRenderPass handle; + // TODO: Where to store framebuffers? VkFramebuffer framebuffers[GPU_SWAPCHAIN_IMG_COUNT]; +} gpu_renderpass; + +typedef struct gpu_cmd_encoder { + VkCommandBuffer cmd_buffer; + VkDescriptorPool descriptor_pool; + gpu_pipeline* pipeline; +} gpu_cmd_encoder; + +typedef struct gpu_cmd_buffer { + VkCommandBuffer cmd_buffer; +} gpu_cmd_buffer; + +typedef struct gpu_buffer { + VkBuffer handle; + VkDeviceMemory memory; + u64 size; +} gpu_buffer; + +typedef struct gpu_texture { + VkImage handle; + VkDeviceMemory memory; + u64 size; + texture_desc desc; + VkImageView view; + VkSampler sampler; + char* debug_label; +} gpu_texture; diff --git a/src/renderer/backends/metal/README.md b/src/renderer/backends/metal/README.md new file mode 100644 index 0000000..f87f5c1 --- /dev/null +++ b/src/renderer/backends/metal/README.md @@ -0,0 +1 @@ +# TODO
\ No newline at end of file diff --git a/src/renderer/backends/opengl/README.md b/src/renderer/backends/opengl/README.md new file mode 100644 index 0000000..f87f5c1 --- /dev/null +++ b/src/renderer/backends/opengl/README.md @@ -0,0 +1 @@ +# TODO
\ No newline at end of file diff --git a/src/renderer/backends/vulkan/README.md b/src/renderer/backends/vulkan/README.md new file mode 100644 index 0000000..220ed64 --- /dev/null +++ b/src/renderer/backends/vulkan/README.md @@ -0,0 +1 @@ +# Vulkan Backend Overview
\ 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..23666c6 --- /dev/null +++ b/src/renderer/backends/vulkan_helpers.h @@ -0,0 +1,199 @@ +#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) \ + do { \ + VkResult res = vulkan_expr; \ + if (res != VK_SUCCESS) { \ + ERROR_EXIT("Vulkan error: %u (%s:%d)", res, __FILE__, __LINE__); \ + } \ + } while (0) + +// 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; + +#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); + +static 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 VkSurfaceFormatKHR choose_swapchain_format( + vulkan_swapchain_support_info* swapchain_support) { + assert(swapchain_support->format_count > 0); + // find a format + for (u32 i = 0; i < swapchain_support->format_count; i++) { + VkSurfaceFormatKHR format = swapchain_support->formats[i]; + if (format.format == VK_FORMAT_B8G8R8A8_SRGB && + format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return format; + } + } + return swapchain_support->formats[0]; +} + +// 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; +// } + +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; +}
\ No newline at end of file diff --git a/src/renderer/bind_group_layouts.h b/src/renderer/bind_group_layouts.h new file mode 100644 index 0000000..246d1ef --- /dev/null +++ b/src/renderer/bind_group_layouts.h @@ -0,0 +1,30 @@ +/** + * @file bind_group_layouts.h + * @author your name (you@domain.com) + * @brief Common bindgroups (descriptor set layouts) + * @version 0.1 + * @date 2024-04-28 + * + * @copyright Copyright (c) 2024 + * + */ +#pragma once +#include "defines.h" +#include "maths_types.h" + +// Three major sets + +// 1. Scene / Global +typedef struct bg_globals { + mat4 view; + mat4 projection; + f32 total_time; + f32 delta_time; +} bg_globals; + +// 2. Material (once per object) + +// 3. Per draw call +typedef struct bg_model { + mat4 model; +} bg_model; diff --git a/src/renderer/immediate.c b/src/renderer/immediate.c new file mode 100644 index 0000000..e76d102 --- /dev/null +++ b/src/renderer/immediate.c @@ -0,0 +1,18 @@ +#include "immediate.h" +#include "maths.h" +#include "primitives.h" +#include "ral_types.h" +#include "render.h" + +void imm_draw_sphere(vec3 pos, f32 radius, vec4 colour) { + // Create the vertices + geometry_data geometry = geo_create_uvsphere(radius, 16, 16); + geo_set_vertex_colours(&geometry, colour); + + // Upload to GPU + mat4 model = mat4_translation(pos); + + // Set pipeline + + // Draw +}
\ No newline at end of file diff --git a/src/renderer/immediate.h b/src/renderer/immediate.h new file mode 100644 index 0000000..b9d7c61 --- /dev/null +++ b/src/renderer/immediate.h @@ -0,0 +1,20 @@ +#pragma once + +#include "geometry.h" +#include "maths_types.h" + +// 3. SIMA (simplified immediate mode api) / render.h +// - dont need to worry about uploading mesh data +// - very useful for debugging +void imm_draw_cuboid(vec3 pos, quat rotation, f32x3 extents, vec4 colour); +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/ral.c b/src/renderer/ral.c new file mode 100644 index 0000000..7d868be --- /dev/null +++ b/src/renderer/ral.c @@ -0,0 +1,42 @@ +#include "ral.h" + +size_t vertex_attrib_size(vertex_attrib_type attr) { + switch (attr) { + case ATTR_F32: + case ATTR_U32: + case ATTR_I32: + return 4; + case ATTR_F32x2: + case ATTR_U32x2: + case ATTR_I32x2: + return 8; + case ATTR_F32x3: + case ATTR_U32x3: + case ATTR_I32x3: + return 12; + case ATTR_F32x4: + case ATTR_U32x4: + case ATTR_I32x4: + return 16; + break; + } +} + +void vertex_desc_add(vertex_description* builder, const char* name, vertex_attrib_type type) { + u32 i = builder->attributes_count; + + size_t size = vertex_attrib_size(type); + builder->attributes[i] = type; + builder->stride += size; + builder->attr_names[i] = name; + + builder->attributes_count++; +} + +vertex_description static_3d_vertex_description() { + vertex_description builder = { .debug_label = "vertex" }; + vertex_desc_add(&builder, "position", ATTR_F32x3); + vertex_desc_add(&builder, "normal", ATTR_F32x3); + vertex_desc_add(&builder, "texCoords", ATTR_F32x2); + return builder; +} diff --git a/src/renderer/ral.h b/src/renderer/ral.h new file mode 100644 index 0000000..376898f --- /dev/null +++ b/src/renderer/ral.h @@ -0,0 +1,171 @@ +/** + * @file ral.h + * @author your name (you@domain.com) + * @brief Render Abstraction Layer + * @details API that a graphics backend *must* implement + * @version 0.1 + * @date 2024-03-31 + * + * @copyright Copyright (c) 2024 + * + */ +#pragma once + +#include "buf.h" +#include "defines.h" +#include "mem.h" +#include "ral_types.h" +#include "str.h" + +// Unrelated forward declares +struct GLFWwindow; + +// Forward declare structs - these must be defined in the backend implementation +typedef struct gpu_swapchain gpu_swapchain; +typedef struct gpu_device gpu_device; +typedef struct gpu_pipeline_layout gpu_pipeline_layout; +typedef struct gpu_pipeline gpu_pipeline; +typedef struct gpu_renderpass gpu_renderpass; +typedef struct gpu_cmd_encoder gpu_cmd_encoder; // Recording +typedef struct gpu_cmd_buffer gpu_cmd_buffer; // Ready for submission +typedef struct gpu_buffer gpu_buffer; +typedef struct gpu_texture gpu_texture; + +#define MAX_SHADER_DATA_LAYOUTS 5 +#define MAX_BUFFERS 256 +#define MAX_TEXTURES 256 + +TYPED_POOL(gpu_buffer, buffer); +TYPED_POOL(gpu_texture, texture); + +// --- Pools +typedef struct gpu_backend_pools { /* TODO: pools for each gpu structure */ +} gpu_backend_pools; + +struct resource_pools { + buffer_pool buffers; + texture_pool textures; +}; + +typedef enum pipeline_kind { + PIPELINE_GRAPHICS, + PIPELINE_COMPUTE, +} pipeline_kind; + +typedef struct shader_desc { + const char* debug_name; + str8 filepath; // where it came from + str8 code; // Either GLSL or SPIRV bytecode + bool is_spirv; +} shader_desc; + +struct graphics_pipeline_desc { + const char* debug_name; + vertex_description vertex_desc; + shader_desc vs; /** @brief Vertex shader stage */ + shader_desc fs; /** @brief Fragment shader stage */ + + // Roughly equivalent to a descriptor set layout each. each layout can have multiple bindings + // examples: + // - uniform buffer reprensenting view projection matrix + // - texture for shadow map + shader_data data_layouts[MAX_SHADER_DATA_LAYOUTS]; + u32 data_layouts_count; + + // gpu_pipeline_layout* layout; + gpu_renderpass* renderpass; + + bool wireframe; + bool depth_test; +}; + +typedef struct gpu_renderpass_desc { /* TODO */ +} gpu_renderpass_desc; + +// --- Lifecycle functions +bool gpu_backend_init(const char* window_name, struct GLFWwindow* window); +void gpu_backend_shutdown(); +void resource_pools_init(arena* a, struct resource_pools* res_pools); + +bool gpu_device_create(gpu_device* out_device); +void gpu_device_destroy(); + +// --- Render Pipeline +gpu_pipeline* gpu_graphics_pipeline_create(struct graphics_pipeline_desc description); +void gpu_pipeline_destroy(gpu_pipeline* pipeline); + +// --- Renderpass +gpu_renderpass* gpu_renderpass_create(const gpu_renderpass_desc* description); +void gpu_renderpass_destroy(gpu_renderpass* pass); + +// --- Swapchain +bool gpu_swapchain_create(gpu_swapchain* out_swapchain); +void gpu_swapchain_destroy(gpu_swapchain* swapchain); + +// --- Command buffer +gpu_cmd_encoder gpu_cmd_encoder_create(); +void gpu_cmd_encoder_destroy(gpu_cmd_encoder* encoder); +void gpu_cmd_encoder_begin(gpu_cmd_encoder encoder); +void gpu_cmd_encoder_begin_render(gpu_cmd_encoder* encoder, gpu_renderpass* renderpass); +void gpu_cmd_encoder_end_render(gpu_cmd_encoder* encoder); +void gpu_cmd_encoder_begin_compute(); +gpu_cmd_encoder* gpu_get_default_cmd_encoder(); + +// --- Data copy commands +/** @brief Copy data from one buffer to another */ +void encode_buffer_copy(gpu_cmd_encoder* encoder, buffer_handle src, u64 src_offset, + buffer_handle dst, u64 dst_offset, u64 copy_size); +/** @brief Upload CPU-side data as array of bytes to a GPU buffer */ +void buffer_upload_bytes(buffer_handle gpu_buf, bytebuffer cpu_buf, u64 offset, u64 size); + +/** @brief Copy data from buffer to buffer using a one time submit command buffer and a wait */ +void copy_buffer_to_buffer_oneshot(buffer_handle src, u64 src_offset, buffer_handle dst, + u64 dst_offset, u64 copy_size); +/** @brief Copy data from buffer to an image using a one time submit command buffer */ +void copy_buffer_to_image_oneshot(buffer_handle src, texture_handle dst); + +// --- Render commands +void encode_bind_pipeline(gpu_cmd_encoder* encoder, pipeline_kind kind, gpu_pipeline* pipeline); +void encode_bind_shader_data(gpu_cmd_encoder* encoder, u32 group, shader_data* data); +void encode_set_default_settings(gpu_cmd_encoder* encoder); +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_set_bind_group(); // TODO +void encode_draw(gpu_cmd_encoder* encoder); +void encode_draw_indexed(gpu_cmd_encoder* encoder, u64 index_count); +void encode_clear_buffer(gpu_cmd_encoder* encoder, buffer_handle buf); + +/** @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 +buffer_handle gpu_buffer_create(u64 size, gpu_buffer_type buf_type, gpu_buffer_flags flags, + const void* data); +void gpu_buffer_destroy(buffer_handle buffer); +void gpu_buffer_upload(const void* data); + +// Textures +/** @brief Create a new GPU texture resource. + * @param create_view creates a texture view (with same dimensions) at the same time + * @param data if not NULL then the data stored at the pointer will be uploaded to the GPU texture + * @note automatically creates a sampler for you */ +texture_handle gpu_texture_create(texture_desc desc, bool create_view, const void* data); +void gpu_texture_destroy(texture_handle); +void gpu_texture_upload(texture_handle texture, const void* data); + +// --- Vertex formats +bytebuffer vertices_as_bytebuffer(arena* a, vertex_format format, vertex_darray* vertices); + +void vertex_desc_add(vertex_description* builder, const char* name, vertex_attrib_type type); + +// --- TEMP +bool gpu_backend_begin_frame(); +void gpu_backend_end_frame(); +void gpu_temp_draw(size_t n_verts); + +// TODO: --- Compute + +// --- Helpers +vertex_description static_3d_vertex_description(); diff --git a/src/renderer/ral_types.h b/src/renderer/ral_types.h new file mode 100644 index 0000000..62a2f1d --- /dev/null +++ b/src/renderer/ral_types.h @@ -0,0 +1,255 @@ +/** + * @file ral_types.h + * @author your name (you@domain.com) + * @brief Struct and enum definitions for RAL + * @version 0.1 + * @date 2024-04-27 + * + * @copyright Copyright (c) 2024 + * + */ +#pragma once + +#include "darray.h" +#include "defines.h" +#include "maths_types.h" + +#define MAX_VERTEX_ATTRIBUTES 16 + +/* #ifndef RENDERER_TYPED_HANDLES */ +CORE_DEFINE_HANDLE(buffer_handle); +CORE_DEFINE_HANDLE(texture_handle); +CORE_DEFINE_HANDLE(sampler_handle); +CORE_DEFINE_HANDLE(shader_handle); +CORE_DEFINE_HANDLE(model_handle); +#define ABSENT_MODEL_HANDLE 999999999 + +/* #define RENDERER_TYPED_HANDLES */ +/* #endif */ + +/* typedef struct gpu_buffer { */ +/* u32 a; */ +/* } gpu_buffer; */ + +/* #ifndef RAL_TYPED_POOLS */ +/* #define RAL_TYPED_POOLS */ +/* #endif */ + +// 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; + +/** @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; + +typedef enum gpu_buffer_type { + CEL_BUFFER_DEFAULT, // on Vulkan this would be a storage buffer? + CEL_BUFFER_VERTEX, + CEL_BUFFER_INDEX, + CEL_BUFFER_UNIFORM, + CEL_BUFFER_COUNT +} gpu_buffer_type; + +typedef enum gpu_buffer_flag { + CEL_BUFFER_FLAG_CPU = 1 << 0, + CEL_BUFFER_FLAG_GPU = 1 << 1, + CEL_BUFFER_FLAG_STORAGE = 1 << 2, + CEL_BUFFER_FLAG_COUNT +} gpu_buffer_flag; +typedef u32 gpu_buffer_flags; + +typedef enum vertex_format { + VERTEX_STATIC_3D, + VERTEX_SPRITE, + VERTEX_SKINNED, + VERTEX_COLOURED_STATIC_3D, + VERTEX_RAW_POS_COLOUR, + VERTEX_COUNT +} vertex_format; + +typedef union vertex { + struct { + vec3 position; + vec3 normal; + vec2 tex_coords; + } static_3d; /** @brief standard vertex format for static geometry in 3D */ + + struct { + vec2 position; + vec4 colour; + vec2 tex_coords; + } sprite; /** @brief vertex format for 2D sprites or quads */ + + 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 + } skinned_3d; /** @brief vertex format for skeletal (animated) geometry in 3D */ + + struct { + vec3 position; + vec2 tex_coords; + vec3 normal; + vec4 colour; + } coloured_static_3d; /** @brief vertex format used for debugging */ + + struct { + vec2 position; + vec3 colour; + } raw_pos_colour; +} vertex; + +#ifndef TYPED_VERTEX_ARRAY +KITC_DECL_TYPED_ARRAY(vertex) +KITC_DECL_TYPED_ARRAY(u32) +#define TYPED_VERTEX_ARRAY +#endif + +// TEMP +typedef struct custom_vertex { + vec2 pos; + vec3 color; +} custom_vertex; + +// Vertex attributes +/// @strip_prefix(ATTR_) +typedef enum vertex_attrib_type { + ATTR_F32, + ATTR_F32x2, + ATTR_F32x3, + ATTR_F32x4, + ATTR_U32, + ATTR_U32x2, + ATTR_U32x3, + ATTR_U32x4, + ATTR_I32, + ATTR_I32x2, + ATTR_I32x3, + ATTR_I32x4, +} vertex_attrib_type; + +typedef struct vertex_description { + char* debug_label; + const char* attr_names[MAX_VERTEX_ATTRIBUTES]; + vertex_attrib_type attributes[MAX_VERTEX_ATTRIBUTES]; + u32 attributes_count; + size_t stride; + bool use_full_vertex_size; +} vertex_description; + +// --- Shaders & Bindings + +typedef enum shader_visibility { + VISIBILITY_VERTEX = 1 << 0, + VISIBILITY_FRAGMENT = 1 << 1, + VISIBILITY_COMPUTE = 1 << 2, +} shader_visibility; + +/** @brief Describes the kind of binding a `shader_binding` is for. This changes how we create + * backing data for it. */ +typedef enum shader_binding_type { + /** + * @brief Binds a buffer to a shader + * @note Vulkan: Becomes a Storage Buffer + */ + SHADER_BINDING_BUFFER, + SHADER_BINDING_BUFFER_ARRAY, + SHADER_BINDING_TEXTURE, + SHADER_BINDING_TEXTURE_ARRAY, + SHADER_BINDING_SAMPLER, + /** + * @brief Binds raw data to a shader + * @note Vulkan: Becomes a Uniform Buffer + */ + SHADER_BINDING_BYTES, + // TODO: Acceleration Structure + SHADER_BINDING_COUNT +} shader_binding_type; + +// pub trait ShaderBindable: Clone + Copy { +// fn bind_to(&self, context: &mut PipelineContext, index: u32); +// } + +typedef struct shader_binding { + const char* label; + shader_binding_type type; + shader_visibility vis; + bool stores_data; /** @brief if this is true then the shader binding has references to live data, + if false then its just being used to describe a layout and .data + should be zeroed */ + union { + struct { + buffer_handle handle; + } buffer; + struct { + void* data; + size_t size; + } bytes; + struct { + texture_handle handle; + } texture; + } data; /** @brief can store any kind of data that we can bind to a shader / descriptor set */ +} shader_binding; + +#define MAX_LAYOUT_BINDINGS 8 + +/** @brief A list of bindings that describe what data a shader / pipeline expects + @note This roughly correlates to a descriptor set layout in Vulkan +*/ +typedef struct shader_data_layout { + char* name; + shader_binding bindings[MAX_LAYOUT_BINDINGS]; + u32 bindings_count; +} shader_data_layout; + +typedef struct shader_data { + shader_data_layout (*shader_data_get_layout)(void* data); + void* data; +} shader_data; + +/* + Usage: + 1. When we create the pipeline, we must call a function that return a layout without .data + fields + 2. When binding +*/ + +typedef enum gpu_cull_mode { CULL_BACK_FACE, CULL_FRONT_FACE, CULL_COUNT } gpu_cull_mode; + +// ? 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 diff --git a/src/renderer/render.c b/src/renderer/render.c index ac6b80a..5723c9e 100644 --- a/src/renderer/render.c +++ b/src/renderer/render.c @@ -1,47 +1,40 @@ -#include <stdlib.h> -#include "mem.h" -#include "transform_hierarchy.h" +#include <glfw3.h> +#include "maths_types.h" #define STB_IMAGE_IMPLEMENTATION #include <stb_image.h> -#define STB_TRUETYPE_IMPLEMENTATION -#include <stb_truetype.h> - -#include "render.h" -#include "render_types.h" - -#include <glad/glad.h> -#include <glfw3.h> - -#include "defines.h" +#include "camera.h" +#include "file.h" #include "log.h" -#include "maths.h" -#include "render_backend.h" - -// FIXME: get rid of these and store dynamic screen realestate -// in renderer -#define SCR_WIDTH 1080 -#define SCR_HEIGHT 800 +#include "mem.h" +#include "ral.h" +#include "ral_types.h" +#include "render.h" -material DEFAULT_MATERIAL = { 0 }; +/** @brief Creates the pipelines built into Celeritas such as rendering static opaque geometry, + debug visualisations, immediate mode UI, etc */ +void default_pipelines_init(renderer* ren); bool renderer_init(renderer* ren) { - INFO("Renderer init"); + // INFO("Renderer init"); // 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; } @@ -49,205 +42,174 @@ bool renderer_init(renderer* ren) { glfwMakeContextCurrent(ren->window); - if (!gfx_backend_init(ren)) { + DEBUG("Start gpu backend init"); + + if (!gpu_backend_init("Celeritas Engine - Vulkan", window)) { FATAL("Couldnt load graphics api backend"); return false; } + gpu_device_create(&ren->device); // TODO: handle errors + gpu_swapchain_create(&ren->swapchain); - ren->blinn_phong = - shader_create_separate("assets/shaders/blinn_phong.vert", "assets/shaders/blinn_phong.frag"); + DEBUG("Initialise GPU resource pools"); + arena pool_arena = arena_create(malloc(1024 * 1024), 1024 * 1024); + ren->resource_pools = arena_alloc(&pool_arena, sizeof(struct resource_pools)); + resource_pools_init(&pool_arena, ren->resource_pools); - default_material_init(); + // ren->blinn_phong = + // shader_create_separate("assets/shaders/blinn_phong.vert", + // "assets/shaders/blinn_phong.frag"); - return true; -} + // ren->skinned = + // shader_create_separate("assets/shaders/skinned.vert", "assets/shaders/blinn_phong.frag"); -void render_frame_begin(renderer* ren) { - vec3 color = ren->config.clear_colour; - clear_screen(color); -} -void render_frame_end(renderer* ren) { - // present frame - glfwSwapBuffers(ren->window); - glfwPollEvents(); -} + // default_material_init(); -void default_material_init() { - INFO("Load default material") - DEFAULT_MATERIAL.ambient_colour = (vec3){ 0.5, 0.5, 0.5 }; - DEFAULT_MATERIAL.diffuse = (vec3){ 0.8, 0.8, 0.8 }; - DEFAULT_MATERIAL.specular = (vec3){ 1.0, 1.0, 1.0 }; - DEFAULT_MATERIAL.diffuse_texture = texture_data_load("assets/textures/white1x1.png", false); - DEFAULT_MATERIAL.specular_texture = texture_data_load("assets/textures/black1x1.png", false); - DEFAULT_MATERIAL.spec_exponent = 32.0; - strcpy(DEFAULT_MATERIAL.name, "Default"); - texture_data_upload(&DEFAULT_MATERIAL.diffuse_texture); - texture_data_upload(&DEFAULT_MATERIAL.specular_texture); -} + // Create default rendering pipeline + /* default_pipelines_init(ren); */ -typedef struct draw_ctx { - model_darray* models; - renderer* ren; - camera* cam; - scene* scene; -} draw_ctx; -bool draw_scene_node(transform_node* node, void* ctx_data) { - if (!node || !node->parent) return true; - draw_ctx* ctx = ctx_data; - model* m = &ctx->models->data[node->model.raw]; - draw_model(ctx->ren, ctx->cam, m, &node->world_matrix_tf, ctx->scene); return true; } - -void draw_scene(arena* frame, model_darray* models, renderer* ren, camera* camera, - transform_hierarchy* tfh, scene* scene) { - draw_ctx* ctx = arena_alloc(frame, sizeof(draw_ctx)); - ctx->models = models; - ctx->ren = ren; - ctx->cam = camera; - ctx->scene = scene; - transform_hierarchy_dfs(transform_hierarchy_root_node(tfh), draw_scene_node, true, ctx); +void renderer_shutdown(renderer* ren) { + gpu_swapchain_destroy(&ren->swapchain); + gpu_pipeline_destroy(&ren->static_opaque_pipeline); + gpu_backend_shutdown(); } -void draw_model(renderer* ren, camera* camera, model* model, mat4* model_tf, scene* scene) { - // TRACE("Drawing model: %s", model->name); - mat4 view; - mat4 proj; - camera_view_projection(camera, SCR_HEIGHT, SCR_WIDTH, &view, &proj); +void default_pipelines_init(renderer* ren) { + // Static opaque geometry + arena scratch = arena_create(malloc(1024 * 1024), 1024 * 1024); + + gpu_renderpass_desc pass_description = {}; + gpu_renderpass* renderpass = gpu_renderpass_create(&pass_description); + + ren->default_renderpass = *renderpass; + + printf("Load shaders\n"); + str8 vert_path = str8lit("build/linux/x86_64/debug/triangle.vert.spv"); + str8 frag_path = str8lit("build/linux/x86_64/debug/triangle.frag.spv"); + /* str8 vert_path = + * str8lit("/home/void/code/celeritas-engine/celeritas-core/build/linux/x86_64/debug/triangle.vert.spv"); + */ + /* str8 frag_path = + * str8lit("/home/void/code/celeritas-engine/celeritas-core/build/linux/x86_64/debug/triangle.frag.spv"); + */ + str8_opt vertex_shader = str8_from_file(&scratch, vert_path); + str8_opt fragment_shader = str8_from_file(&scratch, frag_path); + if (!vertex_shader.has_value || !fragment_shader.has_value) { + ERROR_EXIT("Failed to load shaders from disk") + } - set_shader(ren->blinn_phong); + vertex_description vertex_input; + vertex_input.debug_label = "Standard Static 3D Vertex Format"; + vertex_desc_add(&vertex_input, "inPosition", ATTR_F32x3); + vertex_desc_add(&vertex_input, "inNormal", ATTR_F32x3); + vertex_desc_add(&vertex_input, "inTexCoords", ATTR_F32x2); + + struct graphics_pipeline_desc pipeline_description = { + .debug_name = "Basic Pipeline", + .vertex_desc = vertex_input, + // .data_layouts + // .data_layouts_count + .vs = { .debug_name = "Triangle Vertex Shader", + .filepath = vert_path, + .code = vertex_shader.contents, + .is_spirv = true }, + .fs = { .debug_name = "Triangle Fragment Shader", + .filepath = frag_path, + .code = fragment_shader.contents, + .is_spirv = true }, + .renderpass = renderpass, + .wireframe = false, + .depth_test = false + }; + gpu_pipeline* gfx_pipeline = gpu_graphics_pipeline_create(pipeline_description); + ren->static_opaque_pipeline = *gfx_pipeline; +} - // set camera uniform - uniform_vec3f(ren->blinn_phong.program_id, "viewPos", &camera->position); - // set light uniforms - dir_light_upload_uniforms(ren->blinn_phong, &scene->dir_light); - for (int i = 0; i < scene->n_point_lights; i++) { - point_light_upload_uniforms(ren->blinn_phong, &scene->point_lights[i], '0' + i); +void render_frame_begin(renderer* ren) { + ren->frame_aborted = false; + if (!gpu_backend_begin_frame()) { + ren->frame_aborted = true; + return; + } + gpu_cmd_encoder* enc = gpu_get_default_cmd_encoder(); + // begin recording + gpu_cmd_encoder_begin(*enc); + gpu_cmd_encoder_begin_render(enc, &ren->default_renderpass); + encode_bind_pipeline(enc, PIPELINE_GRAPHICS, &ren->static_opaque_pipeline); + encode_set_default_settings(enc); +} +void render_frame_end(renderer* ren) { + if (ren->frame_aborted) { + return; } + // gpu_temp_draw(3); + gpu_cmd_encoder* enc = gpu_get_default_cmd_encoder(); + gpu_cmd_encoder_end_render(enc); + gpu_cmd_buffer buf = gpu_cmd_encoder_finish(enc); + gpu_queue_submit(&buf); + gpu_backend_end_frame(); +} +void render_frame_draw(renderer* ren) {} - for (size_t i = 0; i < mesh_darray_len(model->meshes); i++) { - mesh* m = &model->meshes->data[i]; - if (vertex_darray_len(m->vertices) == 0) { - continue; - } - // TRACE("Drawing mesh %d", i); - material* mat = &model->materials->data[m->material_index]; - draw_mesh(ren, m, model_tf, mat, &view, &proj); +void draw_mesh(mesh* mesh, mat4* model) { // , mat4* view, mat4* proj) { + gpu_cmd_encoder* enc = gpu_get_default_cmd_encoder(); + encode_set_vertex_buffer(enc, mesh->vertex_buffer); + if (mesh->has_indices) { + encode_set_index_buffer(enc, mesh->index_buffer); } + // Assume this has already been done + /* encode_bind_shader_data(enc, 0, &mvp_uniforms_data); */ + encode_draw_indexed(enc, mesh->index_count); } -void draw_mesh(renderer* ren, mesh* mesh, mat4* model_tf, material* mat, mat4* view, mat4* proj) { - shader lighting_shader = ren->blinn_phong; +void gfx_backend_draw_frame(renderer* ren, camera* camera, mat4 model, texture* tex) {} - // bind buffer - bind_mesh_vertex_buffer(ren->backend_state, mesh); +void geo_set_vertex_colours(geometry_data* geo, vec4 colour) {} - // bind textures - bind_texture(lighting_shader, &mat->diffuse_texture, 0); // bind to slot 0 - bind_texture(lighting_shader, &mat->specular_texture, 1); // bind to slot 1 - uniform_f32(lighting_shader.program_id, "material.shininess", 32.); +// --- NEW - // upload model transform - // mat4 trans = mat4_translation(tf.position); - // mat4 rot = mat4_rotation(tf.rotation); - // mat4 scale = mat4_scale(tf.scale); - // mat4 model_tf = mat4_mult(trans, mat4_mult(rot, scale)); +mesh mesh_create(geometry_data* geometry, bool free_on_upload) { + mesh m = { 0 }; - uniform_mat4f(lighting_shader.program_id, "model", model_tf); - // upload view & projection matrices - uniform_mat4f(lighting_shader.program_id, "view", view); - uniform_mat4f(lighting_shader.program_id, "projection", proj); + // Create and upload vertex buffer + size_t vert_bytes = geometry->vertices->len * sizeof(vertex); + INFO("Creating vertex buffer with size %d (%d x %d)", vert_bytes, geometry->vertices->len, + sizeof(vertex)); + m.vertex_buffer = gpu_buffer_create(vert_bytes, CEL_BUFFER_VERTEX, CEL_BUFFER_FLAG_GPU, + geometry->vertices->data); - // draw triangles - u32 num_vertices = vertex_darray_len(mesh->vertices); - draw_primitives(CEL_PRIMITIVE_TOPOLOGY_TRIANGLE, 0, num_vertices); -} + // Create and upload index buffer + size_t index_bytes = geometry->indices.len * sizeof(u32); + INFO("Creating index buffer with size %d (len: %d)", index_bytes, geometry->indices.len); + m.index_buffer = + gpu_buffer_create(index_bytes, CEL_BUFFER_INDEX, CEL_BUFFER_FLAG_GPU, geometry->indices.data); -void model_upload_meshes(renderer* ren, model* model) { - INFO("Upload mesh vertex data to GPU for model %s", model->name); - - size_t num_meshes = mesh_darray_len(model->meshes); - u32 VBOs[num_meshes]; - u32 VAOs[num_meshes]; - glGenBuffers(num_meshes, VBOs); - glGenVertexArrays(num_meshes, VAOs); - - u64 total_verts = 0; - - TRACE("num meshes %d", num_meshes); - - // upload each mesh to the GPU - for (int mesh_i = 0; mesh_i < num_meshes; mesh_i++) { - model->meshes->data[mesh_i].vao = VAOs[mesh_i]; - model->meshes->data[mesh_i].vbo = VBOs[mesh_i]; - // 3. bind buffers - glBindBuffer(GL_ARRAY_BUFFER, VBOs[mesh_i]); - - size_t num_vertices = vertex_darray_len(model->meshes->data[mesh_i].vertices); - // TRACE("Uploading vertex array data: %d verts", num_vertices); - total_verts += num_vertices; - - // TODO: convert this garbage into a function - f32 verts[num_vertices * 8]; - // for each face - for (int i = 0; i < (num_vertices / 3); i++) { - // for each vert in face - for (int j = 0; j < 3; j++) { - size_t stride = (i * 24) + j * 8; - // printf("i: %d, stride: %ld, loc %d\n", i, stride, i * 3 + j); - vertex vert = model->meshes->data[mesh_i].vertices->data[i]; - // printf("pos %f %f %f\n", vert.position.x, vert.position.y, vert.position.z); - // printf("norm %f %f %f\n", vert.normal.x, vert.normal.y, vert.normal.z); - // printf("tex %f %f\n", vert.uv.x, vert.uv.y); - verts[stride + 0] = - ((vertex*)model->meshes->data[mesh_i].vertices->data)[i * 3 + j].position.x; - verts[stride + 1] = - ((vertex*)model->meshes->data[mesh_i].vertices->data)[i * 3 + j].position.y; - verts[stride + 2] = - ((vertex*)model->meshes->data[mesh_i].vertices->data)[i * 3 + j].position.z; - verts[stride + 3] = - ((vertex*)model->meshes->data[mesh_i].vertices->data)[i * 3 + j].normal.x; - verts[stride + 4] = - ((vertex*)model->meshes->data[mesh_i].vertices->data)[i * 3 + j].normal.y; - verts[stride + 5] = - ((vertex*)model->meshes->data[mesh_i].vertices->data)[i * 3 + j].normal.z; - verts[stride + 6] = ((vertex*)model->meshes->data[mesh_i].vertices->data)[i * 3 + j].uv.x; - verts[stride + 7] = ((vertex*)model->meshes->data[mesh_i].vertices->data)[i * 3 + j].uv.y; - } - } - - // 4. upload data - glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); - - // 5. cont. set mesh vertex layout - glBindVertexArray(model->meshes->data[mesh_i].vao); - // position attribute - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); - glEnableVertexAttribArray(0); - // normal vector attribute - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); - glEnableVertexAttribArray(1); - // tex coords - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); - glEnableVertexAttribArray(2); + m.is_uploaded = true; + m.has_indices = geometry->has_indices; + m.index_count = geometry->indices.len; + m.vertices = geometry; + if (free_on_upload) { + geo_free_data(geometry); } - INFO("Uploaded %d submeshes with a total of %d vertices\n", num_meshes, total_verts); + // TODO: materials? - // 6. reset buffer - glBindBuffer(GL_ARRAY_BUFFER, 0); + return m; } -texture texture_data_load(const char* path, bool invert_y) { +// --- Textures + +texture_data texture_data_load(const char* path, bool invert_y) { TRACE("Load texture %s", path); // load the file data - // texture loading int width, height, num_channels; 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 { @@ -260,66 +222,18 @@ texture texture_data_load(const char* path, bool invert_y) { } else { channel_type = GL_RGB; } + texture_desc desc = { .extents = { width, height }, + .format = CEL_TEXTURE_FORMAT_8_8_8_8_RGBA_UNORM, + .tex_type = CEL_TEXTURE_TYPE_2D }; - return (texture){ .texture_id = 0, - .width = width, - .height = height, - .channel_count = num_channels, - .channel_type = channel_type, - .name = "TODO: Texture names", - .image_data = data }; + return (texture_data){ .description = desc, .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); - uniform_vec3f(shader.program_id, "dirLight.diffuse", &light->diffuse); - uniform_vec3f(shader.program_id, "dirLight.specular", &light->specular); +texture_handle texture_data_upload(texture_data data, bool free_on_upload) { + texture_handle handle = gpu_texture_create(data.description, true, data.image_data); + if (free_on_upload) { + TRACE("Freed stb_image data"); + stbi_image_free(data.image_data); + } + return handle; } - -void point_light_upload_uniforms(shader shader, point_light* light, char index) { - char position_str[] = "pointLights[x].position"; - position_str[12] = (char)index; - char ambient_str[] = "pointLights[x].ambient"; - ambient_str[12] = (char)index; - char diffuse_str[] = "pointLights[x].diffuse"; - diffuse_str[12] = (char)index; - char specular_str[] = "pointLights[x].specular"; - specular_str[12] = (char)index; - char constant_str[] = "pointLights[x].constant"; - constant_str[12] = (char)index; - char linear_str[] = "pointLights[x].linear"; - linear_str[12] = (char)index; - char quadratic_str[] = "pointLights[x].quadratic"; - quadratic_str[12] = (char)index; - uniform_vec3f(shader.program_id, position_str, &light->position); - uniform_vec3f(shader.program_id, ambient_str, &light->ambient); - uniform_vec3f(shader.program_id, diffuse_str, &light->diffuse); - uniform_vec3f(shader.program_id, specular_str, &light->specular); - uniform_f32(shader.program_id, constant_str, light->constant); - uniform_f32(shader.program_id, linear_str, light->linear); - uniform_f32(shader.program_id, quadratic_str, light->quadratic); -}
\ No newline at end of file diff --git a/src/renderer/render.h b/src/renderer/render.h index 1a35488..c193ff9 100644 --- a/src/renderer/render.h +++ b/src/renderer/render.h @@ -1,39 +1,73 @@ +/** + * @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" -#include "loaders.h" +#include "ral_types.h" #include "render_types.h" -#include "transform_hierarchy.h" -// --- Lifecycle -/** @brief initialise the render system frontend */ bool renderer_init(renderer* ren); -/** @brief shutdown the render system frontend */ void renderer_shutdown(renderer* ren); -// --- Frame - void render_frame_begin(renderer* ren); +void render_frame_update_globals(renderer* ren); void render_frame_end(renderer* ren); +void render_frame_draw(renderer* ren); + +// ! TEMP +typedef struct camera camera; +void gfx_backend_draw_frame(renderer* ren, camera* camera, mat4 model, texture* tex); + +typedef struct render_ctx { + mat4 view; + mat4 projection; +} render_ctx; + +// 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); + +// Frontend Resources +texture_data texture_data_load(const char* path, bool invert_y); + +/** + * @brief + * + * @param data + * @param free_on_upload frees the CPU-side pixel data stored in `data` + * @return texture_handle + */ +texture_handle texture_data_upload(texture_data data, bool free_on_upload); + +buffer_handle buffer_create(const char* debug_name, u64 size); +bool buffer_destroy(buffer_handle buffer); +sampler_handle sampler_create(); + +void shader_hot_reload(const char* filepath); + +// models and meshes are implemented **in terms of the above** + +/** + * @brief Creates buffers and returns a struct that holds handles to our resources + * + * @param geometry + * @param free_on_upload frees the CPU-side vertex/index data stored in `geometry` when we + successfully upload that data to the GPU-side buffer + * @return mesh + */ +mesh mesh_create(geometry_data* geometry, bool free_on_upload); + +void draw_mesh(mesh* mesh, mat4* model); //, mat4* view, mat4* proj); // TODO: material + +model_handle model_load(const char* debug_name, const char* filepath); + +void geo_free_data(geometry_data* geo); +void geo_set_vertex_colours(geometry_data* geo, vec4 colour); -// --- models meshes -void model_upload_meshes(renderer* ren, model* model); -void draw_model(renderer* ren, camera* camera, model* model, mat4* tf, scene* scene); -void draw_mesh(renderer* ren, mesh* mesh, mat4* tf, material* mat, mat4* view, mat4* proj); -void draw_scene(arena* frame, model_darray* models, renderer* ren, camera* camera, - transform_hierarchy* tfh, scene* scene); - -// --- -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 +vertex_description static_3d_vertex_description(); diff --git a/src/renderer/render_backend.h b/src/renderer/render_backend.h deleted file mode 100644 index 7e1c16c..0000000 --- a/src/renderer/render_backend.h +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @brief - */ -#pragma once - -#include "maths_types.h" -#include "render_types.h" - -/// --- Lifecycle -bool gfx_backend_init(renderer* ren); -void gfx_backend_shutdown(renderer* ren); - -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); - -// --- Uniforms diff --git a/src/renderer/render_types.h b/src/renderer/render_types.h index aed18db..cc5fd93 100644 --- a/src/renderer/render_types.h +++ b/src/renderer/render_types.h @@ -1,32 +1,23 @@ /** * @file render_types.h - * @author Omniscient - * @brief Type definitions for the majority of data required by the renderer system - * @date 2024-02-24 + * @author your name (you@domain.com) + * @brief + * @version 0.1 + * @date 2024-04-27 + * + * @copyright Copyright (c) 2024 * */ #pragma once -#include "darray.h" -#include "maths_types.h" -#include "str.h" - -struct GLFWwindow; - -#define MAX_MATERIAL_NAME_LEN 256 -#define MAX_TEXTURE_NAME_LEN 256 - -#ifndef RESOURCE_HANDLE_DEFS -CORE_DEFINE_HANDLE(model_handle); -#define ABSENT_MODEL_HANDLE 999999999 -CORE_DEFINE_HANDLE(texture_handle); -#define RESOURCE_HANDLE_DEFS +#include "ral.h" +#include "ral_types.h" +#if defined(CEL_PLATFORM_WINDOWS) +// #include "backend_dx11.h" #endif +#include "backend_vulkan.h" -/* @brief Opaque wrapper around a shader program */ -typedef struct shader { - u32 program_id; -} shader; +struct GLFWwindow; /** @brief configuration passed to the renderer at init time */ typedef struct renderer_config { @@ -36,27 +27,58 @@ typedef struct renderer_config { } renderer_config; typedef struct renderer { - struct GLFWwindow *window; /** Currently all platforms use GLFW*/ - void *backend_state; /** Graphics API-specific state */ + struct GLFWwindow* window; + void* backend_context; renderer_config config; - // shaders - shader blinn_phong; + gpu_device device; + gpu_swapchain swapchain; + gpu_renderpass default_renderpass; + gpu_pipeline static_opaque_pipeline; + bool frame_aborted; + struct resource_pools* resource_pools; } renderer; -// --- Lighting & Materials +typedef struct geometry_data { + vertex_format format; + vertex_darray* vertices; // TODO: make it not a pointer + bool has_indices; + u32_darray indices; + vec3 colour; /** Optional: set vertex colours */ +} geometry_data; + +// 'Upload' a geometry_data (to GPU) -> get back a mesh +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 + bool is_uploaded; + bool is_latent; +} 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 name; + mesh* meshes; + u32 mesh_count; +} model; typedef struct texture { - u32 texture_id; - char name[MAX_TEXTURE_NAME_LEN]; - void *image_data; - u32 width; - u32 height; - u8 channel_count; - u32 channel_type; } texture; +typedef struct texture_data { + texture_desc description; + void* image_data; +} texture_data; + typedef struct blinn_phong_material { - char name[MAX_MATERIAL_NAME_LEN]; + char name[256]; texture diffuse_texture; char diffuse_tex_path[256]; texture specular_texture; @@ -68,18 +90,49 @@ typedef struct blinn_phong_material { bool is_loaded; bool is_uploaded; } blinn_phong_material; -typedef blinn_phong_material material; // when we start using PBR, this will no longer be the case +typedef blinn_phong_material material; // the default blinn-phong material. MUST be initialised with the function below extern material DEFAULT_MATERIAL; void default_material_init(); +#ifndef TYPED_MESH_ARRAY +KITC_DECL_TYPED_ARRAY(mesh) +#define TYPED_MESH_ARRAY +#endif + +#ifndef TYPED_MODEL_ARRAY +KITC_DECL_TYPED_ARRAY(model) +#define TYPED_MODEL_ARRAY +#endif + #ifndef TYPED_MATERIAL_ARRAY -KITC_DECL_TYPED_ARRAY(material) // creates "material_darray" +KITC_DECL_TYPED_ARRAY(material) #define TYPED_MATERIAL_ARRAY #endif -// lights +#ifndef TYPED_ANIMATION_CLIP_ARRAY +#include "animation.h" +KITC_DECL_TYPED_ARRAY(animation_clip) +#define TYPED_ANIMATION_CLIP_ARRAY +#endif + +/** @brief Describes all the data required for the renderer to start executing draws */ +typedef struct render_entity { + /* buffer_handle index_buffer; */ + /* u32 index_count; */ + /* u32 index_offset; */ + /* buffer_handle vertex_buffer; */ + model_handle model; + transform tf; +} render_entity; + +#ifndef TYPED_RENDER_ENTITY_ARRAY +KITC_DECL_TYPED_ARRAY(render_entity) +#define TYPED_RENDER_ENTITY_ARRAY +#endif + +// --- Lights typedef struct point_light { vec3 position; f32 constant, linear, quadratic; @@ -94,85 +147,3 @@ typedef struct directional_light { vec3 diffuse; vec3 specular; } directional_light; - -void point_light_upload_uniforms(shader shader, point_light *light, char index); -void dir_light_upload_uniforms(shader shader, directional_light *light); - -// --- Models & Meshes - -/** @brief Vertex format for a static mesh */ -typedef struct vertex { - vec3 position; - vec3 normal; - vec2 uv; -} vertex; - -#ifndef TYPED_VERTEX_ARRAY -KITC_DECL_TYPED_ARRAY(vertex) // creates "vertex_darray" -#define TYPED_VERTEX_ARRAY -#endif - -typedef struct mesh { - vertex_darray *vertices; - u32 vertex_size; /** size in bytes of each vertex including necessary padding */ - bool has_indices; - u32 *indices; - u32 indices_len; - size_t material_index; - u32 vbo, vao; /** OpenGL data. TODO: dont leak OpenGL details */ -} mesh; - -#ifndef TYPED_MESH_ARRAY -KITC_DECL_TYPED_ARRAY(mesh) // creates "mesh_darray" -#define TYPED_MESH_ARRAY -#endif - -typedef struct model { - str8 name; - mesh_darray *meshes; - aabb_3d bbox; - material_darray *materials; - bool is_loaded; - bool is_uploaded; -} model; - -#ifndef TYPED_MODEL_ARRAY -KITC_DECL_TYPED_ARRAY(model) // creates "model_darray" -#define TYPED_MODEL_ARRAY -#endif - -// --- Scene - -// NOTE: This struct won't stay like this for a long time. It's somewhat temporary -// in order to get a basic scene working without putting burden on the caller of -// draw_model() -typedef struct scene { - directional_light dir_light; - point_light point_lights[4]; - size_t n_point_lights; -} scene; - -// --- Graphics API related - -typedef enum cel_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 { - TEXTURE_TYPE_2D, - TEXTURE_TYPE_3D, - TEXTURE_TYPE_2D_ARRAY, - TEXTURE_TYPE_CUBE_MAP, - TEXTURE_TYPE_COUNT -} gpu_texture_type; - -typedef enum gpu_texture_format { - TEXTURE_FORMAT_8_8_8_8_RGBA_UNORM, - TEXTURE_FORMAT_DEPTH_DEFAULT, - TEXTURE_FORMAT_COUNT -} gpu_texture_format; diff --git a/src/renderer/renderpasses.h b/src/renderer/renderpasses.h new file mode 100644 index 0000000..951ff6e --- /dev/null +++ b/src/renderer/renderpasses.h @@ -0,0 +1,28 @@ +/** + * @file renderpasses.h + * @author your name (you@domain.com) + * @brief Built-in renderpasses to the engine + * @version 0.1 + * @date 2024-04-28 + * + * @copyright Copyright (c) 2024 + * + */ +#pragma once +#include "maths_types.h" +#include "ral.h" +#include "render.h" +#include "render_types.h" + +// Shadowmap pass +// Blinn-phong pass +// Unlit pass +// Debug visualisations pass + +// Don't need to pass in *anything*. +gpu_renderpass* renderpass_blinn_phong_create(); +void renderpass_blinn_phong_execute(gpu_renderpass* pass, render_entity* entities, + size_t entity_count); + +gpu_renderpass* renderpass_shadows_create(); +void renderpass_shadows_execute(gpu_renderpass* pass, render_entity* entities, size_t entity_count);
\ No newline at end of file |