diff --git a/.gitignore b/.gitignore index 8b15bc7..054aa7f 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ compile_commands.json roleplay .cache + +*.o diff --git a/client/Makefile b/client/Makefile index 51ac202..56db5cd 100644 --- a/client/Makefile +++ b/client/Makefile @@ -6,7 +6,7 @@ CPP = /opt/homebrew/opt/llvm/bin/clang++ DSYM = /opt/homebrew/opt/llvm/bin/dsymutil GDB = /opt/homebrew/opt/llvm/bin/lldb -SOURCES = src/main.c src/render.c src/vma.cpp +SOURCES = src/main.c src/render.c src/vma.cpp src/pipeline.c OBJECTS = $(addsuffix .o, $(basename $(SOURCES))) VERT_SPV = $(addsuffix .vert.spv, $(basename $(wildcard shader_src/*.vert))) FRAG_SPV = $(addsuffix .frag.spv, $(basename $(wildcard shader_src/*.frag))) diff --git a/client/include/pipeline.h b/client/include/pipeline.h new file mode 100644 index 0000000..47d2a8b --- /dev/null +++ b/client/include/pipeline.h @@ -0,0 +1,20 @@ +#ifndef PIPELINE_H +#define PIPELINE_H + +#include "vulkan/vulkan_core.h" +#include "cglm/types.h" + +typedef struct GraphicsPipelineStruct { + VkDescriptorPool descriptor_pool; + VkDescriptorSet* descriptors; + + VkPipelineLayout layout; + VkPipeline pipeline; +} GraphicsPipeline; + +struct Vertex2 { + vec2 pos; + vec3 color; +}; + +#endif diff --git a/client/include/render.h b/client/include/render.h index edaedea..43c3142 100644 --- a/client/include/render.h +++ b/client/include/render.h @@ -24,6 +24,8 @@ #include #include +extern const uint32_t MAX_FRAMES_IN_FLIGHT; + typedef struct QueueStruct { VkQueue handle; uint32_t family; @@ -83,9 +85,12 @@ typedef struct RenderContextStruct { VkPipeline ui_pipeline_rect; VkPipeline ui_pipeline_text; + + uint32_t current_frame; } RenderContext; GLFWwindow* init_window(); VkResult init_vulkan(GLFWwindow* window, RenderContext* context); +VkResult draw_frame(RenderContext* context); #endif diff --git a/client/src/main.c b/client/src/main.c index 9e55750..3f17653 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -1,8 +1,15 @@ #include "render.h" +#include "vulkan/vk_enum_string_helper.h" -int render_thread(GLFWwindow* window, RenderContext* render_context) { +VkResult render_thread(GLFWwindow* window, RenderContext* render_context) { while(glfwWindowShouldClose(window) == 0) { glfwPollEvents(); + + VkResult result = draw_frame(render_context); + if(result != VK_SUCCESS) { + fprintf(stderr, "draw_frame error: %s\n", string_VkResult(result)); + return result; + } } return 0; } @@ -26,7 +33,7 @@ int main() { return 2; } - if(render_thread(window, &render_context) != 0) { + if(render_thread(window, &render_context) != VK_SUCCESS) { return 3; } diff --git a/client/src/main.o b/client/src/main.o deleted file mode 100644 index aa6985a..0000000 Binary files a/client/src/main.o and /dev/null differ diff --git a/client/src/pipeline.c b/client/src/pipeline.c new file mode 100644 index 0000000..5f8cc9c --- /dev/null +++ b/client/src/pipeline.c @@ -0,0 +1,249 @@ +#include "pipeline.h" +#include "stdio.h" +#include "stdlib.h" + +VkShaderModule load_shader_file(const char* path, VkDevice device) { + FILE* file; + file = fopen(path, "rb"); + if(file == 0) { + return VK_NULL_HANDLE; + } + + int result = fseek(file, 0, SEEK_END); + if(result != 0) { + return VK_NULL_HANDLE; + } + + long buffer_size = ftell(file); + + result = fseek(file, 0, SEEK_SET); + if(result != 0) { + return VK_NULL_HANDLE; + } + + char * buffer = malloc(buffer_size); + if(buffer == 0) { + return VK_NULL_HANDLE; + } + + size_t read = fread(buffer, 1, buffer_size, file); + + VkShaderModuleCreateInfo shader_info = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = read, + .pCode = (uint32_t*)buffer, + }; + + VkShaderModule shader; + result = vkCreateShaderModule(device, &shader_info, 0, &shader); + free(buffer); + if(result != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + + return shader; +} + +VkResult create_ui_rect_pipeline(VkDevice device, VkRenderPass render_pass, GraphicsPipeline* out) { + if(out == NULL) { + return VK_ERROR_VALIDATION_FAILED_EXT; + } + + VkShaderModule vert_shader = load_shader_file("shader_src/ui_rect.vert.spv", device); + VkShaderModule frag_shader = load_shader_file("shader_src/ui_rect.frag.spv", device); + VkPipelineShaderStageCreateInfo shader_stages[2] = { + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = vert_shader, + .pName = "main", + }, + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = frag_shader, + .pName = "main", + }, + }; + + VkVertexInputBindingDescription bindings[1] = { + { + .binding = 0, // Which buffer 'binding' to use + .stride = sizeof(struct Vertex2), // How many bytes to increase the index between instance + .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, // Whether an instance is a vertex or an index + }, + }; + + VkVertexInputAttributeDescription attributes[3] = { + { + .binding = 0, // Which buffer 'binding' to use + .location = 0, // Which 'location' to export as to shader + .format = VK_FORMAT_R32G32_SFLOAT, // What format to interpret as for shader + .offset = offsetof(struct Vertex2, pos), // What offset from instance start + }, + { + .binding = 0, + .location = 1, + .format = VK_FORMAT_R32G32B32_SFLOAT, + .offset = offsetof(struct Vertex2, color), + }, + }; + + VkPipelineVertexInputStateCreateInfo input_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pVertexBindingDescriptions = bindings, + .vertexBindingDescriptionCount = sizeof(bindings)/sizeof(VkVertexInputBindingDescription), + .pVertexAttributeDescriptions = attributes, + .vertexAttributeDescriptionCount = sizeof(attributes)/sizeof(VkVertexInputAttributeDescription), + }; + + VkPushConstantRange pcr = { + .stageFlags = VK_SHADER_STAGE_ALL, + .offset = 0, + .size = sizeof(VkDeviceAddress), + }; + + VkPipelineLayoutCreateInfo layout_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pushConstantRangeCount = 1, + .pPushConstantRanges = &pcr, + }; + + VkResult result = vkCreatePipelineLayout(device, &layout_info, 0, &out->layout); + if(result != VK_SUCCESS) { + return result; + } + + VkDynamicState dynamic_states[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + }; + + uint32_t dynamic_state_count = sizeof(dynamic_states)/sizeof(VkDynamicState); + + VkPipelineDynamicStateCreateInfo dynamic_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .dynamicStateCount = dynamic_state_count, + .pDynamicStates = dynamic_states, + }; + + VkPipelineInputAssemblyStateCreateInfo input_assembly_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .primitiveRestartEnable = VK_FALSE, + }; + + VkViewport viewport = { + .x = 0.0f, + .y = 0.0f, + .width = (float)(100), + .height = (float)(100), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + + VkRect2D scissor = { + .offset = { + .x = 0, + .y = 0, + }, + .extent = { + .width = 100, + .height = 100, + }, + }; + + VkPipelineViewportStateCreateInfo viewport_state = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, + .pViewports = &viewport, + .scissorCount = 1, + .pScissors = &scissor, + }; + + VkPipelineRasterizationStateCreateInfo raster_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = VK_POLYGON_MODE_FILL, + .lineWidth = 1.0f, + .cullMode = VK_CULL_MODE_BACK_BIT, + .frontFace = VK_FRONT_FACE_CLOCKWISE, + .depthBiasEnable = VK_FALSE, + .depthBiasConstantFactor = 0.0f, + .depthBiasClamp = 0.0f, + .depthBiasSlopeFactor = 0.0f, + }; + + VkPipelineMultisampleStateCreateInfo multisample_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .sampleShadingEnable = VK_FALSE, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + .minSampleShading = 1.0f, + .pSampleMask = 0, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; + + VkPipelineDepthStencilStateCreateInfo depth_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .depthTestEnable = VK_TRUE, + .depthWriteEnable = VK_TRUE, + .depthCompareOp = VK_COMPARE_OP_LESS, + .depthBoundsTestEnable = VK_FALSE, + .maxDepthBounds = 1.0f, + .minDepthBounds = 0.0f, + .stencilTestEnable = VK_FALSE, + .front = {}, + .back = {}, + }; + + VkPipelineColorBlendAttachmentState color_blend_attachment = { + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + .blendEnable = VK_TRUE, + .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, + .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, + }; + + VkPipelineColorBlendStateCreateInfo color_blend_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_COPY, + .attachmentCount = 1, + .pAttachments = &color_blend_attachment, + .blendConstants[0] = 0.0f, + .blendConstants[1] = 0.0f, + .blendConstants[2] = 0.0f, + .blendConstants[3] = 0.0f, + }; + + VkGraphicsPipelineCreateInfo draw_pipeline_info = { + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .stageCount = 2, + .pStages = shader_stages, + .pVertexInputState = &input_info, + .pInputAssemblyState = &input_assembly_info, + .pViewportState = &viewport_state, + .pRasterizationState = &raster_info, + .pColorBlendState = &color_blend_info, + .pDynamicState = &dynamic_info, + .pDepthStencilState = &depth_info, + .pMultisampleState = &multisample_info, + .layout = out->layout, + .renderPass = render_pass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = -1, + }; + + result = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &draw_pipeline_info, 0, &out->pipeline); + if(result != VK_SUCCESS) { + return result; + } + + return VK_SUCCESS; +} diff --git a/client/src/render.c b/client/src/render.c index 33312ff..be35ef6 100644 --- a/client/src/render.c +++ b/client/src/render.c @@ -1,5 +1,5 @@ #include "render.h" -#include "vk_mem_alloc.h" +#include "vulkan/vulkan_core.h" const uint32_t MAX_FRAMES_IN_FLIGHT = 2; @@ -44,7 +44,7 @@ GLFWwindow* init_window() { glfwSetErrorCallback(glfw_error); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); GLFWwindow* window = glfwCreateWindow(800, 500, "roleplay", 0, 0); return window; @@ -755,6 +755,8 @@ VkResult create_swapchain_framebuffers(VkDevice device, uint32_t image_count, Vk return 0; } + VkFramebuffer* framebuffer_ptr = *framebuffers; + for(uint32_t i = 0; i < image_count; i++) { VkImageView attachments[] = { image_views[i], @@ -771,7 +773,7 @@ VkResult create_swapchain_framebuffers(VkDevice device, uint32_t image_count, Vk .layers = 1, }; - VkResult result = vkCreateFramebuffer(device, &framebuffer_info, 0, *framebuffers + sizeof(VkFramebuffer)*i); + VkResult result = vkCreateFramebuffer(device, &framebuffer_info, 0, &framebuffer_ptr[i]); if(result != VK_SUCCESS) { free(*framebuffers); return result; @@ -868,7 +870,7 @@ VkResult create_depth_image(VkDevice device, VkFormat depth_format, VkExtent2D s }; VmaAllocationCreateInfo allocation_info = { - .usage = VMA_MEMORY_USAGE_GPU_ONLY, + .usage = VMA_MEMORY_USAGE_AUTO, }; VkResult result = vmaCreateImage(allocator, &depth_image_info, &allocation_info, depth_image, depth_image_memory, NULL); @@ -1031,7 +1033,6 @@ VkResult init_vulkan(GLFWwindow* window, RenderContext* context) { return result; } - // TODO: create and allocate the depth image result = create_depth_image(context->device, context->depth_format, context->swapchain_extent, context->allocator, context->extra_graphics_pool, context->graphics_queue, &context->depth_image, &context->depth_image_memory, &context->depth_image_view); if(result != VK_SUCCESS) { return result; @@ -1042,9 +1043,96 @@ VkResult init_vulkan(GLFWwindow* window, RenderContext* context) { return result; } - // TODO: create pipelines - + context->current_frame = 0; return VK_SUCCESS; } +VkResult draw_frame(RenderContext* context) { + VkResult result; + + result = vkWaitForFences(context->device, 1, &context->in_flight_fences[context->current_frame], VK_TRUE, UINT64_MAX); + if(result != VK_SUCCESS) { + return result; + } + + result = vkResetFences(context->device, 1, &context->in_flight_fences[context->current_frame]); + if(result != VK_SUCCESS) { + return result; + } + + uint32_t image_index; + result = vkAcquireNextImageKHR(context->device, context->swapchain, UINT64_MAX, context->image_available_semaphores[context->current_frame], VK_NULL_HANDLE, &image_index); + if(result != VK_SUCCESS) { + return result; + } + + + result = vkResetCommandBuffer(context->swapchain_command_buffers[context->current_frame], 0); + if(result != VK_SUCCESS) { + return result; + } + + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + }; + result = vkBeginCommandBuffer(context->swapchain_command_buffers[context->current_frame], &begin_info); + if(result != VK_SUCCESS) { + return result; + } + + VkClearValue clear_color = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + + VkRenderPassBeginInfo render_pass_begin = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .renderPass = context->render_pass, + .framebuffer = context->swapchain_framebuffers[image_index], + .renderArea.offset = {0, 0}, + .renderArea.extent = context->swapchain_extent, + .clearValueCount = 1, + .pClearValues = &clear_color, + }; + vkCmdBeginRenderPass(context->swapchain_command_buffers[context->current_frame], &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE); + vkCmdEndRenderPass(context->swapchain_command_buffers[context->current_frame]); + + result = vkEndCommandBuffer(context->swapchain_command_buffers[context->current_frame]); + if(result != VK_SUCCESS) { + return result; + } + + VkPipelineStageFlags wait_stages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &context->image_available_semaphores[context->current_frame], + .pWaitDstStageMask = wait_stages, + .commandBufferCount = 1, + .pCommandBuffers = &context->swapchain_command_buffers[context->current_frame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &context->render_finished_semaphores[context->current_frame], + }; + + result = vkQueueSubmit(context->graphics_queue.handle, 1, &submit_info, context->in_flight_fences[context->current_frame]); + if(result != VK_SUCCESS) { + return result; + } + + VkPresentInfoKHR present_info = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &context->render_finished_semaphores[context->current_frame], + .swapchainCount = 1, + .pSwapchains = &context->swapchain, + .pImageIndices = &image_index, + .pResults = 0, + }; + + result = vkQueuePresentKHR(context->present_queue.handle, &present_info); + if(result != VK_SUCCESS) { + return result; + } + + context->current_frame = (context->current_frame + 1) % MAX_FRAMES_IN_FLIGHT; + + return VK_SUCCESS; +} diff --git a/client/src/render.o b/client/src/render.o deleted file mode 100644 index d924f7c..0000000 Binary files a/client/src/render.o and /dev/null differ