#include "gpu.h" #include #include #include #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 NELEMS(arr) (sizeof(arr) / sizeof(arr[0])) #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 constexpr size_t extension_count = NELEMS(extensions); static const char vert_shader[] = { #embed VERT_SHADER_SPV }; static const char frag_shader[] = { #embed FRAG_SHADER_SPV }; 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; } static void alloc_dev_mem( const gpu_ctx_t *ctx, VkMemoryRequirements reqs, VkMemoryPropertyFlags props, VkDeviceMemory *mem_out) { // Find appropriate memory type VkPhysicalDeviceMemoryProperties mem_props; vkGetPhysicalDeviceMemoryProperties(ctx->physical_dev, &mem_props); uint32_t mem_type = UINT32_MAX; for (unsigned i = 0; i < mem_props.memoryTypeCount; ++i) { if ((reqs.memoryTypeBits & (1 << i)) == 0) continue; if ((mem_props.memoryTypes[i].propertyFlags & props) == props) { mem_type = i; break; } } assert(mem_type != UINT32_MAX); // Allocate memory VkMemoryAllocateInfo alloc_config = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = reqs.size, .memoryTypeIndex = mem_type, }; const VkResult result = vkAllocateMemory(ctx->dev, &alloc_config, nullptr, mem_out); assert(result == VK_SUCCESS); } static void create_buffer( const gpu_ctx_t *ctx, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags props, VkBuffer *buf_out, VkDeviceMemory *mem_out) { // Create buffer const VkBufferCreateInfo buf_config = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = size, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; const VkResult result = vkCreateBuffer(ctx->dev, &buf_config, nullptr, buf_out); assert(result == VK_SUCCESS); // Allocate and bind memory VkMemoryRequirements mem_reqs; vkGetBufferMemoryRequirements(ctx->dev, *buf_out, &mem_reqs); alloc_dev_mem(ctx, mem_reqs, props, mem_out); vkBindBufferMemory(ctx->dev, *buf_out, *mem_out, 0); } static void load_buffer( const gpu_ctx_t *ctx, VkBuffer dest_buf, const void *data, size_t size, VkBuffer staging_buf, VkDeviceMemory staging_buf_mem, VkCommandPool cmd_pool) { VkResult result; // Copy data to staging buffer void *staging_buf_ptr; vkMapMemory(ctx->dev, staging_buf_mem, 0, size, 0, &staging_buf_ptr); memcpy(staging_buf_ptr, data, size); vkUnmapMemory(ctx->dev, staging_buf_mem); // 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(ctx->dev, &cmd_buf_config, &cmd_buf); assert(result == VK_SUCCESS); // Record copy command VkCommandBufferBeginInfo cmd_begin = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; vkBeginCommandBuffer(cmd_buf, &cmd_begin); VkBufferCopy cmd_copy = { .size = size }; vkCmdCopyBuffer(cmd_buf, staging_buf, dest_buf, 1, &cmd_copy); vkEndCommandBuffer(cmd_buf); // Send copy command VkSubmitInfo copy_submit = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &cmd_buf, }; vkQueueSubmit(ctx->queue, 1, ©_submit, VK_NULL_HANDLE); vkQueueWaitIdle(ctx->queue); vkFreeCommandBuffers(ctx->dev, cmd_pool, 1, &cmd_buf); } void gpu_init(gpu_ctx_t *ctx, const gpu_config_t *config, model_t model) { // Initialise Vulkan instance. const VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = config->application_name, .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, }; VkResult result = vkCreateInstance(&inst_create_info, nullptr, &ctx->inst); assert(result == VK_SUCCESS); // Create surface result = glfwCreateWindowSurface( ctx->inst, config->window, nullptr, &ctx->surface); assert(result == VK_SUCCESS); // Select a physical device uint32_t dev_count = 1; result = vkEnumeratePhysicalDevices( ctx->inst, &dev_count, &ctx->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( 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( ctx->physical_dev, i, ctx->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( ctx->physical_dev, nullptr, &ext_count, ext_props); assert(result == VK_SUCCESS); bool supported[extension_count] = {}; for (unsigned i = 0; i < ext_count; ++i) { 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 < extension_count; ++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 = extension_count, .ppEnabledExtensionNames = extensions, }; result = vkCreateDevice( ctx->physical_dev, &dev_create_info, nullptr, &ctx->dev); assert(result == VK_SUCCESS); 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( 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]; 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( ctx->physical_dev, ctx->surface, &surface_caps); assert(result == VK_SUCCESS); // Select swap chain extent if (surface_caps.currentExtent.width == UINT32_MAX) { int width, height; glfwGetFramebufferSize(config->window, &width, &height); ctx->swapchain_extent.width = CLAMP( (uint32_t)width, surface_caps.minImageExtent.width, surface_caps.maxImageExtent.width); ctx->swapchain_extent.height = CLAMP( (uint32_t)height, surface_caps.minImageExtent.height, surface_caps.maxImageExtent.height); } else { 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); // Create the swap chain const VkSwapchainCreateInfoKHR swapchain_create_info = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .surface = ctx->surface, .minImageCount = swapchain_img_count, .imageFormat = surface_fmt.format, .imageColorSpace = surface_fmt.colorSpace, .imageExtent = ctx->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, }; result = vkCreateSwapchainKHR( ctx->dev, &swapchain_create_info, nullptr, &ctx->swapchain); assert(result == VK_SUCCESS); // Retreive swapchain images VkImage swapchain_imgs[SWAPCHAIN_IMG_BUFFER_SIZE]; ctx->swapchain_img_count = SWAPCHAIN_IMG_BUFFER_SIZE; result = vkGetSwapchainImagesKHR( ctx->dev, ctx->swapchain, &swapchain_img_count, swapchain_imgs); assert(result == VK_SUCCESS); // Create swapchain image views 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( ctx->dev, &img_view_create_info, nullptr, ctx->swapchain_img_views + i); assert(result == VK_SUCCESS); } // Set up depth image const VkFormat depth_img_fmt = VK_FORMAT_D32_SFLOAT; const VkImageCreateInfo depth_img_config = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .extent = { .width = ctx->swapchain_extent.width, .height = ctx->swapchain_extent.height, .depth = 1, }, .mipLevels = 1, .arrayLayers = 1, .format = depth_img_fmt, .tiling = VK_IMAGE_TILING_OPTIMAL, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, .samples = VK_SAMPLE_COUNT_1_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; result = vkCreateImage( ctx->dev, &depth_img_config, nullptr, &ctx->depth_img); assert(result == VK_SUCCESS); VkMemoryRequirements mem_reqs; vkGetImageMemoryRequirements(ctx->dev, ctx->depth_img, &mem_reqs); alloc_dev_mem( ctx, mem_reqs, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &ctx->depth_img_mem); vkBindImageMemory(ctx->dev, ctx->depth_img, ctx->depth_img_mem, 0); // Create depth image view const VkImageViewCreateInfo depth_img_view_config = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = ctx->depth_img, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = depth_img_fmt, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .levelCount = 1, .layerCount = 1, }, }; result = vkCreateImageView( ctx->dev, &depth_img_view_config, nullptr, &ctx->depth_img_view); assert(result == VK_SUCCESS); // Load vertex and fragment shader modules 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[] = { { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = ctx->vert_shader, .pName = "main", }, { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = ctx->frag_shader, .pName = "main", }, }; // Create descriptor set layout const VkDescriptorSetLayoutCreateInfo desc_set_layout_config = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = 1, .pBindings = &(VkDescriptorSetLayoutBinding) { .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, }, }; result = vkCreateDescriptorSetLayout( ctx->dev, &desc_set_layout_config, nullptr, &ctx->desc_set_layout); assert(result == VK_SUCCESS); // Create descriptor pool const VkDescriptorPoolCreateInfo desc_pool_config = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .poolSizeCount = 1, .pPoolSizes = &(VkDescriptorPoolSize) { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = swapchain_img_count, }, .maxSets = swapchain_img_count, }; result = vkCreateDescriptorPool( ctx->dev, &desc_pool_config, nullptr, &ctx->desc_pool); assert(result == VK_SUCCESS); // Create and configure descriptor sets VkDescriptorSetLayout desc_set_layouts[swapchain_img_count]; for (unsigned i = 0; i < swapchain_img_count; ++i) desc_set_layouts[i] = ctx->desc_set_layout; const VkDescriptorSetAllocateInfo desc_sets_alloc_config = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = ctx->desc_pool, .descriptorSetCount = swapchain_img_count, .pSetLayouts = desc_set_layouts, }; result = vkAllocateDescriptorSets( ctx->dev, &desc_sets_alloc_config, ctx->desc_sets); assert(result == VK_SUCCESS); // Create and map MVP buffer const size_t mvp_buffer_size = swapchain_img_count * sizeof(mvp_t); create_buffer( ctx, mvp_buffer_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &ctx->mvp_buffer, &ctx->mvp_buffer_mem); vkMapMemory( ctx->dev, ctx->mvp_buffer_mem, 0, mvp_buffer_size, 0, (void **)&ctx->mvps); // Configure descriptor sets to use MVP buffer for (unsigned i = 0; i < swapchain_img_count; ++i) { const VkWriteDescriptorSet desc_write = { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = ctx->desc_sets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .pBufferInfo = &(VkDescriptorBufferInfo) { .buffer = ctx->mvp_buffer, .offset = i * sizeof(mvp_t), .range = sizeof(mvp_t), }, }; vkUpdateDescriptorSets(ctx->dev, 1, &desc_write, 0, nullptr); } // Set up input configuration const VkVertexInputBindingDescription input_binding_descs[] = { { .binding = 0, .stride = sizeof(vertex_t), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, }, }; const VkVertexInputAttributeDescription input_attribute_descs[] = { { .binding = 0, .location = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(vertex_t, position), }, { .binding = 0, .location = 1, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(vertex_t, normal), }, }; const VkPipelineVertexInputStateCreateInfo vertex_input_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount = NELEMS(input_binding_descs), .pVertexBindingDescriptions = input_binding_descs, .vertexAttributeDescriptionCount = NELEMS(input_attribute_descs), .pVertexAttributeDescriptions = input_attribute_descs, }; const VkPipelineInputAssemblyStateCreateInfo input_assembly_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, }; // Set up viewport configuration const VkViewport viewport = { .width = (float)ctx->swapchain_extent.width, .height = (float)ctx->swapchain_extent.height, .minDepth = 0.0, .maxDepth = 1.0, }; const VkRect2D scissor = { .extent = ctx->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, .setLayoutCount = 1, .pSetLayouts = &ctx->desc_set_layout, }; result = vkCreatePipelineLayout( ctx->dev, &pipeline_layout_config, nullptr, &ctx->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, }; // Set up depth attatchment const VkAttachmentDescription depth_attatchment = { .format = depth_img_fmt, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }; VkAttachmentReference depth_attachment_ref = { .attachment = 1, .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }; // Create render pass const VkAttachmentDescription attachment_descs[] = { colour_attachment, depth_attatchment, }; const VkSubpassDependency dependency = { .srcSubpass = VK_SUBPASS_EXTERNAL, .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, }; const VkRenderPassCreateInfo render_pass_config = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 2, .pAttachments = attachment_descs, .subpassCount = 1, .pSubpasses = &(VkSubpassDescription){ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &colour_attachment_ref, .pDepthStencilAttachment = &depth_attachment_ref, }, .dependencyCount = 1, .pDependencies = &dependency, }; result = vkCreateRenderPass( ctx->dev, &render_pass_config, nullptr, &ctx->render_pass); assert(result == VK_SUCCESS); // Create pipeline const VkPipelineDepthStencilStateCreateInfo depth_stencil_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, .depthTestEnable = VK_TRUE, .depthWriteEnable = VK_TRUE, .depthCompareOp = VK_COMPARE_OP_LESS, .depthBoundsTestEnable = VK_FALSE, .stencilTestEnable = VK_FALSE, }; 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 = ctx->pipeline_layout, .renderPass = ctx->render_pass, .subpass = 0, .pDepthStencilState = &depth_stencil_config, }; result = vkCreateGraphicsPipelines( ctx->dev, VK_NULL_HANDLE, 1, &pipeline_config, nullptr, &ctx->pipeline); assert(result == VK_SUCCESS); // Create framebuffers for (unsigned i = 0; i < swapchain_img_count; ++i) { VkImageView attachments[] = { ctx->swapchain_img_views[i], ctx->depth_img_view, }; const VkFramebufferCreateInfo framebuffer_config = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = ctx->render_pass, .attachmentCount = 2, .pAttachments = attachments, .width = ctx->swapchain_extent.width, .height = ctx->swapchain_extent.height, .layers = 1, }; result = vkCreateFramebuffer( ctx->dev, &framebuffer_config, nullptr, ctx->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, }; 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 = ctx->cmd_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; result = vkAllocateCommandBuffers(ctx->dev, &cmd_buf_config, &ctx->cmd_buf); assert(result == VK_SUCCESS); // Create syncronisation primitives 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( ctx->dev, &semaphore_config, nullptr, &ctx->render_fin); assert(result == VK_SUCCESS); VkFenceCreateInfo fence_config = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = VK_FENCE_CREATE_SIGNALED_BIT, }; result = vkCreateFence(ctx->dev, &fence_config, nullptr, &ctx->in_flight); assert(result == VK_SUCCESS); // Set up staging, vertex and index buffers const size_t vertex_data_size = model.vertex_count * sizeof(vertex_t); const size_t index_data_size = model.index_count * sizeof(vertex_t); ctx->index_count = model.index_count; VkBuffer staging_buf; VkDeviceMemory staging_buf_mem; const size_t staging_buf_size = MAX(vertex_data_size, index_data_size); create_buffer( ctx, staging_buf_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &staging_buf, &staging_buf_mem); create_buffer( ctx, vertex_data_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &ctx->vertex_buf, &ctx->vertex_buf_mem); create_buffer( ctx, index_data_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &ctx->index_buf, &ctx->index_buf_mem); // Create transient command pool const VkCommandPoolCreateInfo transient_cmd_pool_config = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, .queueFamilyIndex = queue_family, }; VkCommandPool transient_cmd_pool; result = vkCreateCommandPool( ctx->dev, &transient_cmd_pool_config, nullptr, &transient_cmd_pool); assert(result == VK_SUCCESS); // Load vertex and index data load_buffer( ctx, ctx->vertex_buf, model.vertices, vertex_data_size, staging_buf, staging_buf_mem, transient_cmd_pool); load_buffer( ctx, ctx->index_buf, model.indices, index_data_size, staging_buf, staging_buf_mem, transient_cmd_pool); // Clean up staging buffer and transient command pool vkDestroyCommandPool(ctx->dev, transient_cmd_pool, nullptr); vkDestroyBuffer(ctx->dev, staging_buf, nullptr); vkFreeMemory(ctx->dev, staging_buf_mem, nullptr); } void gpu_cleanup(const gpu_ctx_t *ctx) { vkDeviceWaitIdle(ctx->dev); vkDestroyBuffer(ctx->dev, ctx->index_buf, nullptr); vkFreeMemory(ctx->dev, ctx->index_buf_mem, nullptr); vkDestroyBuffer(ctx->dev, ctx->vertex_buf, nullptr); vkFreeMemory(ctx->dev, ctx->vertex_buf_mem, nullptr); 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); vkUnmapMemory(ctx->dev, ctx->mvp_buffer_mem); vkDestroyBuffer(ctx->dev, ctx->mvp_buffer, nullptr); vkFreeMemory(ctx->dev, ctx->mvp_buffer_mem, nullptr); vkDestroyDescriptorPool(ctx->dev, ctx->desc_pool, nullptr); vkDestroyDescriptorSetLayout(ctx->dev, ctx->desc_set_layout, nullptr); vkDestroyShaderModule(ctx->dev, ctx->vert_shader, nullptr); vkDestroyShaderModule(ctx->dev, ctx->frag_shader, nullptr); vkDestroyImageView(ctx->dev, ctx->depth_img_view, nullptr); vkDestroyImage(ctx->dev, ctx->depth_img, nullptr); vkFreeMemory(ctx->dev, ctx->depth_img_mem, 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); vkDestroyDevice(ctx->dev, nullptr); vkDestroySurfaceKHR(ctx->inst, ctx->surface, nullptr); vkDestroyInstance(ctx->inst, nullptr); } void gpu_render(const gpu_ctx_t *ctx, const mvp_t *mvp) { 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); // Copy MVP matricies into uniform buffer for this image memcpy(ctx->mvps + img_index, mvp, sizeof(mvp_t)); // 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); // Start render pass VkClearValue clear_values[] = { [0] = { .color = { { 0.0, 0.0, 0.0, 1.0 } } }, [1] = { .depthStencil = { 1.0, 0.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 = 2, .pClearValues = clear_values, }; vkCmdBeginRenderPass( ctx->cmd_buf, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline( ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->pipeline); // Bind vertex and index buffers vkCmdBindVertexBuffers( ctx->cmd_buf, 0, 1, &ctx->vertex_buf, &(VkDeviceSize) { 0 }); vkCmdBindIndexBuffer( ctx->cmd_buf, ctx->index_buf, 0, VK_INDEX_TYPE_UINT16); // Bind descriptor set for this frame vkCmdBindDescriptorSets( ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->pipeline_layout, 0, 1, ctx->desc_sets + img_index, 0, nullptr); // Draw vkCmdDrawIndexed(ctx->cmd_buf, ctx->index_count, 1, 0, 0, 0); // Finish render pass and end writing to command buffer vkCmdEndRenderPass(ctx->cmd_buf); result = vkEndCommandBuffer(ctx->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 = &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); // 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); }