/* * Copyright (c) Camden Dixie O'Brien * SPDX-License-Identifier: AGPL-3.0-only */ #define GLFW_INCLUDE_VULKAN #include "config.h" #include #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) #if defined(CPOW_DTHETA) && defined(POWRATE) #error "Cannot define CPOW_DTHETA and POWRATE simulataneously" #endif typedef struct { float width, height; #ifdef JULIA struct { float re, im; } julia; #endif struct { float re, im; } centre; #ifdef CPOW_DTHETA struct { float re, im; } cpow; #endif #ifdef POWRATE float zpow; #endif float scale; } params_t; static const char *layers[] = { "VK_LAYER_KHRONOS_validation" }; static const char *extensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, }; #define NEXTENSIONS (sizeof(extensions) / sizeof(extensions[0])) // Initial parameters static params_t params = { #ifdef JULIA .julia = { #ifdef JULIA_C_RE .re = JULIA_C_RE, #endif #ifdef JULIA_C_IM .im = JULIA_C_IM, #endif }, #endif #if defined(CENTRE_RE) || defined(CENTRE_IM) .centre = { #ifdef CENTRE_RE .re = CENTRE_RE, #endif #ifdef CENTRE_IM .im = CENTRE_IM, #endif }, #endif #ifdef CPOW_DTHETA .cpow = { .re = 2.0, .im = 0.0 }, #endif #ifdef POWRATE .zpow = 2.0, #endif .scale = 1.0, }; static void mouse_button_callback(GLFWwindow *window, int button, int action, int mods) { (void)window; (void)button; (void)mods; if (action == GLFW_RELEASE) return; double x, y; glfwGetCursorPos(window, &x, &y); double zx = 4.0 * (x / params.width - 0.5); double zy = 4.0 * (y / params.height - 0.5); zx *= params.width / params.height; zx = params.scale * zx + params.centre.re; zy = params.scale * zy + params.centre.im; printf("CLICKED: %.8f + %.8fi\n", zx, zy); params.centre.re = zx; params.centre.im = zy; } 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 glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); GLFWmonitor *monitor = glfwGetPrimaryMonitor(); const GLFWvidmode *mode = glfwGetVideoMode(monitor); GLFWwindow *window = glfwCreateWindow( mode->width, mode->height, "GPU Fractal", monitor, NULL); assert(window); // Register mouse click callback glfwSetMouseButtonCallback(window, mouse_button_callback); // Initialise Vulkan instance. const VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "GPU Fractal", .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); // 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, NULL, &ext_count, ext_props); assert(result == VK_SUCCESS); bool supported[NEXTENSIONS] = { 0 }; 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, NULL, &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, 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); // Initialise shader parameters params.width = (float)swapchain_extent.width; params.height = (float)swapchain_extent.height; #ifdef JULIA_DTHETA const double complex julia_rotz = cexp(I * JULIA_DTHETA); #endif #ifdef CPOW_DTHETA const double complex cpow_rotz = cexp(I * CPOW_DTHETA); #endif 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); #ifdef JULIA_DTHETA double complex julia = params.julia.re + I * params.julia.im; julia *= julia_rotz; params.julia.re = (float)creal(julia); params.julia.im = (float)cimag(julia); #endif #ifdef ZOOMRATE params.scale *= (1 - ZOOMRATE); #endif #ifdef CPOW_DTHETA double complex cpow = params.cpow.re + I * params.cpow.im; cpow *= cpow_rotz; params.cpow.re = (float)creal(cpow); params.cpow.im = (float)cimag(cpow); #endif #ifdef POWRATE params.zpow += POWRATE; #endif } 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(); }