diff --git a/source/engine/renderer/vk_allocator.cpp b/source/engine/renderer/vk_allocator.cpp new file mode 100644 index 0000000..9862302 --- /dev/null +++ b/source/engine/renderer/vk_allocator.cpp @@ -0,0 +1,133 @@ +#include "vk_allocator.h" +#include "vk_utils.h" + +static Device_Memory_Allocator allocator; + +Device_Memory_Allocator* get_allocator() { + return &allocator; +} + +static uint32_t find_memory_type(VkPhysicalDevice physical_device, uint32_t memory_type_bits, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memory_properties; + vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties); + + for (uint32_t i = 0; i < memory_properties.memoryTypeCount; i++) { + if ((memory_type_bits & (1 << i)) != 0 && + (memory_properties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + error("failed to find matching memory type with requested properties"); + return -1; +} + +void Shared_Staging_Memory::initialize(VkPhysicalDevice physical_device, VkDevice device) { + this->physical_device = physical_device; + this->device = device; +} + +void Shared_Staging_Memory::deallocate_all() { + if (handle != VK_NULL_HANDLE) { + vkFreeMemory(device, handle, nullptr); + } + handle = VK_NULL_HANDLE; + size = 0; + memory_type_index = -1; +} + +void Shared_Staging_Memory::ensure_allocation_for_object(VkImage image) { + VkMemoryRequirements memory_requirements; + vkGetImageMemoryRequirements(device, image, &memory_requirements); + ensure_allocation(memory_requirements); +} + +void Shared_Staging_Memory::ensure_allocation_for_object(VkBuffer buffer) { + VkMemoryRequirements memory_requirements; + vkGetBufferMemoryRequirements(device, buffer, &memory_requirements); + ensure_allocation(memory_requirements); +} + +VkDeviceMemory Shared_Staging_Memory::get_handle() const { + return handle; +} + +void Shared_Staging_Memory::ensure_allocation(const VkMemoryRequirements& memory_requirements) { + uint32_t required_memory_type_index = find_memory_type(physical_device, memory_requirements.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + if (size < memory_requirements.size || memory_type_index != required_memory_type_index) { + if (handle != VK_NULL_HANDLE) { + vkFreeMemory(device, handle, nullptr); + } + handle = VK_NULL_HANDLE; + size = 0; + memory_type_index = -1; + + VkMemoryAllocateInfo alloc_info; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.pNext = nullptr; + alloc_info.allocationSize = memory_requirements.size; + alloc_info.memoryTypeIndex = required_memory_type_index; + + VkResult result = vkAllocateMemory(device, &alloc_info, nullptr, &handle); + check_vk_result(result, "vkAllocateMemory"); + size = memory_requirements.size; + memory_type_index = required_memory_type_index; + } +} + +void Device_Memory_Allocator::initialize(VkPhysicalDevice physical_device, VkDevice device) { + this->physical_device = physical_device; + this->device = device; + shared_staging_memory.initialize(physical_device, device); +} + +void Device_Memory_Allocator::deallocate_all() { + for (auto chunk : chunks) { + vkFreeMemory(device, chunk, nullptr); + } + chunks.clear(); + shared_staging_memory.deallocate_all(); +} + +VkDeviceMemory Device_Memory_Allocator::allocate_memory(VkImage image) { + VkMemoryRequirements memory_requirements; + vkGetImageMemoryRequirements(device, image, &memory_requirements); + return allocate_memory(memory_requirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); +} + +VkDeviceMemory Device_Memory_Allocator::allocate_memory(VkBuffer buffer) { + VkMemoryRequirements memory_requirements; + vkGetBufferMemoryRequirements(device, buffer, &memory_requirements); + return allocate_memory(memory_requirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); +} + +VkDeviceMemory Device_Memory_Allocator::allocate_staging_memory(VkImage image) { + VkMemoryRequirements memory_requirements; + vkGetImageMemoryRequirements(device, image, &memory_requirements); + return allocate_memory(memory_requirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); +} + +VkDeviceMemory Device_Memory_Allocator::allocate_staging_memory(VkBuffer buffer) { + VkMemoryRequirements memory_requirements; + vkGetBufferMemoryRequirements(device, buffer, &memory_requirements); + return allocate_memory(memory_requirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); +} + +Shared_Staging_Memory& Device_Memory_Allocator::get_shared_staging_memory() { + return shared_staging_memory; +} + +VkDeviceMemory Device_Memory_Allocator::allocate_memory(const VkMemoryRequirements& memory_requirements, VkMemoryPropertyFlags properties) { + VkMemoryAllocateInfo alloc_info; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.pNext = nullptr; + alloc_info.allocationSize = memory_requirements.size; + alloc_info.memoryTypeIndex = find_memory_type(physical_device, memory_requirements.memoryTypeBits, properties); + + VkDeviceMemory chunk; + VkResult result = vkAllocateMemory(device, &alloc_info, nullptr, &chunk); + check_vk_result(result, "vkAllocateMemory"); + chunks.push_back(chunk); + return chunk; +} diff --git a/source/engine/renderer/vk_allocator.h b/source/engine/renderer/vk_allocator.h new file mode 100644 index 0000000..52039b7 --- /dev/null +++ b/source/engine/renderer/vk_allocator.h @@ -0,0 +1,52 @@ +#pragma once + +#include "vk_definitions.h" +#include + +class Shared_Staging_Memory { +public: + void initialize(VkPhysicalDevice physical_device, VkDevice device); + void deallocate_all(); + + void ensure_allocation_for_object(VkImage image); + void ensure_allocation_for_object(VkBuffer buffer); + VkDeviceMemory get_handle() const; + +private: + void ensure_allocation(const VkMemoryRequirements& memory_requirements); + +private: + VkPhysicalDevice physical_device = VK_NULL_HANDLE; + VkDevice device = VK_NULL_HANDLE; + + VkDeviceMemory handle = VK_NULL_HANDLE; + VkDeviceSize size = 0; + uint32_t memory_type_index = -1; +}; + +// NOTE: in this implementation I do memory allocation for each allocation request. +// TODO: sub-allocate from larger chunks and return chunk handle plus offset withing corresponding chunk. +class Device_Memory_Allocator { +public: + void initialize(VkPhysicalDevice physical_device, VkDevice device); + void deallocate_all(); + + VkDeviceMemory allocate_memory(VkImage image); + VkDeviceMemory allocate_memory(VkBuffer buffer); + VkDeviceMemory allocate_staging_memory(VkImage image); + VkDeviceMemory allocate_staging_memory(VkBuffer buffer); + + Shared_Staging_Memory& get_shared_staging_memory(); + +private: + VkDeviceMemory allocate_memory(const VkMemoryRequirements& memory_requirements, VkMemoryPropertyFlags properties); + +private: + VkPhysicalDevice physical_device = VK_NULL_HANDLE; + VkDevice device = VK_NULL_HANDLE; + + std::vector chunks; + Shared_Staging_Memory shared_staging_memory; +}; + +Device_Memory_Allocator* get_allocator(); diff --git a/source/engine/renderer/vk_definitions.h b/source/engine/renderer/vk_definitions.h new file mode 100644 index 0000000..a329f69 --- /dev/null +++ b/source/engine/renderer/vk_definitions.h @@ -0,0 +1,6 @@ +#pragma once + +#define VK_USE_PLATFORM_WIN32_KHR +#define NOMINMAX + +#include "vulkan/vulkan.h" diff --git a/source/engine/renderer/vk_demo.cpp b/source/engine/renderer/vk_demo.cpp new file mode 100644 index 0000000..360e7a7 --- /dev/null +++ b/source/engine/renderer/vk_demo.cpp @@ -0,0 +1,817 @@ +#include "vk_allocator.h" +#include "vk_resource_manager.h" +#include "vk_demo.h" +#include "vk_init.h" +#include "vk_utils.h" + +#include "stb_image.h" + +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" + +#include "glm/glm.hpp" +#include "glm/gtc/matrix_transform.hpp" +#include "glm/gtx/hash.hpp" + +#include +#include +#include +#include +#include + +const std::string model_path = "../../data/model.obj"; +const std::string texture_path = "../../data/texture.jpg"; + +struct Uniform_Buffer_Object { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 tex_coord; + + bool operator==(const Vertex& other) const { + return pos == other.pos && color == other.color && tex_coord == other.tex_coord; + } + + static std::array get_bindings() { + VkVertexInputBindingDescription binding_desc; + binding_desc.binding = 0; + binding_desc.stride = sizeof(Vertex); + binding_desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + return {binding_desc}; + } + + static std::array get_attributes() { + VkVertexInputAttributeDescription position_attrib; + position_attrib.location = 0; + position_attrib.binding = 0; + position_attrib.format = VK_FORMAT_R32G32B32_SFLOAT; + position_attrib.offset = offsetof(struct Vertex, pos); + + VkVertexInputAttributeDescription color_attrib; + color_attrib.location = 1; + color_attrib.binding = 0; + color_attrib.format = VK_FORMAT_R32G32B32_SFLOAT; + color_attrib.offset = offsetof(struct Vertex, color); + + VkVertexInputAttributeDescription tex_coord_attrib; + tex_coord_attrib.location = 2; + tex_coord_attrib.binding = 0; + tex_coord_attrib.format = VK_FORMAT_R32G32_SFLOAT; + tex_coord_attrib.offset = offsetof(struct Vertex, tex_coord); + + return {position_attrib, color_attrib, tex_coord_attrib}; + } +}; + +struct Model { + std::vector vertices; + std::vector indices; +}; + +namespace std { + template<> struct hash { + size_t operator()(Vertex const& vertex) const { + return ((hash()(vertex.pos) ^ + (hash()(vertex.color) << 1)) >> 1) ^ + (hash()(vertex.tex_coord) << 1); + } + }; +} + +static Model load_model() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, model_path.c_str())) + error("failed to load obj model: " + model_path); + + Model model; + std::unordered_map unique_vertices; + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex; + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + vertex.tex_coord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0 - attrib.texcoords[2 * index.texcoord_index + 1] + }; + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (unique_vertices.count(vertex) == 0) { + unique_vertices[vertex] = model.vertices.size(); + model.vertices.push_back(vertex); + } + model.indices.push_back((uint32_t)unique_vertices[vertex]); + } + } + return model; +} + +static VkFormat find_format_with_features(VkPhysicalDevice physical_device, const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties properties; + vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties); + + if (tiling == VK_IMAGE_TILING_LINEAR && (properties.linearTilingFeatures & features) == features) + return format; + if (tiling == VK_IMAGE_TILING_OPTIMAL && (properties.optimalTilingFeatures & features) == features) + return format; + } + error("failed to find format with requested features"); + return VK_FORMAT_UNDEFINED; // never get here +} + +static VkFormat find_depth_format(VkPhysicalDevice physical_device) { + return find_format_with_features(physical_device, {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, + VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); +} + +Vulkan_Demo::Vulkan_Demo(int window_width, int window_height, const SDL_SysWMinfo& window_sys_info) +: window_width(window_width) +, window_height(window_height) +{ + initialize_vulkan(window_sys_info); + get_allocator()->initialize(get_physical_device(), get_device()); + get_resource_manager()->initialize(get_device()); + + image_acquired = get_resource_manager()->create_semaphore(); + rendering_finished = get_resource_manager()->create_semaphore(); + + create_command_pool(); + create_descriptor_pool(); + + create_uniform_buffer(); + create_texture(); + create_texture_sampler(); + create_depth_buffer_resources(); + + create_descriptor_set_layout(); + create_descriptor_set(); + create_render_pass(); + create_framebuffers(); + create_pipeline_layout(); + create_pipeline(); + + upload_geometry(); + record_render_scene(); // record secondary command buffer before primary ones + record_render_frame(); +} + +Vulkan_Demo::~Vulkan_Demo() { + VkResult result = vkDeviceWaitIdle(get_device()); + if (result < 0) + std::cerr << "vkDeviceWaitIdle returned an error code: " + result; + + get_resource_manager()->release_resources(); + get_allocator()->deallocate_all(); + + deinitialize_vulkan(); +} + +void Vulkan_Demo::create_command_pool() { + VkCommandPoolCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.queueFamilyIndex = get_queue_family_index(); + command_pool = get_resource_manager()->create_command_pool(desc); +} + +void Vulkan_Demo::create_descriptor_pool() { + std::array pool_sizes; + pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + pool_sizes[0].descriptorCount = 1; + pool_sizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + pool_sizes[1].descriptorCount = 1; + + VkDescriptorPoolCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.maxSets = 1; + desc.poolSizeCount = static_cast(pool_sizes.size()); + desc.pPoolSizes = pool_sizes.data(); + + descriptor_pool = get_resource_manager()->create_descriptor_pool(desc); +} + +void Vulkan_Demo::create_uniform_buffer() { + auto size = static_cast(sizeof(Uniform_Buffer_Object)); + uniform_staging_buffer = create_permanent_staging_buffer(size, uniform_staging_buffer_memory); + uniform_buffer = create_buffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); +} + +void Vulkan_Demo::create_texture() { + int image_width, image_height, image_component_count; + auto rgba_pixels = stbi_load(texture_path.c_str(), &image_width, &image_height, &image_component_count, STBI_rgb_alpha); + if (rgba_pixels == nullptr) { + error("failed to load image file"); + } + VkImage staging_image = create_staging_texture(image_width, image_height, VK_FORMAT_R8G8B8A8_UNORM, rgba_pixels, 4); + Defer_Action destroy_staging_image([this, &staging_image]() { + vkDestroyImage(get_device(), staging_image, nullptr); + }); + stbi_image_free(rgba_pixels); + + texture_image = ::create_texture(image_width, image_height, VK_FORMAT_R8G8B8A8_UNORM); + + record_and_run_commands(command_pool, get_queue(), + [&staging_image, &image_width, &image_height, this](VkCommandBuffer command_buffer) { + + record_image_layout_transition(command_buffer, staging_image, VK_FORMAT_R8G8B8A8_UNORM, + VK_ACCESS_HOST_WRITE_BIT, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + + record_image_layout_transition(command_buffer, texture_image, VK_FORMAT_R8G8B8A8_UNORM, + 0, VK_IMAGE_LAYOUT_UNDEFINED, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + // copy staging image's data to device local image + VkImageSubresourceLayers subresource_layers; + subresource_layers.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresource_layers.mipLevel = 0; + subresource_layers.baseArrayLayer = 0; + subresource_layers.layerCount = 1; + + VkImageCopy region; + region.srcSubresource = subresource_layers; + region.srcOffset = {0, 0, 0}; + region.dstSubresource = subresource_layers; + region.dstOffset = {0, 0, 0}; + region.extent.width = image_width; + region.extent.height = image_height; + + vkCmdCopyImage(command_buffer, + staging_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + texture_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, ®ion); + + record_image_layout_transition(command_buffer, texture_image, VK_FORMAT_R8G8B8A8_UNORM, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + }); + + texture_image_view = create_image_view(texture_image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT); +} + +void Vulkan_Demo::create_texture_sampler() { + VkSamplerCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.magFilter = VK_FILTER_LINEAR; + desc.minFilter = VK_FILTER_LINEAR; + desc.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + desc.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + desc.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + desc.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + desc.mipLodBias = 0.0f; + desc.anisotropyEnable = VK_TRUE; + desc.maxAnisotropy = 16; + desc.compareEnable = VK_FALSE; + desc.compareOp = VK_COMPARE_OP_ALWAYS; + desc.minLod = 0.0f; + desc.maxLod = 0.0f; + desc.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + desc.unnormalizedCoordinates = VK_FALSE; + + texture_image_sampler = get_resource_manager()->create_sampler(desc); +} + +void Vulkan_Demo::create_depth_buffer_resources() { + VkFormat depth_format = find_depth_format(get_physical_device()); + depth_image = create_depth_attachment_image(window_width, window_height, depth_format); + depth_image_view = create_image_view(depth_image, depth_format, VK_IMAGE_ASPECT_DEPTH_BIT); + + record_and_run_commands(command_pool, get_queue(), [&depth_format, this](VkCommandBuffer command_buffer) { + record_image_layout_transition(command_buffer, depth_image, depth_format, 0, VK_IMAGE_LAYOUT_UNDEFINED, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + }); +} + +void Vulkan_Demo::create_descriptor_set_layout() { + std::array descriptor_bindings; + descriptor_bindings[0].binding = 0; + descriptor_bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_bindings[0].descriptorCount = 1; + descriptor_bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + descriptor_bindings[0].pImmutableSamplers = nullptr; + + descriptor_bindings[1].binding = 1; + descriptor_bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptor_bindings[1].descriptorCount = 1; + descriptor_bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + descriptor_bindings[1].pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.bindingCount = static_cast(descriptor_bindings.size()); + desc.pBindings = descriptor_bindings.data(); + + descriptor_set_layout = get_resource_manager()->create_descriptor_set_layout(desc); +} + +void Vulkan_Demo::create_descriptor_set() { + VkDescriptorSetAllocateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + desc.pNext = nullptr; + desc.descriptorPool = descriptor_pool; + desc.descriptorSetCount = 1; + desc.pSetLayouts = &descriptor_set_layout; + + VkResult result = vkAllocateDescriptorSets(get_device(), &desc, &descriptor_set); + check_vk_result(result, "vkAllocateDescriptorSets"); + + VkDescriptorBufferInfo buffer_info; + buffer_info.buffer = uniform_buffer; + buffer_info.offset = 0; + buffer_info.range = sizeof(Uniform_Buffer_Object); + + VkDescriptorImageInfo image_info; + image_info.sampler = texture_image_sampler; + image_info.imageView = texture_image_view; + image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + std::array descriptor_writes; + descriptor_writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptor_writes[0].pNext = nullptr; + descriptor_writes[0].dstSet = descriptor_set; + descriptor_writes[0].dstBinding = 0; + descriptor_writes[0].dstArrayElement = 0; + descriptor_writes[0].descriptorCount = 1; + descriptor_writes[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_writes[0].pImageInfo = nullptr; + descriptor_writes[0].pBufferInfo = &buffer_info; + descriptor_writes[0].pTexelBufferView = nullptr; + + descriptor_writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptor_writes[1].dstSet = descriptor_set; + descriptor_writes[1].dstBinding = 1; + descriptor_writes[1].dstArrayElement = 0; + descriptor_writes[1].descriptorCount = 1; + descriptor_writes[1].pNext = nullptr; + descriptor_writes[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptor_writes[1].pImageInfo = &image_info; + descriptor_writes[1].pBufferInfo = nullptr; + descriptor_writes[1].pTexelBufferView = nullptr; + + vkUpdateDescriptorSets(get_device(), (uint32_t)descriptor_writes.size(), descriptor_writes.data(), 0, nullptr); +} + +void Vulkan_Demo::create_render_pass() { + VkAttachmentDescription color_attachment; + color_attachment.flags = 0; + color_attachment.format = get_swapchain_image_format(); + color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentDescription depth_attachment; + depth_attachment.flags = 0; + depth_attachment.format = find_depth_format(get_physical_device()); + depth_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depth_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_attachment.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + depth_attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference color_attachment_ref; + color_attachment_ref.attachment = 0; + color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depth_attachment_ref; + depth_attachment_ref.attachment = 1; + depth_attachment_ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass; + subpass.flags = 0; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.inputAttachmentCount = 0; + subpass.pInputAttachments = nullptr; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment_ref; + subpass.pResolveAttachments = nullptr; + subpass.pDepthStencilAttachment = &depth_attachment_ref; + subpass.preserveAttachmentCount = 0; + subpass.pPreserveAttachments = nullptr; + + std::array attachments{color_attachment, depth_attachment}; + VkRenderPassCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.attachmentCount = static_cast(attachments.size()); + desc.pAttachments = attachments.data(); + desc.subpassCount = 1; + desc.pSubpasses = &subpass; + desc.dependencyCount = 0; + desc.pDependencies = nullptr; + + render_pass = get_resource_manager()->create_render_pass(desc); +} + +void Vulkan_Demo::create_framebuffers() { + std::array attachments = {VK_NULL_HANDLE, depth_image_view}; + + VkFramebufferCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.renderPass = render_pass; + desc.attachmentCount = static_cast(attachments.size()); + desc.pAttachments = attachments.data(); + desc.width = window_width; + desc.height = window_height; + desc.layers = 1; + + const auto& swapchain_image_views = get_swapchain_image_views(); + framebuffers.resize(swapchain_image_views.size()); + for (std::size_t i = 0; i < framebuffers.size(); i++) { + attachments[0] = swapchain_image_views[i]; // set color attachment + framebuffers[i] = get_resource_manager()->create_framebuffer(desc); + } +} + +void Vulkan_Demo::create_pipeline_layout() { + VkPipelineLayoutCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.setLayoutCount = 1; + desc.pSetLayouts = &descriptor_set_layout; + desc.pushConstantRangeCount = 0; + desc.pPushConstantRanges = nullptr; + + pipeline_layout = get_resource_manager()->create_pipeline_layout(desc); +} + +void Vulkan_Demo::create_pipeline() { + Shader_Module vertex_shader("../../shaders/vert.spv"); + Shader_Module fragment_shader("../../shaders/frag.spv"); + + auto get_shader_stage_desc = [](VkShaderStageFlagBits stage, VkShaderModule shader_module, const char* entry) { + VkPipelineShaderStageCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.stage = stage; + desc.module = shader_module; + desc.pName = entry; + desc.pSpecializationInfo = nullptr; + return desc; + }; + std::vector shader_stages_state { + get_shader_stage_desc(VK_SHADER_STAGE_VERTEX_BIT, vertex_shader.handle, "main"), + get_shader_stage_desc(VK_SHADER_STAGE_FRAGMENT_BIT, fragment_shader.handle, "main") + }; + + VkPipelineVertexInputStateCreateInfo vertex_input_state; + vertex_input_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_input_state.pNext = nullptr; + vertex_input_state.flags = 0; + auto bindings = Vertex::get_bindings(); + vertex_input_state.vertexBindingDescriptionCount = (uint32_t)bindings.size(); + vertex_input_state.pVertexBindingDescriptions = bindings.data(); + auto attribs = Vertex::get_attributes(); + vertex_input_state.vertexAttributeDescriptionCount = (uint32_t)attribs.size(); + vertex_input_state.pVertexAttributeDescriptions = attribs.data(); + + VkPipelineInputAssemblyStateCreateInfo input_assembly_state; + input_assembly_state.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly_state.pNext = nullptr; + input_assembly_state.flags = 0; + input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + input_assembly_state.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)window_width; + viewport.height = (float)window_height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor; + scissor.offset = {0, 0}; + scissor.extent = {(uint32_t)window_width, (uint32_t)window_height}; + + VkPipelineViewportStateCreateInfo viewport_state; + viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_state.pNext = nullptr; + viewport_state.flags = 0; + viewport_state.viewportCount = 1; + viewport_state.pViewports = &viewport; + viewport_state.scissorCount = 1; + viewport_state.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterization_state; + rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterization_state.pNext = nullptr; + rasterization_state.flags = 0; + rasterization_state.depthClampEnable = VK_FALSE; + rasterization_state.rasterizerDiscardEnable = VK_FALSE; + rasterization_state.polygonMode = VK_POLYGON_MODE_FILL; + rasterization_state.cullMode = VK_CULL_MODE_BACK_BIT; + rasterization_state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterization_state.depthBiasEnable = VK_FALSE; + rasterization_state.depthBiasConstantFactor = 0.0f; + rasterization_state.depthBiasClamp = 0.0f; + rasterization_state.depthBiasSlopeFactor = 0.0f; + rasterization_state.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo multisample_state; + multisample_state.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisample_state.pNext = nullptr; + multisample_state.flags = 0; + multisample_state.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisample_state.sampleShadingEnable = VK_FALSE; + multisample_state.minSampleShading = 1.0f; + multisample_state.pSampleMask = nullptr; + multisample_state.alphaToCoverageEnable = VK_FALSE; + multisample_state.alphaToOneEnable = VK_FALSE; + + VkPipelineDepthStencilStateCreateInfo depth_stencil_state; + depth_stencil_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depth_stencil_state.pNext = nullptr; + depth_stencil_state.flags = 0; + depth_stencil_state.depthTestEnable = VK_TRUE; + depth_stencil_state.depthWriteEnable = VK_TRUE; + depth_stencil_state.depthCompareOp = VK_COMPARE_OP_LESS; + depth_stencil_state.depthBoundsTestEnable = VK_FALSE; + depth_stencil_state.stencilTestEnable = VK_FALSE; + depth_stencil_state.front = {}; + depth_stencil_state.back = {}; + depth_stencil_state.minDepthBounds = 0.0; + depth_stencil_state.maxDepthBounds = 0.0; + + VkPipelineColorBlendAttachmentState attachment_blend_state; + attachment_blend_state.blendEnable = VK_FALSE; + attachment_blend_state.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + attachment_blend_state.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; + attachment_blend_state.colorBlendOp = VK_BLEND_OP_ADD; + attachment_blend_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + attachment_blend_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + attachment_blend_state.alphaBlendOp = VK_BLEND_OP_ADD; + attachment_blend_state.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + + VkPipelineColorBlendStateCreateInfo blend_state; + blend_state.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + blend_state.pNext = nullptr; + blend_state.flags = 0; + blend_state.logicOpEnable = VK_FALSE; + blend_state.logicOp = VK_LOGIC_OP_COPY; + blend_state.attachmentCount = 1; + blend_state.pAttachments = &attachment_blend_state; + blend_state.blendConstants[0] = 0.0f; + blend_state.blendConstants[1] = 0.0f; + blend_state.blendConstants[2] = 0.0f; + blend_state.blendConstants[3] = 0.0f; + + VkGraphicsPipelineCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.stageCount = static_cast(shader_stages_state.size()); + desc.pStages = shader_stages_state.data(); + desc.pVertexInputState = &vertex_input_state; + desc.pInputAssemblyState = &input_assembly_state; + desc.pTessellationState = nullptr; + desc.pViewportState = &viewport_state; + desc.pRasterizationState = &rasterization_state; + desc.pMultisampleState = &multisample_state; + desc.pDepthStencilState = &depth_stencil_state; + desc.pColorBlendState = &blend_state; + desc.pDynamicState = nullptr; + desc.layout = pipeline_layout; + desc.renderPass = render_pass; + desc.subpass = 0; + desc.basePipelineHandle = VK_NULL_HANDLE; + desc.basePipelineIndex = -1; + + pipeline = get_resource_manager()->create_graphics_pipeline(desc); +} + +void Vulkan_Demo::upload_geometry() { + Model model = load_model(); + model_indices_count = static_cast(model.indices.size()); + + { + const VkDeviceSize size = model.vertices.size() * sizeof(model.vertices[0]); + vertex_buffer = create_buffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); + VkBuffer staging_buffer = create_staging_buffer(size, model.vertices.data()); + Defer_Action destroy_staging_buffer([&staging_buffer, this]() { + vkDestroyBuffer(get_device(), staging_buffer, nullptr); + }); + record_and_run_commands(command_pool, get_queue(), [&staging_buffer, &size, this](VkCommandBuffer command_buffer) { + VkBufferCopy region; + region.srcOffset = 0; + region.dstOffset = 0; + region.size = size; + vkCmdCopyBuffer(command_buffer, staging_buffer, vertex_buffer, 1, ®ion); + }); + } + { + const VkDeviceSize size = model.indices.size() * sizeof(model.indices[0]); + index_buffer = create_buffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT); + VkBuffer staging_buffer = create_staging_buffer(size, model.indices.data()); + Defer_Action destroy_staging_buffer([&staging_buffer, this]() { + vkDestroyBuffer(get_device(), staging_buffer, nullptr); + }); + record_and_run_commands(command_pool, get_queue(), [&staging_buffer, &size, this](VkCommandBuffer command_buffer) { + VkBufferCopy region; + region.srcOffset = 0; + region.dstOffset = 0; + region.size = size; + vkCmdCopyBuffer(command_buffer, staging_buffer, index_buffer, 1, ®ion); + }); + } +} + +void Vulkan_Demo::record_render_scene() { + VkCommandBufferAllocateInfo alloc_info; + alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + alloc_info.pNext = nullptr; + alloc_info.commandPool = command_pool; + alloc_info.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY; + alloc_info.commandBufferCount = 1; + VkResult result = vkAllocateCommandBuffers(get_device(), &alloc_info, &render_scene_command_buffer); + check_vk_result(result, "vkAllocateCommandBuffers"); + + VkCommandBufferInheritanceInfo inheritance_info; + inheritance_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO; + inheritance_info.pNext = nullptr; + inheritance_info.renderPass = render_pass; + inheritance_info.subpass = 0; + inheritance_info.framebuffer = VK_NULL_HANDLE; + inheritance_info.occlusionQueryEnable = VK_FALSE; + inheritance_info.queryFlags = 0; + inheritance_info.pipelineStatistics = 0; + + VkCommandBufferBeginInfo begin_info; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.pNext = nullptr; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT | VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + begin_info.pInheritanceInfo = &inheritance_info; + + result = vkBeginCommandBuffer(render_scene_command_buffer, &begin_info); + check_vk_result(result, "vkBeginCommandBuffer"); + + const VkDeviceSize offset = 0; + vkCmdBindVertexBuffers(render_scene_command_buffer, 0, 1, &vertex_buffer, &offset); + vkCmdBindIndexBuffer(render_scene_command_buffer, index_buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdBindDescriptorSets(render_scene_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set, 0, nullptr); + vkCmdBindPipeline(render_scene_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + vkCmdDrawIndexed(render_scene_command_buffer, model_indices_count, 1, 0, 0, 0); + + result = vkEndCommandBuffer(render_scene_command_buffer); + check_vk_result(result, "vkEndCommandBuffer"); +} + +void Vulkan_Demo::record_render_frame() { + VkCommandBufferAllocateInfo alloc_info; + alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + alloc_info.pNext = nullptr; + alloc_info.commandPool = command_pool; + alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + alloc_info.commandBufferCount = static_cast(get_swapchain_image_views().size()); + render_frame_command_buffers.resize(get_swapchain_image_views().size()); + VkResult result = vkAllocateCommandBuffers(get_device(), &alloc_info, render_frame_command_buffers.data()); + check_vk_result(result, "vkAllocateCommandBuffers"); + + VkCommandBufferBeginInfo begin_info; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.pNext = nullptr; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + begin_info.pInheritanceInfo = nullptr; + + std::array clear_values; + clear_values[0].color = {0.3f, 0.2f, 0.1f, 0.0f}; + clear_values[1].depthStencil = {1.0, 0}; + + VkRenderPassBeginInfo render_pass_begin_info; + render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + render_pass_begin_info.pNext = nullptr; + render_pass_begin_info.renderPass = render_pass; + render_pass_begin_info.framebuffer = VK_NULL_HANDLE; // will be initialized later in the recording loop + render_pass_begin_info.renderArea.offset = {0, 0}; + render_pass_begin_info.renderArea.extent = {(uint32_t)window_width, (uint32_t)window_height}; + render_pass_begin_info.clearValueCount = static_cast(clear_values.size()); + render_pass_begin_info.pClearValues = clear_values.data(); + + VkBufferMemoryBarrier barrier; + barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + barrier.pNext = nullptr; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_UNIFORM_READ_BIT; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.buffer = uniform_buffer; + barrier.offset = 0; + barrier.size = sizeof(Uniform_Buffer_Object); + + for (std::size_t i = 0; i < render_frame_command_buffers.size(); i++) { + VkResult result = vkBeginCommandBuffer(render_frame_command_buffers[i], &begin_info); + check_vk_result(result, "vkBeginCommandBuffer"); + + VkBufferCopy region; + region.srcOffset = 0; + region.dstOffset = 0; + region.size = sizeof(Uniform_Buffer_Object); + vkCmdCopyBuffer(render_frame_command_buffers[i], uniform_staging_buffer, uniform_buffer, 1, ®ion); + + vkCmdPipelineBarrier(render_frame_command_buffers[i], VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, + 0, nullptr, 1, &barrier, 0, nullptr); + + render_pass_begin_info.framebuffer = framebuffers[i]; + vkCmdBeginRenderPass(render_frame_command_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS); + vkCmdExecuteCommands(render_frame_command_buffers[i], 1, &render_scene_command_buffer); + vkCmdEndRenderPass(render_frame_command_buffers[i]); + + result = vkEndCommandBuffer(render_frame_command_buffers[i]); + check_vk_result(result, "vkEndCommandBuffer"); + } +} + +void Vulkan_Demo::update_uniform_buffer() { + static auto start_time = std::chrono::high_resolution_clock::now(); + + auto current_time = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration_cast(current_time - start_time).count() / 1000.f; + + Uniform_Buffer_Object ubo; + ubo.model = glm::rotate(glm::mat4(), time * glm::radians(30.0f), glm::vec3(0, 1, 0)) * + glm::scale(glm::mat4(), glm::vec3(0.015f)); + + ubo.view = glm::lookAt(glm::vec3(0.5, 1.4, 2.8), glm::vec3(0, 0.7, 0), glm::vec3(0, 1, 0)); + + // Vulkan clip space has inverted Y and half Z. + const glm::mat4 clip( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + 0.0f, 0.0f, 0.5f, 1.0f); + + ubo.proj = clip * glm::perspective(glm::radians(45.0f), window_width / (float)window_height, 0.1f, 50.0f); + + void* data; + VkResult result = vkMapMemory(get_device(), uniform_staging_buffer_memory, 0, sizeof(ubo), 0, &data); + check_vk_result(result, "vkMapMemory"); + memcpy(data, &ubo, sizeof(ubo)); + vkUnmapMemory(get_device(), uniform_staging_buffer_memory); +} + +void Vulkan_Demo::run_frame() { + update_uniform_buffer(); + + uint32_t swapchain_image_index; + VkResult result = vkAcquireNextImageKHR(get_device(), get_swapchain(), UINT64_MAX, image_acquired, VK_NULL_HANDLE, &swapchain_image_index); + check_vk_result(result, "vkAcquireNextImageKHR"); + + VkPipelineStageFlags wait_dst_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSubmitInfo submit_info; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.pNext = nullptr; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &image_acquired; + submit_info.pWaitDstStageMask = &wait_dst_stage_mask; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &render_frame_command_buffers[swapchain_image_index]; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &rendering_finished; + + result = vkQueueSubmit(get_queue(), 1, &submit_info, VK_NULL_HANDLE); + check_vk_result(result, "vkQueueSubmit"); + + VkSwapchainKHR swapchain = get_swapchain(); + VkPresentInfoKHR present_info; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + present_info.pNext = nullptr; + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = &rendering_finished; + present_info.swapchainCount = 1; + present_info.pSwapchains = &swapchain; + present_info.pImageIndices = &swapchain_image_index; + present_info.pResults = nullptr; + + result = vkQueuePresentKHR(get_queue(), &present_info); + check_vk_result(result, "vkQueuePresentKHR"); +} diff --git a/source/engine/renderer/vk_demo.h b/source/engine/renderer/vk_demo.h new file mode 100644 index 0000000..9864e20 --- /dev/null +++ b/source/engine/renderer/vk_demo.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include "vk_definitions.h" + +struct SDL_SysWMinfo; + +class Vulkan_Demo { +public: + Vulkan_Demo(int window_width, int window_height, const SDL_SysWMinfo& window_sys_info); + ~Vulkan_Demo(); + + void run_frame(); + +private: + void create_command_pool(); + void create_descriptor_pool(); + + void create_uniform_buffer(); + void create_texture(); + void create_texture_sampler(); + void create_depth_buffer_resources(); + + void create_descriptor_set_layout(); + void create_descriptor_set(); + void create_render_pass(); + void create_framebuffers(); + void create_pipeline_layout(); + void create_pipeline(); + + void upload_geometry(); + void record_render_scene(); + void record_render_frame(); + void update_uniform_buffer(); + +private: + const int window_width = 0; + const int window_height = 0; + + VkSemaphore image_acquired = VK_NULL_HANDLE; + VkSemaphore rendering_finished = VK_NULL_HANDLE; + + VkCommandPool command_pool = VK_NULL_HANDLE; + VkDescriptorPool descriptor_pool = VK_NULL_HANDLE; + + VkBuffer uniform_staging_buffer = VK_NULL_HANDLE; + VkDeviceMemory uniform_staging_buffer_memory = VK_NULL_HANDLE; + VkBuffer uniform_buffer = VK_NULL_HANDLE; + VkImage texture_image = VK_NULL_HANDLE; + VkImageView texture_image_view = VK_NULL_HANDLE; + VkSampler texture_image_sampler = VK_NULL_HANDLE; + VkImage depth_image = VK_NULL_HANDLE; + VkImageView depth_image_view = VK_NULL_HANDLE; + + VkDescriptorSetLayout descriptor_set_layout = VK_NULL_HANDLE; + VkDescriptorSet descriptor_set = VK_NULL_HANDLE; + VkRenderPass render_pass = VK_NULL_HANDLE; + std::vector framebuffers; + VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; + + VkBuffer vertex_buffer = VK_NULL_HANDLE; + VkBuffer index_buffer = VK_NULL_HANDLE; + uint32_t model_indices_count = 0; + + std::vector render_frame_command_buffers; // command buffer per swapchain image + VkCommandBuffer render_scene_command_buffer; +}; diff --git a/source/engine/renderer/vk_init.cpp b/source/engine/renderer/vk_init.cpp new file mode 100644 index 0000000..1e08f91 --- /dev/null +++ b/source/engine/renderer/vk_init.cpp @@ -0,0 +1,333 @@ +#include "vk_init.h" +#include "vk_utils.h" + +#define SDL_MAIN_HANDLED +#include "sdl/SDL_syswm.h" + +#include +#include +#include +#include +#include + +struct Vulkan_Globals { + VkInstance instance = VK_NULL_HANDLE; + VkPhysicalDevice physical_device = VK_NULL_HANDLE; + VkSurfaceKHR surface = VK_NULL_HANDLE; + uint32_t queue_family_index = 0; + VkDevice device = VK_NULL_HANDLE; + VkQueue queue = VK_NULL_HANDLE; + VkSurfaceFormatKHR surface_format = {}; + VkSwapchainKHR swapchain = VK_NULL_HANDLE; + std::vector swapchain_images; + std::vector swapchain_image_views; +}; + +static Vulkan_Globals vulkan_globals; + +static const std::vector instance_extensions = { + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_WIN32_SURFACE_EXTENSION_NAME +}; + +static const std::vector device_extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +static bool is_extension_available(const std::vector& properties, const char* extension_name) { + for (const auto& property : properties) { + if (strcmp(property.extensionName, extension_name) == 0) + return true; + } + return false; +} + +static uint32_t select_queue_family(VkPhysicalDevice physical_device, VkSurfaceKHR surface) { + uint32_t queue_family_count; + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr); + + std::vector queue_families(queue_family_count); + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_families.data()); + + // select queue family with presentation and graphics support + for (uint32_t i = 0; i < queue_family_count; i++) { + VkBool32 presentation_supported; + auto result = vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, i, surface, &presentation_supported); + check_vk_result(result, "vkGetPhysicalDeviceSurfaceSupportKHR"); + + if (presentation_supported && (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) + return i; + } + error("failed to find queue family"); + return -1; +} + +static VkInstance create_instance() { + uint32_t count = 0; + VkResult result = vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); + check_vk_result(result, "vkEnumerateInstanceExtensionProperties"); + + std::vector extension_properties(count); + result = vkEnumerateInstanceExtensionProperties(nullptr, &count, extension_properties.data()); + check_vk_result(result, "vkEnumerateInstanceExtensionProperties"); + + for (auto name : instance_extensions) { + if (!is_extension_available(extension_properties, name)) + error(std::string("required instance extension is not available: ") + name); + } + + VkInstanceCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.pApplicationInfo = nullptr; + desc.enabledLayerCount = 0; + desc.ppEnabledLayerNames = nullptr; + desc.enabledExtensionCount = static_cast(instance_extensions.size()); + desc.ppEnabledExtensionNames = instance_extensions.data(); + + VkInstance instance; + result = vkCreateInstance(&desc, nullptr, &instance); + check_vk_result(result, "vkCreateInstance"); + return instance; +} + +static VkPhysicalDevice select_physical_device(VkInstance instance) { + uint32_t count; + VkResult result = vkEnumeratePhysicalDevices(instance, &count, nullptr); + check_vk_result(result, "vkEnumeratePhysicalDevices"); + + if (count == 0) + error("no physical device found"); + + std::vector physical_devices(count); + result = vkEnumeratePhysicalDevices(instance, &count, physical_devices.data()); + check_vk_result(result, "vkEnumeratePhysicalDevices"); + return physical_devices[0]; // just get the first one +} + +static VkSurfaceKHR create_surface(VkInstance instance, const SDL_SysWMinfo& window_sys_info) { + VkWin32SurfaceCreateInfoKHR desc; + desc.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + desc.pNext = nullptr; + desc.flags = 0; + desc.hinstance = ::GetModuleHandle(nullptr); + desc.hwnd = window_sys_info.info.win.window; + + VkSurfaceKHR surface; + VkResult result = vkCreateWin32SurfaceKHR(instance, &desc, nullptr, &surface); + check_vk_result(result, "vkCreateWin32SurfaceKHR"); + return surface; +} + +static VkDevice create_device(VkPhysicalDevice physical_device, uint32_t queue_family_index) { + uint32_t count = 0; + VkResult result = vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &count, nullptr); + check_vk_result(result, "vkEnumerateDeviceExtensionProperties"); + + std::vector extension_properties(count); + result = vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &count, extension_properties.data()); + check_vk_result(result, "vkEnumerateDeviceExtensionProperties"); + + for (auto name : device_extensions) { + if (!is_extension_available(extension_properties, name)) + error(std::string("required device extension is not available: ") + name); + } + + const float priority = 1.0; + VkDeviceQueueCreateInfo queue_desc; + queue_desc.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_desc.pNext = nullptr; + queue_desc.flags = 0; + queue_desc.queueFamilyIndex = queue_family_index; + queue_desc.queueCount = 1; + queue_desc.pQueuePriorities = &priority; + + VkDeviceCreateInfo device_desc; + device_desc.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_desc.pNext = nullptr; + device_desc.flags = 0; + device_desc.queueCreateInfoCount = 1; + device_desc.pQueueCreateInfos = &queue_desc; + device_desc.enabledLayerCount = 0; + device_desc.ppEnabledLayerNames = nullptr; + device_desc.enabledExtensionCount = static_cast(device_extensions.size()); + device_desc.ppEnabledExtensionNames = device_extensions.data(); + device_desc.pEnabledFeatures = nullptr; + + VkDevice device; + result = vkCreateDevice(physical_device, &device_desc, nullptr, &device); + check_vk_result(result, "vkCreateDevice"); + return device; +} + +static VkSurfaceFormatKHR select_surface_format(VkPhysicalDevice physical_device, VkSurfaceKHR surface) { + uint32_t format_count; + VkResult result = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, nullptr); + check_vk_result(result, "vkGetPhysicalDeviceSurfaceFormatsKHR"); + assert(format_count > 0); + + std::vector candidates(format_count); + result = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, candidates.data()); + check_vk_result(result, "vkGetPhysicalDeviceSurfaceFormatsKHR"); + + // special case that means we can choose any format + if (candidates.size() == 1 && candidates[0].format == VK_FORMAT_UNDEFINED) { + VkSurfaceFormatKHR surface_format; + surface_format.format = VK_FORMAT_R8G8B8A8_UNORM; + surface_format.colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + return surface_format; + } + return candidates[0]; +} + +static VkSwapchainKHR create_swapchain(VkPhysicalDevice physical_device, VkDevice device, VkSurfaceKHR surface, VkSurfaceFormatKHR surface_format) { + VkSurfaceCapabilitiesKHR surface_caps; + VkResult result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &surface_caps); + check_vk_result(result, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); + + VkExtent2D image_extent = surface_caps.currentExtent; + if (image_extent.width == 0xffffffff && image_extent.height == 0xffffffff) { + image_extent.width = std::min(surface_caps.maxImageExtent.width, std::max(surface_caps.minImageExtent.width, 640u)); + image_extent.height = std::min(surface_caps.maxImageExtent.height, std::max(surface_caps.minImageExtent.height, 480u)); + } + + // transfer destination usage is required by image clear operations + if ((surface_caps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) == 0) + error("VK_IMAGE_USAGE_TRANSFER_DST_BIT is not supported by the swapchain"); + VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + // determine present mode and swapchain image count + uint32_t present_mode_count; + result = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_mode_count, nullptr); + check_vk_result(result, "vkGetPhysicalDeviceSurfacePresentModesKHR"); + std::vector present_modes(present_mode_count); + result = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_mode_count, present_modes.data()); + check_vk_result(result, "vkGetPhysicalDeviceSurfacePresentModesKHR"); + + VkPresentModeKHR present_mode; + uint32_t image_count; + + auto it = std::find(present_modes.cbegin(), present_modes.cend(), VK_PRESENT_MODE_MAILBOX_KHR); + if (it != present_modes.cend()) { + present_mode = VK_PRESENT_MODE_MAILBOX_KHR; + image_count = std::max(3u, surface_caps.minImageCount); + if (surface_caps.maxImageCount > 0) { + image_count = std::min(image_count, surface_caps.maxImageCount); + } + } + else { + present_mode = VK_PRESENT_MODE_FIFO_KHR; + image_count = surface_caps.minImageCount; + } + + // create swap chain + VkSwapchainCreateInfoKHR desc; + desc.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + desc.pNext = nullptr; + desc.flags = 0; + desc.surface = surface; + desc.minImageCount = image_count; + desc.imageFormat = surface_format.format; + desc.imageColorSpace = surface_format.colorSpace; + desc.imageExtent = image_extent; + desc.imageArrayLayers = 1; + desc.imageUsage = image_usage; + desc.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + desc.queueFamilyIndexCount = 0; + desc.pQueueFamilyIndices = nullptr; + desc.preTransform = surface_caps.currentTransform; + desc.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + desc.presentMode = present_mode; + desc.clipped = VK_TRUE; + desc.oldSwapchain = VK_NULL_HANDLE; + + VkSwapchainKHR swapchain; + result = vkCreateSwapchainKHR(device, &desc, nullptr, &swapchain); + check_vk_result(result, "vkCreateSwapchainKHR"); + return swapchain; +} + +void initialize_vulkan(const SDL_SysWMinfo& window_sys_info) { + auto& g = vulkan_globals; + + g.instance = create_instance(); + g.physical_device = select_physical_device(g.instance); + g.surface = create_surface(g.instance, window_sys_info); + g.queue_family_index = select_queue_family(g.physical_device, g.surface); + g.device = create_device(g.physical_device, g.queue_family_index); + + vkGetDeviceQueue(g.device, g.queue_family_index, 0, &g.queue); + + g.surface_format = select_surface_format(g.physical_device, g.surface); + g.swapchain = create_swapchain(g.physical_device, g.device, g.surface, g.surface_format); + + uint32_t image_count; + VkResult result = vkGetSwapchainImagesKHR(g.device, g.swapchain, &image_count, nullptr); + check_vk_result(result, "vkGetSwapchainImagesKHR"); + g.swapchain_images.resize(image_count); + result = vkGetSwapchainImagesKHR(g.device, g.swapchain, &image_count, g.swapchain_images.data()); + check_vk_result(result, "vkGetSwapchainImagesKHR"); + + g.swapchain_image_views.resize(image_count); + for (std::size_t i = 0; i < image_count; i++) { + VkImageViewCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.image = g.swapchain_images[i]; + desc.viewType = VK_IMAGE_VIEW_TYPE_2D; + desc.format = g.surface_format.format; + desc.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + desc.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + desc.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + desc.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + desc.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + desc.subresourceRange.baseMipLevel = 0; + desc.subresourceRange.levelCount = 1; + desc.subresourceRange.baseArrayLayer = 0; + desc.subresourceRange.layerCount = 1; + result = vkCreateImageView(g.device, &desc, nullptr, &g.swapchain_image_views[i]); + check_vk_result(result, "vkCreateImageView"); + } +} + +void deinitialize_vulkan() { + auto& g = vulkan_globals; + for (auto image_view : g.swapchain_image_views) { + vkDestroyImageView(g.device, image_view, nullptr); + } + vkDestroySwapchainKHR(g.device, g.swapchain, nullptr); + vkDestroyDevice(g.device, nullptr); + vkDestroySurfaceKHR(g.instance, g.surface, nullptr); + vkDestroyInstance(g.instance, nullptr); + g = Vulkan_Globals(); +} + +VkPhysicalDevice get_physical_device() { + return vulkan_globals.physical_device; +} + +VkDevice get_device() { + return vulkan_globals.device; +} + +uint32_t get_queue_family_index() { + return vulkan_globals.queue_family_index; +} + +VkQueue get_queue() { + return vulkan_globals.queue; +} + +VkSwapchainKHR get_swapchain() { + return vulkan_globals.swapchain; +} + +VkFormat get_swapchain_image_format() { + return vulkan_globals.surface_format.format; +} + +const std::vector& get_swapchain_image_views() { + return vulkan_globals.swapchain_image_views; +} diff --git a/source/engine/renderer/vk_init.h b/source/engine/renderer/vk_init.h new file mode 100644 index 0000000..ddc986a --- /dev/null +++ b/source/engine/renderer/vk_init.h @@ -0,0 +1,17 @@ +#pragma once + +#include "vk_definitions.h" +#include + +struct SDL_SysWMinfo; + +void initialize_vulkan(const SDL_SysWMinfo& window_sys_info); +void deinitialize_vulkan(); + +VkPhysicalDevice get_physical_device(); +VkDevice get_device(); +uint32_t get_queue_family_index(); +VkQueue get_queue(); +VkSwapchainKHR get_swapchain(); +VkFormat get_swapchain_image_format(); +const std::vector& get_swapchain_image_views(); diff --git a/source/engine/renderer/vk_resource_manager.cpp b/source/engine/renderer/vk_resource_manager.cpp new file mode 100644 index 0000000..fece90b --- /dev/null +++ b/source/engine/renderer/vk_resource_manager.cpp @@ -0,0 +1,174 @@ +#include "vk_resource_manager.h" + +static Resource_Manager resource_manager; + +Resource_Manager* get_resource_manager() { + return &resource_manager; +} + +void Resource_Manager::initialize(VkDevice device) { + this->device = device; +} + +void Resource_Manager::release_resources() { + for (auto semaphore : semaphores) { + vkDestroySemaphore(device, semaphore, nullptr); + } + semaphores.clear(); + + for (auto command_pool : command_pools) { + vkDestroyCommandPool(device, command_pool, nullptr); + } + command_pools.clear(); + + for (auto descriptor_pool : descriptor_pools) { + vkDestroyDescriptorPool(device, descriptor_pool, nullptr); + } + descriptor_pools.clear(); + + for (auto buffer : buffers) { + vkDestroyBuffer(device, buffer, nullptr); + } + buffers.clear(); + + for (auto image : images) { + vkDestroyImage(device, image, nullptr); + } + images.clear(); + + for (auto image_view : image_views) { + vkDestroyImageView(device, image_view, nullptr); + } + image_views.clear(); + + for (auto sampler : samplers) { + vkDestroySampler(device, sampler, nullptr); + } + samplers.clear(); + + for (auto render_pass : render_passes) { + vkDestroyRenderPass(device, render_pass, nullptr); + } + render_passes.clear(); + + for (auto framebuffer : framebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + framebuffers.clear(); + + for (auto descriptor_set_layout : descriptor_set_layouts) { + vkDestroyDescriptorSetLayout(device, descriptor_set_layout, nullptr); + } + descriptor_set_layouts.clear(); + + for (auto pipeline_layout : pipeline_layouts) { + vkDestroyPipelineLayout(device, pipeline_layout, nullptr); + } + pipeline_layouts.clear(); + + for (auto pipeline : graphics_pipelines) { + vkDestroyPipeline(device, pipeline, nullptr); + } + graphics_pipelines.clear(); +} + +VkSemaphore Resource_Manager::create_semaphore() { + VkSemaphoreCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + + VkSemaphore semaphore; + VkResult result = vkCreateSemaphore(device, &desc, nullptr, &semaphore); + check_vk_result(result, "vkCreateSemaphore"); + semaphores.push_back(semaphore); + return semaphore; +} + +VkCommandPool Resource_Manager::create_command_pool(const VkCommandPoolCreateInfo& desc) { + VkCommandPool command_pool; + VkResult result = vkCreateCommandPool(device, &desc, nullptr, &command_pool); + check_vk_result(result, "vkCreateCommandPool"); + command_pools.push_back(command_pool); + return command_pool; +} + +VkDescriptorPool Resource_Manager::create_descriptor_pool(const VkDescriptorPoolCreateInfo& desc) { + VkDescriptorPool descriptor_pool; + VkResult result = vkCreateDescriptorPool(device, &desc, nullptr, &descriptor_pool); + check_vk_result(result, "vkCreateDescriptorPool"); + descriptor_pools.push_back(descriptor_pool); + return descriptor_pool; +} + +VkBuffer Resource_Manager::create_buffer(const VkBufferCreateInfo& desc) { + VkBuffer buffer; + VkResult result = vkCreateBuffer(device, &desc, nullptr, &buffer); + check_vk_result(result, "vkCreateBuffer"); + buffers.push_back(buffer); + return buffer; +} + +VkImage Resource_Manager::create_image(const VkImageCreateInfo& desc) { + VkImage image; + VkResult result = vkCreateImage(device, &desc, nullptr, &image); + check_vk_result(result, "vkCreateImage"); + images.push_back(image); + return image; +} + +VkImageView Resource_Manager::create_image_view(const VkImageViewCreateInfo& desc) { + VkImageView image_view; + VkResult result = vkCreateImageView(device, &desc, nullptr, &image_view); + check_vk_result(result, "vkCreateImageView"); + image_views.push_back(image_view); + return image_view; +} + +VkSampler Resource_Manager::create_sampler(const VkSamplerCreateInfo& desc) { + VkSampler sampler; + VkResult result = vkCreateSampler(device, &desc, nullptr, &sampler); + check_vk_result(result, "vkCreateSampler"); + samplers.push_back(sampler); + return sampler; +} + +VkRenderPass Resource_Manager::create_render_pass(const VkRenderPassCreateInfo& desc) { + VkRenderPass render_pass; + VkResult result = vkCreateRenderPass(device, &desc, nullptr, &render_pass); + check_vk_result(result, "vkCreateRenderPass"); + render_passes.push_back(render_pass); + return render_pass; +} + +VkFramebuffer Resource_Manager::create_framebuffer(const VkFramebufferCreateInfo& desc) { + VkFramebuffer framebuffer; + VkResult result = vkCreateFramebuffer(device, &desc, nullptr, &framebuffer); + check_vk_result(result, "vkCreateFramebuffer"); + framebuffers.push_back(framebuffer); + return framebuffer; +} + +VkDescriptorSetLayout Resource_Manager::create_descriptor_set_layout(const VkDescriptorSetLayoutCreateInfo& desc) { + VkDescriptorSetLayout descriptor_set_layout; + VkResult result = vkCreateDescriptorSetLayout(device, &desc, nullptr, &descriptor_set_layout); + check_vk_result(result, "vkCreateDescriptorSetLayout"); + descriptor_set_layouts.push_back(descriptor_set_layout); + return descriptor_set_layout; +} + +VkPipelineLayout Resource_Manager::create_pipeline_layout(const VkPipelineLayoutCreateInfo& desc) { + VkPipelineLayout pipeline_layout; + VkResult result = vkCreatePipelineLayout(device, &desc, nullptr, &pipeline_layout); + check_vk_result(result, "vkCreatePipelineLayout"); + pipeline_layouts.push_back(pipeline_layout); + return pipeline_layout; +} + +VkPipeline Resource_Manager::create_graphics_pipeline(const VkGraphicsPipelineCreateInfo& desc) { + VkPipeline pipeline; + VkResult result = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &desc, nullptr, &pipeline); + check_vk_result(result, "vkCreateGraphicsPipelines"); + graphics_pipelines.push_back(pipeline); + return pipeline; +} diff --git a/source/engine/renderer/vk_resource_manager.h b/source/engine/renderer/vk_resource_manager.h new file mode 100644 index 0000000..79129c0 --- /dev/null +++ b/source/engine/renderer/vk_resource_manager.h @@ -0,0 +1,40 @@ +#pragma once + +#include "vk_utils.h" +#include + +class Resource_Manager { +public: + void initialize(VkDevice device); + void release_resources(); + + VkSemaphore create_semaphore(); + VkCommandPool create_command_pool(const VkCommandPoolCreateInfo& desc); + VkDescriptorPool create_descriptor_pool(const VkDescriptorPoolCreateInfo& desc); + VkBuffer create_buffer(const VkBufferCreateInfo& desc); + VkImage create_image(const VkImageCreateInfo& desc); + VkImageView create_image_view(const VkImageViewCreateInfo& desc); + VkSampler create_sampler(const VkSamplerCreateInfo& desc); + VkRenderPass create_render_pass(const VkRenderPassCreateInfo& desc); + VkFramebuffer create_framebuffer(const VkFramebufferCreateInfo& desc); + VkDescriptorSetLayout create_descriptor_set_layout(const VkDescriptorSetLayoutCreateInfo& desc); + VkPipelineLayout create_pipeline_layout(const VkPipelineLayoutCreateInfo& desc); + VkPipeline create_graphics_pipeline(const VkGraphicsPipelineCreateInfo& desc); + +private: + VkDevice device = VK_NULL_HANDLE; + std::vector semaphores; + std::vector command_pools; + std::vector descriptor_pools; + std::vector buffers; + std::vector images; + std::vector image_views; + std::vector samplers; + std::vector descriptor_set_layouts; + std::vector render_passes; + std::vector framebuffers; + std::vector pipeline_layouts; + std::vector graphics_pipelines; +}; + +Resource_Manager* get_resource_manager(); diff --git a/source/engine/renderer/vk_utils.cpp b/source/engine/renderer/vk_utils.cpp new file mode 100644 index 0000000..37cbf0c --- /dev/null +++ b/source/engine/renderer/vk_utils.cpp @@ -0,0 +1,348 @@ +#include "vk_allocator.h" +#include "vk_resource_manager.h" +#include "vk_init.h" +#include "vk_utils.h" +#include +#include + +void check_vk_result(VkResult result, const std::string& functionName) { + if (result < 0) { + error(functionName + " has returned error code with value " + std::to_string(result)); + } +} + +void error(const std::string& message) { + std::cout << message << std::endl; + throw std::runtime_error(message); +} + +static std::vector read_binary_file_contents(const std::string& file_name) { + std::ifstream stream(file_name, std::ios_base::binary | std::ios_base::ate); + auto file_size = stream.tellg(); + stream.seekg(0, std::ios_base::beg); + + std::vector buffer(file_size); + stream.read(buffer.data(), file_size); + if (!stream) + throw std::runtime_error("failed to read file contents: " + file_name); + return buffer; +} + +Shader_Module::Shader_Module(const std::string& spirv_file_name) { + auto data = read_binary_file_contents(spirv_file_name); + if (data.size() % 4 != 0) + error("SPIR-V binary file size is not multiple of 4: " + spirv_file_name); + + VkShaderModuleCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.codeSize = data.size(); + desc.pCode = reinterpret_cast(data.data()); + + VkResult result = vkCreateShaderModule(get_device(), &desc, nullptr, &handle); + check_vk_result(result, "vkCreateShaderModule"); +} + +Shader_Module::~Shader_Module() { + vkDestroyShaderModule(get_device(), handle, nullptr); +} + +void record_and_run_commands(VkCommandPool command_pool, VkQueue queue, std::function recorder) { + + VkCommandBufferAllocateInfo alloc_info; + alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + alloc_info.pNext = nullptr; + alloc_info.commandPool = command_pool; + alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + alloc_info.commandBufferCount = 1; + + VkCommandBuffer command_buffer; + VkResult result = vkAllocateCommandBuffers(get_device(), &alloc_info, &command_buffer); + check_vk_result(result, "vkAllocateCommandBuffers"); + + VkCommandBufferBeginInfo begin_info; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.pNext = nullptr; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + begin_info.pInheritanceInfo = nullptr; + + result = vkBeginCommandBuffer(command_buffer, &begin_info); + check_vk_result(result, "vkBeginCommandBuffer"); + recorder(command_buffer); + result = vkEndCommandBuffer(command_buffer); + check_vk_result(result, "vkEndCommandBuffer"); + + VkSubmitInfo submit_info; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.pNext = nullptr; + submit_info.waitSemaphoreCount = 0; + submit_info.pWaitSemaphores = nullptr; + submit_info.pWaitDstStageMask = nullptr; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &command_buffer; + submit_info.signalSemaphoreCount = 0; + submit_info.pSignalSemaphores = nullptr; + + result = vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE); + check_vk_result(result, "vkQueueSubmit"); + result = vkQueueWaitIdle(queue); + check_vk_result(result, "vkQueueWaitIdle"); + vkFreeCommandBuffers(get_device(), command_pool, 1, &command_buffer); +} + +static bool has_depth_component(VkFormat format) { + switch (format) { + case VK_FORMAT_D16_UNORM: + case VK_FORMAT_X8_D24_UNORM_PACK32: + case VK_FORMAT_D32_SFLOAT: + case VK_FORMAT_D16_UNORM_S8_UINT: + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return true; + default: + return false; + } +} + +static bool has_stencil_component(VkFormat format) { + switch (format) { + case VK_FORMAT_S8_UINT: + case VK_FORMAT_D16_UNORM_S8_UINT: + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return true; + default: + return false; + } +} + +void record_image_layout_transition(VkCommandBuffer command_buffer, VkImage image, VkFormat format, + VkAccessFlags src_access_flags, VkImageLayout old_layout, VkAccessFlags dst_access_flags, VkImageLayout new_layout) { + + VkImageMemoryBarrier barrier; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.pNext = nullptr; + barrier.srcAccessMask = src_access_flags; + barrier.dstAccessMask = dst_access_flags; + barrier.oldLayout = old_layout; + barrier.newLayout = new_layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + + bool depth = has_depth_component(format); + bool stencil = has_stencil_component(format); + if (depth && stencil) + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + else if (depth) + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + else if (stencil) + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; + else + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + + vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, + 0, nullptr, 0, nullptr, 1, &barrier); +} + +VkImage create_texture(int image_width, int image_height, VkFormat format) { + VkImageCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.imageType = VK_IMAGE_TYPE_2D; + desc.format = format; + desc.extent.width = image_width; + desc.extent.height = image_height; + desc.extent.depth = 1; + desc.mipLevels = 1; + desc.arrayLayers = 1; + desc.samples = VK_SAMPLE_COUNT_1_BIT; + desc.tiling = VK_IMAGE_TILING_OPTIMAL; + desc.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + desc.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + desc.queueFamilyIndexCount = 0; + desc.pQueueFamilyIndices = nullptr; + desc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VkImage image = get_resource_manager()->create_image(desc); + + VkDeviceMemory memory = get_allocator()->allocate_memory(image); + VkResult result = vkBindImageMemory(get_device(), image, memory, 0); + check_vk_result(result, "vkBindImageMemory"); + return image; +} + +VkImage create_staging_texture(int image_width, int image_height, VkFormat format, const uint8_t* pixels, int bytes_per_pixel) { + VkImageCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.imageType = VK_IMAGE_TYPE_2D; + desc.format = format; + desc.extent.width = image_width; + desc.extent.height = image_height; + desc.extent.depth = 1; + desc.mipLevels = 1; + desc.arrayLayers = 1; + desc.samples = VK_SAMPLE_COUNT_1_BIT; + desc.tiling = VK_IMAGE_TILING_LINEAR; + desc.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + desc.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + desc.queueFamilyIndexCount = 0; + desc.pQueueFamilyIndices = nullptr; + desc.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + + VkImage image; + VkResult result = vkCreateImage(get_device(), &desc, nullptr, &image); + check_vk_result(result, "vkCreateImage"); + + get_allocator()->get_shared_staging_memory().ensure_allocation_for_object(image); + VkDeviceMemory memory = get_allocator()->get_shared_staging_memory().get_handle(); + result = vkBindImageMemory(get_device(), image, memory, 0); + check_vk_result(result, "vkBindImageMemory"); + + VkImageSubresource staging_image_subresource; + staging_image_subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + staging_image_subresource.mipLevel = 0; + staging_image_subresource.arrayLayer = 0; + VkSubresourceLayout staging_image_layout; + vkGetImageSubresourceLayout(get_device(), image, &staging_image_subresource, &staging_image_layout); + + void* data; + result = vkMapMemory(get_device(), memory, 0, staging_image_layout.size, 0, &data); + check_vk_result(result, "vkMapMemory"); + + const int bytes_per_row = image_width * bytes_per_pixel; + if (staging_image_layout.rowPitch == bytes_per_row) { + memcpy(data, pixels, bytes_per_row * image_height); + } else { + auto bytes = static_cast(data); + for (int i = 0; i < image_height; i++) { + memcpy(&bytes[i * staging_image_layout.rowPitch], &pixels[i * bytes_per_row], bytes_per_row); + } + } + vkUnmapMemory(get_device(), memory); + return image; +} + +VkImage create_depth_attachment_image(int image_width, int image_height, VkFormat format) { + VkImageCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.imageType = VK_IMAGE_TYPE_2D; + desc.format = format; + desc.extent.width = image_width; + desc.extent.height = image_height; + desc.extent.depth = 1; + desc.mipLevels = 1; + desc.arrayLayers = 1; + desc.samples = VK_SAMPLE_COUNT_1_BIT; + desc.tiling = VK_IMAGE_TILING_OPTIMAL; + desc.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + desc.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + desc.queueFamilyIndexCount = 0; + desc.pQueueFamilyIndices = nullptr; + desc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VkImage image = get_resource_manager()->create_image(desc); + + VkDeviceMemory memory = get_allocator()->allocate_memory(image); + VkResult result = vkBindImageMemory(get_device(), image, memory, 0); + check_vk_result(result, "vkBindImageMemory"); + return image; +} + +VkImageView create_image_view(VkImage image, VkFormat format, VkImageAspectFlags aspect_flags) { + VkImageViewCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.image = image; + desc.viewType = VK_IMAGE_VIEW_TYPE_2D; + desc.format = format; + desc.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + desc.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + desc.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + desc.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + desc.subresourceRange.aspectMask = aspect_flags; + desc.subresourceRange.baseMipLevel = 0; + desc.subresourceRange.levelCount = 1; + desc.subresourceRange.baseArrayLayer = 0; + desc.subresourceRange.layerCount = 1; + + return get_resource_manager()->create_image_view(desc); +} + +VkBuffer create_buffer(VkDeviceSize size, VkBufferUsageFlags usage) { + VkBufferCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.size = size; + desc.usage = usage; + desc.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + desc.queueFamilyIndexCount = 0; + desc.pQueueFamilyIndices = nullptr; + + VkBuffer buffer = get_resource_manager()->create_buffer(desc); + + VkDeviceMemory memory = get_allocator()->allocate_memory(buffer); + VkResult result = vkBindBufferMemory(get_device(), buffer, memory, 0); + check_vk_result(result, "vkBindBufferMemory"); + return buffer; +} + +VkBuffer create_staging_buffer(VkDeviceSize size, const void* data) { + VkBufferCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.size = size; + desc.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + desc.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + desc.queueFamilyIndexCount = 0; + desc.pQueueFamilyIndices = nullptr; + + VkBuffer buffer; + VkResult result = vkCreateBuffer(get_device(), &desc, nullptr, &buffer); + check_vk_result(result, "vkCreateBuffer"); + + get_allocator()->get_shared_staging_memory().ensure_allocation_for_object(buffer); + VkDeviceMemory memory = get_allocator()->get_shared_staging_memory().get_handle(); + result = vkBindBufferMemory(get_device(), buffer, memory, 0); + check_vk_result(result, "vkBindBufferMemory"); + + void* buffer_data; + result = vkMapMemory(get_device(), memory, 0, size, 0, &buffer_data); + check_vk_result(result, "vkMapMemory"); + memcpy(buffer_data, data, size); + vkUnmapMemory(get_device(), memory); + return buffer; +} + +VkBuffer create_permanent_staging_buffer(VkDeviceSize size, VkDeviceMemory& memory) { + VkBufferCreateInfo desc; + desc.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + desc.pNext = nullptr; + desc.flags = 0; + desc.size = size; + desc.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + desc.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + desc.queueFamilyIndexCount = 0; + desc.pQueueFamilyIndices = nullptr; + + VkBuffer buffer = get_resource_manager()->create_buffer(desc); + + memory = get_allocator()->allocate_staging_memory(buffer); + VkResult result = vkBindBufferMemory(get_device(), buffer, memory, 0); + check_vk_result(result, "vkBindBufferMemory"); + return buffer; +} diff --git a/source/engine/renderer/vk_utils.h b/source/engine/renderer/vk_utils.h new file mode 100644 index 0000000..7f5bc71 --- /dev/null +++ b/source/engine/renderer/vk_utils.h @@ -0,0 +1,43 @@ +#pragma once + +#include "vk_definitions.h" +#include +#include + +struct Defer_Action { + Defer_Action(std::function action) + : action(action) {} + ~Defer_Action() { + action(); + } + std::function action; +}; + +struct Shader_Module { + Shader_Module(const std::string& spriv_file_name); + ~Shader_Module(); + VkShaderModule handle; +}; + +// Errors +void check_vk_result(VkResult result, const std::string& function_name); +void error(const std::string& message); + +// Command buffers +void record_and_run_commands(VkCommandPool command_pool, VkQueue queue, std::function recorder); + +void record_image_layout_transition( + VkCommandBuffer command_buffer, VkImage image, VkFormat format, + VkAccessFlags src_access_flags, VkImageLayout old_layout, + VkAccessFlags dst_access_flags, VkImageLayout new_layout); + +// Images +VkImage create_texture(int image_width, int image_height, VkFormat format); +VkImage create_staging_texture(int image_width, int image_height, VkFormat format, const uint8_t* pixels, int bytes_per_pixel); +VkImage create_depth_attachment_image(int image_width, int image_height, VkFormat format); +VkImageView create_image_view(VkImage image, VkFormat format, VkImageAspectFlags aspect_flags); + +// Buffers +VkBuffer create_buffer(VkDeviceSize size, VkBufferUsageFlags usage); +VkBuffer create_staging_buffer(VkDeviceSize size, const void* data); +VkBuffer create_permanent_staging_buffer(VkDeviceSize size, VkDeviceMemory& memory);