commit b13d30775467d2aa7669ddb7b708722c62692a3e Author: Camden Dixie O'Brien Date: Fri Jan 17 14:32:07 2025 +0000 Create basic GPU fractal renderer diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..bd976e6 --- /dev/null +++ b/build.sh @@ -0,0 +1,5 @@ +#!/bin/sh +set -e +glslc -fshader-stage=vert vert_shader.glsl -o vert_shader.spv +glslc -fshader-stage=frag frag_shader.glsl -o frag_shader.spv +clang -std=c11 -pedantic -Wall -Wextra -lglfw -lvulkan main.c -o gpu-fractal diff --git a/frag_shader.glsl b/frag_shader.glsl new file mode 100644 index 0000000..c3a56db --- /dev/null +++ b/frag_shader.glsl @@ -0,0 +1,53 @@ +/* + * Copyright (c) Camden Dixie O'Brien + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#version 450 + +#define MAXITER 1000 + +layout(location = 0) out vec4 out_colour; + +layout(push_constant) uniform Constants { + vec2 img_size; + vec2 shift; + float zoom; +} params; + +vec2 ss_offsets[4] = vec2[]( + vec2(-0.25, -0.25), + vec2( 0.25, -0.25), + vec2(-0.25, 0.25), + vec2( 0.25, 0.25) +); + +vec2 maptoz(vec2 xy) +{ + return params.zoom * ((xy / params.img_size.xy) * 4.0 - 2.0) + params.shift; +} + +float mandelbrot(vec2 c) { + vec2 z = vec2(0.0, 0.0); + for (int i = 0; i < MAXITER; ++i) { + z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c; + if (length(z) > 2.0) + return float(i) / float(MAXITER); + } + return 0.0; +} + +vec3 colour(float i) { + return vec3(i, i * i, i * i * i); +} + +void main() +{ + vec2 c; + vec3 col = vec3(0.0, 0.0, 0.0); + for (int i = 0; i < 4; ++i) { + c = maptoz(gl_FragCoord.xy + ss_offsets[i]); + col += colour(mandelbrot(c)); + } + out_colour = vec4(col, 1.0); +} diff --git a/main.c b/main.c new file mode 100644 index 0000000..b6007ca --- /dev/null +++ b/main.c @@ -0,0 +1,580 @@ +/* + * Copyright (c) Camden Dixie O'Brien + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#define GLFW_INCLUDE_VULKAN + +#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 (4 * 1024) + +typedef struct { + float width, height; + struct { + float x, y; + } shift; + float zoom; +} params_t; + +static const char *layers[] = { "VK_LAYER_KHRONOS_validation" }; +static const char *swapchain_ext_name = VK_KHR_SWAPCHAIN_EXTENSION_NAME; + +static VkShaderModule load_shader_module(VkDevice dev, const char *path) +{ + static char buf[MAX_SHADER_SIZE] + __attribute__((aligned(__alignof__(uint32_t)))); + FILE *f = fopen(path, "rb"); + const int size = fread(buf, 1, MAX_SHADER_SIZE, f); + assert(size > 0 && feof(f)); + fclose(f); + + const VkShaderModuleCreateInfo create_info = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = size, + .pCode = (uint32_t *)buf, + }; + VkShaderModule mod; + const VkResult result + = vkCreateShaderModule(dev, &create_info, NULL, &mod); + assert(result == VK_SUCCESS); + + return mod; +} + +int main(void) +{ + // Set up window and surface + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + GLFWwindow *window + = glfwCreateWindow(900, 900, "GPU Fractal", NULL, NULL); + assert(window); + + // Initialise Vulkan instance. + const VkApplicationInfo app_info = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "Vulkan Test", + .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, NULL, &inst); + assert(result == VK_SUCCESS); + + // Create surface + VkSurfaceKHR surface; + result = glfwCreateWindowSurface(inst, window, NULL, &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); + + // 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 = 1, + .ppEnabledExtensionNames = &swapchain_ext_name, + }; + VkDevice dev; + result = vkCreateDevice(physical_dev, &dev_create_info, NULL, &dev); + assert(result == VK_SUCCESS); + VkQueue queue; + vkGetDeviceQueue(dev, queue_family, 0, &queue); + + // Check that the physical device has swap chain support + uint32_t ext_count = EXT_PROP_BUFFER_SIZE; + VkExtensionProperties ext_props[EXT_PROP_BUFFER_SIZE]; + result = vkEnumerateDeviceExtensionProperties( + physical_dev, NULL, &ext_count, ext_props); + assert(result == VK_SUCCESS); + bool swapchain_support = false; + for (unsigned i = 0; i < ext_count; ++i) { + if (strcmp(swapchain_ext_name, ext_props[i].extensionName) == 0) { + swapchain_support = true; + break; + } + } + assert(swapchain_support); + + // 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, NULL, &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, NULL, swapchain_img_views + i); + } + + // Load vertex and fragment shader modules + VkShaderModule vert_shader = load_shader_module(dev, "vert_shader.spv"); + VkShaderModule frag_shader = load_shader_module(dev, "frag_shader.spv"); + 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 and viewport 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, + }; + 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 VkPushConstantRange push_constant_range = { + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .offset = 0, + .size = sizeof(params_t), + }; + const VkPipelineLayoutCreateInfo pipeline_layout_config = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pushConstantRangeCount = 1, + .pPushConstantRanges = &push_constant_range, + }; + VkPipelineLayout pipeline_layout; + result = vkCreatePipelineLayout( + dev, &pipeline_layout_config, NULL, &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, NULL, &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, NULL, &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, NULL, 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, NULL, &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, NULL, &img_avail); + assert(result == VK_SUCCESS); + result = vkCreateSemaphore(dev, &semaphore_config, NULL, &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, NULL, &in_flight); + assert(result == VK_SUCCESS); + + params_t params = { + .width = (float)swapchain_extent.width, + .height = (float)swapchain_extent.height, + .shift = { + .x = -0.743643887037158704752191506114774, + .y = 0.131825904205311970493132056385139, + }, + .zoom = 1.0, + }; + + 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); + + vkCmdPushConstants( + cmd_buf, pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, + sizeof(params), ¶ms); + + // 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); + + // Increment zoom + params.zoom *= 0.99; + } + + vkDeviceWaitIdle(dev); + + /* + * Cleanup + */ + + vkDestroySemaphore(dev, img_avail, NULL); + vkDestroySemaphore(dev, render_fin, NULL); + vkDestroyFence(dev, in_flight, NULL); + vkDestroyCommandPool(dev, cmd_pool, NULL); + vkDestroyPipeline(dev, pipeline, NULL); + vkDestroyRenderPass(dev, render_pass, NULL); + vkDestroyPipelineLayout(dev, pipeline_layout, NULL); + vkDestroyShaderModule(dev, vert_shader, NULL); + vkDestroyShaderModule(dev, frag_shader, NULL); + for (unsigned i = 0; i < swapchain_img_count; ++i) { + vkDestroyFramebuffer(dev, framebuffers[i], NULL); + vkDestroyImageView(dev, swapchain_img_views[i], NULL); + } + vkDestroySwapchainKHR(dev, swapchain, NULL); + vkDestroyDevice(dev, NULL); + vkDestroySurfaceKHR(inst, surface, NULL); + vkDestroyInstance(inst, NULL); + glfwDestroyWindow(window); + glfwTerminate(); +} diff --git a/vert_shader.glsl b/vert_shader.glsl new file mode 100644 index 0000000..0bbac37 --- /dev/null +++ b/vert_shader.glsl @@ -0,0 +1,17 @@ +/* + * Copyright (c) Camden Dixie O'Brien + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#version 450 + +vec2 positions[4] = vec2[]( + vec2(-1.0, -1.0), + vec2( 1.0, -1.0), + vec2(-1.0, 1.0), + vec2( 1.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); +}