From 6d49f022ecb32e09f9ff1faeeccf07c8cd60f970 Mon Sep 17 00:00:00 2001 From: bailwillharr Date: Mon, 29 May 2023 14:52:14 +0100 Subject: [PATCH] tidy up graphics code --- include/gfx_device.h | 10 + src/gfx_device_vulkan.cpp | 3246 +++++++++++++++++++------------------ src/resources/font.cpp | 2 +- src/resources/texture.cpp | 1 + test/src/game.cpp | 12 +- 5 files changed, 1683 insertions(+), 1588 deletions(-) diff --git a/include/gfx_device.h b/include/gfx_device.h index 13da9c6..2ad99fc 100644 --- a/include/gfx_device.h +++ b/include/gfx_device.h @@ -22,14 +22,22 @@ class GFXDevice { gfx::DrawBuffer* BeginRender(); + /* - draw_buffer MUST be a valid pointer returned by BeginRender(). + - draw_buffer is invalid after this function has been called. */ void FinishRender(gfx::DrawBuffer* draw_buffer); + /* - draw_buffer MUST be a valid pointer returned by BeginRender() + - pipeline MUST be a valid pointer returned by CreatePipeline() */ void CmdBindPipeline(gfx::DrawBuffer* draw_buffer, const gfx::Pipeline* pipeline); + /* - draw_buffer MUST be a valid pointer returned by BeginRender() + - buffer MUST be a valid pointer returned by CreateBuffer */ void CmdBindVertexBuffer(gfx::DrawBuffer* draw_buffer, uint32_t binding, const gfx::Buffer* buffer); + /* - draw_buffer MUST be a valid pointer returned by BeginRender() + - buffer MUST be a valid pointer returned by CreateBuffer */ void CmdBindIndexBuffer(gfx::DrawBuffer* draw_buffer, const gfx::Buffer* buffer); @@ -57,6 +65,8 @@ class GFXDevice { gfx::DescriptorSet* AllocateDescriptorSet( const gfx::DescriptorSetLayout* layout); + void FreeDescriptorSet(const gfx::DescriptorSet* set); + // This updates all copies of the descriptor. // This cannot be used after any frames have been renderered void UpdateDescriptorUniformBuffer(const gfx::DescriptorSet* set, diff --git a/src/gfx_device_vulkan.cpp b/src/gfx_device_vulkan.cpp index f616b49..d558f28 100644 --- a/src/gfx_device_vulkan.cpp +++ b/src/gfx_device_vulkan.cpp @@ -1,23 +1,24 @@ // The implementation of the graphics layer using Vulkan 1.3. /* IMPORTANT INFORMATION - * - * When allocating memory with VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT, always set a memory priority. - * This feature uses the device extension VK_EXT_memory_priority. Depth buffers have a priority of 1.0f. - * Other, non-essential allocations will have a priority of 0.5f. - * + * + * When allocating memory with VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT, + * always set a memory priority. This feature uses the device extension + * VK_EXT_memory_priority. Depth buffers have a priority of 1.0f. Other, + * non-essential allocations will have a priority of 0.5f. + * * Call vkResetCommandPool before reusing it in another frame. * Otherwise, the pool will keep on growing until you run out of memory. * - NVIDIA Vulkan Dos and Don'ts - * + * */ /* TODO * * - Support index buffers of both UINT16 and UINT32 types - * - Allow descriptor sets and layouts to also be combined texture samplers (maybe support other descriptor types?) + * - Allow textures, samplers, etc to be created after rendering has begun * - Use pipeline cache - * + * */ #include @@ -46,1584 +47,1665 @@ #include "log.h" #include "util/files.h" -inline static void checkVulkanError(VkResult errorCode, int lineNo) -{ - if (errorCode != VK_SUCCESS) { - const std::string message("VULKAN ERROR ON LINE " + std::to_string(lineNo)); - throw std::runtime_error(message.c_str()); - } +inline static void checkVulkanError(VkResult errorCode, int lineNo) { + if (errorCode != VK_SUCCESS) { + const std::string message("VULKAN ERROR ON LINE " + std::to_string(lineNo)); + throw std::runtime_error(message.c_str()); + } } #undef VKCHECK -#define VKCHECK(ErrCode) \ - checkVulkanError(ErrCode, __LINE__) +#define VKCHECK(ErrCode) checkVulkanError(ErrCode, __LINE__) namespace engine { - static constexpr uint32_t FRAMES_IN_FLIGHT = 2; // This improved FPS by 5x! (on Intel IGPU) - - static constexpr size_t PUSH_CONSTANT_MAX_SIZE = 128; // bytes - static constexpr VkIndexType INDEX_TYPE = VK_INDEX_TYPE_UINT32; - - // structures and enums - - struct FrameData { - VkFence renderFence = VK_NULL_HANDLE; - VkSemaphore transferSemaphore = VK_NULL_HANDLE; - VkSemaphore renderSemaphore = VK_NULL_HANDLE; - VkSemaphore presentSemaphore = VK_NULL_HANDLE; - - VkCommandPool graphicsPool = VK_NULL_HANDLE; - VkCommandBuffer drawBuf = VK_NULL_HANDLE; - - VkCommandPool transferPool = VK_NULL_HANDLE; - VkCommandBuffer transferBuf = VK_NULL_HANDLE; - }; - - // handles - - struct gfx::Buffer { - gfx::BufferType type; - VkBuffer buffer = VK_NULL_HANDLE; - VmaAllocation allocation = nullptr; - VkDeviceSize size = 0; - }; - - struct gfx::Pipeline { - VkPipelineLayout layout = VK_NULL_HANDLE; - VkPipeline handle = VK_NULL_HANDLE; - }; - - struct gfx::Image { - VkImage image = VK_NULL_HANDLE; - VkImageView view = VK_NULL_HANDLE; - VmaAllocation allocation = VK_NULL_HANDLE; - }; - - struct gfx::Sampler { - VkSampler sampler = VK_NULL_HANDLE; - }; - - struct gfx::DrawBuffer { - FrameData frameData{}; - uint32_t currentFrameIndex = 0; // corresponds to the frameData - uint32_t imageIndex = 0; // for swapchain present - }; - - struct gfx::DescriptorSetLayout { - VkDescriptorSetLayout layout; - }; - - struct gfx::DescriptorSet { - std::array sets; // frames in flight cannot use the same descriptor set in case the buffer needs updating - }; - - struct gfx::UniformBuffer { - gfx::Buffer stagingBuffer{}; - std::array gpuBuffers; - }; - - // enum converters - - namespace converters { - - static VkFormat getVertexAttribFormat(gfx::VertexAttribFormat fmt) - { - switch (fmt) { - case gfx::VertexAttribFormat::kFloat2: - return VK_FORMAT_R32G32_SFLOAT; - case gfx::VertexAttribFormat::kFloat3: - return VK_FORMAT_R32G32B32_SFLOAT; - case gfx::VertexAttribFormat::kFloat4: - return VK_FORMAT_R32G32B32A32_SFLOAT; - } - throw std::runtime_error("Unknown vertex attribute format"); - } - - static VkBufferUsageFlagBits getBufferUsageFlag(gfx::BufferType type) - { - switch (type) { - case gfx::BufferType::kVertex: - return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - case gfx::BufferType::kIndex: - return VK_BUFFER_USAGE_INDEX_BUFFER_BIT; - case gfx::BufferType::kUniform: - return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; - default: - throw std::runtime_error("This buffer type does not have usage bits"); - } - } - - [[maybe_unused]] static VkFilter getFilter(gfx::Filter filter) - { - switch (filter) { - case gfx::Filter::kLinear: - return VK_FILTER_LINEAR; - case gfx::Filter::kNearest: - return VK_FILTER_NEAREST; - } - throw std::runtime_error("Unknown filter"); - } - - [[maybe_unused]] static VkSamplerMipmapMode getSamplerMipmapMode(gfx::Filter filter) - { - switch (filter) { - case gfx::Filter::kLinear: - return VK_SAMPLER_MIPMAP_MODE_LINEAR; - case gfx::Filter::kNearest: - return VK_SAMPLER_MIPMAP_MODE_NEAREST; - } - throw std::runtime_error("Unknown filter"); - } - - [[maybe_unused]] static VkSampleCountFlags getSampleCountFlags(gfx::MSAALevel level) - { - switch (level) { - case gfx::MSAALevel::kOff: - return VK_SAMPLE_COUNT_1_BIT; - case gfx::MSAALevel::k2X: - return VK_SAMPLE_COUNT_2_BIT; - case gfx::MSAALevel::k4X: - return VK_SAMPLE_COUNT_4_BIT; - case gfx::MSAALevel::k8X: - return VK_SAMPLE_COUNT_8_BIT; - case gfx::MSAALevel::k16X: - return VK_SAMPLE_COUNT_16_BIT; - default: - throw std::runtime_error("Unknown MSAA level"); - } - } - - static VkDescriptorType getDescriptorType(gfx::DescriptorType type) - { - switch (type) { - case gfx::DescriptorType::kUniformBuffer: - return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - case gfx::DescriptorType::kCombinedImageSampler: - return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - default: - throw std::runtime_error("Unknown descriptor type"); - } - } - - static VkShaderStageFlags getShaderStageFlags(gfx::ShaderStageFlags::Flags flags) - { - VkShaderStageFlags out = 0; - if (flags & gfx::ShaderStageFlags::kVertex) out |= VK_SHADER_STAGE_VERTEX_BIT; - if (flags & gfx::ShaderStageFlags::kFragment) out |= VK_SHADER_STAGE_FRAGMENT_BIT; - return out; - } - - [[maybe_unused]] static VkCullModeFlags getCullModeFlags(gfx::CullMode mode) { - switch (mode) { - case gfx::CullMode::kCullNone: - return VK_CULL_MODE_NONE; - case gfx::CullMode::kCullFront: - return VK_CULL_MODE_FRONT_BIT; - case gfx::CullMode::kCullBack: - return VK_CULL_MODE_BACK_BIT; - case gfx::CullMode::kCullFrontAndBack: - return VK_CULL_MODE_FRONT_AND_BACK; - } - } - - } - - // functions - - static VkShaderModule compileShader(VkDevice device, shaderc_shader_kind kind, const std::string& source, const char* filename) - { - - shaderc::Compiler compiler; - shaderc::CompileOptions options; - - options.SetSourceLanguage(shaderc_source_language_glsl); - options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_3); - options.SetOptimizationLevel(shaderc_optimization_level_performance); - options.SetTargetSpirv(shaderc_spirv_version_1_6); - options.SetAutoBindUniforms(false); - - // preprocess - shaderc::PreprocessedSourceCompilationResult preprocessed = compiler.PreprocessGlsl(source, kind, filename, options); - - if (preprocessed.GetCompilationStatus() != shaderc_compilation_status_success) - { - throw std::runtime_error("PREPROCESS ERR " + preprocessed.GetErrorMessage()); - } - - std::string shaderStr{ preprocessed.cbegin(), preprocessed.cend() }; - - // compile - shaderc::SpvCompilationResult compiledShader = compiler.CompileGlslToSpv(shaderStr.c_str(), kind, filename, options); - - if (compiledShader.GetCompilationStatus() != shaderc_compilation_status_success) - { - throw std::runtime_error("COMPILE ERR " + compiledShader.GetErrorMessage()); - } - - std::vector shaderBytecode = { compiledShader.cbegin(), compiledShader.cend() };// not sure why sample code copy vector like this - - VkShaderModuleCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - createInfo.codeSize = shaderBytecode.size() * sizeof(uint32_t); - createInfo.pCode = compiledShader.cbegin(); - - VkShaderModule shaderModule; - if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { - throw std::runtime_error("Failed to create shader module!"); - } - - return shaderModule; - - } - - static void copyBuffer(VkDevice device, VkCommandPool commandPool, VkQueue queue, VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) - { - [[maybe_unused]] VkResult res; - - VkCommandBufferAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandPool = commandPool; - allocInfo.commandBufferCount = 1; - - VkCommandBuffer commandBuffer; - res = vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); - assert(res == VK_SUCCESS); - - { // record the command buffer - VkCommandBufferBeginInfo beginInfo{}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - res = vkBeginCommandBuffer(commandBuffer, &beginInfo); - assert(res == VK_SUCCESS); - - VkBufferCopy copyRegion{}; - copyRegion.srcOffset = 0; - copyRegion.dstOffset = 0; - copyRegion.size = size; - vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); - - res = vkEndCommandBuffer(commandBuffer); - assert(res == VK_SUCCESS); - } - - // submit - VkSubmitInfo submitInfo{}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffer; - - res = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); - assert(res == VK_SUCCESS); - - res = vkQueueWaitIdle(queue); - assert(res == VK_SUCCESS); - - vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); - - } - - // class definitions - - struct GFXDevice::Impl { - - // device settings - gfx::GraphicsSettings graphicsSettings; - - SDL_Window* window = nullptr; - Instance instance{}; - VkSurfaceKHR surface = VK_NULL_HANDLE; - Device device{}; - VmaAllocator allocator{}; - SwapchainInfo swapchainInfo{}; - Swapchain swapchain{}; - - VkDescriptorPool descriptorPool; - std::array, FRAMES_IN_FLIGHT> uniformBufferWriteQueues{}; - - // For one-off transfer operations not bound to a specific frame-in-flight - VkCommandPool transferCommandPool = VK_NULL_HANDLE; - // For one-off operation on the draw queue family not bound to a specific frame-in-flight - VkCommandPool graphicsCommandPool = VK_NULL_HANDLE; - - uint64_t FRAMECOUNT = 0; - - FrameData frameData[FRAMES_IN_FLIGHT] = {}; - - bool swapchainIsOutOfDate = false; - - }; - - GFXDevice::GFXDevice(const char* appName, const char* appVersion, SDL_Window* window, gfx::GraphicsSettings settings) - { - - pimpl = std::make_unique(); - - VkResult res; - - pimpl->window = window; - pimpl->graphicsSettings = settings; - - // initialise vulkan - - res = volkInitialize(); - if (res != VK_SUCCESS) { - throw std::runtime_error("Unable to load vulkan, is it installed?"); - } - - uint32_t vulkanVersion = volkGetInstanceVersion(); - assert(vulkanVersion != 0); - if (vulkanVersion < VK_API_VERSION_1_3) { - throw std::runtime_error("The loaded Vulkan version must be at least 1.3"); - } - - pimpl->instance = createVulkanInstance(pimpl->window, appName, appVersion, pimpl->graphicsSettings.enable_validation, MessageSeverity::SEV_WARNING); - - if (SDL_Vulkan_CreateSurface(pimpl->window, pimpl->instance.instance, &pimpl->surface) == false) { - throw std::runtime_error("Unable to create window surface"); - }; - - DeviceRequirements deviceRequirements{}; - deviceRequirements.requiredExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); - deviceRequirements.optionalExtensions.push_back(VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME); - deviceRequirements.optionalExtensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); - deviceRequirements.requiredFeatures.samplerAnisotropy = VK_TRUE; - //deviceRequirements.requiredFeatures.fillModeNonSolid = VK_TRUE; - deviceRequirements.formats.push_back( - FormatRequirements{ - .format = VK_FORMAT_R8G8B8A8_SRGB, - .properties = VkFormatProperties{ - .linearTilingFeatures = {}, - .optimalTilingFeatures = - VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | - VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT, - .bufferFeatures = {}, - } - } - ); - deviceRequirements.formats.push_back( - FormatRequirements{ - .format = VK_FORMAT_R32G32_SFLOAT, - .properties = VkFormatProperties{ - .linearTilingFeatures = {}, - .optimalTilingFeatures = {}, - .bufferFeatures = VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT, - } - } - ); - deviceRequirements.formats.push_back( - FormatRequirements{ - .format = VK_FORMAT_R32G32B32_SFLOAT, - .properties = VkFormatProperties{ - .linearTilingFeatures = {}, - .optimalTilingFeatures = {}, - .bufferFeatures = VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT, - } - } - ); - deviceRequirements.formats.push_back( - FormatRequirements{ - .format = VK_FORMAT_R32G32B32A32_SFLOAT, - .properties = VkFormatProperties{ - .linearTilingFeatures = {}, - .optimalTilingFeatures = {}, - .bufferFeatures = VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT, - } - } - ); - deviceRequirements.formats.push_back( // Depth buffer format - FormatRequirements{ - .format = VK_FORMAT_D16_UNORM, - .properties = VkFormatProperties{ - .linearTilingFeatures = {}, - .optimalTilingFeatures = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, - .bufferFeatures = {}, - } - } - ); - - pimpl->device = createDevice(pimpl->instance.instance, deviceRequirements, pimpl->surface); - - pimpl->allocator = createAllocator(pimpl->instance.instance, pimpl->device); - - pimpl->swapchainInfo.device = pimpl->device.device; - pimpl->swapchainInfo.allocator = pimpl->allocator; - pimpl->swapchainInfo.physicalDevice = pimpl->device.physicalDevice; - pimpl->swapchainInfo.surface = pimpl->surface; - pimpl->swapchainInfo.window = pimpl->window; - pimpl->swapchainInfo.vsync = pimpl->graphicsSettings.vsync; - pimpl->swapchainInfo.waitForPresent = pimpl->graphicsSettings.wait_for_present; - createSwapchain(&pimpl->swapchain, pimpl->swapchainInfo); - - /* make synchronisation primitives for rendering and allocate command buffers */ - - for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { - VkFenceCreateInfo fenceInfo{ - .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, - .pNext = nullptr, - .flags = VK_FENCE_CREATE_SIGNALED_BIT - }; - res = vkCreateFence(pimpl->device.device, &fenceInfo, nullptr, &pimpl->frameData[i].renderFence); - if (res != VK_SUCCESS) throw std::runtime_error("Failed to create fence!"); - - VkSemaphoreCreateInfo smphInfo{ - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, - .pNext = nullptr, - .flags = 0 - }; - VKCHECK(vkCreateSemaphore(pimpl->device.device, &smphInfo, nullptr, &pimpl->frameData[i].transferSemaphore)); - VKCHECK(vkCreateSemaphore(pimpl->device.device, &smphInfo, nullptr, &pimpl->frameData[i].renderSemaphore)); - VKCHECK(vkCreateSemaphore(pimpl->device.device, &smphInfo, nullptr, &pimpl->frameData[i].presentSemaphore)); - - VkCommandPoolCreateInfo poolInfo{ - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .pNext = nullptr, - .flags = 0, // Command buffers cannot be individually reset (more performant this way) - .queueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily - }; - VKCHECK(vkCreateCommandPool(pimpl->device.device, &poolInfo, nullptr, &pimpl->frameData[i].graphicsPool)); - poolInfo.queueFamilyIndex = pimpl->device.queues.transferQueueFamily; - VKCHECK(vkCreateCommandPool(pimpl->device.device, &poolInfo, nullptr, &pimpl->frameData[i].transferPool)); - - VkCommandBufferAllocateInfo cmdAllocInfo{ - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .pNext = nullptr, - .commandPool = pimpl->frameData[i].graphicsPool, - .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, - .commandBufferCount = 1 - }; - VKCHECK(vkAllocateCommandBuffers(pimpl->device.device, &cmdAllocInfo, &pimpl->frameData[i].drawBuf)); - cmdAllocInfo.commandPool = pimpl->frameData[i].transferPool; - VKCHECK(vkAllocateCommandBuffers(pimpl->device.device, &cmdAllocInfo, &pimpl->frameData[i].transferBuf)); - } - - /* create command pool for one-off transfer operations */ - VkCommandPoolCreateInfo transferPoolInfo{ - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .pNext = nullptr, - .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, // These command buffers don't last very long - .queueFamilyIndex = pimpl->device.queues.transferQueueFamily - }; - VKCHECK(vkCreateCommandPool(pimpl->device.device, &transferPoolInfo, nullptr, &pimpl->transferCommandPool)); - /* create command pool for one-off draw queue operations */ - VkCommandPoolCreateInfo graphicsPoolInfo{ - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .pNext = nullptr, - .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, // These command buffers don't last very long - .queueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily - }; - VKCHECK(vkCreateCommandPool(pimpl->device.device, &graphicsPoolInfo, nullptr, &pimpl->graphicsCommandPool)); - - /* create a global descriptor pool */ - std::vector poolSizes{}; - poolSizes.push_back({ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 100u }); - poolSizes.push_back({ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 100u }); - - VkDescriptorPoolCreateInfo descriptorPoolInfo{}; - descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - descriptorPoolInfo.pNext = nullptr; - descriptorPoolInfo.flags = 0; - descriptorPoolInfo.maxSets = 1000u; - descriptorPoolInfo.poolSizeCount = (uint32_t)poolSizes.size(); - descriptorPoolInfo.pPoolSizes = poolSizes.data(); - VKCHECK(vkCreateDescriptorPool(pimpl->device.device, &descriptorPoolInfo, nullptr, &pimpl->descriptorPool)); - - } - - GFXDevice::~GFXDevice() - { - vkDestroyDescriptorPool(pimpl->device.device, pimpl->descriptorPool, nullptr); - - vkDestroyCommandPool(pimpl->device.device, pimpl->graphicsCommandPool, nullptr); - vkDestroyCommandPool(pimpl->device.device, pimpl->transferCommandPool, nullptr); - - for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { - vkDestroyCommandPool(pimpl->device.device, pimpl->frameData[i].transferPool, nullptr); - vkDestroyCommandPool(pimpl->device.device, pimpl->frameData[i].graphicsPool, nullptr); - vkDestroySemaphore(pimpl->device.device, pimpl->frameData[i].presentSemaphore, nullptr); - vkDestroySemaphore(pimpl->device.device, pimpl->frameData[i].renderSemaphore, nullptr); - vkDestroySemaphore(pimpl->device.device, pimpl->frameData[i].transferSemaphore, nullptr); - vkDestroyFence(pimpl->device.device, pimpl->frameData[i].renderFence, nullptr); - } - - destroySwapchain(pimpl->swapchain); - destroyAllocator(pimpl->allocator); - destroyDevice(pimpl->device); - vkDestroySurfaceKHR(pimpl->instance.instance, pimpl->surface, nullptr); - destroyVulkanInstance(pimpl->instance); - } - - void GFXDevice::GetViewportSize(uint32_t* w, uint32_t* h) - { - int width, height; - SDL_Vulkan_GetDrawableSize(pimpl->window, &width, &height); - if (width == 0 || height == 0) { - *w = (uint32_t)pimpl->swapchain.extent.width; - *h = (uint32_t)pimpl->swapchain.extent.height; - } - else { - *w = (uint32_t)width; - *h = (uint32_t)height; - } - } - - gfx::DrawBuffer* GFXDevice::BeginRender() - { - VkResult res; - - if (pimpl->FRAMECOUNT == 0) { - // perform clean-up before any rendering - vkResetCommandPool(pimpl->device.device, pimpl->transferCommandPool, VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT); - vkResetCommandPool(pimpl->device.device, pimpl->graphicsCommandPool, VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT); - } - - const uint32_t currentFrameIndex = pimpl->FRAMECOUNT % FRAMES_IN_FLIGHT; - const FrameData frameData = pimpl->frameData[currentFrameIndex]; - - vmaSetCurrentFrameIndex(pimpl->allocator, (uint32_t)pimpl->FRAMECOUNT); - - /* wait until the previous frame RENDERING has finished */ - res = vkWaitForFences(pimpl->device.device, 1, &frameData.renderFence, VK_TRUE, 1000000000LL); - VKCHECK(res); - res = vkResetFences(pimpl->device.device, 1, &frameData.renderFence); - VKCHECK(res); - - /* perform any pending uniform buffer writes */ - VKCHECK(vkResetCommandPool(pimpl->device.device, frameData.transferPool, 0)); - - VkCommandBufferBeginInfo transferBeginInfo{ - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - .pNext = nullptr, - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, - .pInheritanceInfo = nullptr // ignored - }; - VKCHECK(vkBeginCommandBuffer(frameData.transferBuf, &transferBeginInfo)); - - // transfer cmds... - - std::vector barriers{}; - for (gfx::UniformBuffer* uniformBuffer : pimpl->uniformBufferWriteQueues[currentFrameIndex]) { - - VkBufferCopy copyRegion{}; - copyRegion.srcOffset = 0; - copyRegion.dstOffset = 0; - copyRegion.size = uniformBuffer->stagingBuffer.size; - vkCmdCopyBuffer( - frameData.transferBuf, - uniformBuffer->stagingBuffer.buffer, - uniformBuffer->gpuBuffers[currentFrameIndex].buffer, - 1, - ©Region - ); - - VkBufferMemoryBarrier2& barrier = barriers.emplace_back(); - barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; - barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; - barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; - barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; - barrier.dstAccessMask = 0; - barrier.srcQueueFamilyIndex = pimpl->device.queues.transferQueueFamily; - barrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; - barrier.buffer = uniformBuffer->gpuBuffers[currentFrameIndex].buffer; - barrier.offset = 0; - barrier.size = uniformBuffer->gpuBuffers[currentFrameIndex].size; - } - pimpl->uniformBufferWriteQueues[currentFrameIndex].clear(); - - VkDependencyInfo dependencyInfo{}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - dependencyInfo.bufferMemoryBarrierCount = (uint32_t)barriers.size(); - dependencyInfo.pBufferMemoryBarriers = barriers.data(); - vkCmdPipelineBarrier2(frameData.transferBuf, &dependencyInfo); - - VKCHECK(vkEndCommandBuffer(frameData.transferBuf)); - - VkSubmitInfo transferSubmitInfo{ - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .pNext = nullptr, - .waitSemaphoreCount = 0, // needs to wait for render but the fence does that - .pWaitSemaphores = nullptr, - .pWaitDstStageMask = nullptr, - .commandBufferCount = 1, - .pCommandBuffers = &frameData.transferBuf, - .signalSemaphoreCount = 1, - .pSignalSemaphores = &frameData.transferSemaphore, - }; - res = vkQueueSubmit(pimpl->device.queues.transferQueues[0], 1, &transferSubmitInfo, VK_NULL_HANDLE); - assert(res == VK_SUCCESS); - - uint32_t swapchainImageIndex; - - do { - if (pimpl->swapchainIsOutOfDate) { - // re-create swapchain - vkQueueWaitIdle(pimpl->device.queues.drawQueues[0]); - vkQueueWaitIdle(pimpl->device.queues.presentQueue); - createSwapchain(&pimpl->swapchain, pimpl->swapchainInfo); - } - // THIS FUNCTION BLOCKS UNTIL AN IMAGE IS AVAILABLE (it waits for vsync) - res = vkAcquireNextImageKHR( - pimpl->device.device, pimpl->swapchain.swapchain, 1000000000LL, - frameData.presentSemaphore, VK_NULL_HANDLE, &swapchainImageIndex); - if (res != VK_SUBOPTIMAL_KHR && res != VK_ERROR_OUT_OF_DATE_KHR) VKCHECK(res); - if (res == VK_SUCCESS) pimpl->swapchainIsOutOfDate = false; - } while (pimpl->swapchainIsOutOfDate); - - /* record command buffer */ - res = vkResetCommandPool(pimpl->device.device, frameData.graphicsPool, 0); - VKCHECK(res); - - VkCommandBufferBeginInfo beginInfo{ - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - .pNext = nullptr, - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, - .pInheritanceInfo = nullptr // ignored - }; - res = vkBeginCommandBuffer(frameData.drawBuf, &beginInfo); - VKCHECK(res); - - { // RECORDING - - /* change barriers to perform a queue ownership acquire operation */ - for (VkBufferMemoryBarrier2& barrier : barriers) { - barrier.srcStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT; - barrier.srcAccessMask = 0; - barrier.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT; - barrier.dstAccessMask = VK_ACCESS_2_UNIFORM_READ_BIT; - } - vkCmdPipelineBarrier2(frameData.drawBuf, &dependencyInfo); - - std::array clearValues{}; // Using same value for all components enables compression according to NVIDIA Best Practices - clearValues[0].color.float32[0] = 1.0f; - clearValues[0].color.float32[1] = 1.0f; - clearValues[0].color.float32[2] = 1.0f; - clearValues[0].color.float32[3] = 1.0f; - clearValues[1].depthStencil.depth = 1.0f; - - VkRenderPassBeginInfo passBegin{}; - passBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - passBegin.pNext = nullptr; - passBegin.renderPass = pimpl->swapchain.renderpass; - passBegin.framebuffer = pimpl->swapchain.framebuffers[swapchainImageIndex]; - passBegin.renderArea.extent = pimpl->swapchain.extent; - passBegin.renderArea.offset = { 0, 0 }; - passBegin.clearValueCount = (uint32_t)clearValues.size(); - passBegin.pClearValues = clearValues.data(); - vkCmdBeginRenderPass(frameData.drawBuf, &passBegin, VK_SUBPASS_CONTENTS_INLINE); - - VkViewport viewport{}; - viewport.x = 0.0f; - viewport.y = (float)pimpl->swapchain.extent.height; - viewport.width = (float)pimpl->swapchain.extent.width; - viewport.height = -(float)pimpl->swapchain.extent.height; - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - vkCmdSetViewport(frameData.drawBuf, 0, 1, &viewport); - - VkRect2D scissor{}; - scissor.offset = { 0, 0 }; - scissor.extent = pimpl->swapchain.extent; - vkCmdSetScissor(frameData.drawBuf, 0, 1, &scissor); - - } - - // hand command buffer over to caller - gfx::DrawBuffer* drawBuffer = new gfx::DrawBuffer; - drawBuffer->frameData = frameData; - drawBuffer->currentFrameIndex = currentFrameIndex; - drawBuffer->imageIndex = swapchainImageIndex; - return drawBuffer; - - } - - void GFXDevice::FinishRender(gfx::DrawBuffer* drawBuffer) - { - assert(drawBuffer != nullptr); - - uint32_t swapchainImageIndex = drawBuffer->imageIndex; - VkResult res; - - vkCmdEndRenderPass(drawBuffer->frameData.drawBuf); - - res = vkEndCommandBuffer(drawBuffer->frameData.drawBuf); - VKCHECK(res); - - // SUBMIT - - std::vector waitSemaphores{}; - std::vector waitDstStageMasks{}; - - waitSemaphores.push_back(drawBuffer->frameData.presentSemaphore); - waitDstStageMasks.push_back(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); - waitSemaphores.push_back(drawBuffer->frameData.transferSemaphore); - waitDstStageMasks.push_back(VK_PIPELINE_STAGE_VERTEX_SHADER_BIT); - - VkSubmitInfo submitInfo{ - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .pNext = nullptr, - .waitSemaphoreCount = (uint32_t)waitSemaphores.size(), - .pWaitSemaphores = waitSemaphores.data(), - .pWaitDstStageMask = waitDstStageMasks.data(), - .commandBufferCount = 1, - .pCommandBuffers = &drawBuffer->frameData.drawBuf, - .signalSemaphoreCount = 1, - .pSignalSemaphores = &drawBuffer->frameData.renderSemaphore, - }; - res = vkQueueSubmit(pimpl->device.queues.drawQueues[0], 1, &submitInfo, drawBuffer->frameData.renderFence); - assert(res == VK_SUCCESS); - // VKCHECK(res); // expensive operation for some reason - - // PRESENT - - VkPresentInfoKHR presentInfo{ - .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .pNext = nullptr, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &drawBuffer->frameData.renderSemaphore, - .swapchainCount = 1, - .pSwapchains = &pimpl->swapchain.swapchain, - .pImageIndices = &swapchainImageIndex, - .pResults = nullptr - }; - res = vkQueuePresentKHR(pimpl->device.queues.presentQueue, &presentInfo); - if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) { - // flag to re-create the swapchain before next render - pimpl->swapchainIsOutOfDate = true; - } - else if (res != VK_SUCCESS) throw std::runtime_error("Failed to queue present! Code: " + std::to_string(res)); - - pimpl->FRAMECOUNT++; - - delete drawBuffer; - } - - void GFXDevice::CmdBindPipeline(gfx::DrawBuffer* drawBuffer, const gfx::Pipeline* pipeline) - { - assert(drawBuffer != nullptr); - vkCmdBindPipeline(drawBuffer->frameData.drawBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->handle); - } - - void GFXDevice::CmdBindVertexBuffer(gfx::DrawBuffer* drawBuffer, uint32_t binding, const gfx::Buffer* buffer) - { - assert(drawBuffer != nullptr); - assert(buffer != nullptr); - assert(buffer->type == gfx::BufferType::kVertex); - const VkDeviceSize offset = 0; - vkCmdBindVertexBuffers(drawBuffer->frameData.drawBuf, binding, 1, &buffer->buffer, &offset); - } - - void GFXDevice::CmdBindIndexBuffer(gfx::DrawBuffer* drawBuffer, const gfx::Buffer* buffer) - { - assert(drawBuffer != nullptr); - assert(buffer != nullptr); - assert(buffer->type == gfx::BufferType::kIndex); - vkCmdBindIndexBuffer(drawBuffer->frameData.drawBuf, buffer->buffer, 0, INDEX_TYPE); - } - - void GFXDevice::CmdDrawIndexed(gfx::DrawBuffer* drawBuffer, uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex, int32_t vertexOffset, uint32_t firstInstance) - { - assert(drawBuffer != nullptr); - vkCmdDrawIndexed(drawBuffer->frameData.drawBuf, indexCount, instanceCount, firstIndex, vertexOffset, firstInstance); - } - - void GFXDevice::CmdPushConstants(gfx::DrawBuffer* drawBuffer, const gfx::Pipeline* pipeline, uint32_t offset, uint32_t size, const void* data) - { - assert(drawBuffer != nullptr); - vkCmdPushConstants(drawBuffer->frameData.drawBuf, pipeline->layout, VK_SHADER_STAGE_VERTEX_BIT, offset, size, data); - } - - void GFXDevice::CmdBindDescriptorSet(gfx::DrawBuffer* drawBuffer, const gfx::Pipeline* pipeline, const gfx::DescriptorSet* set, uint32_t setNumber) - { - vkCmdBindDescriptorSets(drawBuffer->frameData.drawBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->layout, setNumber, 1, &set->sets[drawBuffer->currentFrameIndex], 0, nullptr); - } - - gfx::Pipeline* GFXDevice::CreatePipeline(const gfx::PipelineInfo& info) - { - - [[maybe_unused]] VkResult res; - - gfx::Pipeline* pipeline = new gfx::Pipeline; - - auto vertShaderCode = util::ReadTextFile(info.vert_shader_path); - auto fragShaderCode = util::ReadTextFile(info.frag_shader_path); - - VkShaderModule vertShaderModule = compileShader(pimpl->device.device, shaderc_vertex_shader, vertShaderCode->data(), info.vert_shader_path); - VkShaderModule fragShaderModule = compileShader(pimpl->device.device, shaderc_fragment_shader, fragShaderCode->data(), info.frag_shader_path); - - // get vertex attrib layout: - VkVertexInputBindingDescription bindingDescription{ }; - bindingDescription.binding = 0; - bindingDescription.stride = info.vertex_format.stride; - bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - - std::vector attribDescs{}; - attribDescs.reserve(info.vertex_format.attribute_descriptions.size()); - for (const auto& desc : info.vertex_format.attribute_descriptions) { - VkVertexInputAttributeDescription vulkanAttribDesc{}; - vulkanAttribDesc.location = desc.location; - vulkanAttribDesc.binding = 0; - vulkanAttribDesc.format = converters::getVertexAttribFormat(desc.format); - vulkanAttribDesc.offset = desc.offset; - attribDescs.push_back(vulkanAttribDesc); - } - - VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; - vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; - vertShaderStageInfo.module = vertShaderModule; - vertShaderStageInfo.pName = "main"; - vertShaderStageInfo.pSpecializationInfo = nullptr; - - VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; - fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; - fragShaderStageInfo.module = fragShaderModule; - fragShaderStageInfo.pName = "main"; - fragShaderStageInfo.pSpecializationInfo = nullptr; - - VkPipelineShaderStageCreateInfo shaderStages[2] = { vertShaderStageInfo, fragShaderStageInfo }; - - // set the vertex input layout - VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; - vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; - vertexInputInfo.vertexAttributeDescriptionCount = (uint32_t)attribDescs.size(); - vertexInputInfo.pVertexAttributeDescriptions = attribDescs.data(); - - VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; - inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - inputAssembly.primitiveRestartEnable = VK_FALSE; - - VkViewport viewport{}; - viewport.x = 0.0f; - viewport.y = (float)pimpl->swapchain.extent.height; - viewport.width = (float)pimpl->swapchain.extent.width; - viewport.height = -(float)pimpl->swapchain.extent.height; - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - - VkRect2D scissor{}; - scissor.offset = { 0, 0 }; - scissor.extent = pimpl->swapchain.extent; - - // Dynamic states removes the need to re-create pipelines whenever the window size changes - std::vector dynamicStates = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - - VkPipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamicState.dynamicStateCount = (uint32_t)dynamicStates.size(); - dynamicState.pDynamicStates = dynamicStates.data(); - - VkPipelineViewportStateCreateInfo viewportState{}; - viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportState.viewportCount = 1; - viewportState.pViewports = &viewport; - viewportState.scissorCount = 1; - viewportState.pScissors = &scissor; - - VkPipelineRasterizationStateCreateInfo rasterizer{}; - rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizer.depthClampEnable = VK_FALSE; - rasterizer.rasterizerDiscardEnable = VK_FALSE; // enabling this will not run the fragment shaders at all - rasterizer.polygonMode = VK_POLYGON_MODE_FILL; - rasterizer.lineWidth = 1.0f; - if (info.backface_culling == true) { - rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; - } - else { - rasterizer.cullMode = VK_CULL_MODE_NONE; - } - rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; - rasterizer.depthBiasEnable = VK_FALSE; - rasterizer.depthBiasConstantFactor = 0.0f; // ignored - rasterizer.depthBiasClamp = 0.0f; // ignored - rasterizer.depthBiasSlopeFactor = 0.0f; // ignored - - VkPipelineMultisampleStateCreateInfo multisampling{}; - multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - multisampling.sampleShadingEnable = VK_FALSE; - multisampling.minSampleShading = 1.0f; // ignored - multisampling.pSampleMask = nullptr; // ignored - multisampling.alphaToCoverageEnable = VK_FALSE; // ignored - multisampling.alphaToOneEnable = VK_FALSE; // ignored - - VkPipelineColorBlendAttachmentState colorBlendAttachment{}; - colorBlendAttachment.colorWriteMask = - VK_COLOR_COMPONENT_R_BIT | - VK_COLOR_COMPONENT_G_BIT | - VK_COLOR_COMPONENT_B_BIT | - VK_COLOR_COMPONENT_A_BIT; - if (info.alpha_blending) { - colorBlendAttachment.blendEnable = VK_TRUE; - colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; - colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; - colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; - colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; - colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; - } - else { - colorBlendAttachment.blendEnable = VK_FALSE; - } - - VkPipelineColorBlendStateCreateInfo colorBlending{}; - colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlending.logicOpEnable = VK_FALSE; - colorBlending.logicOp = VK_LOGIC_OP_COPY; // ignored - colorBlending.attachmentCount = 1; - colorBlending.pAttachments = &colorBlendAttachment; - colorBlending.blendConstants[0] = 0.0f; // ignored - colorBlending.blendConstants[1] = 0.0f; // ignored - colorBlending.blendConstants[2] = 0.0f; // ignored - colorBlending.blendConstants[3] = 0.0f; // ignored - - VkPipelineDepthStencilStateCreateInfo depthStencil{}; - depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; - depthStencil.depthTestEnable = VK_TRUE; - depthStencil.depthWriteEnable = info.write_z ? VK_TRUE : VK_FALSE; - depthStencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; - depthStencil.depthBoundsTestEnable = VK_FALSE; - depthStencil.minDepthBounds = 0.0f; - depthStencil.maxDepthBounds = 1.0f; - depthStencil.stencilTestEnable = VK_FALSE; - depthStencil.front = {}; - depthStencil.back = {}; - - VkPushConstantRange pushConstantRange{}; - pushConstantRange.offset = 0; - pushConstantRange.size = PUSH_CONSTANT_MAX_SIZE; - pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - - std::vector descriptorSetLayouts(info.descriptor_set_layouts.size()); - for (size_t i = 0; i < descriptorSetLayouts.size(); i++) { - descriptorSetLayouts[i] = info.descriptor_set_layouts[i]->layout; - } - - VkPipelineLayoutCreateInfo layoutInfo{}; - layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - layoutInfo.setLayoutCount = (uint32_t)descriptorSetLayouts.size(); - layoutInfo.pSetLayouts = descriptorSetLayouts.data(); - layoutInfo.pushConstantRangeCount = 1; - layoutInfo.pPushConstantRanges = &pushConstantRange; - - res = vkCreatePipelineLayout(pimpl->device.device, &layoutInfo, nullptr, &pipeline->layout); - assert(res == VK_SUCCESS); - - VkGraphicsPipelineCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - createInfo.stageCount = 2; - createInfo.pStages = shaderStages; - createInfo.pVertexInputState = &vertexInputInfo; - createInfo.pInputAssemblyState = &inputAssembly; - createInfo.pViewportState = &viewportState; // TODO: maybe this isn't needed? - createInfo.pRasterizationState = &rasterizer; - createInfo.pMultisampleState = &multisampling; - createInfo.pDepthStencilState = &depthStencil; - createInfo.pColorBlendState = &colorBlending; - createInfo.pDynamicState = &dynamicState; - createInfo.layout = pipeline->layout; - createInfo.renderPass = pimpl->swapchain.renderpass; - createInfo.subpass = 0; - createInfo.basePipelineHandle = VK_NULL_HANDLE; - createInfo.basePipelineIndex = -1; - - res = vkCreateGraphicsPipelines(pimpl->device.device, VK_NULL_HANDLE, 1, &createInfo, nullptr, &pipeline->handle); - assert(res == VK_SUCCESS); - - vkDestroyShaderModule(pimpl->device.device, fragShaderModule, nullptr); - vkDestroyShaderModule(pimpl->device.device, vertShaderModule, nullptr); - - return pipeline; - - } - - void GFXDevice::DestroyPipeline(const gfx::Pipeline* pipeline) - { - vkDestroyPipeline(pimpl->device.device, pipeline->handle, nullptr); - vkDestroyPipelineLayout(pimpl->device.device, pipeline->layout, nullptr); - - delete pipeline; - } - - gfx::DescriptorSetLayout* GFXDevice::CreateDescriptorSetLayout(const std::vector& bindings) - { - gfx::DescriptorSetLayout* out = new gfx::DescriptorSetLayout{}; - - std::vector vulkanBindings{}; - uint32_t i = 0; - for (const auto& binding : bindings) { - auto& vulkanBinding = vulkanBindings.emplace_back(); - vulkanBinding.binding = i; // This should be as low as possible to avoid wasting memory - vulkanBinding.descriptorType = converters::getDescriptorType(binding.descriptor_type); - vulkanBinding.descriptorCount = 1; // if > 1, accessible as an array in the shader - vulkanBinding.stageFlags = converters::getShaderStageFlags(binding.stage_flags); - - ++i; - } - - VkDescriptorSetLayoutCreateInfo info{}; - info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - info.pNext = nullptr; - info.flags = 0; - info.bindingCount = (uint32_t)vulkanBindings.size(); - info.pBindings = vulkanBindings.data(); - VKCHECK(vkCreateDescriptorSetLayout(pimpl->device.device, &info, nullptr, &out->layout)); - - return out; - } - - void GFXDevice::DestroyDescriptorSetLayout(const gfx::DescriptorSetLayout* layout) - { - vkDestroyDescriptorSetLayout(pimpl->device.device, layout->layout, nullptr); - delete layout; - } - - gfx::DescriptorSet* GFXDevice::AllocateDescriptorSet(const gfx::DescriptorSetLayout* layout) - { - gfx::DescriptorSet* set = new gfx::DescriptorSet{}; - - for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { - VkDescriptorSetAllocateInfo allocInfo{ - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - .pNext = nullptr, - .descriptorPool = pimpl->descriptorPool, - .descriptorSetCount = 1, - .pSetLayouts = &layout->layout - }; - VkResult res; - res = vkAllocateDescriptorSets(pimpl->device.device, &allocInfo, &set->sets[i]); - if (res == VK_ERROR_FRAGMENTED_POOL) throw std::runtime_error("Descriptor pool is fragmented!"); - if (res == VK_ERROR_OUT_OF_POOL_MEMORY) throw std::runtime_error("Descriptor pool is out of memory!"); - VKCHECK(res); - } - - return set; - } - - void GFXDevice::UpdateDescriptorUniformBuffer(const gfx::DescriptorSet* set, uint32_t binding, const gfx::UniformBuffer* buffer, size_t offset, size_t range) - { - assert(pimpl->FRAMECOUNT == 0); - - for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { - VkDescriptorBufferInfo bufferInfo{ - .buffer = buffer->gpuBuffers[i].buffer, - .offset = offset, - .range = range - }; - VkWriteDescriptorSet descriptorWrite{ - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .pNext = nullptr, - .dstSet = set->sets[i], - .dstBinding = binding, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .pImageInfo = nullptr, - .pBufferInfo = &bufferInfo, - .pTexelBufferView = nullptr - }; - vkUpdateDescriptorSets(pimpl->device.device, 1, &descriptorWrite, 0, nullptr); - } - } - - void GFXDevice::UpdateDescriptorCombinedImageSampler(const gfx::DescriptorSet *set, uint32_t binding, const gfx::Image* image, const gfx::Sampler* sampler) - { - //assert(pimpl->FRAMECOUNT == 0); - - VkDescriptorImageInfo imageInfo{}; - imageInfo.sampler = sampler->sampler; - imageInfo.imageView = image->view; - imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - - for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { - VkWriteDescriptorSet descriptorWrite{ - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .pNext = nullptr, - .dstSet = set->sets[i], - .dstBinding = binding, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .pImageInfo = &imageInfo, - .pBufferInfo = nullptr, - .pTexelBufferView = nullptr - }; - vkUpdateDescriptorSets(pimpl->device.device, 1, &descriptorWrite, 0, nullptr); - } - } - - gfx::UniformBuffer* GFXDevice::CreateUniformBuffer(uint64_t size, const void* initialData) - { - gfx::UniformBuffer* out = new gfx::UniformBuffer{}; - - /* first make staging buffer */ - out->stagingBuffer.size = size; - out->stagingBuffer.type = gfx::BufferType::kUniform; - { - VkBufferCreateInfo stagingBufferInfo{}; - stagingBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - stagingBufferInfo.size = out->stagingBuffer.size; - stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - stagingBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - stagingBufferInfo.flags = 0; - - VmaAllocationCreateInfo stagingAllocInfo{}; - stagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO; - stagingAllocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; - stagingAllocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - - VKCHECK(vmaCreateBuffer(pimpl->allocator, &stagingBufferInfo, &stagingAllocInfo, &out->stagingBuffer.buffer, &out->stagingBuffer.allocation, nullptr)); - - void* dataDest; - VKCHECK(vmaMapMemory(pimpl->allocator, out->stagingBuffer.allocation, &dataDest)); - memcpy(dataDest, initialData, out->stagingBuffer.size); - vmaUnmapMemory(pimpl->allocator, out->stagingBuffer.allocation); - } - - /* create the device-local set of buffers */ - for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { - out->gpuBuffers[i].size = out->stagingBuffer.size; - out->gpuBuffers[i].type = gfx::BufferType::kUniform; - - VkBufferCreateInfo gpuBufferInfo{}; - gpuBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - gpuBufferInfo.size = out->gpuBuffers[i].size; - gpuBufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - gpuBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - gpuBufferInfo.flags = 0; - - VmaAllocationCreateInfo gpuAllocationInfo{}; - gpuAllocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; - gpuAllocationInfo.flags = 0; - - VKCHECK(vmaCreateBuffer(pimpl->allocator, &gpuBufferInfo, &gpuAllocationInfo, &out->gpuBuffers[i].buffer, &out->gpuBuffers[i].allocation, nullptr)); - - /* copy staging buffer into both */ - copyBuffer(pimpl->device.device, pimpl->transferCommandPool, pimpl->device.queues.transferQueues[0], out->stagingBuffer.buffer, out->gpuBuffers[i].buffer, out->stagingBuffer.size); - } - - return out; - - } - - void GFXDevice::DestroyUniformBuffer(const gfx::UniformBuffer* uniformBuffer) - { - for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { - vmaDestroyBuffer(pimpl->allocator, uniformBuffer->gpuBuffers[i].buffer, uniformBuffer->gpuBuffers[i].allocation); - } - - vmaDestroyBuffer(pimpl->allocator, uniformBuffer->stagingBuffer.buffer, uniformBuffer->stagingBuffer.allocation); - - delete uniformBuffer; - } - - void GFXDevice::WriteUniformBuffer(gfx::UniformBuffer* buffer, uint64_t offset, uint64_t size, const void* data) - { - assert(offset + size <= buffer->stagingBuffer.size); - - /* first update the staging buffer */ - void* dataDest; - VKCHECK(vmaMapMemory(pimpl->allocator, buffer->stagingBuffer.allocation, &dataDest)); - memcpy(dataDest, (uint8_t*)data + offset, size); - vmaUnmapMemory(pimpl->allocator, buffer->stagingBuffer.allocation); - - /* queue the writes to each gpu buffer */ - // This is required as buffers cannot be updated if they are currently in use - for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { - pimpl->uniformBufferWriteQueues[i].insert(buffer); - } - - } - - gfx::Buffer* GFXDevice::CreateBuffer(gfx::BufferType type, uint64_t size, const void* data) - { - [[maybe_unused]] VkResult res; - - auto out = new gfx::Buffer{}; - out->size = size; - out->type = type; - - VkBuffer stagingBuffer; - VmaAllocation stagingAllocation; - - // first create the staging buffer - { - VkBufferCreateInfo stagingBufferInfo{}; - stagingBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - stagingBufferInfo.size = out->size; - stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - stagingBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - stagingBufferInfo.flags = 0; - - VmaAllocationCreateInfo stagingAllocInfo{}; - stagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO; - stagingAllocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; - stagingAllocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - - VKCHECK(vmaCreateBuffer(pimpl->allocator, &stagingBufferInfo, &stagingAllocInfo, &stagingBuffer, &stagingAllocation, nullptr)); - - void* dataDest; - VKCHECK(vmaMapMemory(pimpl->allocator, stagingAllocation, &dataDest)); - memcpy(dataDest, data, out->size); - vmaUnmapMemory(pimpl->allocator, stagingAllocation); - } - - // create the actual buffer on the GPU - { - VkBufferCreateInfo gpuBufferInfo{}; - gpuBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - gpuBufferInfo.size = out->size; - gpuBufferInfo.usage = converters::getBufferUsageFlag(type) | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - gpuBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - gpuBufferInfo.flags = 0; - - VmaAllocationCreateInfo gpuAllocationInfo{}; - gpuAllocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; - gpuAllocationInfo.flags = 0; - - VKCHECK(vmaCreateBuffer(pimpl->allocator, &gpuBufferInfo, &gpuAllocationInfo, &out->buffer, &out->allocation, nullptr)); - } - - // copy the data from the staging buffer to the gpu buffer - copyBuffer(pimpl->device.device, pimpl->transferCommandPool, pimpl->device.queues.transferQueues[0], stagingBuffer, out->buffer, out->size); - - // destroy staging buffer - vmaDestroyBuffer(pimpl->allocator, stagingBuffer, stagingAllocation); - return out; - - } - - void GFXDevice::DestroyBuffer(const gfx::Buffer* buffer) - { - vmaDestroyBuffer(pimpl->allocator, buffer->buffer, buffer->allocation); - delete buffer; - } - - // imageData must have pixel format R8G8B8A8_SRGB - gfx::Image* GFXDevice::CreateImage(uint32_t w, uint32_t h, const void* imageData) - { - assert(imageData != nullptr); - if (pimpl->FRAMECOUNT != 0) { - //throw std::runtime_error("Framecount must be 0 when creating a texture"); - } - - gfx::Image* out = new gfx::Image{}; - - uint32_t mipLevels = static_cast(std::floor(std::log2(std::max(w, h)))) + 1; - VkFormat imageFormat = VK_FORMAT_R8G8B8A8_SRGB; - - VkBuffer stagingBuffer = VK_NULL_HANDLE; - VmaAllocation stagingAllocation = VK_NULL_HANDLE; - VkDeviceSize stagingBufferSize = (VkDeviceSize)w * (VkDeviceSize)h * 4; - - /* create staging buffer */ - { - VkBufferCreateInfo stagingBufferInfo{}; - stagingBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - stagingBufferInfo.size = stagingBufferSize; - stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - stagingBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - stagingBufferInfo.flags = 0; - - VmaAllocationCreateInfo stagingAllocInfo{}; - stagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO; - stagingAllocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; - stagingAllocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - - VKCHECK(vmaCreateBuffer(pimpl->allocator, &stagingBufferInfo, &stagingAllocInfo, &stagingBuffer, &stagingAllocation, nullptr)); - - void* dataDest; - VKCHECK(vmaMapMemory(pimpl->allocator, stagingAllocation, &dataDest)); - memcpy(dataDest, imageData, stagingBufferSize); - vmaUnmapMemory(pimpl->allocator, stagingAllocation); - } - - VkImageCreateInfo imageInfo{}; - imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.flags = 0; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.format = imageFormat; - imageInfo.extent.width = w; - imageInfo.extent.height = h; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = mipLevels; - imageInfo.arrayLayers = 1; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - - VmaAllocationCreateInfo allocCreateInfo{}; - allocCreateInfo.flags = 0; - allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; - allocCreateInfo.priority = 0.5f; - - VKCHECK(vmaCreateImage(pimpl->allocator, &imageInfo, &allocCreateInfo, &out->image, &out->allocation, nullptr)); - - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = out->image; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = imageFormat; - viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = mipLevels; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - VKCHECK(vkCreateImageView(pimpl->device.device, &viewInfo, nullptr, &out->view)); - - /* begin command buffer */ - VkCommandBufferAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandPool = pimpl->graphicsCommandPool; - allocInfo.commandBufferCount = 1; - - VkCommandBuffer commandBuffer; - VKCHECK(vkAllocateCommandBuffers(pimpl->device.device, &allocInfo, &commandBuffer)); - - { // record the command buffer - VkCommandBufferBeginInfo beginInfo{}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - VKCHECK(vkBeginCommandBuffer(commandBuffer, &beginInfo)); - - // barrier: (all mip levels): UNDEFINED -> TRANSFER_DST_OPTIMAL - // Used for copying staging buffer AND blitting mipmaps - // Must happen before vkCmdCopyBufferToImage performs a TRANSFER_WRITE in the COPY stage. - VkImageMemoryBarrier2 beforeCopyBarrier{}; - beforeCopyBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; - beforeCopyBarrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; - beforeCopyBarrier.srcAccessMask = VK_ACCESS_2_NONE; - beforeCopyBarrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; - beforeCopyBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; - beforeCopyBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - beforeCopyBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - beforeCopyBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; - beforeCopyBarrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; - beforeCopyBarrier.image = out->image; - beforeCopyBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - beforeCopyBarrier.subresourceRange.baseMipLevel = 0; - beforeCopyBarrier.subresourceRange.levelCount = mipLevels; - beforeCopyBarrier.subresourceRange.baseArrayLayer = 0; - beforeCopyBarrier.subresourceRange.layerCount = 1; - VkDependencyInfo beforeCopyDependency{}; - beforeCopyDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - beforeCopyDependency.imageMemoryBarrierCount = 1; - beforeCopyDependency.pImageMemoryBarriers = &beforeCopyBarrier; - vkCmdPipelineBarrier2(commandBuffer, &beforeCopyDependency); - - // copy staging buffer to mipLevel 0 (full res image) - VkBufferImageCopy region{}; - region.bufferOffset = 0; - region.bufferRowLength = 0; - region.bufferImageHeight = 0; - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.imageSubresource.mipLevel = 0; - region.imageSubresource.baseArrayLayer = 0; - region.imageSubresource.layerCount = 1; - region.imageOffset.x = 0; - region.imageOffset.y = 0; - region.imageOffset.z = 0; - region.imageExtent = imageInfo.extent; - vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, out->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); - - int32_t mipWidth = w; - int32_t mipHeight = h; - for (uint32_t i = 1; i < mipLevels; i++) { - - // barrier: (i - 1) TRANSFER_DST_OPTIMAL -> TRANSFER_SRC_OPTIMAL - // Must happen after TRANSFER_WRITE in the COPY stage and BLIT stage. - VkImageMemoryBarrier2 beforeBlitBarrier{}; - beforeBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; - beforeBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT; - beforeBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; - beforeBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; - beforeBlitBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; - beforeBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - beforeBlitBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; - beforeBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; - beforeBlitBarrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; - beforeBlitBarrier.image = out->image; - beforeBlitBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - beforeBlitBarrier.subresourceRange.baseMipLevel = (i - 1); - beforeBlitBarrier.subresourceRange.levelCount = 1; - beforeBlitBarrier.subresourceRange.baseArrayLayer = 0; - beforeBlitBarrier.subresourceRange.layerCount = 1; - VkDependencyInfo beforeBlitDependency{}; - beforeBlitDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - beforeBlitDependency.imageMemoryBarrierCount = 1; - beforeBlitDependency.pImageMemoryBarriers = &beforeBlitBarrier; - vkCmdPipelineBarrier2(commandBuffer, &beforeBlitDependency); - - VkImageBlit blit{}; - blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - blit.srcSubresource.mipLevel = i - 1; - blit.srcSubresource.baseArrayLayer = 0; - blit.srcSubresource.layerCount = 1; - blit.srcOffsets[0] = { 0, 0, 0 }; - blit.srcOffsets[1] = { mipWidth, mipHeight, 1 }; - blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - blit.dstSubresource.mipLevel = i; - blit.dstSubresource.baseArrayLayer = 0; - blit.dstSubresource.layerCount = 1; - blit.dstOffsets[0] = { 0, 0, 0}; - blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 }; - vkCmdBlitImage(commandBuffer, - out->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - out->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, &blit, VK_FILTER_LINEAR); - - // barrier: (i - 1) TRANSFER_SRC_OPTIMAL -> SHADER_READ_ONLY_OPTIMALs - // Must happen after usage in the BLIT stage. - // Must happen before SHADER_SAMPLED_READ in the FRAGMENT_SHADER stage - VkImageMemoryBarrier2 afterBlitBarrier{}; - afterBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; - afterBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; - afterBlitBarrier.srcAccessMask = 0; - afterBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; - afterBlitBarrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; - afterBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; - afterBlitBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - afterBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; - afterBlitBarrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; - afterBlitBarrier.image = out->image; - afterBlitBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - afterBlitBarrier.subresourceRange.baseMipLevel = (i - 1); - afterBlitBarrier.subresourceRange.levelCount = 1; - afterBlitBarrier.subresourceRange.baseArrayLayer = 0; - afterBlitBarrier.subresourceRange.layerCount = 1; - VkDependencyInfo afterBlitDependency{}; - afterBlitDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - afterBlitDependency.imageMemoryBarrierCount = 1; - afterBlitDependency.pImageMemoryBarriers = &afterBlitBarrier; - vkCmdPipelineBarrier2(commandBuffer, &afterBlitDependency); - - if (mipWidth > 1) mipWidth /= 2; - if (mipHeight > 2) mipHeight /= 2; - } - - // Final mipLevel is never transitioned from TRANSFER_DST_OPTIMAL - // barrier: (mipLevels - 1) TRANSFER_DST_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL - VkImageMemoryBarrier2 finalBlitBarrier{}; - finalBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; - finalBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT | VK_PIPELINE_STAGE_2_COPY_BIT; - finalBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; - finalBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; - finalBlitBarrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; - finalBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - finalBlitBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - finalBlitBarrier.srcQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; - finalBlitBarrier.dstQueueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily; - finalBlitBarrier.image = out->image; - finalBlitBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - finalBlitBarrier.subresourceRange.baseMipLevel = (mipLevels - 1); - finalBlitBarrier.subresourceRange.levelCount = 1; - finalBlitBarrier.subresourceRange.baseArrayLayer = 0; - finalBlitBarrier.subresourceRange.layerCount = 1; - VkDependencyInfo afterBlitDependency{}; - afterBlitDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - afterBlitDependency.imageMemoryBarrierCount = 1; - afterBlitDependency.pImageMemoryBarriers = &finalBlitBarrier; - vkCmdPipelineBarrier2(commandBuffer, &afterBlitDependency); - - VKCHECK(vkEndCommandBuffer(commandBuffer)); - } - - // submit - VkSubmitInfo submitInfo{}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffer; - - VKCHECK(vkQueueSubmit(pimpl->device.queues.drawQueues[0], 1, &submitInfo, VK_NULL_HANDLE)); - - VKCHECK(vkQueueWaitIdle(pimpl->device.queues.drawQueues[0])); - - vkFreeCommandBuffers(pimpl->device.device, pimpl->graphicsCommandPool, 1, &commandBuffer); - - vmaDestroyBuffer(pimpl->allocator, stagingBuffer, stagingAllocation); - - return out; - } - - void GFXDevice::DestroyImage(const gfx::Image *image) - { - vkDestroyImageView(pimpl->device.device, image->view, nullptr); - vmaDestroyImage(pimpl->allocator, image->image, image->allocation); - delete image; - } - - const gfx::Sampler *GFXDevice::CreateSampler(const gfx::SamplerInfo& info) - { - gfx::Sampler* out = new gfx::Sampler{}; - - VkSamplerCreateInfo samplerInfo{}; - samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - samplerInfo.magFilter = converters::getFilter(info.magnify); - samplerInfo.minFilter = converters::getFilter(info.minify); - samplerInfo.mipmapMode = converters::getSamplerMipmapMode(info.mipmap); - samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.mipLodBias = 0.0f; - samplerInfo.anisotropyEnable = info.anisotropic_filtering ? VK_TRUE : VK_FALSE; - samplerInfo.maxAnisotropy = pimpl->device.properties.limits.maxSamplerAnisotropy; - samplerInfo.minLod = 0.0f; - samplerInfo.maxLod = VK_LOD_CLAMP_NONE; - - VKCHECK(vkCreateSampler(pimpl->device.device, &samplerInfo, nullptr, &out->sampler)); - - return out; - } - - void GFXDevice::DestroySampler(const gfx::Sampler *sampler) - { - vkDestroySampler(pimpl->device.device, sampler->sampler, nullptr); - delete sampler; - } - - void GFXDevice::LogPerformanceInfo() - { - VmaTotalStatistics pStats{}; - vmaCalculateStatistics(pimpl->allocator, &pStats); - - VkPhysicalDeviceMemoryProperties2 memProps{}; - memProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2; - vkGetPhysicalDeviceMemoryProperties2(pimpl->device.physicalDevice, &memProps); - - LOG_INFO("GPU Memory Statistics:"); - - for (uint32_t i = 0; i < memProps.memoryProperties.memoryHeapCount; i++) { - const VmaStatistics& statistics = pStats.memoryHeap[i].statistics; - VkMemoryHeap heap = memProps.memoryProperties.memoryHeaps[i]; - LOG_INFO("Memory heap {}", i); - if (heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) { - LOG_INFO(" DEVICE_LOCAL"); - } - LOG_INFO(" Memory blocks allocated: {} ({} MiB)", statistics.blockCount, statistics.allocationBytes / (1024 * 1024)); - LOG_INFO(" Number of allocations: {} ({} MiB)", statistics.allocationCount, statistics.allocationBytes / (1024 * 1024)); - LOG_INFO(" Max size: {} MiB", heap.size / (1024 * 1024)); - } - } - - uint64_t GFXDevice::GetFrameCount() - { - return pimpl->FRAMECOUNT; - } - - void GFXDevice::WaitIdle() - { - vkDeviceWaitIdle(pimpl->device.device); - } - +static constexpr uint32_t FRAMES_IN_FLIGHT = + 2; // This improved FPS by 5x! (on Intel IGPU) + +static constexpr size_t PUSH_CONSTANT_MAX_SIZE = 128; // bytes +static constexpr VkIndexType INDEX_TYPE = VK_INDEX_TYPE_UINT32; + +// structures and enums + +struct FrameData { + VkFence renderFence = VK_NULL_HANDLE; + VkSemaphore transferSemaphore = VK_NULL_HANDLE; + VkSemaphore renderSemaphore = VK_NULL_HANDLE; + VkSemaphore presentSemaphore = VK_NULL_HANDLE; + + VkCommandPool graphicsPool = VK_NULL_HANDLE; + VkCommandBuffer drawBuf = VK_NULL_HANDLE; + + VkCommandPool transferPool = VK_NULL_HANDLE; + VkCommandBuffer transferBuf = VK_NULL_HANDLE; +}; + +// handles + +struct gfx::Buffer { + gfx::BufferType type; + VkBuffer buffer = VK_NULL_HANDLE; + VmaAllocation allocation = nullptr; + VkDeviceSize size = 0; +}; + +struct gfx::Pipeline { + VkPipelineLayout layout = VK_NULL_HANDLE; + VkPipeline handle = VK_NULL_HANDLE; +}; + +struct gfx::Image { + VkImage image = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; + VmaAllocation allocation = VK_NULL_HANDLE; +}; + +struct gfx::Sampler { + VkSampler sampler = VK_NULL_HANDLE; +}; + +struct gfx::DrawBuffer { + FrameData frameData{}; + uint32_t currentFrameIndex = 0; // corresponds to the frameData + uint32_t imageIndex = 0; // for swapchain present +}; + +struct gfx::DescriptorSetLayout { + VkDescriptorSetLayout layout; +}; + +struct gfx::DescriptorSet { + std::array + sets; // frames in flight cannot use the same descriptor set in case the + // buffer needs updating +}; + +struct gfx::UniformBuffer { + gfx::Buffer stagingBuffer{}; + std::array gpuBuffers; +}; + +// enum converters + +namespace converters { + +static VkFormat getVertexAttribFormat(gfx::VertexAttribFormat fmt) { + switch (fmt) { + case gfx::VertexAttribFormat::kFloat2: + return VK_FORMAT_R32G32_SFLOAT; + case gfx::VertexAttribFormat::kFloat3: + return VK_FORMAT_R32G32B32_SFLOAT; + case gfx::VertexAttribFormat::kFloat4: + return VK_FORMAT_R32G32B32A32_SFLOAT; + } + throw std::runtime_error("Unknown vertex attribute format"); } + +static VkBufferUsageFlagBits getBufferUsageFlag(gfx::BufferType type) { + switch (type) { + case gfx::BufferType::kVertex: + return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + case gfx::BufferType::kIndex: + return VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + case gfx::BufferType::kUniform: + return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + default: + throw std::runtime_error("This buffer type does not have usage bits"); + } +} + +[[maybe_unused]] static VkFilter getFilter(gfx::Filter filter) { + switch (filter) { + case gfx::Filter::kLinear: + return VK_FILTER_LINEAR; + case gfx::Filter::kNearest: + return VK_FILTER_NEAREST; + } + throw std::runtime_error("Unknown filter"); +} + +[[maybe_unused]] static VkSamplerMipmapMode getSamplerMipmapMode( + gfx::Filter filter) { + switch (filter) { + case gfx::Filter::kLinear: + return VK_SAMPLER_MIPMAP_MODE_LINEAR; + case gfx::Filter::kNearest: + return VK_SAMPLER_MIPMAP_MODE_NEAREST; + } + throw std::runtime_error("Unknown filter"); +} + +[[maybe_unused]] static VkSampleCountFlags getSampleCountFlags( + gfx::MSAALevel level) { + switch (level) { + case gfx::MSAALevel::kOff: + return VK_SAMPLE_COUNT_1_BIT; + case gfx::MSAALevel::k2X: + return VK_SAMPLE_COUNT_2_BIT; + case gfx::MSAALevel::k4X: + return VK_SAMPLE_COUNT_4_BIT; + case gfx::MSAALevel::k8X: + return VK_SAMPLE_COUNT_8_BIT; + case gfx::MSAALevel::k16X: + return VK_SAMPLE_COUNT_16_BIT; + default: + throw std::runtime_error("Unknown MSAA level"); + } +} + +static VkDescriptorType getDescriptorType(gfx::DescriptorType type) { + switch (type) { + case gfx::DescriptorType::kUniformBuffer: + return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + case gfx::DescriptorType::kCombinedImageSampler: + return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + default: + throw std::runtime_error("Unknown descriptor type"); + } +} + +static VkShaderStageFlags getShaderStageFlags( + gfx::ShaderStageFlags::Flags flags) { + VkShaderStageFlags out = 0; + if (flags & gfx::ShaderStageFlags::kVertex) out |= VK_SHADER_STAGE_VERTEX_BIT; + if (flags & gfx::ShaderStageFlags::kFragment) + out |= VK_SHADER_STAGE_FRAGMENT_BIT; + return out; +} + +[[maybe_unused]] static VkCullModeFlags getCullModeFlags(gfx::CullMode mode) { + switch (mode) { + case gfx::CullMode::kCullNone: + return VK_CULL_MODE_NONE; + case gfx::CullMode::kCullFront: + return VK_CULL_MODE_FRONT_BIT; + case gfx::CullMode::kCullBack: + return VK_CULL_MODE_BACK_BIT; + case gfx::CullMode::kCullFrontAndBack: + return VK_CULL_MODE_FRONT_AND_BACK; + } +} + +} // namespace converters + +// functions + +static VkShaderModule compileShader(VkDevice device, shaderc_shader_kind kind, + const std::string& source, + const char* filename) { + shaderc::Compiler compiler; + shaderc::CompileOptions options; + + options.SetSourceLanguage(shaderc_source_language_glsl); + options.SetTargetEnvironment(shaderc_target_env_vulkan, + shaderc_env_version_vulkan_1_3); + options.SetOptimizationLevel(shaderc_optimization_level_performance); + options.SetTargetSpirv(shaderc_spirv_version_1_6); + options.SetAutoBindUniforms(false); + + // preprocess + shaderc::PreprocessedSourceCompilationResult preprocessed = + compiler.PreprocessGlsl(source, kind, filename, options); + + if (preprocessed.GetCompilationStatus() != + shaderc_compilation_status_success) { + throw std::runtime_error("PREPROCESS ERR " + + preprocessed.GetErrorMessage()); + } + + std::string shaderStr{preprocessed.cbegin(), preprocessed.cend()}; + + // compile + shaderc::SpvCompilationResult compiledShader = + compiler.CompileGlslToSpv(shaderStr.c_str(), kind, filename, options); + + if (compiledShader.GetCompilationStatus() != + shaderc_compilation_status_success) { + throw std::runtime_error("COMPILE ERR " + compiledShader.GetErrorMessage()); + } + + std::vector shaderBytecode = { + compiledShader.cbegin(), + compiledShader.cend()}; // not sure why sample code copy vector like this + + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = shaderBytecode.size() * sizeof(uint32_t); + createInfo.pCode = compiledShader.cbegin(); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != + VK_SUCCESS) { + throw std::runtime_error("Failed to create shader module!"); + } + + return shaderModule; +} + +static void copyBuffer(VkDevice device, VkCommandPool commandPool, + VkQueue queue, VkBuffer srcBuffer, VkBuffer dstBuffer, + VkDeviceSize size) { + [[maybe_unused]] VkResult res; + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + res = vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + VKCHECK(res); + + { // record the command buffer + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + res = vkBeginCommandBuffer(commandBuffer, &beginInfo); + VKCHECK(res); + + VkBufferCopy copyRegion{}; + copyRegion.srcOffset = 0; + copyRegion.dstOffset = 0; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + res = vkEndCommandBuffer(commandBuffer); + VKCHECK(res); + } + + // submit + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + res = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + VKCHECK(res); + + res = vkQueueWaitIdle(queue); + VKCHECK(res); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); +} + +// class definitions + +struct GFXDevice::Impl { + // device settings + gfx::GraphicsSettings graphicsSettings; + + SDL_Window* window = nullptr; + Instance instance{}; + VkSurfaceKHR surface = VK_NULL_HANDLE; + Device device{}; + VmaAllocator allocator{}; + SwapchainInfo swapchainInfo{}; + Swapchain swapchain{}; + + VkDescriptorPool descriptorPool; + std::array, FRAMES_IN_FLIGHT> + uniformBufferWriteQueues{}; + + // For one-off transfer operations not bound to a specific frame-in-flight + VkCommandPool transferCommandPool = VK_NULL_HANDLE; + // For one-off operation on the draw queue family not bound to a specific + // frame-in-flight + VkCommandPool graphicsCommandPool = VK_NULL_HANDLE; + + uint64_t FRAMECOUNT = 0; + + FrameData frameData[FRAMES_IN_FLIGHT] = {}; + + bool swapchainIsOutOfDate = false; +}; + +GFXDevice::GFXDevice(const char* appName, const char* appVersion, + SDL_Window* window, gfx::GraphicsSettings settings) { + pimpl = std::make_unique(); + + VkResult res; + + pimpl->window = window; + pimpl->graphicsSettings = settings; + + // initialise vulkan + + res = volkInitialize(); + if (res != VK_SUCCESS) { + throw std::runtime_error("Unable to load vulkan, is it installed?"); + } + + uint32_t vulkanVersion = volkGetInstanceVersion(); + assert(vulkanVersion != 0); + if (vulkanVersion < VK_API_VERSION_1_3) { + throw std::runtime_error("The loaded Vulkan version must be at least 1.3"); + } + + pimpl->instance = createVulkanInstance( + pimpl->window, appName, appVersion, + pimpl->graphicsSettings.enable_validation, MessageSeverity::SEV_WARNING); + + if (SDL_Vulkan_CreateSurface(pimpl->window, pimpl->instance.instance, + &pimpl->surface) == false) { + throw std::runtime_error("Unable to create window surface"); + }; + + DeviceRequirements deviceRequirements{}; + deviceRequirements.requiredExtensions.push_back( + VK_KHR_SWAPCHAIN_EXTENSION_NAME); + deviceRequirements.optionalExtensions.push_back( + VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME); + deviceRequirements.optionalExtensions.push_back( + VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); + deviceRequirements.requiredFeatures.samplerAnisotropy = VK_TRUE; + // deviceRequirements.requiredFeatures.fillModeNonSolid = VK_TRUE; + deviceRequirements.formats.push_back(FormatRequirements{ + .format = VK_FORMAT_R8G8B8A8_SRGB, + .properties = VkFormatProperties{ + .linearTilingFeatures = {}, + .optimalTilingFeatures = + VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | + VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | + VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | + VK_FORMAT_FEATURE_TRANSFER_DST_BIT, + .bufferFeatures = {}, + }}); + deviceRequirements.formats.push_back(FormatRequirements{ + .format = VK_FORMAT_R32G32_SFLOAT, + .properties = VkFormatProperties{ + .linearTilingFeatures = {}, + .optimalTilingFeatures = {}, + .bufferFeatures = VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT, + }}); + deviceRequirements.formats.push_back(FormatRequirements{ + .format = VK_FORMAT_R32G32B32_SFLOAT, + .properties = VkFormatProperties{ + .linearTilingFeatures = {}, + .optimalTilingFeatures = {}, + .bufferFeatures = VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT, + }}); + deviceRequirements.formats.push_back(FormatRequirements{ + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .properties = VkFormatProperties{ + .linearTilingFeatures = {}, + .optimalTilingFeatures = {}, + .bufferFeatures = VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT, + }}); + deviceRequirements.formats.push_back( // Depth buffer format + FormatRequirements{.format = VK_FORMAT_D16_UNORM, + .properties = VkFormatProperties{ + .linearTilingFeatures = {}, + .optimalTilingFeatures = + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, + .bufferFeatures = {}, + }}); + + pimpl->device = createDevice(pimpl->instance.instance, deviceRequirements, + pimpl->surface); + + pimpl->allocator = createAllocator(pimpl->instance.instance, pimpl->device); + + pimpl->swapchainInfo.device = pimpl->device.device; + pimpl->swapchainInfo.allocator = pimpl->allocator; + pimpl->swapchainInfo.physicalDevice = pimpl->device.physicalDevice; + pimpl->swapchainInfo.surface = pimpl->surface; + pimpl->swapchainInfo.window = pimpl->window; + pimpl->swapchainInfo.vsync = pimpl->graphicsSettings.vsync; + pimpl->swapchainInfo.waitForPresent = + pimpl->graphicsSettings.wait_for_present; + createSwapchain(&pimpl->swapchain, pimpl->swapchainInfo); + + /* make synchronisation primitives for rendering and allocate command buffers + */ + + for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { + VkFenceCreateInfo fenceInfo{.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .pNext = nullptr, + .flags = VK_FENCE_CREATE_SIGNALED_BIT}; + res = vkCreateFence(pimpl->device.device, &fenceInfo, nullptr, + &pimpl->frameData[i].renderFence); + if (res != VK_SUCCESS) throw std::runtime_error("Failed to create fence!"); + + VkSemaphoreCreateInfo smphInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = nullptr, + .flags = 0}; + VKCHECK(vkCreateSemaphore(pimpl->device.device, &smphInfo, nullptr, + &pimpl->frameData[i].transferSemaphore)); + VKCHECK(vkCreateSemaphore(pimpl->device.device, &smphInfo, nullptr, + &pimpl->frameData[i].renderSemaphore)); + VKCHECK(vkCreateSemaphore(pimpl->device.device, &smphInfo, nullptr, + &pimpl->frameData[i].presentSemaphore)); + + VkCommandPoolCreateInfo poolInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = nullptr, + .flags = 0, // Command buffers cannot be individually reset (more + // performant this way) + .queueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily}; + VKCHECK(vkCreateCommandPool(pimpl->device.device, &poolInfo, nullptr, + &pimpl->frameData[i].graphicsPool)); + poolInfo.queueFamilyIndex = pimpl->device.queues.transferQueueFamily; + VKCHECK(vkCreateCommandPool(pimpl->device.device, &poolInfo, nullptr, + &pimpl->frameData[i].transferPool)); + + VkCommandBufferAllocateInfo cmdAllocInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = nullptr, + .commandPool = pimpl->frameData[i].graphicsPool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1}; + VKCHECK(vkAllocateCommandBuffers(pimpl->device.device, &cmdAllocInfo, + &pimpl->frameData[i].drawBuf)); + cmdAllocInfo.commandPool = pimpl->frameData[i].transferPool; + VKCHECK(vkAllocateCommandBuffers(pimpl->device.device, &cmdAllocInfo, + &pimpl->frameData[i].transferBuf)); + } + + /* create command pool for one-off transfer operations */ + VkCommandPoolCreateInfo transferPoolInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = nullptr, + .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, // These command buffers + // don't last very long + .queueFamilyIndex = pimpl->device.queues.transferQueueFamily}; + VKCHECK(vkCreateCommandPool(pimpl->device.device, &transferPoolInfo, nullptr, + &pimpl->transferCommandPool)); + /* create command pool for one-off draw queue operations */ + VkCommandPoolCreateInfo graphicsPoolInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = nullptr, + .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, // These command buffers + // don't last very long + .queueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily}; + VKCHECK(vkCreateCommandPool(pimpl->device.device, &graphicsPoolInfo, nullptr, + &pimpl->graphicsCommandPool)); + + /* create a global descriptor pool */ + std::vector poolSizes{}; + poolSizes.push_back({VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 100u}); + poolSizes.push_back({VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 100u}); + + VkDescriptorPoolCreateInfo descriptorPoolInfo{}; + descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + descriptorPoolInfo.pNext = nullptr; + descriptorPoolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + descriptorPoolInfo.maxSets = 1000u; + descriptorPoolInfo.poolSizeCount = (uint32_t)poolSizes.size(); + descriptorPoolInfo.pPoolSizes = poolSizes.data(); + VKCHECK(vkCreateDescriptorPool(pimpl->device.device, &descriptorPoolInfo, + nullptr, &pimpl->descriptorPool)); +} + +GFXDevice::~GFXDevice() { + vkDestroyDescriptorPool(pimpl->device.device, pimpl->descriptorPool, nullptr); + + vkDestroyCommandPool(pimpl->device.device, pimpl->graphicsCommandPool, + nullptr); + vkDestroyCommandPool(pimpl->device.device, pimpl->transferCommandPool, + nullptr); + + for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { + vkDestroyCommandPool(pimpl->device.device, pimpl->frameData[i].transferPool, + nullptr); + vkDestroyCommandPool(pimpl->device.device, pimpl->frameData[i].graphicsPool, + nullptr); + vkDestroySemaphore(pimpl->device.device, + pimpl->frameData[i].presentSemaphore, nullptr); + vkDestroySemaphore(pimpl->device.device, + pimpl->frameData[i].renderSemaphore, nullptr); + vkDestroySemaphore(pimpl->device.device, + pimpl->frameData[i].transferSemaphore, nullptr); + vkDestroyFence(pimpl->device.device, pimpl->frameData[i].renderFence, + nullptr); + } + + destroySwapchain(pimpl->swapchain); + destroyAllocator(pimpl->allocator); + destroyDevice(pimpl->device); + vkDestroySurfaceKHR(pimpl->instance.instance, pimpl->surface, nullptr); + destroyVulkanInstance(pimpl->instance); +} + +void GFXDevice::GetViewportSize(uint32_t* w, uint32_t* h) { + int width, height; + SDL_Vulkan_GetDrawableSize(pimpl->window, &width, &height); + if (width == 0 || height == 0) { + *w = (uint32_t)pimpl->swapchain.extent.width; + *h = (uint32_t)pimpl->swapchain.extent.height; + } else { + *w = (uint32_t)width; + *h = (uint32_t)height; + } +} + +gfx::DrawBuffer* GFXDevice::BeginRender() { + VkResult res; + + const uint32_t currentFrameIndex = pimpl->FRAMECOUNT % FRAMES_IN_FLIGHT; + const FrameData frameData = pimpl->frameData[currentFrameIndex]; + + vmaSetCurrentFrameIndex(pimpl->allocator, (uint32_t)pimpl->FRAMECOUNT); + + /* wait until the previous frame RENDERING has finished */ + res = vkWaitForFences(pimpl->device.device, 1, &frameData.renderFence, + VK_TRUE, 1000000000LL); + VKCHECK(res); + res = vkResetFences(pimpl->device.device, 1, &frameData.renderFence); + VKCHECK(res); + + /* perform any pending uniform buffer writes */ + VKCHECK(vkResetCommandPool(pimpl->device.device, frameData.transferPool, 0)); + + VkCommandBufferBeginInfo transferBeginInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = nullptr, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + .pInheritanceInfo = nullptr // ignored + }; + VKCHECK(vkBeginCommandBuffer(frameData.transferBuf, &transferBeginInfo)); + + // transfer cmds... + + std::vector barriers{}; + for (gfx::UniformBuffer* uniformBuffer : + pimpl->uniformBufferWriteQueues[currentFrameIndex]) { + VkBufferCopy copyRegion{}; + copyRegion.srcOffset = 0; + copyRegion.dstOffset = 0; + copyRegion.size = uniformBuffer->stagingBuffer.size; + vkCmdCopyBuffer(frameData.transferBuf, uniformBuffer->stagingBuffer.buffer, + uniformBuffer->gpuBuffers[currentFrameIndex].buffer, 1, + ©Region); + + VkBufferMemoryBarrier2& barrier = barriers.emplace_back(); + barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstAccessMask = 0; + barrier.srcQueueFamilyIndex = pimpl->device.queues.transferQueueFamily; + barrier.dstQueueFamilyIndex = + pimpl->device.queues.presentAndDrawQueueFamily; + barrier.buffer = uniformBuffer->gpuBuffers[currentFrameIndex].buffer; + barrier.offset = 0; + barrier.size = uniformBuffer->gpuBuffers[currentFrameIndex].size; + } + pimpl->uniformBufferWriteQueues[currentFrameIndex].clear(); + + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.bufferMemoryBarrierCount = (uint32_t)barriers.size(); + dependencyInfo.pBufferMemoryBarriers = barriers.data(); + vkCmdPipelineBarrier2(frameData.transferBuf, &dependencyInfo); + + VKCHECK(vkEndCommandBuffer(frameData.transferBuf)); + + VkSubmitInfo transferSubmitInfo{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = nullptr, + .waitSemaphoreCount = + 0, // needs to wait for render but the fence does that + .pWaitSemaphores = nullptr, + .pWaitDstStageMask = nullptr, + .commandBufferCount = 1, + .pCommandBuffers = &frameData.transferBuf, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &frameData.transferSemaphore, + }; + res = vkQueueSubmit(pimpl->device.queues.transferQueues[0], 1, + &transferSubmitInfo, VK_NULL_HANDLE); + VKCHECK(res); + + uint32_t swapchainImageIndex; + + do { + if (pimpl->swapchainIsOutOfDate) { + // re-create swapchain + vkQueueWaitIdle(pimpl->device.queues.drawQueues[0]); + vkQueueWaitIdle(pimpl->device.queues.presentQueue); + createSwapchain(&pimpl->swapchain, pimpl->swapchainInfo); + } + // THIS FUNCTION BLOCKS UNTIL AN IMAGE IS AVAILABLE (it waits for vsync) + res = vkAcquireNextImageKHR( + pimpl->device.device, pimpl->swapchain.swapchain, 1000000000LL, + frameData.presentSemaphore, VK_NULL_HANDLE, &swapchainImageIndex); + if (res != VK_SUBOPTIMAL_KHR && res != VK_ERROR_OUT_OF_DATE_KHR) + VKCHECK(res); + if (res == VK_SUCCESS) pimpl->swapchainIsOutOfDate = false; + } while (pimpl->swapchainIsOutOfDate); + + /* record command buffer */ + res = vkResetCommandPool(pimpl->device.device, frameData.graphicsPool, 0); + VKCHECK(res); + + VkCommandBufferBeginInfo beginInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = nullptr, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + .pInheritanceInfo = nullptr // ignored + }; + res = vkBeginCommandBuffer(frameData.drawBuf, &beginInfo); + VKCHECK(res); + + { // RECORDING + + /* change barriers to perform a queue ownership acquire operation */ + for (VkBufferMemoryBarrier2& barrier : barriers) { + barrier.srcStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_UNIFORM_READ_BIT; + } + vkCmdPipelineBarrier2(frameData.drawBuf, &dependencyInfo); + + std::array + clearValues{}; // Using same value for all components enables + // compression according to NVIDIA Best Practices + clearValues[0].color.float32[0] = 1.0f; + clearValues[0].color.float32[1] = 1.0f; + clearValues[0].color.float32[2] = 1.0f; + clearValues[0].color.float32[3] = 1.0f; + clearValues[1].depthStencil.depth = 1.0f; + + VkRenderPassBeginInfo passBegin{}; + passBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + passBegin.pNext = nullptr; + passBegin.renderPass = pimpl->swapchain.renderpass; + passBegin.framebuffer = pimpl->swapchain.framebuffers[swapchainImageIndex]; + passBegin.renderArea.extent = pimpl->swapchain.extent; + passBegin.renderArea.offset = {0, 0}; + passBegin.clearValueCount = (uint32_t)clearValues.size(); + passBegin.pClearValues = clearValues.data(); + vkCmdBeginRenderPass(frameData.drawBuf, &passBegin, + VK_SUBPASS_CONTENTS_INLINE); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = (float)pimpl->swapchain.extent.height; + viewport.width = (float)pimpl->swapchain.extent.width; + viewport.height = -(float)pimpl->swapchain.extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(frameData.drawBuf, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = pimpl->swapchain.extent; + vkCmdSetScissor(frameData.drawBuf, 0, 1, &scissor); + } + + // hand command buffer over to caller + gfx::DrawBuffer* drawBuffer = new gfx::DrawBuffer; + drawBuffer->frameData = frameData; + drawBuffer->currentFrameIndex = currentFrameIndex; + drawBuffer->imageIndex = swapchainImageIndex; + return drawBuffer; +} + +void GFXDevice::FinishRender(gfx::DrawBuffer* drawBuffer) { + assert(drawBuffer != nullptr); + + uint32_t swapchainImageIndex = drawBuffer->imageIndex; + VkResult res; + + vkCmdEndRenderPass(drawBuffer->frameData.drawBuf); + + res = vkEndCommandBuffer(drawBuffer->frameData.drawBuf); + VKCHECK(res); + + // SUBMIT + + std::vector waitSemaphores{}; + std::vector waitDstStageMasks{}; + + waitSemaphores.push_back(drawBuffer->frameData.presentSemaphore); + waitDstStageMasks.push_back(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + waitSemaphores.push_back(drawBuffer->frameData.transferSemaphore); + waitDstStageMasks.push_back(VK_PIPELINE_STAGE_VERTEX_SHADER_BIT); + + VkSubmitInfo submitInfo{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = nullptr, + .waitSemaphoreCount = (uint32_t)waitSemaphores.size(), + .pWaitSemaphores = waitSemaphores.data(), + .pWaitDstStageMask = waitDstStageMasks.data(), + .commandBufferCount = 1, + .pCommandBuffers = &drawBuffer->frameData.drawBuf, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &drawBuffer->frameData.renderSemaphore, + }; + res = vkQueueSubmit(pimpl->device.queues.drawQueues[0], 1, &submitInfo, + drawBuffer->frameData.renderFence); + VKCHECK(res); + + // PRESENT + + VkPresentInfoKHR presentInfo{ + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = nullptr, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &drawBuffer->frameData.renderSemaphore, + .swapchainCount = 1, + .pSwapchains = &pimpl->swapchain.swapchain, + .pImageIndices = &swapchainImageIndex, + .pResults = nullptr}; + res = vkQueuePresentKHR(pimpl->device.queues.presentQueue, &presentInfo); + if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) { + // flag to re-create the swapchain before next render + pimpl->swapchainIsOutOfDate = true; + } else if (res != VK_SUCCESS) + throw std::runtime_error("Failed to queue present! Code: " + + std::to_string(res)); + + pimpl->FRAMECOUNT++; + + delete drawBuffer; +} + +void GFXDevice::CmdBindPipeline(gfx::DrawBuffer* drawBuffer, + const gfx::Pipeline* pipeline) { + assert(drawBuffer != nullptr); + assert(pipeline != nullptr); + vkCmdBindPipeline(drawBuffer->frameData.drawBuf, + VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->handle); +} + +void GFXDevice::CmdBindVertexBuffer(gfx::DrawBuffer* drawBuffer, + uint32_t binding, + const gfx::Buffer* buffer) { + assert(drawBuffer != nullptr); + assert(buffer != nullptr); + assert(buffer->type == gfx::BufferType::kVertex); + const VkDeviceSize offset = 0; + vkCmdBindVertexBuffers(drawBuffer->frameData.drawBuf, binding, 1, + &buffer->buffer, &offset); +} + +void GFXDevice::CmdBindIndexBuffer(gfx::DrawBuffer* drawBuffer, + const gfx::Buffer* buffer) { + assert(drawBuffer != nullptr); + assert(buffer != nullptr); + assert(buffer->type == gfx::BufferType::kIndex); + vkCmdBindIndexBuffer(drawBuffer->frameData.drawBuf, buffer->buffer, 0, + INDEX_TYPE); +} + +void GFXDevice::CmdDrawIndexed(gfx::DrawBuffer* drawBuffer, uint32_t indexCount, + uint32_t instanceCount, uint32_t firstIndex, + int32_t vertexOffset, uint32_t firstInstance) { + assert(drawBuffer != nullptr); + vkCmdDrawIndexed(drawBuffer->frameData.drawBuf, indexCount, instanceCount, + firstIndex, vertexOffset, firstInstance); +} + +void GFXDevice::CmdPushConstants(gfx::DrawBuffer* drawBuffer, + const gfx::Pipeline* pipeline, uint32_t offset, + uint32_t size, const void* data) { + assert(drawBuffer != nullptr); + vkCmdPushConstants(drawBuffer->frameData.drawBuf, pipeline->layout, + VK_SHADER_STAGE_VERTEX_BIT, offset, size, data); +} + +void GFXDevice::CmdBindDescriptorSet(gfx::DrawBuffer* drawBuffer, + const gfx::Pipeline* pipeline, + const gfx::DescriptorSet* set, + uint32_t setNumber) { + vkCmdBindDescriptorSets( + drawBuffer->frameData.drawBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, + pipeline->layout, setNumber, 1, &set->sets[drawBuffer->currentFrameIndex], + 0, nullptr); +} + +gfx::Pipeline* GFXDevice::CreatePipeline(const gfx::PipelineInfo& info) { + [[maybe_unused]] VkResult res; + + gfx::Pipeline* pipeline = new gfx::Pipeline; + + auto vertShaderCode = util::ReadTextFile(info.vert_shader_path); + auto fragShaderCode = util::ReadTextFile(info.frag_shader_path); + + VkShaderModule vertShaderModule = + compileShader(pimpl->device.device, shaderc_vertex_shader, + vertShaderCode->data(), info.vert_shader_path); + VkShaderModule fragShaderModule = + compileShader(pimpl->device.device, shaderc_fragment_shader, + fragShaderCode->data(), info.frag_shader_path); + + // get vertex attrib layout: + VkVertexInputBindingDescription bindingDescription{}; + bindingDescription.binding = 0; + bindingDescription.stride = info.vertex_format.stride; + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + std::vector attribDescs{}; + attribDescs.reserve(info.vertex_format.attribute_descriptions.size()); + for (const auto& desc : info.vertex_format.attribute_descriptions) { + VkVertexInputAttributeDescription vulkanAttribDesc{}; + vulkanAttribDesc.location = desc.location; + vulkanAttribDesc.binding = 0; + vulkanAttribDesc.format = converters::getVertexAttribFormat(desc.format); + vulkanAttribDesc.offset = desc.offset; + attribDescs.push_back(vulkanAttribDesc); + } + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + vertShaderStageInfo.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + fragShaderStageInfo.pSpecializationInfo = nullptr; + + VkPipelineShaderStageCreateInfo shaderStages[2] = {vertShaderStageInfo, + fragShaderStageInfo}; + + // set the vertex input layout + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = + VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.vertexAttributeDescriptionCount = + (uint32_t)attribDescs.size(); + vertexInputInfo.pVertexAttributeDescriptions = attribDescs.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = (float)pimpl->swapchain.extent.height; + viewport.width = (float)pimpl->swapchain.extent.width; + viewport.height = -(float)pimpl->swapchain.extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = pimpl->swapchain.extent; + + // Dynamic states removes the need to re-create pipelines whenever the window + // size changes + std::vector dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR}; + + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = (uint32_t)dynamicStates.size(); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = + VK_FALSE; // enabling this will not run the fragment shaders at all + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + if (info.backface_culling == true) { + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + } else { + rasterizer.cullMode = VK_CULL_MODE_NONE; + } + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; // ignored + rasterizer.depthBiasClamp = 0.0f; // ignored + rasterizer.depthBiasSlopeFactor = 0.0f; // ignored + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.minSampleShading = 1.0f; // ignored + multisampling.pSampleMask = nullptr; // ignored + multisampling.alphaToCoverageEnable = VK_FALSE; // ignored + multisampling.alphaToOneEnable = VK_FALSE; // ignored + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + if (info.alpha_blending) { + colorBlendAttachment.blendEnable = VK_TRUE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + colorBlendAttachment.dstColorBlendFactor = + VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + } else { + colorBlendAttachment.blendEnable = VK_FALSE; + } + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; // ignored + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; // ignored + colorBlending.blendConstants[1] = 0.0f; // ignored + colorBlending.blendConstants[2] = 0.0f; // ignored + colorBlending.blendConstants[3] = 0.0f; // ignored + + VkPipelineDepthStencilStateCreateInfo depthStencil{}; + depthStencil.sType = + VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = info.write_z ? VK_TRUE : VK_FALSE; + depthStencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; + depthStencil.depthBoundsTestEnable = VK_FALSE; + depthStencil.minDepthBounds = 0.0f; + depthStencil.maxDepthBounds = 1.0f; + depthStencil.stencilTestEnable = VK_FALSE; + depthStencil.front = {}; + depthStencil.back = {}; + + VkPushConstantRange pushConstantRange{}; + pushConstantRange.offset = 0; + pushConstantRange.size = PUSH_CONSTANT_MAX_SIZE; + pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + std::vector descriptorSetLayouts( + info.descriptor_set_layouts.size()); + for (size_t i = 0; i < descriptorSetLayouts.size(); i++) { + descriptorSetLayouts[i] = info.descriptor_set_layouts[i]->layout; + } + + VkPipelineLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + layoutInfo.setLayoutCount = (uint32_t)descriptorSetLayouts.size(); + layoutInfo.pSetLayouts = descriptorSetLayouts.data(); + layoutInfo.pushConstantRangeCount = 1; + layoutInfo.pPushConstantRanges = &pushConstantRange; + + res = vkCreatePipelineLayout(pimpl->device.device, &layoutInfo, nullptr, + &pipeline->layout); + assert(res == VK_SUCCESS); + + VkGraphicsPipelineCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + createInfo.stageCount = 2; + createInfo.pStages = shaderStages; + createInfo.pVertexInputState = &vertexInputInfo; + createInfo.pInputAssemblyState = &inputAssembly; + createInfo.pViewportState = &viewportState; // TODO: maybe this isn't needed? + createInfo.pRasterizationState = &rasterizer; + createInfo.pMultisampleState = &multisampling; + createInfo.pDepthStencilState = &depthStencil; + createInfo.pColorBlendState = &colorBlending; + createInfo.pDynamicState = &dynamicState; + createInfo.layout = pipeline->layout; + createInfo.renderPass = pimpl->swapchain.renderpass; + createInfo.subpass = 0; + createInfo.basePipelineHandle = VK_NULL_HANDLE; + createInfo.basePipelineIndex = -1; + + res = vkCreateGraphicsPipelines(pimpl->device.device, VK_NULL_HANDLE, 1, + &createInfo, nullptr, &pipeline->handle); + assert(res == VK_SUCCESS); + + vkDestroyShaderModule(pimpl->device.device, fragShaderModule, nullptr); + vkDestroyShaderModule(pimpl->device.device, vertShaderModule, nullptr); + + return pipeline; +} + +void GFXDevice::DestroyPipeline(const gfx::Pipeline* pipeline) { + vkDestroyPipeline(pimpl->device.device, pipeline->handle, nullptr); + vkDestroyPipelineLayout(pimpl->device.device, pipeline->layout, nullptr); + + delete pipeline; +} + +gfx::DescriptorSetLayout* GFXDevice::CreateDescriptorSetLayout( + const std::vector& bindings) { + gfx::DescriptorSetLayout* out = new gfx::DescriptorSetLayout{}; + + std::vector vulkanBindings{}; + uint32_t i = 0; + for (const auto& binding : bindings) { + auto& vulkanBinding = vulkanBindings.emplace_back(); + vulkanBinding.binding = + i; // This should be as low as possible to avoid wasting memory + vulkanBinding.descriptorType = + converters::getDescriptorType(binding.descriptor_type); + vulkanBinding.descriptorCount = + 1; // if > 1, accessible as an array in the shader + vulkanBinding.stageFlags = + converters::getShaderStageFlags(binding.stage_flags); + + ++i; + } + + VkDescriptorSetLayoutCreateInfo info{}; + info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + info.pNext = nullptr; + info.flags = 0; + info.bindingCount = (uint32_t)vulkanBindings.size(); + info.pBindings = vulkanBindings.data(); + VKCHECK(vkCreateDescriptorSetLayout(pimpl->device.device, &info, nullptr, + &out->layout)); + + return out; +} + +void GFXDevice::DestroyDescriptorSetLayout( + const gfx::DescriptorSetLayout* layout) { + vkDestroyDescriptorSetLayout(pimpl->device.device, layout->layout, nullptr); + delete layout; +} + +gfx::DescriptorSet* GFXDevice::AllocateDescriptorSet( + const gfx::DescriptorSetLayout* layout) { + gfx::DescriptorSet* set = new gfx::DescriptorSet{}; + + for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { + VkDescriptorSetAllocateInfo allocInfo{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .pNext = nullptr, + .descriptorPool = pimpl->descriptorPool, + .descriptorSetCount = 1, + .pSetLayouts = &layout->layout}; + VkResult res; + res = vkAllocateDescriptorSets(pimpl->device.device, &allocInfo, + &set->sets[i]); + if (res == VK_ERROR_FRAGMENTED_POOL) + throw std::runtime_error("Descriptor pool is fragmented!"); + if (res == VK_ERROR_OUT_OF_POOL_MEMORY) + throw std::runtime_error("Descriptor pool is out of memory!"); + VKCHECK(res); + } + + return set; +} + +void GFXDevice::FreeDescriptorSet(const gfx::DescriptorSet* set) { + VKCHECK(vkFreeDescriptorSets(pimpl->device.device, pimpl->descriptorPool, + set->sets.size(), set->sets.data())); +} + +void GFXDevice::UpdateDescriptorUniformBuffer(const gfx::DescriptorSet* set, + uint32_t binding, + const gfx::UniformBuffer* buffer, + size_t offset, size_t range) { + assert(pimpl->FRAMECOUNT == 0); + + for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo{.buffer = buffer->gpuBuffers[i].buffer, + .offset = offset, + .range = range}; + VkWriteDescriptorSet descriptorWrite{ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = set->sets[i], + .dstBinding = binding, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pImageInfo = nullptr, + .pBufferInfo = &bufferInfo, + .pTexelBufferView = nullptr}; + vkUpdateDescriptorSets(pimpl->device.device, 1, &descriptorWrite, 0, + nullptr); + } +} + +void GFXDevice::UpdateDescriptorCombinedImageSampler( + const gfx::DescriptorSet* set, uint32_t binding, const gfx::Image* image, + const gfx::Sampler* sampler) { + if (pimpl->FRAMECOUNT != 0) abort(); + + VkDescriptorImageInfo imageInfo{}; + imageInfo.sampler = sampler->sampler; + imageInfo.imageView = image->view; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { + VkWriteDescriptorSet descriptorWrite{ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = set->sets[i], + .dstBinding = binding, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &imageInfo, + .pBufferInfo = nullptr, + .pTexelBufferView = nullptr}; + vkUpdateDescriptorSets(pimpl->device.device, 1, &descriptorWrite, 0, + nullptr); + } +} + +gfx::UniformBuffer* GFXDevice::CreateUniformBuffer(uint64_t size, + const void* initialData) { + gfx::UniformBuffer* out = new gfx::UniformBuffer{}; + + /* first make staging buffer */ + out->stagingBuffer.size = size; + out->stagingBuffer.type = gfx::BufferType::kUniform; + { + VkBufferCreateInfo stagingBufferInfo{}; + stagingBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + stagingBufferInfo.size = out->stagingBuffer.size; + stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + stagingBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + stagingBufferInfo.flags = 0; + + VmaAllocationCreateInfo stagingAllocInfo{}; + stagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO; + stagingAllocInfo.flags = + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + stagingAllocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + VKCHECK(vmaCreateBuffer(pimpl->allocator, &stagingBufferInfo, + &stagingAllocInfo, &out->stagingBuffer.buffer, + &out->stagingBuffer.allocation, nullptr)); + + void* dataDest; + VKCHECK(vmaMapMemory(pimpl->allocator, out->stagingBuffer.allocation, + &dataDest)); + memcpy(dataDest, initialData, out->stagingBuffer.size); + vmaUnmapMemory(pimpl->allocator, out->stagingBuffer.allocation); + } + + /* create the device-local set of buffers */ + for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { + out->gpuBuffers[i].size = out->stagingBuffer.size; + out->gpuBuffers[i].type = gfx::BufferType::kUniform; + + VkBufferCreateInfo gpuBufferInfo{}; + gpuBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + gpuBufferInfo.size = out->gpuBuffers[i].size; + gpuBufferInfo.usage = + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + gpuBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + gpuBufferInfo.flags = 0; + + VmaAllocationCreateInfo gpuAllocationInfo{}; + gpuAllocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + gpuAllocationInfo.flags = 0; + + VKCHECK(vmaCreateBuffer(pimpl->allocator, &gpuBufferInfo, + &gpuAllocationInfo, &out->gpuBuffers[i].buffer, + &out->gpuBuffers[i].allocation, nullptr)); + + /* copy staging buffer into both */ + copyBuffer(pimpl->device.device, pimpl->transferCommandPool, + pimpl->device.queues.transferQueues[0], + out->stagingBuffer.buffer, out->gpuBuffers[i].buffer, + out->stagingBuffer.size); + } + + return out; +} + +void GFXDevice::DestroyUniformBuffer(const gfx::UniformBuffer* uniformBuffer) { + for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { + vmaDestroyBuffer(pimpl->allocator, uniformBuffer->gpuBuffers[i].buffer, + uniformBuffer->gpuBuffers[i].allocation); + } + + vmaDestroyBuffer(pimpl->allocator, uniformBuffer->stagingBuffer.buffer, + uniformBuffer->stagingBuffer.allocation); + + delete uniformBuffer; +} + +void GFXDevice::WriteUniformBuffer(gfx::UniformBuffer* buffer, uint64_t offset, + uint64_t size, const void* data) { + assert(offset + size <= buffer->stagingBuffer.size); + + /* first update the staging buffer */ + void* dataDest; + VKCHECK(vmaMapMemory(pimpl->allocator, buffer->stagingBuffer.allocation, + &dataDest)); + memcpy(dataDest, (uint8_t*)data + offset, size); + vmaUnmapMemory(pimpl->allocator, buffer->stagingBuffer.allocation); + + /* queue the writes to each gpu buffer */ + // This is required as buffers cannot be updated if they are currently in use + for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { + pimpl->uniformBufferWriteQueues[i].insert(buffer); + } +} + +gfx::Buffer* GFXDevice::CreateBuffer(gfx::BufferType type, uint64_t size, + const void* data) { + [[maybe_unused]] VkResult res; + + auto out = new gfx::Buffer{}; + out->size = size; + out->type = type; + + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + // first create the staging buffer + { + VkBufferCreateInfo stagingBufferInfo{}; + stagingBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + stagingBufferInfo.size = out->size; + stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + stagingBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + stagingBufferInfo.flags = 0; + + VmaAllocationCreateInfo stagingAllocInfo{}; + stagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO; + stagingAllocInfo.flags = + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + stagingAllocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + VKCHECK(vmaCreateBuffer(pimpl->allocator, &stagingBufferInfo, + &stagingAllocInfo, &stagingBuffer, + &stagingAllocation, nullptr)); + + void* dataDest; + VKCHECK(vmaMapMemory(pimpl->allocator, stagingAllocation, &dataDest)); + memcpy(dataDest, data, out->size); + vmaUnmapMemory(pimpl->allocator, stagingAllocation); + } + + // create the actual buffer on the GPU + { + VkBufferCreateInfo gpuBufferInfo{}; + gpuBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + gpuBufferInfo.size = out->size; + gpuBufferInfo.usage = + converters::getBufferUsageFlag(type) | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + gpuBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + gpuBufferInfo.flags = 0; + + VmaAllocationCreateInfo gpuAllocationInfo{}; + gpuAllocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + gpuAllocationInfo.flags = 0; + + VKCHECK(vmaCreateBuffer(pimpl->allocator, &gpuBufferInfo, + &gpuAllocationInfo, &out->buffer, &out->allocation, + nullptr)); + } + + // copy the data from the staging buffer to the gpu buffer + copyBuffer(pimpl->device.device, pimpl->transferCommandPool, + pimpl->device.queues.transferQueues[0], stagingBuffer, out->buffer, + out->size); + + // destroy staging buffer + vmaDestroyBuffer(pimpl->allocator, stagingBuffer, stagingAllocation); + return out; +} + +void GFXDevice::DestroyBuffer(const gfx::Buffer* buffer) { + vmaDestroyBuffer(pimpl->allocator, buffer->buffer, buffer->allocation); + delete buffer; +} + +// imageData must have pixel format R8G8B8A8_SRGB +gfx::Image* GFXDevice::CreateImage(uint32_t w, uint32_t h, + const void* imageData) { + assert(imageData != nullptr); + if (pimpl->FRAMECOUNT != 0) { + throw std::runtime_error("Framecount must be 0 when creating a texture"); + } + + gfx::Image* out = new gfx::Image{}; + + uint32_t mipLevels = + static_cast(std::floor(std::log2(std::max(w, h)))) + 1; + VkFormat imageFormat = VK_FORMAT_R8G8B8A8_SRGB; + + VkBuffer stagingBuffer = VK_NULL_HANDLE; + VmaAllocation stagingAllocation = VK_NULL_HANDLE; + VkDeviceSize stagingBufferSize = (VkDeviceSize)w * (VkDeviceSize)h * 4; + + /* create staging buffer */ + { + VkBufferCreateInfo stagingBufferInfo{}; + stagingBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + stagingBufferInfo.size = stagingBufferSize; + stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + stagingBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + stagingBufferInfo.flags = 0; + + VmaAllocationCreateInfo stagingAllocInfo{}; + stagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO; + stagingAllocInfo.flags = + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + stagingAllocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + VKCHECK(vmaCreateBuffer(pimpl->allocator, &stagingBufferInfo, + &stagingAllocInfo, &stagingBuffer, + &stagingAllocation, nullptr)); + + void* dataDest; + VKCHECK(vmaMapMemory(pimpl->allocator, stagingAllocation, &dataDest)); + memcpy(dataDest, imageData, stagingBufferSize); + vmaUnmapMemory(pimpl->allocator, stagingAllocation); + } + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.flags = 0; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.format = imageFormat; + imageInfo.extent.width = w; + imageInfo.extent.height = h; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = mipLevels; + imageInfo.arrayLayers = 1; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VmaAllocationCreateInfo allocCreateInfo{}; + allocCreateInfo.flags = 0; + allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocCreateInfo.priority = 0.5f; + + VKCHECK(vmaCreateImage(pimpl->allocator, &imageInfo, &allocCreateInfo, + &out->image, &out->allocation, nullptr)); + + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = out->image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = imageFormat; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = mipLevels; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VKCHECK( + vkCreateImageView(pimpl->device.device, &viewInfo, nullptr, &out->view)); + + /* begin command buffer */ + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = pimpl->graphicsCommandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + VKCHECK(vkAllocateCommandBuffers(pimpl->device.device, &allocInfo, + &commandBuffer)); + + { // record the command buffer + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + VKCHECK(vkBeginCommandBuffer(commandBuffer, &beginInfo)); + + // barrier: (all mip levels): UNDEFINED -> TRANSFER_DST_OPTIMAL + // Used for copying staging buffer AND blitting mipmaps + // Must happen before vkCmdCopyBufferToImage performs a TRANSFER_WRITE in + // the COPY stage. + VkImageMemoryBarrier2 beforeCopyBarrier{}; + beforeCopyBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + beforeCopyBarrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + beforeCopyBarrier.srcAccessMask = VK_ACCESS_2_NONE; + beforeCopyBarrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; + beforeCopyBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + beforeCopyBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + beforeCopyBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + beforeCopyBarrier.srcQueueFamilyIndex = + pimpl->device.queues.presentAndDrawQueueFamily; + beforeCopyBarrier.dstQueueFamilyIndex = + pimpl->device.queues.presentAndDrawQueueFamily; + beforeCopyBarrier.image = out->image; + beforeCopyBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + beforeCopyBarrier.subresourceRange.baseMipLevel = 0; + beforeCopyBarrier.subresourceRange.levelCount = mipLevels; + beforeCopyBarrier.subresourceRange.baseArrayLayer = 0; + beforeCopyBarrier.subresourceRange.layerCount = 1; + VkDependencyInfo beforeCopyDependency{}; + beforeCopyDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + beforeCopyDependency.imageMemoryBarrierCount = 1; + beforeCopyDependency.pImageMemoryBarriers = &beforeCopyBarrier; + vkCmdPipelineBarrier2(commandBuffer, &beforeCopyDependency); + + // copy staging buffer to mipLevel 0 (full res image) + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset.x = 0; + region.imageOffset.y = 0; + region.imageOffset.z = 0; + region.imageExtent = imageInfo.extent; + vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, out->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + int32_t mipWidth = w; + int32_t mipHeight = h; + for (uint32_t i = 1; i < mipLevels; i++) { + // barrier: (i - 1) TRANSFER_DST_OPTIMAL -> TRANSFER_SRC_OPTIMAL + // Must happen after TRANSFER_WRITE in the COPY stage and BLIT stage. + VkImageMemoryBarrier2 beforeBlitBarrier{}; + beforeBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + beforeBlitBarrier.srcStageMask = + VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT; + beforeBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + beforeBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; + beforeBlitBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; + beforeBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + beforeBlitBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + beforeBlitBarrier.srcQueueFamilyIndex = + pimpl->device.queues.presentAndDrawQueueFamily; + beforeBlitBarrier.dstQueueFamilyIndex = + pimpl->device.queues.presentAndDrawQueueFamily; + beforeBlitBarrier.image = out->image; + beforeBlitBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + beforeBlitBarrier.subresourceRange.baseMipLevel = (i - 1); + beforeBlitBarrier.subresourceRange.levelCount = 1; + beforeBlitBarrier.subresourceRange.baseArrayLayer = 0; + beforeBlitBarrier.subresourceRange.layerCount = 1; + VkDependencyInfo beforeBlitDependency{}; + beforeBlitDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + beforeBlitDependency.imageMemoryBarrierCount = 1; + beforeBlitDependency.pImageMemoryBarriers = &beforeBlitBarrier; + vkCmdPipelineBarrier2(commandBuffer, &beforeBlitDependency); + + VkImageBlit blit{}; + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.mipLevel = i - 1; + blit.srcSubresource.baseArrayLayer = 0; + blit.srcSubresource.layerCount = 1; + blit.srcOffsets[0] = {0, 0, 0}; + blit.srcOffsets[1] = {mipWidth, mipHeight, 1}; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.mipLevel = i; + blit.dstSubresource.baseArrayLayer = 0; + blit.dstSubresource.layerCount = 1; + blit.dstOffsets[0] = {0, 0, 0}; + blit.dstOffsets[1] = {mipWidth > 1 ? mipWidth / 2 : 1, + mipHeight > 1 ? mipHeight / 2 : 1, 1}; + vkCmdBlitImage(commandBuffer, out->image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, out->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, + VK_FILTER_LINEAR); + + // barrier: (i - 1) TRANSFER_SRC_OPTIMAL -> SHADER_READ_ONLY_OPTIMALs + // Must happen after usage in the BLIT stage. + // Must happen before SHADER_SAMPLED_READ in the FRAGMENT_SHADER stage + VkImageMemoryBarrier2 afterBlitBarrier{}; + afterBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + afterBlitBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT; + afterBlitBarrier.srcAccessMask = 0; + afterBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + afterBlitBarrier.dstAccessMask = + VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT; + afterBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + afterBlitBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + afterBlitBarrier.srcQueueFamilyIndex = + pimpl->device.queues.presentAndDrawQueueFamily; + afterBlitBarrier.dstQueueFamilyIndex = + pimpl->device.queues.presentAndDrawQueueFamily; + afterBlitBarrier.image = out->image; + afterBlitBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + afterBlitBarrier.subresourceRange.baseMipLevel = (i - 1); + afterBlitBarrier.subresourceRange.levelCount = 1; + afterBlitBarrier.subresourceRange.baseArrayLayer = 0; + afterBlitBarrier.subresourceRange.layerCount = 1; + VkDependencyInfo afterBlitDependency{}; + afterBlitDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + afterBlitDependency.imageMemoryBarrierCount = 1; + afterBlitDependency.pImageMemoryBarriers = &afterBlitBarrier; + vkCmdPipelineBarrier2(commandBuffer, &afterBlitDependency); + + if (mipWidth > 1) mipWidth /= 2; + if (mipHeight > 2) mipHeight /= 2; + } + + // Final mipLevel is never transitioned from TRANSFER_DST_OPTIMAL + // barrier: (mipLevels - 1) TRANSFER_DST_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL + VkImageMemoryBarrier2 finalBlitBarrier{}; + finalBlitBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + finalBlitBarrier.srcStageMask = + VK_PIPELINE_STAGE_2_BLIT_BIT | VK_PIPELINE_STAGE_2_COPY_BIT; + finalBlitBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + finalBlitBarrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + finalBlitBarrier.dstAccessMask = + VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT; + finalBlitBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + finalBlitBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + finalBlitBarrier.srcQueueFamilyIndex = + pimpl->device.queues.presentAndDrawQueueFamily; + finalBlitBarrier.dstQueueFamilyIndex = + pimpl->device.queues.presentAndDrawQueueFamily; + finalBlitBarrier.image = out->image; + finalBlitBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + finalBlitBarrier.subresourceRange.baseMipLevel = (mipLevels - 1); + finalBlitBarrier.subresourceRange.levelCount = 1; + finalBlitBarrier.subresourceRange.baseArrayLayer = 0; + finalBlitBarrier.subresourceRange.layerCount = 1; + VkDependencyInfo afterBlitDependency{}; + afterBlitDependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + afterBlitDependency.imageMemoryBarrierCount = 1; + afterBlitDependency.pImageMemoryBarriers = &finalBlitBarrier; + vkCmdPipelineBarrier2(commandBuffer, &afterBlitDependency); + + VKCHECK(vkEndCommandBuffer(commandBuffer)); + } + + // submit + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + VKCHECK(vkQueueSubmit(pimpl->device.queues.drawQueues[0], 1, &submitInfo, + VK_NULL_HANDLE)); + + VKCHECK(vkQueueWaitIdle(pimpl->device.queues.drawQueues[0])); + + vkFreeCommandBuffers(pimpl->device.device, pimpl->graphicsCommandPool, 1, + &commandBuffer); + + vmaDestroyBuffer(pimpl->allocator, stagingBuffer, stagingAllocation); + + return out; +} + +void GFXDevice::DestroyImage(const gfx::Image* image) { + vkDestroyImageView(pimpl->device.device, image->view, nullptr); + vmaDestroyImage(pimpl->allocator, image->image, image->allocation); + delete image; +} + +const gfx::Sampler* GFXDevice::CreateSampler(const gfx::SamplerInfo& info) { + gfx::Sampler* out = new gfx::Sampler{}; + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = converters::getFilter(info.magnify); + samplerInfo.minFilter = converters::getFilter(info.minify); + samplerInfo.mipmapMode = converters::getSamplerMipmapMode(info.mipmap); + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.anisotropyEnable = + info.anisotropic_filtering ? VK_TRUE : VK_FALSE; + samplerInfo.maxAnisotropy = + pimpl->device.properties.limits.maxSamplerAnisotropy; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = VK_LOD_CLAMP_NONE; + + VKCHECK(vkCreateSampler(pimpl->device.device, &samplerInfo, nullptr, + &out->sampler)); + + return out; +} + +void GFXDevice::DestroySampler(const gfx::Sampler* sampler) { + vkDestroySampler(pimpl->device.device, sampler->sampler, nullptr); + delete sampler; +} + +void GFXDevice::LogPerformanceInfo() { + VmaTotalStatistics pStats{}; + vmaCalculateStatistics(pimpl->allocator, &pStats); + + VkPhysicalDeviceMemoryProperties2 memProps{}; + memProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2; + vkGetPhysicalDeviceMemoryProperties2(pimpl->device.physicalDevice, &memProps); + + LOG_INFO("GPU Memory Statistics:"); + + for (uint32_t i = 0; i < memProps.memoryProperties.memoryHeapCount; i++) { + const VmaStatistics& statistics = pStats.memoryHeap[i].statistics; + VkMemoryHeap heap = memProps.memoryProperties.memoryHeaps[i]; + LOG_INFO("Memory heap {}", i); + if (heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) { + LOG_INFO(" DEVICE_LOCAL"); + } + LOG_INFO(" Memory blocks allocated: {} ({} MiB)", statistics.blockCount, + statistics.allocationBytes / (1024 * 1024)); + LOG_INFO(" Number of allocations: {} ({} MiB)", + statistics.allocationCount, + statistics.allocationBytes / (1024 * 1024)); + LOG_INFO(" Max size: {} MiB", heap.size / (1024 * 1024)); + } +} + +uint64_t GFXDevice::GetFrameCount() { return pimpl->FRAMECOUNT; } + +void GFXDevice::WaitIdle() { vkDeviceWaitIdle(pimpl->device.device); } + +} // namespace engine diff --git a/src/resources/font.cpp b/src/resources/font.cpp index 4429ce4..3c4742c 100644 --- a/src/resources/font.cpp +++ b/src/resources/font.cpp @@ -55,7 +55,7 @@ std::unique_ptr> Font::GetTextBitmap( CharacterRenderInfo renderInfo{}; - renderInfo.advance = advanceWidth * sf; + renderInfo.advance = static_cast(static_cast(advanceWidth) * sf); if (stbtt_IsGlyphEmpty(font_info_.get(), glyph_index) == 0) { renderInfo.isEmpty = false; diff --git a/src/resources/texture.cpp b/src/resources/texture.cpp index e569984..dbf1c13 100644 --- a/src/resources/texture.cpp +++ b/src/resources/texture.cpp @@ -100,6 +100,7 @@ Texture::Texture(RenderData* render_data, const uint8_t* bitmap, int width, Texture::~Texture() { LOG_INFO("Destroying texture..."); + gfx_->FreeDescriptorSet(descriptor_set_); gfx_->DestroyImage(image_); } diff --git a/test/src/game.cpp b/test/src/game.cpp index e13667a..8b01cf5 100644 --- a/test/src/game.cpp +++ b/test/src/game.cpp @@ -175,16 +175,18 @@ void PlayGame(GameSettings settings) { time_elapsed += ts; if (time_elapsed >= 1.0f) { time_elapsed = 0; - LOG_INFO("Time step: {}", ts); int fpsWidth, fpsHeight; - auto fpsBitmap = + LOG_INFO("Creating new bitmap..."); + /*auto fpsBitmap = app.GetResource("builtin.mono") - ->GetTextBitmap("fps", 768.0f, fpsWidth, fpsHeight); - textbox_renderable->material->texture_ = + ->GetTextBitmap(std::to_string(ts), 768.0f, fpsWidth, fpsHeight);*/ + LOG_INFO("Bitmap created! Loading into new texture..."); + /*textbox_renderable->material->texture_ = std::make_unique( &app.render_data_, fpsBitmap->data(), fpsWidth, fpsHeight, - engine::resources::Texture::Filtering::kBilinear); + engine::resources::Texture::Filtering::kBilinear);*/ + LOG_INFO("Texture created!"); } }; }