diff --git a/CMakeLists.txt b/CMakeLists.txt index 22603bb..f16c4cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,10 @@ add_shaders(shaders frag.glsl ) -add_executable(demo demo.c) +add_executable(demo + main.c + gpu.c +) configure_target(demo) -target_link_libraries(demo PRIVATE glfw m shaders vulkan) +target_include_directories(demo PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(demo PRIVATE glfw m vulkan shaders) diff --git a/demo.c b/gpu.c similarity index 61% rename from demo.c rename to gpu.c index 98e8578..39790d2 100644 --- a/demo.c +++ b/gpu.c @@ -1,66 +1,36 @@ -#define GLFW_INCLUDE_VULKAN +#include "gpu.h" -#include #include -#include -#include #include #include -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) -#define CLAMP(x, min, max) MAX((min), MIN(x, (max))) - -#define NELEMS(arr) (sizeof(arr) / sizeof(arr[0])) - #define QUEUE_FAMILY_PROP_BUFFER_SIZE 16 #define EXT_PROP_BUFFER_SIZE 256 #define SURFACE_FMT_BUFFER_SIZE 64 #define PRESENT_MODE_BUFFER_SIZE 8 #define SWAPCHAIN_IMG_BUFFER_SIZE 4 -#define MAX_SHADER_SIZE (8 * 1024) +#define NELEMS(arr) (sizeof(arr) / sizeof(arr[0])) -typedef struct { - float position[2], colour[3]; -} vertex_t; +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define CLAMP(x, min, max) MAX((min), MIN(x, (max))) static const char *layers[] = { "VK_LAYER_KHRONOS_validation" }; static const char *extensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, }; -static const char vert_shader_code[] = { +static constexpr size_t extension_count = NELEMS(extensions); + +static const char vert_shader[] = { #embed VERT_SHADER_SPV }; -static const char frag_shader_code[] = { +static const char frag_shader[] = { #embed FRAG_SHADER_SPV }; -static const vertex_t vertices[] = { - { { 0.0, -0.5 }, { 1.0, 0.0, 0.0 } }, - { { 0.5, 0.5 }, { 0.0, 1.0, 0.0 } }, - { { -0.5, 0.5 }, { 0.0, 0.0, 1.0 } }, -}; - -#define NEXTENSIONS NELEMS(extensions) - -static void -key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) -{ - (void)window; - (void)scancode; - (void)mods; - - switch (key) { - case GLFW_KEY_Q: - if (action == GLFW_PRESS) - glfwSetWindowShouldClose(window, GL_TRUE); - break; - } -} - static VkShaderModule load_shader_module(VkDevice dev, const char *code, size_t code_size) { @@ -77,22 +47,12 @@ load_shader_module(VkDevice dev, const char *code, size_t code_size) return mod; } -int main() +void gpu_init(gpu_ctx_t *ctx, const gpu_config_t *config) { - // Set up window - glfwInit(); - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - GLFWwindow *window = glfwCreateWindow(800, 600, "Foo", nullptr, nullptr); - assert(window); - - // Register input callbacks - glfwSetKeyCallback(window, key_callback); - // Initialise Vulkan instance. const VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, - .pApplicationName = "Foo", + .pApplicationName = config->application_name, .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), @@ -109,19 +69,19 @@ int main() .enabledLayerCount = sizeof(layers) / sizeof(layers[0]), .ppEnabledLayerNames = layers, }; - VkInstance inst; - VkResult result = vkCreateInstance(&inst_create_info, nullptr, &inst); + VkResult result + = vkCreateInstance(&inst_create_info, nullptr, &ctx->inst); assert(result == VK_SUCCESS); // Create surface - VkSurfaceKHR surface; - result = glfwCreateWindowSurface(inst, window, nullptr, &surface); + result = glfwCreateWindowSurface( + ctx->inst, config->window, nullptr, &ctx->surface); assert(result == VK_SUCCESS); // Select a physical device - VkPhysicalDevice physical_dev; uint32_t dev_count = 1; - result = vkEnumeratePhysicalDevices(inst, &dev_count, &physical_dev); + result = vkEnumeratePhysicalDevices( + ctx->inst, &dev_count, &ctx->physical_dev); assert(result == VK_SUCCESS || result == VK_INCOMPLETE); // Select queue family @@ -129,14 +89,14 @@ int main() VkQueueFamilyProperties queue_family_props[QUEUE_FAMILY_PROP_BUFFER_SIZE]; vkGetPhysicalDeviceQueueFamilyProperties( - physical_dev, &queue_family_count, queue_family_props); + ctx->physical_dev, &queue_family_count, queue_family_props); uint32_t queue_family; bool found_queue_family = false; for (unsigned i = 0; i < queue_family_count; ++i) { if (queue_family_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { VkBool32 surface_support = false; vkGetPhysicalDeviceSurfaceSupportKHR( - physical_dev, i, surface, &surface_support); + ctx->physical_dev, i, ctx->surface, &surface_support); if (surface_support) { found_queue_family = true; queue_family = i; @@ -150,18 +110,18 @@ int main() uint32_t ext_count = EXT_PROP_BUFFER_SIZE; VkExtensionProperties ext_props[EXT_PROP_BUFFER_SIZE]; result = vkEnumerateDeviceExtensionProperties( - physical_dev, nullptr, &ext_count, ext_props); + ctx->physical_dev, nullptr, &ext_count, ext_props); assert(result == VK_SUCCESS); - bool supported[NEXTENSIONS] = {}; + bool supported[extension_count] = {}; for (unsigned i = 0; i < ext_count; ++i) { - for (unsigned j = 0; j < NEXTENSIONS; ++j) { + for (unsigned j = 0; j < extension_count; ++j) { if (strcmp(extensions[j], ext_props[i].extensionName) == 0) { supported[j] = true; break; } } } - for (unsigned j = 0; j < NEXTENSIONS; ++j) { + for (unsigned j = 0; j < extension_count; ++j) { if (!supported[j]) { fprintf(stderr, "Unsupported: %s\n", extensions[j]); assert(false); @@ -180,21 +140,20 @@ int main() .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pQueueCreateInfos = &queue_create_info, .queueCreateInfoCount = 1, - .enabledExtensionCount = NEXTENSIONS, + .enabledExtensionCount = extension_count, .ppEnabledExtensionNames = extensions, }; - VkDevice dev; - result = vkCreateDevice(physical_dev, &dev_create_info, nullptr, &dev); + result = vkCreateDevice( + ctx->physical_dev, &dev_create_info, nullptr, &ctx->dev); assert(result == VK_SUCCESS); - VkQueue queue; - vkGetDeviceQueue(dev, queue_family, 0, &queue); + vkGetDeviceQueue(ctx->dev, queue_family, 0, &ctx->queue); // Select a surface format, preferring R8G8B8A8_SRGB with // SRGB_NONLINEAR colour space. uint32_t surface_fmt_count = SURFACE_FMT_BUFFER_SIZE; VkSurfaceFormatKHR surface_fmts[SURFACE_FMT_BUFFER_SIZE]; result = vkGetPhysicalDeviceSurfaceFormatsKHR( - physical_dev, surface, &surface_fmt_count, surface_fmts); + ctx->physical_dev, ctx->surface, &surface_fmt_count, surface_fmts); assert(result == VK_SUCCESS); assert(surface_fmt_count > 0); VkSurfaceFormatKHR surface_fmt = surface_fmts[0]; @@ -209,26 +168,26 @@ int main() // Query surface capabilities VkSurfaceCapabilitiesKHR surface_caps; result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR( - physical_dev, surface, &surface_caps); + ctx->physical_dev, ctx->surface, &surface_caps); assert(result == VK_SUCCESS); // Select swap chain extent - VkExtent2D swapchain_extent; if (surface_caps.currentExtent.width == UINT32_MAX) { int width, height; - glfwGetFramebufferSize(window, &width, &height); - swapchain_extent.width = CLAMP( + glfwGetFramebufferSize(config->window, &width, &height); + ctx->swapchain_extent.width = CLAMP( (uint32_t)width, surface_caps.minImageExtent.width, surface_caps.maxImageExtent.width); - swapchain_extent.height = CLAMP( + ctx->swapchain_extent.height = CLAMP( (uint32_t)height, surface_caps.minImageExtent.height, surface_caps.maxImageExtent.height); } else { - swapchain_extent = surface_caps.currentExtent; + ctx->swapchain_extent = surface_caps.currentExtent; } // Select image count uint32_t swapchain_img_count = surface_caps.minImageCount + 1; + assert(swapchain_img_count <= SWAPCHAIN_IMG_BUFFER_SIZE); assert( surface_caps.maxImageCount == 0 || swapchain_img_count <= surface_caps.maxImageCount); @@ -236,11 +195,11 @@ int main() // Create the swap chain const VkSwapchainCreateInfoKHR swapchain_create_info = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, - .surface = surface, + .surface = ctx->surface, .minImageCount = swapchain_img_count, .imageFormat = surface_fmt.format, .imageColorSpace = surface_fmt.colorSpace, - .imageExtent = swapchain_extent, + .imageExtent = ctx->swapchain_extent, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, @@ -250,20 +209,18 @@ int main() .clipped = VK_TRUE, .oldSwapchain = VK_NULL_HANDLE, }; - VkSwapchainKHR swapchain; result = vkCreateSwapchainKHR( - dev, &swapchain_create_info, nullptr, &swapchain); + ctx->dev, &swapchain_create_info, nullptr, &ctx->swapchain); assert(result == VK_SUCCESS); // Retreive swapchain images VkImage swapchain_imgs[SWAPCHAIN_IMG_BUFFER_SIZE]; - swapchain_img_count = SWAPCHAIN_IMG_BUFFER_SIZE; + ctx->swapchain_img_count = SWAPCHAIN_IMG_BUFFER_SIZE; result = vkGetSwapchainImagesKHR( - dev, swapchain, &swapchain_img_count, swapchain_imgs); + ctx->dev, ctx->swapchain, &swapchain_img_count, swapchain_imgs); assert(result == VK_SUCCESS); // Create swapchain image views - VkImageView swapchain_img_views[SWAPCHAIN_IMG_BUFFER_SIZE]; VkImageViewCreateInfo img_view_create_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .viewType = VK_IMAGE_VIEW_TYPE_2D, @@ -285,26 +242,28 @@ int main() for (unsigned i = 0; i < swapchain_img_count; ++i) { img_view_create_info.image = swapchain_imgs[i]; result = vkCreateImageView( - dev, &img_view_create_info, nullptr, swapchain_img_views + i); + ctx->dev, &img_view_create_info, nullptr, + ctx->swapchain_img_views + i); + assert(result == VK_SUCCESS); } // Load vertex and fragment shader modules - VkShaderModule vert_shader = load_shader_module( - dev, vert_shader_code, sizeof(vert_shader_code)); - VkShaderModule frag_shader = load_shader_module( - dev, frag_shader_code, sizeof(frag_shader_code)); + ctx->vert_shader + = load_shader_module(ctx->dev, vert_shader, sizeof(vert_shader)); + ctx->frag_shader + = load_shader_module(ctx->dev, frag_shader, sizeof(frag_shader)); const VkPipelineShaderStageCreateInfo shader_stages[] = { - [0] = { - .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_VERTEX_BIT, + .module = ctx->vert_shader, + .pName = "main", }, - [1] = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - .stage = VK_SHADER_STAGE_FRAGMENT_BIT, - .module = frag_shader, - .pName = "main", + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = ctx->frag_shader, + .pName = "main", }, }; @@ -344,10 +303,10 @@ int main() // Set up viewport configuration const VkViewport viewport = { - .width = (float)swapchain_extent.width, - .height = (float)swapchain_extent.height, + .width = (float)ctx->swapchain_extent.width, + .height = (float)ctx->swapchain_extent.height, }; - const VkRect2D scissor = { .extent = swapchain_extent }; + const VkRect2D scissor = { .extent = ctx->swapchain_extent }; VkPipelineViewportStateCreateInfo viewport_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1, @@ -391,9 +350,8 @@ int main() .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .pushConstantRangeCount = 0, }; - VkPipelineLayout pipeline_layout; result = vkCreatePipelineLayout( - dev, &pipeline_layout_config, nullptr, &pipeline_layout); + ctx->dev, &pipeline_layout_config, nullptr, &ctx->pipeline_layout); assert(result == VK_SUCCESS); // Set up colour attachment @@ -435,9 +393,8 @@ int main() .dependencyCount = 1, .pDependencies = &dependency, }; - VkRenderPass render_pass; result = vkCreateRenderPass( - dev, &render_pass_config, nullptr, &render_pass); + ctx->dev, &render_pass_config, nullptr, &ctx->render_pass); assert(result == VK_SUCCESS); // Create pipeline @@ -451,42 +408,41 @@ int main() .pRasterizationState = &rasterizer_config, .pMultisampleState = &multisampling_config, .pColorBlendState = &colour_blend_config, - .layout = pipeline_layout, - .renderPass = render_pass, + .layout = ctx->pipeline_layout, + .renderPass = ctx->render_pass, .subpass = 0, }; - VkPipeline pipeline; result = vkCreateGraphicsPipelines( - dev, VK_NULL_HANDLE, 1, &pipeline_config, nullptr, &pipeline); + ctx->dev, VK_NULL_HANDLE, 1, &pipeline_config, nullptr, + &ctx->pipeline); assert(result == VK_SUCCESS); // Create framebuffers - VkFramebuffer framebuffers[SWAPCHAIN_IMG_BUFFER_SIZE]; for (unsigned i = 0; i < swapchain_img_count; ++i) { const VkFramebufferCreateInfo framebuffer_config = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, - .renderPass = render_pass, + .renderPass = ctx->render_pass, .attachmentCount = 1, - .pAttachments = swapchain_img_views + i, - .width = swapchain_extent.width, - .height = swapchain_extent.height, + .pAttachments = ctx->swapchain_img_views + i, + .width = ctx->swapchain_extent.width, + .height = ctx->swapchain_extent.height, .layers = 1, }; result = vkCreateFramebuffer( - dev, &framebuffer_config, nullptr, framebuffers + i); + ctx->dev, &framebuffer_config, nullptr, ctx->framebuffers + i); assert(result == VK_SUCCESS); } // Create vertex buffer - const VkBufferCreateInfo vertex_buffer_config = { + ctx->vertex_data_size = config->vertex_data_size; + const VkBufferCreateInfo vertex_buf_config = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .size = sizeof(vertices), + .size = ctx->vertex_data_size, .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; - VkBuffer vertex_buffer; result = vkCreateBuffer( - dev, &vertex_buffer_config, nullptr, &vertex_buffer); + ctx->dev, &vertex_buf_config, nullptr, &ctx->vertex_buf); assert(result == VK_SUCCESS); // Allocate memory to vertex buffer @@ -494,9 +450,10 @@ int main() = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; VkMemoryRequirements mem_requirements; - vkGetBufferMemoryRequirements(dev, vertex_buffer, &mem_requirements); + vkGetBufferMemoryRequirements( + ctx->dev, ctx->vertex_buf, &mem_requirements); VkPhysicalDeviceMemoryProperties mem_props; - vkGetPhysicalDeviceMemoryProperties(physical_dev, &mem_props); + vkGetPhysicalDeviceMemoryProperties(ctx->physical_dev, &mem_props); uint32_t mem_type = UINT32_MAX; for (unsigned i = 0; i < mem_props.memoryTypeCount; ++i) { const bool valid_type @@ -515,18 +472,18 @@ int main() .allocationSize = mem_requirements.size, .memoryTypeIndex = mem_type, }; - VkDeviceMemory vertex_buffer_mem; result = vkAllocateMemory( - dev, &allocate_config, nullptr, &vertex_buffer_mem); + ctx->dev, &allocate_config, nullptr, &ctx->vertex_buf_mem); assert(result == VK_SUCCESS); - vkBindBufferMemory(dev, vertex_buffer, vertex_buffer_mem, 0); + vkBindBufferMemory(ctx->dev, ctx->vertex_buf, ctx->vertex_buf_mem, 0); // Copy vertex data into vertex buffer - void *vertex_data; + void *vertex_buf_ptr; vkMapMemory( - dev, vertex_buffer_mem, 0, sizeof(vertices), 0, &vertex_data); - memcpy(vertex_data, vertices, sizeof(vertices)); - vkUnmapMemory(dev, vertex_buffer_mem); + ctx->dev, ctx->vertex_buf_mem, 0, ctx->vertex_data_size, 0, + &vertex_buf_ptr); + memcpy(vertex_buf_ptr, config->vertex_data, ctx->vertex_data_size); + vkUnmapMemory(ctx->dev, ctx->vertex_buf_mem); // Create command pool const VkCommandPoolCreateInfo cmd_pool_config = { @@ -534,135 +491,139 @@ int main() .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex = queue_family, }; - VkCommandPool cmd_pool; - result = vkCreateCommandPool(dev, &cmd_pool_config, nullptr, &cmd_pool); + result = vkCreateCommandPool( + ctx->dev, &cmd_pool_config, nullptr, &ctx->cmd_pool); assert(result == VK_SUCCESS); // Allocate command buffer const VkCommandBufferAllocateInfo cmd_buf_config = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .commandPool = cmd_pool, + .commandPool = ctx->cmd_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; - VkCommandBuffer cmd_buf; - result = vkAllocateCommandBuffers(dev, &cmd_buf_config, &cmd_buf); + result + = vkAllocateCommandBuffers(ctx->dev, &cmd_buf_config, &ctx->cmd_buf); assert(result == VK_SUCCESS); // Create syncronisation primitives - VkSemaphore img_avail, render_fin; - VkSemaphoreCreateInfo semaphore_config - = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; - result = vkCreateSemaphore(dev, &semaphore_config, nullptr, &img_avail); + VkSemaphoreCreateInfo semaphore_config = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + result = vkCreateSemaphore( + ctx->dev, &semaphore_config, nullptr, &ctx->img_avail); assert(result == VK_SUCCESS); - result = vkCreateSemaphore(dev, &semaphore_config, nullptr, &render_fin); + result = vkCreateSemaphore( + ctx->dev, &semaphore_config, nullptr, &ctx->render_fin); assert(result == VK_SUCCESS); - VkFence in_flight; VkFenceCreateInfo fence_config = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = VK_FENCE_CREATE_SIGNALED_BIT, }; - result = vkCreateFence(dev, &fence_config, nullptr, &in_flight); + result + = vkCreateFence(ctx->dev, &fence_config, nullptr, &ctx->in_flight); + assert(result == VK_SUCCESS); +} + +void gpu_cleanup(const gpu_ctx_t *ctx) +{ + vkDeviceWaitIdle(ctx->dev); + + vkDestroySemaphore(ctx->dev, ctx->img_avail, nullptr); + vkDestroySemaphore(ctx->dev, ctx->render_fin, nullptr); + vkDestroyFence(ctx->dev, ctx->in_flight, nullptr); + vkDestroyCommandPool(ctx->dev, ctx->cmd_pool, nullptr); + vkDestroyPipeline(ctx->dev, ctx->pipeline, nullptr); + vkDestroyRenderPass(ctx->dev, ctx->render_pass, nullptr); + vkDestroyPipelineLayout(ctx->dev, ctx->pipeline_layout, nullptr); + vkDestroyShaderModule(ctx->dev, ctx->vert_shader, nullptr); + vkDestroyShaderModule(ctx->dev, ctx->frag_shader, nullptr); + for (unsigned i = 0; i < ctx->swapchain_img_count; ++i) { + vkDestroyFramebuffer(ctx->dev, ctx->framebuffers[i], nullptr); + vkDestroyImageView(ctx->dev, ctx->swapchain_img_views[i], nullptr); + } + vkDestroySwapchainKHR(ctx->dev, ctx->swapchain, nullptr); + vkDestroyBuffer(ctx->dev, ctx->vertex_buf, nullptr); + vkFreeMemory(ctx->dev, ctx->vertex_buf_mem, nullptr); + vkDestroyDevice(ctx->dev, nullptr); + vkDestroySurfaceKHR(ctx->inst, ctx->surface, nullptr); + vkDestroyInstance(ctx->inst, nullptr); +} + +void gpu_render(const gpu_ctx_t *ctx) +{ + VkResult result; + + // Wait for previous frame to finish + vkWaitForFences(ctx->dev, 1, &ctx->in_flight, VK_TRUE, UINT64_MAX); + vkResetFences(ctx->dev, 1, &ctx->in_flight); + + // Aquire image from swapchain + uint32_t img_index; + result = vkAcquireNextImageKHR( + ctx->dev, ctx->swapchain, UINT64_MAX, ctx->img_avail, VK_NULL_HANDLE, + &img_index); assert(result == VK_SUCCESS); - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); + // Reset command buffer, then begin writing + vkResetCommandBuffer(ctx->cmd_buf, 0); + const VkCommandBufferBeginInfo cmd_begin = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + }; + result = vkBeginCommandBuffer(ctx->cmd_buf, &cmd_begin); + assert(result == VK_SUCCESS); - // Wait for previous frame to finish - vkWaitForFences(dev, 1, &in_flight, VK_TRUE, UINT64_MAX); - vkResetFences(dev, 1, &in_flight); + // Start render pass + VkClearValue clear_value = { { { 0.0, 0.0, 0.0, 1.0 } } }; + VkRenderPassBeginInfo render_pass_begin = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .renderPass = ctx->render_pass, + .framebuffer = ctx->framebuffers[img_index], + .renderArea.extent = ctx->swapchain_extent, + .clearValueCount = 1, + .pClearValues = &clear_value, + }; + vkCmdBeginRenderPass( + ctx->cmd_buf, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE); - // Aquire image from swapchain - uint32_t img_index; - result = vkAcquireNextImageKHR( - dev, swapchain, UINT64_MAX, img_avail, VK_NULL_HANDLE, - &img_index); - assert(result == VK_SUCCESS); + // Bind graphics pipeline and vertex buffer, then draw + vkCmdBindPipeline( + ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->pipeline); + vkCmdBindVertexBuffers( + ctx->cmd_buf, 0, 1, &ctx->vertex_buf, &(VkDeviceSize) { 0 }); + vkCmdDraw( + ctx->cmd_buf, ctx->vertex_data_size / sizeof(vertex_t), 1, 0, 0); - // Reset command buffer, then begin writing - vkResetCommandBuffer(cmd_buf, 0); - const VkCommandBufferBeginInfo cmd_begin = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - }; - result = vkBeginCommandBuffer(cmd_buf, &cmd_begin); - assert(result == VK_SUCCESS); + // Finish render pass and end writing to command buffer + vkCmdEndRenderPass(ctx->cmd_buf); + result = vkEndCommandBuffer(ctx->cmd_buf); + assert(result == VK_SUCCESS); - // Start render pass - VkClearValue clear_value = { { { 0.0, 0.0, 0.0, 1.0 } } }; - VkRenderPassBeginInfo render_pass_begin = { - .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, - .renderPass = render_pass, - .framebuffer = framebuffers[img_index], - .renderArea.extent = swapchain_extent, - .clearValueCount = 1, - .pClearValues = &clear_value, - }; - vkCmdBeginRenderPass( - cmd_buf, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE); + // Submit command buffer + VkPipelineStageFlags wait_stage + = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + const VkSubmitInfo submit = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &ctx->img_avail, + .pWaitDstStageMask = &wait_stage, + .commandBufferCount = 1, + .pCommandBuffers = &ctx->cmd_buf, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &ctx->render_fin, + }; + result = vkQueueSubmit(ctx->queue, 1, &submit, ctx->in_flight); + assert(result == VK_SUCCESS); - // Bind graphics pipeline and vertex buffer, then draw - vkCmdBindPipeline( - cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - vkCmdBindVertexBuffers( - cmd_buf, 0, 1, &vertex_buffer, &(VkDeviceSize) { 0 }); - vkCmdDraw(cmd_buf, NELEMS(vertices), 1, 0, 0); - - // Finish render pass and end writing to command buffer - vkCmdEndRenderPass(cmd_buf); - result = vkEndCommandBuffer(cmd_buf); - assert(result == VK_SUCCESS); - - // Submit command buffer - VkPipelineStageFlags wait_stage - = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - const VkSubmitInfo submit = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &img_avail, - .pWaitDstStageMask = &wait_stage, - .commandBufferCount = 1, - .pCommandBuffers = &cmd_buf, - .signalSemaphoreCount = 1, - .pSignalSemaphores = &render_fin, - }; - result = vkQueueSubmit(queue, 1, &submit, in_flight); - assert(result == VK_SUCCESS); - - // Present frame - const VkPresentInfoKHR present_info = { - .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &render_fin, - .swapchainCount = 1, - .pSwapchains = &swapchain, - .pImageIndices = &img_index, - }; - result = vkQueuePresentKHR(queue, &present_info); - assert(result == VK_SUCCESS); - } - - vkDeviceWaitIdle(dev); - - // Clean up - vkDestroySemaphore(dev, img_avail, nullptr); - vkDestroySemaphore(dev, render_fin, nullptr); - vkDestroyFence(dev, in_flight, nullptr); - vkDestroyCommandPool(dev, cmd_pool, nullptr); - vkDestroyPipeline(dev, pipeline, nullptr); - vkDestroyRenderPass(dev, render_pass, nullptr); - vkDestroyPipelineLayout(dev, pipeline_layout, nullptr); - vkDestroyShaderModule(dev, vert_shader, nullptr); - vkDestroyShaderModule(dev, frag_shader, nullptr); - for (unsigned i = 0; i < swapchain_img_count; ++i) { - vkDestroyFramebuffer(dev, framebuffers[i], nullptr); - vkDestroyImageView(dev, swapchain_img_views[i], nullptr); - } - vkDestroySwapchainKHR(dev, swapchain, nullptr); - vkDestroyBuffer(dev, vertex_buffer, nullptr); - vkFreeMemory(dev, vertex_buffer_mem, nullptr); - vkDestroyDevice(dev, nullptr); - vkDestroySurfaceKHR(inst, surface, nullptr); - vkDestroyInstance(inst, nullptr); - glfwDestroyWindow(window); - glfwTerminate(); + // Present frame + const VkPresentInfoKHR present_info = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &ctx->render_fin, + .swapchainCount = 1, + .pSwapchains = &ctx->swapchain, + .pImageIndices = &img_index, + }; + result = vkQueuePresentKHR(ctx->queue, &present_info); + assert(result == VK_SUCCESS); } diff --git a/gpu.h b/gpu.h new file mode 100644 index 0000000..2a50499 --- /dev/null +++ b/gpu.h @@ -0,0 +1,50 @@ +#ifndef GPU_H +#define GPU_H + +#define GLFW_INCLUDE_VULKAN +#include + +#define SWAPCHAIN_IMG_BUFFER_SIZE 4 + +typedef struct { + float position[2], colour[3]; +} vertex_t; + +typedef struct { + VkInstance inst; + VkSurfaceKHR surface; + VkPhysicalDevice physical_dev; + VkDevice dev; + VkQueue queue; + VkSwapchainKHR swapchain; + VkExtent2D swapchain_extent; + uint32_t swapchain_img_count; + VkImageView swapchain_img_views[SWAPCHAIN_IMG_BUFFER_SIZE]; + VkFramebuffer framebuffers[SWAPCHAIN_IMG_BUFFER_SIZE]; + VkShaderModule vert_shader; + VkShaderModule frag_shader; + VkPipelineLayout pipeline_layout; + VkRenderPass render_pass; + VkPipeline pipeline; + VkCommandPool cmd_pool; + VkCommandBuffer cmd_buf; + VkFence in_flight; + VkSemaphore render_fin; + VkSemaphore img_avail; + VkBuffer vertex_buf; + VkDeviceMemory vertex_buf_mem; + size_t vertex_data_size; +} gpu_ctx_t; + +typedef struct { + const char *application_name; + GLFWwindow *window; + const vertex_t *vertex_data; + size_t vertex_data_size; +} gpu_config_t; + +void gpu_init(gpu_ctx_t *ctx, const gpu_config_t *config); +void gpu_cleanup(const gpu_ctx_t *ctx); +void gpu_render(const gpu_ctx_t *ctx); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..c4f2dc8 --- /dev/null +++ b/main.c @@ -0,0 +1,60 @@ +#include "gpu.h" + +#include +#include + +static const vertex_t vertices[] = { + { { 0.0, -0.5 }, { 1.0, 0.0, 0.0 } }, + { { 0.5, 0.5 }, { 0.0, 1.0, 0.0 } }, + { { -0.5, 0.5 }, { 0.0, 0.0, 1.0 } }, +}; + +static void +key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) +{ + (void)window; + (void)scancode; + (void)mods; + + switch (key) { + case GLFW_KEY_Q: + if (action == GLFW_PRESS) + glfwSetWindowShouldClose(window, GL_TRUE); + break; + } +} + +int main() +{ + // Set up window + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + GLFWwindow *window = glfwCreateWindow(800, 600, "Foo", nullptr, nullptr); + assert(window); + + // Register input callbacks + glfwSetKeyCallback(window, key_callback); + + // Initialise GPU + const gpu_config_t gpu_config = { + .application_name = "Foo", + .window = window, + .vertex_data = vertices, + .vertex_data_size = sizeof(vertices), + }; + gpu_ctx_t gpu; + gpu_init(&gpu, &gpu_config); + + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + gpu_render(&gpu); + } + + // Clean up + gpu_cleanup(&gpu); + glfwDestroyWindow(window); + glfwTerminate(); + + return 0; +}