commit cf7a0f099a6eee4f6ed862e48e6bb354b959002f Author: Camden Dixie O'Brien Date: Thu Sep 25 14:23:58 2025 +0100 Create Vulkan demo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..22603bb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.10) + +project(renderer LANGUAGES C) + +macro(configure_target target) + set_property(TARGET ${target} PROPERTY C_STANDARD 23) + set_property(TARGET ${target} PROPERTY C_EXTENSIONS OFF) + target_compile_options(${target} PRIVATE -Wall -Wextra -pedantic) +endmacro() + +find_program(glslc glslc) + +function(add_shaders target) + set(bin_dir "${CMAKE_CURRENT_BINARY_DIR}/${target}") + foreach(src ${ARGN}) + get_filename_component(stage ${src} NAME_WLE) + set(bin "${bin_dir}/${stage}.spv") + list(APPEND bins ${bin}) + add_custom_command( + OUTPUT ${bin} + COMMAND ${glslc} -fshader-stage=${stage} + ${CMAKE_CURRENT_SOURCE_DIR}/${src} -o ${bin} + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${src}" + ) + endforeach() + add_custom_target(${target}-spirv DEPENDS ${bins}) + add_library(${target} INTERFACE) + target_compile_definitions(${target} INTERFACE + VERT_SHADER_SPV="${bin_dir}/vert.spv" + FRAG_SHADER_SPV="${bin_dir}/frag.spv") + add_dependencies(${target} ${target}-spirv) +endfunction() + +add_shaders(shaders + vert.glsl + frag.glsl +) + +add_executable(demo demo.c) +configure_target(demo) +target_link_libraries(demo PRIVATE glfw m shaders vulkan) diff --git a/demo.c b/demo.c new file mode 100644 index 0000000..9d93bdc --- /dev/null +++ b/demo.c @@ -0,0 +1,576 @@ +#define GLFW_INCLUDE_VULKAN + +#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 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) + +static const char *layers[] = { "VK_LAYER_KHRONOS_validation" }; +static const char *extensions[] = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, +}; + +static const char vert_shader_code[] = { +#embed VERT_SHADER_SPV +}; + +static const char frag_shader_code[] = { +#embed FRAG_SHADER_SPV +}; + +#define NEXTENSIONS (sizeof(extensions) / sizeof(extensions[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; + } +} + +static VkShaderModule +load_shader_module(VkDevice dev, const char *code, size_t code_size) +{ + const VkShaderModuleCreateInfo create_info = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = code_size, + .pCode = (uint32_t *)code, + }; + VkShaderModule mod; + const VkResult result + = vkCreateShaderModule(dev, &create_info, nullptr, &mod); + assert(result == VK_SUCCESS); + + return mod; +} + +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 Vulkan instance. + const VkApplicationInfo app_info = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "Foo", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_API_VERSION_1_0, + }; + uint32_t glfw_ext_count = 0; + const char **glfw_ext_names + = glfwGetRequiredInstanceExtensions(&glfw_ext_count); + const VkInstanceCreateInfo inst_create_info = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &app_info, + .enabledExtensionCount = glfw_ext_count, + .ppEnabledExtensionNames = glfw_ext_names, + .enabledLayerCount = sizeof(layers) / sizeof(layers[0]), + .ppEnabledLayerNames = layers, + }; + VkInstance inst; + VkResult result = vkCreateInstance(&inst_create_info, nullptr, &inst); + assert(result == VK_SUCCESS); + + // Create surface + VkSurfaceKHR surface; + result = glfwCreateWindowSurface(inst, window, nullptr, &surface); + assert(result == VK_SUCCESS); + + // Select a physical device + VkPhysicalDevice physical_dev; + uint32_t dev_count = 1; + result = vkEnumeratePhysicalDevices(inst, &dev_count, &physical_dev); + assert(result == VK_SUCCESS || result == VK_INCOMPLETE); + + // Select queue family + uint32_t queue_family_count = QUEUE_FAMILY_PROP_BUFFER_SIZE; + VkQueueFamilyProperties + queue_family_props[QUEUE_FAMILY_PROP_BUFFER_SIZE]; + vkGetPhysicalDeviceQueueFamilyProperties( + 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); + if (surface_support) { + found_queue_family = true; + queue_family = i; + break; + } + } + } + assert(found_queue_family); + + // Check that the physical device supports all extensions + uint32_t ext_count = EXT_PROP_BUFFER_SIZE; + VkExtensionProperties ext_props[EXT_PROP_BUFFER_SIZE]; + result = vkEnumerateDeviceExtensionProperties( + physical_dev, nullptr, &ext_count, ext_props); + assert(result == VK_SUCCESS); + bool supported[NEXTENSIONS] = {}; + for (unsigned i = 0; i < ext_count; ++i) { + for (unsigned j = 0; j < NEXTENSIONS; ++j) { + if (strcmp(extensions[j], ext_props[i].extensionName) == 0) { + supported[j] = true; + break; + } + } + } + for (unsigned j = 0; j < NEXTENSIONS; ++j) { + if (!supported[j]) { + fprintf(stderr, "Unsupported: %s\n", extensions[j]); + assert(false); + } + } + + // Create the logical device and get the queue handle. + float queue_priority = 1.0; + const VkDeviceQueueCreateInfo queue_create_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = queue_family, + .queueCount = 1, + .pQueuePriorities = &queue_priority, + }; + const VkDeviceCreateInfo dev_create_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pQueueCreateInfos = &queue_create_info, + .queueCreateInfoCount = 1, + .enabledExtensionCount = NEXTENSIONS, + .ppEnabledExtensionNames = extensions, + }; + VkDevice dev; + result = vkCreateDevice(physical_dev, &dev_create_info, nullptr, &dev); + assert(result == VK_SUCCESS); + VkQueue queue; + vkGetDeviceQueue(dev, queue_family, 0, &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); + assert(result == VK_SUCCESS); + assert(surface_fmt_count > 0); + VkSurfaceFormatKHR surface_fmt = surface_fmts[0]; + for (unsigned i = 0; i < surface_fmt_count; ++i) { + if (surface_fmts[i].format == VK_FORMAT_R8G8B8A8_SRGB + && surface_fmts[i].colorSpace + == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + surface_fmt = surface_fmts[i]; + } + } + + // Query surface capabilities + VkSurfaceCapabilitiesKHR surface_caps; + result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + physical_dev, 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( + (uint32_t)width, surface_caps.minImageExtent.width, + surface_caps.maxImageExtent.width); + swapchain_extent.height = CLAMP( + (uint32_t)height, surface_caps.minImageExtent.height, + surface_caps.maxImageExtent.height); + } else { + swapchain_extent = surface_caps.currentExtent; + } + + // Select image count + uint32_t swapchain_img_count = surface_caps.minImageCount + 1; + assert( + surface_caps.maxImageCount == 0 + || swapchain_img_count <= surface_caps.maxImageCount); + + // Create the swap chain + const VkSwapchainCreateInfoKHR swapchain_create_info = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = surface, + .minImageCount = swapchain_img_count, + .imageFormat = surface_fmt.format, + .imageColorSpace = surface_fmt.colorSpace, + .imageExtent = swapchain_extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .preTransform = surface_caps.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = VK_PRESENT_MODE_FIFO_KHR, + .clipped = VK_TRUE, + .oldSwapchain = VK_NULL_HANDLE, + }; + VkSwapchainKHR swapchain; + result = vkCreateSwapchainKHR( + dev, &swapchain_create_info, nullptr, &swapchain); + assert(result == VK_SUCCESS); + + // Retreive swapchain images + VkImage swapchain_imgs[SWAPCHAIN_IMG_BUFFER_SIZE]; + swapchain_img_count = SWAPCHAIN_IMG_BUFFER_SIZE; + result = vkGetSwapchainImagesKHR( + dev, 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, + .format = surface_fmt.format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + 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); + } + + // 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)); + 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", + }, + [1] = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = frag_shader, + .pName = "main", + }, + }; + + // Set up input configuration + const VkPipelineVertexInputStateCreateInfo vertex_input_config = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + }; + const VkPipelineInputAssemblyStateCreateInfo input_assembly_config = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, + }; + + // Set up viewport configuration + const VkViewport viewport = { + .width = (float)swapchain_extent.width, + .height = (float)swapchain_extent.height, + }; + const VkRect2D scissor = { .extent = swapchain_extent }; + VkPipelineViewportStateCreateInfo viewport_config = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, + .pViewports = &viewport, + .scissorCount = 1, + .pScissors = &scissor, + }; + + // Populate rasterizer configuration + const VkPipelineRasterizationStateCreateInfo rasterizer_config = { + .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_NONE, + }; + + // Populate multisampling configuration + const VkPipelineMultisampleStateCreateInfo multisampling_config = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .sampleShadingEnable = VK_FALSE, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + }; + + // Set up colour blending configuration + const VkPipelineColorBlendAttachmentState colour_blend_attatchment = { + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT + | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + .blendEnable = VK_FALSE, + }; + const VkPipelineColorBlendStateCreateInfo colour_blend_config = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .logicOpEnable = VK_FALSE, + .attachmentCount = 1, + .pAttachments = &colour_blend_attatchment, + }; + + // Create pipeline layout + const VkPipelineLayoutCreateInfo pipeline_layout_config = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pushConstantRangeCount = 0, + }; + VkPipelineLayout pipeline_layout; + result = vkCreatePipelineLayout( + dev, &pipeline_layout_config, nullptr, &pipeline_layout); + assert(result == VK_SUCCESS); + + // Set up colour attachment + const VkAttachmentDescription colour_attachment = { + .format = surface_fmt.format, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + }; + const VkAttachmentReference colour_attachment_ref = { + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }; + const VkSubpassDescription subpass = { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .colorAttachmentCount = 1, + .pColorAttachments = &colour_attachment_ref, + }; + + // Create render pass + const VkSubpassDependency dependency = { + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = 0, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + }; + const VkRenderPassCreateInfo render_pass_config = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .attachmentCount = 1, + .pAttachments = &colour_attachment, + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 1, + .pDependencies = &dependency, + }; + VkRenderPass render_pass; + result = vkCreateRenderPass( + dev, &render_pass_config, nullptr, &render_pass); + assert(result == VK_SUCCESS); + + // Create pipeline + const VkGraphicsPipelineCreateInfo pipeline_config = { + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .stageCount = 2, + .pStages = shader_stages, + .pVertexInputState = &vertex_input_config, + .pInputAssemblyState = &input_assembly_config, + .pViewportState = &viewport_config, + .pRasterizationState = &rasterizer_config, + .pMultisampleState = &multisampling_config, + .pColorBlendState = &colour_blend_config, + .layout = pipeline_layout, + .renderPass = render_pass, + .subpass = 0, + }; + VkPipeline pipeline; + result = vkCreateGraphicsPipelines( + dev, VK_NULL_HANDLE, 1, &pipeline_config, nullptr, &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, + .attachmentCount = 1, + .pAttachments = swapchain_img_views + i, + .width = swapchain_extent.width, + .height = swapchain_extent.height, + .layers = 1, + }; + result = vkCreateFramebuffer( + dev, &framebuffer_config, nullptr, framebuffers + i); + assert(result == VK_SUCCESS); + } + + // Create command pool + const VkCommandPoolCreateInfo cmd_pool_config = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = queue_family, + }; + VkCommandPool cmd_pool; + result = vkCreateCommandPool(dev, &cmd_pool_config, nullptr, &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, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + VkCommandBuffer cmd_buf; + result = vkAllocateCommandBuffers(dev, &cmd_buf_config, &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); + assert(result == VK_SUCCESS); + result = vkCreateSemaphore(dev, &semaphore_config, nullptr, &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); + assert(result == VK_SUCCESS); + + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + // Wait for previous frame to finish + vkWaitForFences(dev, 1, &in_flight, VK_TRUE, UINT64_MAX); + vkResetFences(dev, 1, &in_flight); + + // 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); + + // 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); + + // 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); + + // Bind graphics pipeline and draw + vkCmdBindPipeline( + cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + vkCmdDraw(cmd_buf, 4, 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); + vkDestroyDevice(dev, nullptr); + vkDestroySurfaceKHR(inst, surface, nullptr); + vkDestroyInstance(inst, nullptr); + glfwDestroyWindow(window); + glfwTerminate(); +} diff --git a/frag.glsl b/frag.glsl new file mode 100644 index 0000000..dce8d2e --- /dev/null +++ b/frag.glsl @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec3 frag_colour; +layout(location = 0) out vec4 out_colour; + +void main() +{ + out_colour = vec4(frag_colour, 1.0); +} diff --git a/vert.glsl b/vert.glsl new file mode 100644 index 0000000..e7fe154 --- /dev/null +++ b/vert.glsl @@ -0,0 +1,20 @@ +#version 450 + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colours[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +layout(location = 0) out vec3 frag_colour; + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + frag_colour = colours[gl_VertexIndex]; +}