#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 STAGING_BUFFER_SIZE 0x4'0000 // 256 KiB #define MAX_MODEL_COUNT 4 #define SHADOW_IMG_SIZE 4096 #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))) typedef struct { mat4_t vp; mat4_t light_vp; } vps_t; typedef struct { vps_t vps; light_params_t light; } push_t; 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 floor_vert_shader[] = { #embed FLOOR_VERT_SHADER_SPV }; static const char model_vert_shader[] = { #embed MODEL_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) { VkResult result; // Copy data to staging buffer memcpy(ctx->staging_ptr, data, size); // Allocate command buffer const VkCommandBufferAllocateInfo cmd_buf_config = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = ctx->transient_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, ctx->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, ctx->transient_cmd_pool, 1, &cmd_buf); } static void create_img( const gpu_ctx_t *ctx, VkFormat fmt, uint32_t width, uint32_t height, VkImageUsageFlags usage, VkSampleCountFlags samples, VkImageAspectFlags aspect, VkImage *img, VkDeviceMemory *mem, VkImageView *view) { VkResult result; // Create image const VkImageCreateInfo img_config = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .extent = { .width = width, .height = height, .depth = 1 }, .mipLevels = 1, .arrayLayers = 1, .format = fmt, .tiling = VK_IMAGE_TILING_OPTIMAL, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .usage = usage, .samples = samples, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; result = vkCreateImage(ctx->dev, &img_config, nullptr, img); assert(result == VK_SUCCESS); // Allocate and bind memory VkMemoryRequirements mem_reqs; vkGetImageMemoryRequirements(ctx->dev, *img, &mem_reqs); alloc_dev_mem(ctx, mem_reqs, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mem); vkBindImageMemory(ctx->dev, *img, *mem, 0); // Create view const VkImageViewCreateInfo view_config = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = *img, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = fmt, .subresourceRange = { .aspectMask = aspect, .levelCount = 1, .layerCount = 1, }, }; result = vkCreateImageView(ctx->dev, &view_config, nullptr, view); assert(result == VK_SUCCESS); } void gpu_init(gpu_ctx_t *ctx, GLFWwindow *window, const char *name) { // Initialise Vulkan instance. const VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = 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, 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); // Check that the physics device supports 8 samples VkPhysicalDeviceProperties physical_dev_props; vkGetPhysicalDeviceProperties(ctx->physical_dev, &physical_dev_props); const VkSampleCountFlags sample_counts = physical_dev_props.limits.framebufferColorSampleCounts & physical_dev_props.limits.framebufferDepthSampleCounts; assert((sample_counts & VK_SAMPLE_COUNT_8_BIT) != 0); // 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(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 min_swapchain_img_count = surface_caps.minImageCount + 1; assert(min_swapchain_img_count <= MAX_FRAMES_IN_FLIGHT); assert( surface_caps.maxImageCount == 0 || min_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 = min_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[MAX_FRAMES_IN_FLIGHT]; ctx->swapchain_img_count = MAX_FRAMES_IN_FLIGHT; result = vkGetSwapchainImagesKHR( ctx->dev, ctx->swapchain, &ctx->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 < ctx->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 shadow image create_img( ctx, VK_FORMAT_D32_SFLOAT, SHADOW_IMG_SIZE, SHADOW_IMG_SIZE, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_ASPECT_DEPTH_BIT, &ctx->shadow_img, &ctx->shadow_mem, &ctx->shadow_view); // Create colour image (for multisampling) create_img( ctx, surface_fmt.format, ctx->swapchain_extent.width, ctx->swapchain_extent.height, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_SAMPLE_COUNT_8_BIT, VK_IMAGE_ASPECT_COLOR_BIT, &ctx->colour_img, &ctx->colour_mem, &ctx->colour_view); // Set up depth image create_img( ctx, VK_FORMAT_D32_SFLOAT, ctx->swapchain_extent.width, ctx->swapchain_extent.height, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_SAMPLE_COUNT_8_BIT, VK_IMAGE_ASPECT_DEPTH_BIT, &ctx->depth_img, &ctx->depth_mem, &ctx->depth_view); // 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 }; const VkPipelineViewportStateCreateInfo viewport_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1, .pViewports = &viewport, .scissorCount = 1, .pScissors = &scissor, }; // Attachment configs const VkAttachmentDescription shadow_depth_attachment = { .format = VK_FORMAT_D32_SFLOAT, .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_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }; const VkAttachmentDescription colour_attachment = { .format = surface_fmt.format, .samples = VK_SAMPLE_COUNT_8_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_COLOR_ATTACHMENT_OPTIMAL, }; const VkAttachmentDescription render_depth_attachment = { .format = VK_FORMAT_D32_SFLOAT, .samples = VK_SAMPLE_COUNT_8_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, }; const VkAttachmentDescription colour_resolve_attachment = { .format = surface_fmt.format, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .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, }; // Create shadow pass const VkRenderPassCreateInfo shadow_pass_config = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 1, .pAttachments = &shadow_depth_attachment, .subpassCount = 1, .pSubpasses = &(VkSubpassDescription){ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .pDepthStencilAttachment = &(VkAttachmentReference) { .attachment = 0, .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }, }, .dependencyCount = 1, .pDependencies = &(VkSubpassDependency) { .srcSubpass = VK_SUBPASS_EXTERNAL, .srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, .dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, }, }; result = vkCreateRenderPass( ctx->dev, &shadow_pass_config, nullptr, &ctx->shadow_pass); assert(result == VK_SUCCESS); // Create render pass const VkRenderPassCreateInfo render_pass_config = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 3, .pAttachments = (VkAttachmentDescription[]) { colour_attachment, render_depth_attachment, colour_resolve_attachment, }, .subpassCount = 1, .pSubpasses = &(VkSubpassDescription){ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &(VkAttachmentReference) { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }, .pDepthStencilAttachment = &(VkAttachmentReference) { .attachment = 1, .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }, .pResolveAttachments = &(VkAttachmentReference) { .attachment = 2, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }, }, .dependencyCount = 1, .pDependencies = &(VkSubpassDependency) { .srcSubpass = VK_SUBPASS_EXTERNAL, .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | 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, }, }; result = vkCreateRenderPass( ctx->dev, &render_pass_config, nullptr, &ctx->render_pass); assert(result == VK_SUCCESS); // Create descriptor pool const size_t max_desc_sets = MAX_MODEL_COUNT * ctx->swapchain_img_count; const VkDescriptorPoolSize desc_pool_sizes[] = { { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 2 * max_desc_sets, }, { .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = max_desc_sets, }, }; const VkDescriptorPoolCreateInfo desc_pool_config = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .poolSizeCount = 2, .pPoolSizes = desc_pool_sizes, .maxSets = max_desc_sets, }; result = vkCreateDescriptorPool( ctx->dev, &desc_pool_config, nullptr, &ctx->desc_pool); assert(result == VK_SUCCESS); // 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, }; // Push constant configs const VkPushConstantRange push_constants[] = { { .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, .offset = offsetof(push_t, vps), .size = sizeof(vps_t), }, { .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, .offset = offsetof(push_t, light), .size = sizeof(light_params_t), }, }; // Load vertex and fragment shader modules ctx->floor_vert_shader = load_shader_module( ctx->dev, floor_vert_shader, sizeof(floor_vert_shader)); ctx->model_vert_shader = load_shader_module( ctx->dev, model_vert_shader, sizeof(model_vert_shader)); ctx->frag_shader = load_shader_module(ctx->dev, frag_shader, sizeof(frag_shader)); // Set up depth stencil config 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, }; // Create shadow descriptor set layout const VkDescriptorSetLayoutCreateInfo floor_desc_set_layout_config = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = 1, .pBindings = &(VkDescriptorSetLayoutBinding) { .binding = 2, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, }, }; result = vkCreateDescriptorSetLayout( ctx->dev, &floor_desc_set_layout_config, nullptr, &ctx->floor_desc_set_layout); assert(result == VK_SUCCESS); // Create floor pipeline const VkPipelineShaderStageCreateInfo floor_shader_stages[] = { { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = ctx->floor_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", }, }; const VkPipelineRasterizationStateCreateInfo floor_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, }; const VkPipelineLayoutCreateInfo floor_pipeline_layout_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = &ctx->floor_desc_set_layout, .pushConstantRangeCount = 2, .pPushConstantRanges = push_constants, }; result = vkCreatePipelineLayout( ctx->dev, &floor_pipeline_layout_config, nullptr, &ctx->floor_pipeline_layout); assert(result == VK_SUCCESS); const VkGraphicsPipelineCreateInfo floor_pipeline_config = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = 2, .pStages = floor_shader_stages, .pVertexInputState = &(VkPipelineVertexInputStateCreateInfo) { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, }, .pInputAssemblyState = &(VkPipelineInputAssemblyStateCreateInfo) { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, }, .pViewportState = &viewport_config, .pRasterizationState = &floor_rasterizer_config, .pMultisampleState = &(VkPipelineMultisampleStateCreateInfo) { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .sampleShadingEnable = VK_FALSE, .rasterizationSamples = VK_SAMPLE_COUNT_8_BIT, }, .pColorBlendState = &colour_blend_config, .layout = ctx->floor_pipeline_layout, .renderPass = ctx->render_pass, .subpass = 0, .pDepthStencilState = &depth_stencil_config, }; result = vkCreateGraphicsPipelines( ctx->dev, VK_NULL_HANDLE, 1, &floor_pipeline_config, nullptr, &ctx->floor_pipeline); assert(result == VK_SUCCESS); // Create descriptor set layout const VkDescriptorSetLayoutBinding desc_set_layout_bindings[] = { { .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, }, { .binding = 1, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, }, { .binding = 2, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, }, }; const VkDescriptorSetLayoutCreateInfo desc_set_layout_config = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = NELEMS(desc_set_layout_bindings), .pBindings = desc_set_layout_bindings, }; result = vkCreateDescriptorSetLayout( ctx->dev, &desc_set_layout_config, nullptr, &ctx->desc_set_layout); assert(result == VK_SUCCESS); // Set up model 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), }, { .binding = 0, .location = 2, .format = VK_FORMAT_R16G16B16A16_UINT, .offset = offsetof(vertex_t, bones), }, { .binding = 0, .location = 3, .format = VK_FORMAT_R32G32B32A32_SFLOAT, .offset = offsetof(vertex_t, weights), }, }; 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, }; // Create model pipeline layout const VkPipelineLayoutCreateInfo model_pipeline_layout_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = &ctx->desc_set_layout, .pushConstantRangeCount = 2, .pPushConstantRanges = push_constants, }; result = vkCreatePipelineLayout( ctx->dev, &model_pipeline_layout_config, nullptr, &ctx->model_pipeline_layout); assert(result == VK_SUCCESS); // Create shadow pipeline const VkPipelineViewportStateCreateInfo shadow_viewport_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1, .pViewports = &(VkViewport) { .width = SHADOW_IMG_SIZE, .height = SHADOW_IMG_SIZE, .minDepth = 0.0, .maxDepth = 1.0, }, .scissorCount = 1, .pScissors = &(VkRect2D) { .extent = { .width = SHADOW_IMG_SIZE, .height = SHADOW_IMG_SIZE, }, }, }; const VkPipelineShaderStageCreateInfo shadow_shader_stages[] = { { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = ctx->model_vert_shader, .pName = "main", }, }; const VkPipelineRasterizationStateCreateInfo shadow_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, .frontFace = VK_FRONT_FACE_CLOCKWISE, }; const VkGraphicsPipelineCreateInfo shadow_pipeline_config = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = NELEMS(shadow_shader_stages), .pStages = shadow_shader_stages, .pVertexInputState = &vertex_input_config, .pInputAssemblyState = &input_assembly_config, .pViewportState = &shadow_viewport_config, .pRasterizationState = &shadow_rasterizer_config, .pMultisampleState = &(VkPipelineMultisampleStateCreateInfo) { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .sampleShadingEnable = VK_FALSE, .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, }, .layout = ctx->model_pipeline_layout, .renderPass = ctx->shadow_pass, .subpass = 0, .pDepthStencilState = &depth_stencil_config, }; result = vkCreateGraphicsPipelines( ctx->dev, VK_NULL_HANDLE, 1, &shadow_pipeline_config, nullptr, &ctx->shadow_pipeline); assert(result == VK_SUCCESS); // Create model pipeline const VkPipelineShaderStageCreateInfo model_shader_stages[] = { { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = ctx->model_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", }, }; const VkPipelineRasterizationStateCreateInfo model_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_BACK_BIT, .frontFace = VK_FRONT_FACE_CLOCKWISE, }; const VkGraphicsPipelineCreateInfo model_pipeline_config = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = NELEMS(model_shader_stages), .pStages = model_shader_stages, .pVertexInputState = &vertex_input_config, .pInputAssemblyState = &input_assembly_config, .pViewportState = &viewport_config, .pRasterizationState = &model_rasterizer_config, .pMultisampleState = &(VkPipelineMultisampleStateCreateInfo) { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .sampleShadingEnable = VK_FALSE, .rasterizationSamples = VK_SAMPLE_COUNT_8_BIT, }, .pColorBlendState = &colour_blend_config, .layout = ctx->model_pipeline_layout, .renderPass = ctx->render_pass, .subpass = 0, .pDepthStencilState = &depth_stencil_config, }; result = vkCreateGraphicsPipelines( ctx->dev, VK_NULL_HANDLE, 1, &model_pipeline_config, nullptr, &ctx->model_pipeline); assert(result == VK_SUCCESS); // Create shadow sampler const VkSamplerCreateInfo shadow_sampler_config = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_LINEAR, .minFilter = VK_FILTER_LINEAR, .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .compareEnable = VK_TRUE, .compareOp = VK_COMPARE_OP_LESS_OR_EQUAL, .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, .unnormalizedCoordinates = VK_FALSE, }; result = vkCreateSampler( ctx->dev, &shadow_sampler_config, nullptr, &ctx->shadow_sampler); assert(result == VK_SUCCESS); // Create framebuffers const VkFramebufferCreateInfo shadow_framebuf_config = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = ctx->shadow_pass, .attachmentCount = 1, .pAttachments = &ctx->shadow_view, .width = SHADOW_IMG_SIZE, .height = SHADOW_IMG_SIZE, .layers = 1, }; result = vkCreateFramebuffer( ctx->dev, &shadow_framebuf_config, nullptr, &ctx->shadow_framebuf); assert(result == VK_SUCCESS); for (unsigned i = 0; i < ctx->swapchain_img_count; ++i) { const VkFramebufferCreateInfo framebuffer_config = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = ctx->render_pass, .attachmentCount = 3, .pAttachments = (VkImageView[]) { ctx->colour_view, ctx->depth_view, ctx->swapchain_img_views[i], }, .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); // Create and map staging buffer create_buffer( ctx, STAGING_BUFFER_SIZE, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &ctx->staging_buf, &ctx->staging_mem); vkMapMemory( ctx->dev, ctx->staging_mem, 0, STAGING_BUFFER_SIZE, 0, &ctx->staging_ptr); // 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, }; result = vkCreateCommandPool( ctx->dev, &transient_cmd_pool_config, nullptr, &ctx->transient_cmd_pool); assert(result == VK_SUCCESS); // Allocate floor descriptor sets VkDescriptorSetLayout desc_set_layouts[ctx->swapchain_img_count]; for (unsigned i = 0; i < ctx->swapchain_img_count; ++i) desc_set_layouts[i] = ctx->floor_desc_set_layout; const VkDescriptorSetAllocateInfo desc_sets_alloc_config = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = ctx->desc_pool, .descriptorSetCount = ctx->swapchain_img_count, .pSetLayouts = desc_set_layouts, }; result = vkAllocateDescriptorSets( ctx->dev, &desc_sets_alloc_config, ctx->floor_desc_sets); assert(result == VK_SUCCESS); // Write to floor descriptor sets for (unsigned i = 0; i < ctx->swapchain_img_count; ++i) { const VkWriteDescriptorSet desc_writes[] = { { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = ctx->floor_desc_sets[i], .dstBinding = 2, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .pImageInfo = &(VkDescriptorImageInfo) { .sampler = ctx->shadow_sampler, .imageView = ctx->shadow_view, .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, }, }, }; vkUpdateDescriptorSets( ctx->dev, NELEMS(desc_writes), desc_writes, 0, nullptr); } } void gpu_cleanup(const gpu_ctx_t *ctx) { vkDeviceWaitIdle(ctx->dev); vkDestroyCommandPool(ctx->dev, ctx->transient_cmd_pool, nullptr); vkDestroyBuffer(ctx->dev, ctx->staging_buf, nullptr); vkFreeMemory(ctx->dev, ctx->staging_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->floor_pipeline, nullptr); vkDestroyPipelineLayout(ctx->dev, ctx->floor_pipeline_layout, nullptr); vkDestroyPipeline(ctx->dev, ctx->shadow_pipeline, nullptr); vkDestroyPipeline(ctx->dev, ctx->model_pipeline, nullptr); vkDestroyPipelineLayout(ctx->dev, ctx->model_pipeline_layout, nullptr); vkDestroyDescriptorPool(ctx->dev, ctx->desc_pool, nullptr); vkDestroyDescriptorSetLayout(ctx->dev, ctx->desc_set_layout, nullptr); vkDestroyDescriptorSetLayout( ctx->dev, ctx->floor_desc_set_layout, nullptr); vkDestroyShaderModule(ctx->dev, ctx->floor_vert_shader, nullptr); vkDestroyShaderModule(ctx->dev, ctx->model_vert_shader, nullptr); vkDestroyShaderModule(ctx->dev, ctx->frag_shader, nullptr); vkDestroyRenderPass(ctx->dev, ctx->shadow_pass, nullptr); vkDestroyRenderPass(ctx->dev, ctx->render_pass, nullptr); vkDestroyImageView(ctx->dev, ctx->colour_view, nullptr); vkDestroyImage(ctx->dev, ctx->colour_img, nullptr); vkFreeMemory(ctx->dev, ctx->colour_mem, nullptr); vkDestroyImageView(ctx->dev, ctx->shadow_view, nullptr); vkDestroyImage(ctx->dev, ctx->shadow_img, nullptr); vkFreeMemory(ctx->dev, ctx->shadow_mem, nullptr); vkDestroyImageView(ctx->dev, ctx->depth_view, nullptr); vkDestroyImage(ctx->dev, ctx->depth_img, nullptr); vkFreeMemory(ctx->dev, ctx->depth_mem, nullptr); vkDestroySampler(ctx->dev, ctx->shadow_sampler, nullptr); vkDestroyFramebuffer(ctx->dev, ctx->shadow_framebuf, 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_model_load( const gpu_ctx_t *ctx, const model_t *model, gpu_model_t *out) { VkResult result; out->bone_count = model->bone_count; out->index_count = model->index_count; // Create buffers const size_t vertex_size = model->vertex_count * sizeof(vertex_t); assert(vertex_size < STAGING_BUFFER_SIZE); create_buffer( ctx, vertex_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &out->vertex_buf, &out->vertex_mem); load_buffer(ctx, out->vertex_buf, model->vertices, vertex_size); // Load data into buffers const size_t index_size = model->index_count * sizeof(index_t); assert(index_size < STAGING_BUFFER_SIZE); create_buffer( ctx, index_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &out->index_buf, &out->index_mem); load_buffer(ctx, out->index_buf, model->indices, index_size); // Allocate descriptor sets VkDescriptorSetLayout desc_set_layouts[ctx->swapchain_img_count]; for (unsigned i = 0; i < ctx->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 = ctx->swapchain_img_count, .pSetLayouts = desc_set_layouts, }; result = vkAllocateDescriptorSets( ctx->dev, &desc_sets_alloc_config, out->desc_sets); assert(result == VK_SUCCESS); // Create and map transform buffer const size_t transform_size = ctx->swapchain_img_count * sizeof(mat4_t); create_buffer( ctx, transform_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &out->transform_buf, &out->transform_mem); vkMapMemory( ctx->dev, out->transform_mem, 0, transform_size, 0, (void **)&out->transform_ptr); // Create and map bone matrix buffer const size_t bone_size = ctx->swapchain_img_count * model->bone_count * sizeof(mat4_t); create_buffer( ctx, bone_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &out->bone_buf, &out->bone_mem); vkMapMemory( ctx->dev, out->bone_mem, 0, bone_size, 0, (void **)&out->bone_ptr); // Write to descriptor sets for (unsigned i = 0; i < ctx->swapchain_img_count; ++i) { const VkWriteDescriptorSet desc_writes[] = { { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = out->desc_sets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .pBufferInfo = &(VkDescriptorBufferInfo) { .buffer = out->transform_buf, .offset = i * sizeof(mat4_t), .range = sizeof(mat4_t), }, }, { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = out->desc_sets[i], .dstBinding = 1, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .pBufferInfo = &(VkDescriptorBufferInfo) { .buffer = out->bone_buf, .offset = i * model->bone_count * sizeof(mat4_t), .range = model->bone_count * sizeof(mat4_t), }, }, { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = out->desc_sets[i], .dstBinding = 2, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .pImageInfo = &(VkDescriptorImageInfo) { .sampler = ctx->shadow_sampler, .imageView = ctx->shadow_view, .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, }, }, }; vkUpdateDescriptorSets( ctx->dev, NELEMS(desc_writes), desc_writes, 0, nullptr); } } void gpu_model_cleanup(const gpu_ctx_t *ctx, const gpu_model_t *model) { vkDeviceWaitIdle(ctx->dev); vkDestroyBuffer(ctx->dev, model->bone_buf, nullptr); vkFreeMemory(ctx->dev, model->bone_mem, nullptr); vkDestroyBuffer(ctx->dev, model->transform_buf, nullptr); vkFreeMemory(ctx->dev, model->transform_mem, nullptr); vkDestroyBuffer(ctx->dev, model->index_buf, nullptr); vkFreeMemory(ctx->dev, model->index_mem, nullptr); vkDestroyBuffer(ctx->dev, model->vertex_buf, nullptr); vkFreeMemory(ctx->dev, model->vertex_mem, nullptr); } void gpu_render( const gpu_ctx_t *ctx, mat4_t vp, light_params_t light, size_t model_count, const gpu_model_t *models, const mat4_t **bone_matrices, const mat4_t *model_transforms) { VkResult result; // Compute light VP const mat4_t light_view = mat4_lookat( light.sun_dir, (vec3_t) { 0, 0, 0 }, (vec3_t) { 0, 1, 0 }); const mat4_t light_proj = mat4_orthographic(8, 8, -10, 10); const mat4_t light_vp = mat4_mul(light_proj, light_view); // 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 curr_img; result = vkAcquireNextImageKHR( ctx->dev, ctx->swapchain, UINT64_MAX, ctx->img_avail, VK_NULL_HANDLE, &curr_img); assert(result == VK_SUCCESS); // Update models' transforms and bones for (unsigned i = 0; i < model_count; ++i) { models[i].transform_ptr[curr_img] = model_transforms[i]; memcpy( models[i].bone_ptr + (models[i].bone_count * curr_img), bone_matrices[i], models[i].bone_count * sizeof(mat4_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); // Run shadow pass VkRenderPassBeginInfo shadow_pass_begin = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = ctx->shadow_pass, .framebuffer = ctx->shadow_framebuf, .renderArea.extent = { .width = SHADOW_IMG_SIZE, .height = SHADOW_IMG_SIZE, }, .clearValueCount = 1, .pClearValues = &(VkClearValue){ .depthStencil = { 1.0, 0.0 } }, }; vkCmdBeginRenderPass( ctx->cmd_buf, &shadow_pass_begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline( ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->shadow_pipeline); vkCmdPushConstants( ctx->cmd_buf, ctx->model_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vps_t), &(vps_t) { .vp = light_vp }); for (unsigned i = 0; i < model_count; ++i) { vkCmdBindVertexBuffers( ctx->cmd_buf, 0, 1, &models[i].vertex_buf, &(VkDeviceSize) { 0 }); vkCmdBindIndexBuffer( ctx->cmd_buf, models[i].index_buf, 0, VK_INDEX_TYPE_UINT16); vkCmdBindDescriptorSets( ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->model_pipeline_layout, 0, 1, models[i].desc_sets + curr_img, 0, nullptr); vkCmdDrawIndexed(ctx->cmd_buf, models[i].index_count, 1, 0, 0, 0); } vkCmdEndRenderPass(ctx->cmd_buf); // Wait until shadow map is fully written and transition to shader // read-only layout. const VkImageMemoryBarrier shadows_ready = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, .oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .image = ctx->shadow_img, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .levelCount = 1, .layerCount = 1, }, }; vkCmdPipelineBarrier( ctx->cmd_buf, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &shadows_ready); 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[curr_img], .renderArea.extent = ctx->swapchain_extent, .clearValueCount = 2, .pClearValues = clear_values, }; vkCmdBeginRenderPass( ctx->cmd_buf, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE); const vps_t vps = { .vp = vp, .light_vp = light_vp }; // Draw floor vkCmdBindPipeline( ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->floor_pipeline); vkCmdPushConstants( ctx->cmd_buf, ctx->floor_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, offsetof(push_t, vps), sizeof(vps_t), &vps); vkCmdPushConstants( ctx->cmd_buf, ctx->floor_pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, offsetof(push_t, light), sizeof(light_params_t), &light); vkCmdBindDescriptorSets( ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->floor_pipeline_layout, 0, 1, ctx->floor_desc_sets + curr_img, 0, nullptr); vkCmdDraw(ctx->cmd_buf, 4, 1, 0, 0); // Draw models vkCmdBindPipeline( ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->model_pipeline); vkCmdPushConstants( ctx->cmd_buf, ctx->model_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, offsetof(push_t, vps), sizeof(vps_t), &vps); vkCmdPushConstants( ctx->cmd_buf, ctx->model_pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, offsetof(push_t, light), sizeof(light_params_t), &light); for (unsigned i = 0; i < model_count; ++i) { vkCmdBindVertexBuffers( ctx->cmd_buf, 0, 1, &models[i].vertex_buf, &(VkDeviceSize) { 0 }); vkCmdBindIndexBuffer( ctx->cmd_buf, models[i].index_buf, 0, VK_INDEX_TYPE_UINT16); vkCmdBindDescriptorSets( ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->model_pipeline_layout, 0, 1, models[i].desc_sets + curr_img, 0, nullptr); vkCmdDrawIndexed(ctx->cmd_buf, models[i].index_count, 1, 0, 0, 0); } vkCmdEndRenderPass(ctx->cmd_buf); // Wait until render pass is finished reading shadow map and transition // back to depth stencil layout const VkImageMemoryBarrier shadows_done = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, .oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, .image = ctx->shadow_img, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .levelCount = 1, .layerCount = 1, }, }; vkCmdPipelineBarrier( ctx->cmd_buf, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, 0, 0, nullptr, 0, nullptr, 1, &shadows_done); 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 = &curr_img, }; result = vkQueuePresentKHR(ctx->queue, &present_info); assert(result == VK_SUCCESS); }