Files
gpu-renderer/gpu.c
T

1406 lines
47 KiB
C

#include "gpu.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#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, &copy_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, 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 = VK_SAMPLE_COUNT_1_BIT,
.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);
// 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_IMAGE_ASPECT_DEPTH_BIT, &ctx->shadow_img, &ctx->shadow_mem,
&ctx->shadow_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_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 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 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 render_depth_attachment = {
.format = VK_FORMAT_D32_SFLOAT,
.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,
};
// 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 VkAttachmentDescription attachment_descs[] = {
colour_attachment,
render_depth_attachment,
};
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 = NELEMS(attachment_descs),
.pAttachments = attachment_descs,
.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,
},
},
.dependencyCount = 1,
.pDependencies = &dependency,
};
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);
// 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,
};
// 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 = &multisampling_config,
.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 = &multisampling_config,
.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 = &multisampling_config,
.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) {
VkImageView attachments[] = {
ctx->swapchain_img_views[i],
ctx->depth_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);
// 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->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);
}