#include "vulkan_instance.hpp" #include "exceptions.hpp" /* #include "font.hpp" */ #include "vulkan_util.hpp" #include #include #include #include #include #include #include #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define TINYOBJLOADER_IMPLEMENTATION #include "tiny_obj_loader.h" #include #include #include #include #include #define VULKAN_HPP_NO_CONSTRUCTORS #include #define VULKAN_INSTANCE_SWAP_CHAIN_AS_TRANSFER_DST #ifdef VULKAN_INSTANCE_SWAP_CHAIN_AS_TRANSFER_DST #elif defined VULKAN_INSTANCE_SWAP_CHAIN_AS_RENDER_TARGET #else static_assert(false, "either VULKAN_INSTANCE_SWAP_CHAIN_AS_TRANSFER_DST or VULKAN_INSTANCE_SWAP_CHAIN_AS_RENDER_TARGET has to be defined"); #endif namespace gz::vlk { using std::uint32_t; // // SETTINGS // const unsigned int WIDTH = 600; const unsigned int HEIGHT = 600; // DEBUG #ifdef NDEBUG constexpr bool enableValidationLayers = false; #else constexpr bool enableValidationLayers = true; #endif // TODO: why needed at two separate places? constexpr vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCI { .pNext = &settings::validationFeatures, .messageSeverity = settings::debugMessageSevereties, .messageType = settings::debugMessageTypes, .pfnUserCallback = VulkanInstance::debugLog, .pUserData = nullptr, }; // // // IMPLEMENTATION OF VULKANINSTANCE // // // PUBLIC INTERFACE: FOR MAIN FUNCTION void VulkanInstance::init() { createWindow(); if (glfwVulkanSupported() == GLFW_FALSE) { throw std::runtime_error("Vulkan not supported."); } createInstance(); setupDebugMessenger(); createSurface(); selectPhysicalDevice(); setValidSettings(); MAX_FRAMES_IN_FLIGHT = settings.getCopyOr("max_frames_in_flight", 1); createLogicalDevice(); createSwapChain(); createCommandPools(); createCommandBuffers(commandBuffersBegin); createCommandBuffers(commandBuffersEnd); createSyncObjects(); /* createDescriptorPool(); */ /* createDescriptorSets(); // after UniformBuffers */ VulkanInstance::registerObjectUsingVulkan("VulkanInstance", &instance, &physicalDevice, &device, &graphicsQ, &presentQ, &transferQ, &commandPoolGraphics, &commandPoolTransfer, &surface, &swapChain, &scImages, &scImageViews, &commandBuffersBegin, &commandBuffersEnd); } void VulkanInstance::cleanup() { vk::Result result = device.waitIdle(); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to wait for device idle", "VulkanInstance::cleanup"); } // call callbacks in reverse order for (auto it = cleanupCallbacks.rbegin(); it != cleanupCallbacks.rend(); it++) { (*it)(); } /* vLog.log0("cleanup:", commandBuffersToSubmitThisFrame.size()); */ destroyCommandBuffers(commandBuffersBegin); destroyCommandBuffers(commandBuffersEnd); /* device.destroyImageView(textureImageView, nullptr); */ /* device.destroySampler(textureSampler, nullptr); */ /* device.destroyImage(textureImage, nullptr); */ /* device.freeMemory(textureImageMemory, nullptr); */ cleanupSwapChain(); for (size_t i = 0; i < getMaxFramesInFlight(); i++) { device.destroySemaphore(imageAvailableSemaphores[i], nullptr); device.destroySemaphore(renderFinishedSemaphores[i], nullptr); device.destroyFence(inFlightFences[i], nullptr); } /* device.freeCommandBuffers(commandPoolGraphics, static_cast(commandBuffers2D.size()), commandBuffers2D.data()); */ /* device.freeCommandBuffers(commandPoolGraphics, static_cast(commandBuffers3D.size()), commandBuffers3D.data()); */ device.destroyCommandPool(commandPoolGraphics, nullptr); device.destroyCommandPool(commandPoolTransfer, nullptr); allocator.cleanup(); device.destroy(); instance.destroySurfaceKHR(surface); cleanupDebugMessenger(); instance.destroy(); glfwDestroyWindow(window); glfwTerminate(); } uint32_t VulkanInstance::beginFrameDraw() { vk::Result result = device.waitForFences(inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to wait for fence", "VulkanInstance::beginFrameDraw"); } uint32_t imageIndex; uint32_t blockFor = 0; /* uint64_t blockFor = UINT64_MAX; */ result = device.acquireNextImageKHR(swapChain, blockFor, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == vk::Result::eErrorOutOfDateKHR or frameBufferResized) { // result == VK_SUBOPTIMAL_KHR or vLog.log0("drawFrame: result:", result, "frameBufferResized:", frameBufferResized); frameBufferResized = false; recreateSwapChain(); return imageIndex; } else if (result != vk::Result::eSuboptimalKHR and result != vk::Result::eSuccess) { throw getVkException(result, "Failed to acquire swap chain image.", "beginFrameDraw"); } result = device.resetFences(1, &inFlightFences[currentFrame]); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to reset fences", "VulkanInstance::beginFrameDraw"); } // clear image commandBuffersBegin[currentFrame].reset(NO_CMD_RESET_FLAGS); vk::CommandBufferBeginInfo commandBufferBI; result = commandBuffersBegin[currentFrame].begin(commandBufferBI); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to begin clear command buffer", "VulkanInstance::beginFrameDraw"); } // transition to transfer dst layout #ifdef VULKAN_INSTANCE_SWAP_CHAIN_AS_TRANSFER_DST vk::ImageLayout targetLayout = vk::ImageLayout::eTransferDstOptimal; #elif defined VULKAN_INSTANCE_SWAP_CHAIN_AS_RENDER_TARGET vk::ImageLayout targetLayout = vk::ImageLayout::eColorAttachmentOptimal; #endif transitionImageLayout(scImages[imageIndex], scImageFormat, vk::ImageLayout::eUndefined, targetLayout, &commandBuffersBegin[currentFrame]); result = commandBuffersBegin[currentFrame].end(); if (result != vk::Result::eSuccess) { vLog.error("Failed to record clear command buffer", "VkResult:", result); throw getVkException(result, "Failed to record command buffer", "VulkanInstance::beginFrameDraw"); } commandBuffersToSubmitThisFrame.push_back(commandBuffersBegin[currentFrame]); return imageIndex; } void VulkanInstance::endFrameDraw(uint32_t imageIndex) { commandBuffersEnd[currentFrame].reset(NO_CMD_RESET_FLAGS); vk::CommandBufferBeginInfo commandBufferBI; vk::Result result = commandBuffersEnd[currentFrame].begin(commandBufferBI); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to begin command buffer", "VulkanInstance::endFrameDraw"); } #ifdef VULKAN_INSTANCE_SWAP_CHAIN_AS_TRANSFER_DST vk::ImageLayout currentLayout = vk::ImageLayout::eTransferDstOptimal; #elif defined VULKAN_INSTANCE_SWAP_CHAIN_AS_RENDER_TARGET vk::ImageLayout currentLayout = vk::ImageLayout::eColorAttachmentOptimal; #endif // transition to present layout transitionImageLayout(scImages[imageIndex], scImageFormat, currentLayout, vk::ImageLayout::ePresentSrcKHR, &commandBuffersEnd[currentFrame]); result = commandBuffersEnd[currentFrame].end(); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to record command buffer", "VulkanInstance::endFrameDraw"); } commandBuffersToSubmitThisFrame.push_back(commandBuffersEnd[currentFrame]); // submit all command buffers vk::SubmitInfo submitI; submitI.setWaitSemaphores(imageAvailableSemaphores[currentFrame]); vk::PipelineStageFlags dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; submitI.setWaitDstStageMask(dstStageMask); submitI.setSignalSemaphores(renderFinishedSemaphores[currentFrame]); submitI.setCommandBuffers(commandBuffersToSubmitThisFrame); result = graphicsQ.submit(submitI, inFlightFences[currentFrame]); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to submit command buffers", "VulkanInstance::endFrameDraw"); } commandBuffersToSubmitThisFrame.clear(); // present the image vk::PresentInfoKHR presentI { .pImageIndices = &imageIndex, .pResults = nullptr, }; presentI.setWaitSemaphores(renderFinishedSemaphores[currentFrame]); presentI.setSwapchains(swapChain); result = presentQ.presentKHR(presentI); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed present image", "VulkanInstance::endFrameDraw"); } currentFrame = ++currentFrame % getMaxFramesInFlight(); } // PUBLIC INTERFACE: INIT AND CLEANUP void VulkanInstance::registerSwapChainRecreateCallback(std::function callbackF) { scRecreateCallbacks.push_back(callbackF); } void VulkanInstance::registerCleanupCallback(std::function callbackF) { cleanupCallbacks.push_back(callbackF); } // PUBLIC INTERFACE: VARIOUS UTILITY void VulkanInstance::submitThisFrame(vk::CommandBuffer& cmdBuffer) { commandBuffersToSubmitThisFrame.push_back(cmdBuffer); } void VulkanInstance::copyBuffer(vk::Buffer srcBuffer, vk::Buffer dstBuffer, vk::DeviceSize size) { vk::CommandBuffer cmdBuffer = beginSingleTimeCommands(POOL_TRANSFER); vk::BufferCopy copyRegion { .srcOffset = 0, .dstOffset = 0, .size = size, }; cmdBuffer.copyBuffer(srcBuffer, dstBuffer, copyRegion); endSingleTimeCommands(cmdBuffer, POOL_TRANSFER); } void VulkanInstance::copyToDeviceBuffer(const void* srcData, vk::DeviceSize srcSize, vk::Buffer& dstBuffer, MemoryInfo& dstMemory, uint32_t dstOffset) { // create staging buffer vk::Buffer stagingBuffer; MemoryInfo stagingBufferMemory; createBuffer(srcSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); // fill staging buffer void* stagingMemory; vk::Result result = device.mapMemory(stagingBufferMemory.memory, stagingBufferMemory.offset, srcSize, NO_MEM_FLAGS, &stagingMemory); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to map staging buffer for vertices", "VulkanInstance::copyToDeviceBuffer"); } memcpy(stagingMemory, srcData, srcSize); device.unmapMemory(stagingBufferMemory.memory); // copy to device local memory vk::CommandBuffer cmdBuffer = beginSingleTimeCommands(POOL_TRANSFER); vk::BufferCopy copyRegion { .srcOffset = 0, .dstOffset = dstOffset, .size = srcSize, }; cmdBuffer.copyBuffer(stagingBuffer, dstBuffer, copyRegion); endSingleTimeCommands(cmdBuffer, POOL_TRANSFER); destroyBuffer(stagingBuffer, stagingBufferMemory); vLog.log0("copyToDeviceBuffer: copied data at", srcData, "of size", srcSize, "to dstBuffer"); } vk::CommandBuffer VulkanInstance::beginSingleTimeCommands(InstanceCommandPool commandPool) { vk::CommandBufferAllocateInfo allocI { .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1, }; if (commandPool == POOL_TRANSFER) { allocI.setCommandPool(commandPoolTransfer); } else { allocI.setCommandPool(commandPoolGraphics); } vk::CommandBuffer cmdBuffer; vk::Result result = device.allocateCommandBuffers(&allocI, &cmdBuffer); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to allocate command buffer", "beginSingleTimeCommands"); } vk::CommandBufferBeginInfo beginI { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit, }; result = cmdBuffer.begin(beginI); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to begin command buffer", "beginSingleTimeCommands"); } return cmdBuffer; } void VulkanInstance::endSingleTimeCommands(vk::CommandBuffer cmdBuffer, InstanceCommandPool commandPool) { vk::Result result = cmdBuffer.end(); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to end commandBuffer", "endSingleTimeCommands"); } vk::CommandPool cmdPool; vk::Queue q; if (commandPool == POOL_TRANSFER) { cmdPool = commandPoolTransfer; q = transferQ; } else { cmdPool = commandPoolGraphics; q = graphicsQ; } vk::SubmitInfo submitI; submitI.setCommandBuffers(cmdBuffer); result = q.submit(submitI); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to submit commandBuffer", "endSingleTimeCommands"); } result = q.waitIdle(); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to wait for queue idle", "endSingleTimeCommands"); } device.freeCommandBuffers(cmdPool, cmdBuffer); } // PUBLIC INTERFACE: CREATION AND DESTRUCTION // COMMAND BUFFER void VulkanInstance::createCommandBuffers(std::vector& commandBuffers) { commandBuffers.resize(getMaxFramesInFlight()); vk::CommandBufferAllocateInfo commandBufferAI { .commandPool = commandPoolGraphics, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = static_cast(commandBuffers.size()), }; vk::Result result = device.allocateCommandBuffers(&commandBufferAI, commandBuffers.data()); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create allocate buffers", "VulkanInstance::createCommandBuffers"); } } void VulkanInstance::destroyCommandBuffers(std::vector& commandBuffers) { device.freeCommandBuffers(commandPoolGraphics, commandBuffers); commandBuffers.resize(0); } // GRAPHICS PIPELINE template void VulkanInstance::createGraphicsPipeline(vk::GraphicsPipelineCreateInfo&& pipelineCI, const std::vector& descriptorSetLayouts, const std::vector& pushConstantRanges, bool useDepthStencil, Pipeline& pipeline) { // 1) sanity checks if (pipelineCI.pStages == nullptr) { throw VkUserError("pStages is nullptr", "VulkanInstance::createGraphicsPipeline"); } if (static_cast(pipelineCI.renderPass) == VK_NULL_HANDLE) { throw VkUserError("renderPass is VK_NULL_HANDLE", "VulkanInstance::createGraphicsPipeline"); } // 2) create layout vk::PipelineLayoutCreateInfo pipelineLayoutCI; // TODO /* pipelineLayoutCI.setPushConstantRanges() */ if (pushConstantRanges.size() > 0) { pipelineLayoutCI.setPushConstantRanges(pushConstantRanges); } if (descriptorSetLayouts.size() > 0) { pipelineLayoutCI.setSetLayouts(descriptorSetLayouts); } VkPipelineLayoutCreateInfo pipelineLayoutCI2 = static_cast(pipelineLayoutCI); vLog.log0("createGraphicsPipeline: ", pipelineLayoutCI2.setLayoutCount); vk::Result result = device.createPipelineLayout(&pipelineLayoutCI, nullptr, &pipeline.layout); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create pipeline layout", "VulkanInstance::createGraphicsPipeline"); } vLog.log0("createGraphicsPipeline: created layout: ( descriptorSetLayouts:", descriptorSetLayouts.size(), ")"); vLog.log0("createGraphicsPipeline:", pipelineLayoutCI.setLayoutCount); pipelineCI.setLayout(pipeline.layout); // 3) construct default create infos // default vertexInputStateCI vk::VertexInputBindingDescription bindingD = VertexT::getBindingDescription(); // array auto attributeD = VertexT::getAttributeDescriptions(); vk::PipelineVertexInputStateCreateInfo vertexInputStateCI; vertexInputStateCI.setVertexAttributeDescriptions(attributeD); vertexInputStateCI.setVertexBindingDescriptions(bindingD); // default inputAssemblyStateCI vk::PipelineInputAssemblyStateCreateInfo inputAssemblyStateCI { .topology = vk::PrimitiveTopology::eTriangleList, .primitiveRestartEnable = VK_FALSE, }; // default viewportState vk::Viewport viewport { .x = 0.0f, .y = 0.0f, .width = static_cast(scExtent.width), .height = static_cast(scExtent.height), .minDepth = 0.0f, .maxDepth = 1.0f, }; vk::Rect2D scissor { .offset = { 0, 0 }, .extent = scExtent, }; vk::PipelineViewportStateCreateInfo viewportStateCI { .viewportCount = 1, .pViewports = &viewport, .scissorCount = 1, .pScissors = &scissor, }; // default rasterizationState vk::PipelineRasterizationStateCreateInfo rasterizationStateCI { .depthClampEnable = VK_FALSE, .rasterizerDiscardEnable = VK_FALSE, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, // not clockwise because of inverted y axis from glm .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = VK_FALSE, .lineWidth = 1.0f, /* .depthBiasConstantFactor = 0.0f, */ /* .depthBiasClamp = 0.0f, */ /* .depthBiasSlopeFactor = 0.0f, */ }; // default multisampleStateCI vk::PipelineMultisampleStateCreateInfo multisampleStateCI { .rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = VK_FALSE, /* .minSampleShading = 1.0f, */ /* .pSampleMask = nullptr, */ /* .alphaToCoverageEnable = VK_FALSE, */ /* .alphaToOneEnable = VK_FALSE, */ }; // default colorBlendStateCI vk::PipelineColorBlendAttachmentState colorBlendAttachment { /* .blendEnable = VK_FALSE, */ .blendEnable = VK_TRUE, .srcColorBlendFactor = vk::BlendFactor::eOne, .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, .colorBlendOp = vk::BlendOp::eAdd, .srcAlphaBlendFactor = vk::BlendFactor::eOne, .dstAlphaBlendFactor = vk::BlendFactor::eOne, .alphaBlendOp = vk::BlendOp::eAdd, .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, }; vk::PipelineColorBlendStateCreateInfo colorBlendStateCI { .logicOpEnable = VK_FALSE, }; colorBlendStateCI.setAttachments(colorBlendAttachment); colorBlendStateCI.blendConstants[0] = 0.0f; colorBlendStateCI.blendConstants[1] = 0.0f; colorBlendStateCI.blendConstants[2] = 0.0f; colorBlendStateCI.blendConstants[3] = 0.0f; /* std::vector dynamicStates = { */ /* vk::DynamicState::eViewport, */ /* vk::DynamicState::eLineWidth */ /* }; */ /* vk::PipelineDynamicStateCreateInfo dynamicState { */ /* dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); */ /* dynamicState.pDynamicStates = dynamicStates.data(); */ // default depthStencilStateCI vk::PipelineDepthStencilStateCreateInfo depthStencilStateCI { .depthTestEnable = VK_TRUE, .depthWriteEnable = VK_TRUE, .depthCompareOp = vk::CompareOp::eLess, .depthBoundsTestEnable = VK_FALSE, /* .minDepthBounds = 0.0f, */ /* .maxDepthBounds = 1.0f, */ .stencilTestEnable = VK_FALSE, /* .front = {}, */ /* .back = {}, */ }; if (pipelineCI.pVertexInputState == nullptr) { pipelineCI.setPVertexInputState(&vertexInputStateCI); } if (pipelineCI.pInputAssemblyState == nullptr) { pipelineCI.setPInputAssemblyState(&inputAssemblyStateCI); } if (pipelineCI.pViewportState == nullptr) { pipelineCI.setPViewportState(&viewportStateCI); } if (pipelineCI.pRasterizationState == nullptr) { pipelineCI.setPRasterizationState(&rasterizationStateCI); } if (pipelineCI.pMultisampleState == nullptr) { pipelineCI.setPMultisampleState(&multisampleStateCI); } if (useDepthStencil and pipelineCI.pDepthStencilState == nullptr) { pipelineCI.setPDepthStencilState(&depthStencilStateCI); } if (pipelineCI.pColorBlendState == nullptr) { pipelineCI.setPColorBlendState(&colorBlendStateCI); } // result, vector auto [result2, pipelines] = device.createGraphicsPipelines(VK_NULL_HANDLE, pipelineCI, NO_ALLOC); pipeline.pipeline = pipelines.front(); if (result2 != vk::Result::eSuccess) { throw getVkException(result, "Could not create graphics pipeline", "createGraphicsPipeline"); } vLog.log0("createGraphicsPipeline: Created graphics pipeline ( useDepthStencil:", useDepthStencil, ")" ); } template void VulkanInstance::createGraphicsPipeline(vk::GraphicsPipelineCreateInfo&& pipelineCI, const std::vector& descriptorSetLayouts, const std::vector& pushConstantRanges, bool useDepthStencil, Pipeline& pipeline); template void VulkanInstance::createGraphicsPipeline(vk::GraphicsPipelineCreateInfo&& pipelineCI, const std::vector& descriptorSetLayouts, const std::vector& pushConstantRanges, bool useDepthStencil, Pipeline& pipeline); // SHADER MODULE vk::ShaderModule VulkanInstance::createShaderModule(const std::vector& code) { vk::ShaderModuleCreateInfo shaderModuleCI { .codeSize = code.size(), .pCode = reinterpret_cast(code.data()), }; auto [result, shaderModule] = device.createShaderModule(shaderModuleCI, NO_ALLOC); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create shader module. Code size: " + std::to_string(code.size()), "VulkanInstance::createShaderModule"); } return shaderModule; } /* void VulkanInstance::createShaderModules(const std::vector>& shaders, std::vector& shaderStages,) { */ /* vk::ShaderStageFlagBits::eAll */ /* for (auto& [filepath, specI] : shaders) { */ /* if (filepath.contains()) */ /* shaderStages.emplace_back(vk::PipelineShaderStageCreateInfo { */ /* .stage = vk::ShaderStageFlagBits::eFragment, */ /* .module = fragShaderModule, */ /* .pName = "main", */ /* .pSpecializationInfo = nullptr, */ /* }); */ /* } */ /* } */ // BUFFER void VulkanInstance::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::Buffer& buffer, MemoryInfo& bufferMI, vk::SharingMode sharingMode, std::vector* qFamiliesWithAccess) { vk::BufferCreateInfo bufferCI { .size = size, .usage = usage, .sharingMode = sharingMode, }; /* std::vector queueFamiliesWithAccess; */ if (sharingMode == vk::SharingMode::eConcurrent) { if (qFamiliesWithAccess == nullptr) { throw VkUserError("Sharing mode is vk::SharingMode::eConcurrent but qFamiliesWithAccess is nullptr", "VulkanInstance::createBuffer"); } bufferCI.setQueueFamilyIndices(*qFamiliesWithAccess); } vk::Result result = device.createBuffer(&bufferCI, nullptr, &buffer); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create buffer", "VulkanInstance::createBuffer"); } vk::BufferMemoryRequirementsInfo2 bufMemReq { .buffer = buffer, }; vk::MemoryRequirements2 memReq = device.getBufferMemoryRequirements2(bufMemReq); vk::MemoryPropertyFlags wantedProperties = vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent; vk::MemoryAllocateInfo memoryAI { .allocationSize = memReq.memoryRequirements.size, .memoryTypeIndex = findMemoryType(memReq.memoryRequirements.memoryTypeBits, wantedProperties), }; allocator.allocate(memoryAI, memReq, bufferMI); result = device.bindBufferMemory(buffer, bufferMI.memory, bufferMI.offset); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to bind buffer to its memory", "VulkanInstance::createBuffer"); } vLog.log0("createBuffer: created and bound buffer of size", memoryAI.allocationSize); } void VulkanInstance::destroyBuffer(vk::Buffer& buffer, MemoryInfo& bufferMemory) { device.destroyBuffer(buffer, NO_ALLOC); allocator.free(bufferMemory); buffer = VK_NULL_HANDLE; } // VERTEX BUFFER template void VulkanInstance::createVertexBuffer(size_t vertexCount, vk::Buffer& vertexBuffer, MemoryInfo& vertexBufferMemory, vk::DeviceSize& vertexBufferSize) { vertexBufferSize = vertexCount * sizeof(VertexT); // create vertex buffer vk::SharingMode sharingMode = vk::SharingMode::eExclusive; std::vector qFamiliesWithAccess; if (qFamilyIndices.transferFamily.has_value()) { // prefer dedicated transfer family qFamiliesWithAccess = { qFamilyIndices.graphicsFamily.value(), qFamilyIndices.transferFamily.value() }; } createBuffer(vertexBufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory , sharingMode, &qFamiliesWithAccess); vLog.log0("createVertexBuffer: Created vertex buffer with size", vertexBufferSize); } template void VulkanInstance::createVertexBuffer(size_t vertexCount, vk::Buffer& vertexBuffer, MemoryInfo& vertexBufferMemory, vk::DeviceSize& vertexBufferSize); template void VulkanInstance::createVertexBuffer(size_t vertexCount, vk::Buffer& vertexBuffer, MemoryInfo& vertexBufferMemory, vk::DeviceSize& vertexBufferSize); // INDEX BUFFER template void VulkanInstance::createIndexBuffer(size_t indexCount, vk::Buffer& indexBuffer, MemoryInfo& indexBufferMemory, vk::DeviceSize& indexBufferSize) { indexBufferSize = indexCount * sizeof(T); // create index buffer vk::SharingMode sharingMode = vk::SharingMode::eExclusive; std::vector qFamiliesWithAccess; if (qFamilyIndices.transferFamily.has_value()) { // prefer dedicated transfer family qFamiliesWithAccess = { qFamilyIndices.graphicsFamily.value(), qFamilyIndices.transferFamily.value() }; } createBuffer(indexBufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory, sharingMode, &qFamiliesWithAccess); vLog.log0("createIndexBuffer: Created index buffer with size:", indexBufferSize); } template void VulkanInstance::createIndexBuffer(size_t indexCount, vk::Buffer& indexBuffer, MemoryInfo& indexBufferMemory, vk::DeviceSize& indexBufferSize); template void VulkanInstance::createIndexBuffer(size_t indexCount, vk::Buffer& indexBuffer, MemoryInfo& indexBufferMemory, vk::DeviceSize& indexBufferSize); // FRAMEBUFFERS void VulkanInstance::createFramebuffers(std::vector& framebuffers, std::vector& imageViews, vk::RenderPass& renderPass, vk::ImageView depthImageView) { framebuffers.resize(imageViews.size()); vk::FramebufferCreateInfo framebufferCI { .renderPass = renderPass, .width = scExtent.width, .height = scExtent.height, .layers = 1, }; std::vector attachments; for (size_t i = 0; i < framebuffers.size(); i++) { if (static_cast(depthImageView) != VK_NULL_HANDLE) { attachments = { imageViews[i] , depthImageView }; } else { attachments = { imageViews[i] }; } framebufferCI.setAttachments(attachments); vk::Result result = device.createFramebuffer(&framebufferCI, NO_ALLOC, &framebuffers[i]); if (result != vk::Result::eSuccess) { throw getVkException(result, "Could not create framebuffer", "VulkanInstance::createFramebuffers"); } } vLog.log0("createFramebuffers: Created", framebuffers.size(), "framebuffers."); } void VulkanInstance::destroyFramebuffers(std::vector& framebuffers) { for (auto& framebuffer : framebuffers) { device.destroyFramebuffer(framebuffer); } } // TEXTURE SAMPLER void VulkanInstance::createTextureSampler(vk::Sampler& sampler) { vk::SamplerCreateInfo samplerCI { // TODO: LINEAR or NEAREST .magFilter = vk::Filter::eLinear, .minFilter = vk::Filter::eLinear, // TODO .mipmapMode = vk::SamplerMipmapMode::eLinear, .addressModeU = vk::SamplerAddressMode::eRepeat, .addressModeV = vk::SamplerAddressMode::eRepeat, .addressModeW = vk::SamplerAddressMode::eRepeat, .mipLodBias = 0.0f, .anisotropyEnable = bool2VkBool(settings.get("anisotropy_enable")), .maxAnisotropy = settings.get("max_anisotropy"), .compareEnable = VK_FALSE, .compareOp = vk::CompareOp::eAlways, .minLod = 0.0f, .maxLod = 0.0f, .borderColor = vk::BorderColor::eIntOpaqueBlack, .unnormalizedCoordinates = VK_FALSE, }; vk::Result result = device.createSampler(&samplerCI, nullptr, &sampler); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create texture sampler.", "VulkanInstance::createTextureSampler"); } } void VulkanInstance::destroyTextureSampler(vk::Sampler& sampler) { device.destroySampler(sampler, NO_ALLOC); sampler = VK_NULL_HANDLE; } // DESCRIPTORS void VulkanInstance::createDescriptorSetLayout(const std::vector& bindings, vk::DescriptorSetLayout& layout) { vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCI; descriptorSetLayoutCI.setBindings(bindings); vk::Result result; std::tie(result, layout) = device.createDescriptorSetLayout(descriptorSetLayoutCI, NO_ALLOC); if (result != vk::Result::eSuccess) { throw getVkException(result, "Could not create descriptorSetLayout", "VulkanInstance::createDescriptorSetLayout"); } vLog.log0("createDescriptorSetLayout: Created descriptor set layout with the following bindings:", bindings); } void VulkanInstance::createDescriptorPool(const std::vector& poolSizes, uint32_t maxSets, vk::DescriptorPool& pool) { vk::DescriptorPoolCreateInfo poolCI { .maxSets = maxSets }; poolCI.setPoolSizes(poolSizes); vk::Result result; std::tie(result, pool) = device.createDescriptorPool(poolCI); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create descriptor pool", "VulkanInstance::createDescriptorPool"); } vLog.log0("createDescriptorResources: Created descriptor pool with maxSets", maxSets, "and", poolSizes.size(), "pool sizes"); } void VulkanInstance::createDescriptorSet(const vk::DescriptorSetLayout& layout, const vk::DescriptorPool& pool, vk::DescriptorSet& set) { vk::DescriptorSetAllocateInfo setAI { .descriptorPool = pool, }; setAI.setSetLayouts(layout); auto [result, sets] = device.allocateDescriptorSets(setAI); set = sets.front(); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create descriptor set", "VulkanInstance::createDescriptorSet"); } } void VulkanInstance::createDescriptorSets(const std::vector& layouts, const vk::DescriptorPool& pool, std::vector& sets) { vk::DescriptorSetAllocateInfo setAI { .descriptorPool = pool, }; setAI.setSetLayouts(layouts); vk::Result result; std::tie(result, sets) = device.allocateDescriptorSets(setAI); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create descriptor set", "VulkanInstance::createDescriptorSet"); } } // PUBLIC INTERFACE: IMAGE UTILITY void VulkanInstance::createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags memoryProperties, vk::Image& image, MemoryInfo& imageMI) { vk::ImageCreateInfo imageCI { .imageType = vk::ImageType::e2D, .format = format, .extent { .width = width, .height = height, .depth = 1, }, .mipLevels = 1, .arrayLayers = 1, .samples = vk::SampleCountFlagBits::e1, // use linear when direct texel access is needed .tiling = tiling, .usage = usage, .sharingMode = vk::SharingMode::eExclusive, .initialLayout = vk::ImageLayout::eUndefined, /* .flags = 0, */ }; vk::Result result = device.createImage(&imageCI, NO_ALLOC, &image); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create image", "VulkanInstance::createImage"); } vk::ImageMemoryRequirementsInfo2 imMemReq { .image = image }; vk::MemoryRequirements2 memReq = device.getImageMemoryRequirements2(imMemReq); vk::MemoryAllocateInfo memoryAI { .allocationSize = memReq.memoryRequirements.size, .memoryTypeIndex = findMemoryType(memReq.memoryRequirements.memoryTypeBits, memoryProperties), }; allocator.allocate(memoryAI, memReq, imageMI); result = device.bindImageMemory(image, imageMI.memory, imageMI.offset); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to bind image to its memory", "VulkanInstance::createImage"); } vLog.log0("createImage: created and bound image of size", memoryAI.allocationSize, "with format", format, "and extent", imageCI.extent); } void VulkanInstance::destroyImage(vk::Image& image, MemoryInfo& imageMemory) { device.destroyImage(image, NO_ALLOC); allocator.free(imageMemory); image = VK_NULL_HANDLE; } // IMAGE VIEW void VulkanInstance::createImageView(vk::Format format, const vk::Image& image, vk::ImageView& imageView, vk::ImageAspectFlags aspectFlags) { vk::ImageViewCreateInfo imageViewCI { .image = image, .viewType = vk::ImageViewType::e2D, .format = format, .components { .r = vk::ComponentSwizzle::eIdentity, .g = vk::ComponentSwizzle::eIdentity, .b = vk::ComponentSwizzle::eIdentity, .a = vk::ComponentSwizzle::eIdentity, }, .subresourceRange { .aspectMask = aspectFlags, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; vk::Result result = device.createImageView(&imageViewCI, NO_ALLOC, &imageView); if (result != vk::Result::eSuccess) { throw getVkException(result, "Could not create image view", "VulkanInstance::createImageViews"); } vLog.log0("createImageView: Created image view with format", format); } void VulkanInstance::destroyImageView(vk::ImageView& imageView) { device.destroyImageView(imageView, NO_ALLOC); imageView = VK_NULL_HANDLE; } void VulkanInstance::copyBufferToImage(vk::Buffer buffer, vk::Image image, int32_t offsetX, int32_t offsetY, uint32_t width, uint32_t height) { vk::CommandBuffer cmdBuffer = beginSingleTimeCommands(POOL_TRANSFER); vk::BufferImageCopy region { .bufferOffset = NO_OFFSET, .bufferRowLength = 0, .bufferImageHeight = 0, .imageSubresource { .aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1, }, .imageOffset = { offsetX, offsetY, 0 }, .imageExtent = { width, height, 1 }, }; cmdBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, region); endSingleTimeCommands(cmdBuffer, POOL_TRANSFER); } void VulkanInstance::copyImageToImage(vk::CommandBuffer& cmdBuffer, vk::Image srcImage, vk::Image dstImage, vk::Extent2D extent) { vk::ImageCopy region { .srcSubresource { .aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1, }, .srcOffset = { 0, 0, 0 }, .dstSubresource { .aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1, }, .dstOffset = { 0, 0, 0 }, .extent = { extent.width, extent.height, 1 }, }; cmdBuffer.copyImage(srcImage, vk::ImageLayout::eTransferSrcOptimal, dstImage, vk::ImageLayout::eTransferDstOptimal, region); } bool hasStencilComponent(vk::Format format) { return format == vk::Format::eD32SfloatS8Uint or format == vk::Format::eD24UnormS8Uint; } void VulkanInstance::transitionImageLayout(vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, vk::CommandBuffer* cmdBuffer) { vk::ImageMemoryBarrier2 barrier { .oldLayout = oldLayout, .newLayout = newLayout, // not using barrier for queue famlily ownership transfer .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = image, .subresourceRange { .aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; InstanceCommandPool commandPool; if (oldLayout == vk::ImageLayout::eUndefined and newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) { barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; if (hasStencilComponent(format)) { barrier.subresourceRange.aspectMask |= vk::ImageAspectFlagBits::eStencil; } barrier.srcStageMask = vk::PipelineStageFlagBits2::eNone; barrier.srcAccessMask = vk::AccessFlagBits2::eNone; barrier.dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests; barrier.dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite; commandPool = POOL_GRAPHICS; } else if (oldLayout == vk::ImageLayout::eUndefined and newLayout == vk::ImageLayout::eTransferDstOptimal) { barrier.srcStageMask = vk::PipelineStageFlagBits2::eNone; barrier.srcAccessMask = vk::AccessFlagBits2::eNone; barrier.dstStageMask = vk::PipelineStageFlagBits2::eTransfer; barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite; commandPool = POOL_TRANSFER; } else if (oldLayout == vk::ImageLayout::eUndefined and newLayout == vk::ImageLayout::eColorAttachmentOptimal) { barrier.srcStageMask = vk::PipelineStageFlagBits2::eNone; barrier.srcAccessMask = vk::AccessFlagBits2::eNone; barrier.dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput; barrier.dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite; commandPool = POOL_TRANSFER; } else if (oldLayout == vk::ImageLayout::eShaderReadOnlyOptimal and newLayout == vk::ImageLayout::eTransferDstOptimal) { barrier.srcStageMask = vk::PipelineStageFlagBits2::eFragmentShader; barrier.srcAccessMask = vk::AccessFlagBits2::eShaderRead; barrier.dstStageMask = vk::PipelineStageFlagBits2::eTransfer; barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite; commandPool = POOL_GRAPHICS; } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal and newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { barrier.srcStageMask = vk::PipelineStageFlagBits2::eTransfer; barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite; barrier.dstStageMask = vk::PipelineStageFlagBits2::eFragmentShader; barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead; commandPool = POOL_GRAPHICS; } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal and newLayout == vk::ImageLayout::ePresentSrcKHR) { barrier.srcStageMask = vk::PipelineStageFlagBits2::eTransfer; barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite; barrier.dstAccessMask = vk::AccessFlagBits2::eMemoryRead; barrier.dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput; commandPool = POOL_GRAPHICS; } else if (oldLayout == vk::ImageLayout::eColorAttachmentOptimal and newLayout == vk::ImageLayout::ePresentSrcKHR) { barrier.srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput; barrier.srcAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite; barrier.dstAccessMask = vk::AccessFlagBits2::eMemoryRead; barrier.dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput; commandPool = POOL_GRAPHICS; } else if (oldLayout == vk::ImageLayout::eColorAttachmentOptimal and newLayout == vk::ImageLayout::eTransferSrcOptimal) { barrier.srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput; barrier.srcAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite; barrier.dstAccessMask = vk::AccessFlagBits2::eTransferRead; barrier.dstStageMask = vk::PipelineStageFlagBits2::eTransfer; commandPool = POOL_GRAPHICS; } else { throw VkUserError(std::string("Unsupported layout transition") + ::toString(oldLayout) + "->" + ::toString(newLayout), "VulkanInstance::transitionImageLayout"); } // if not provided, get a single time command buffer vk::CommandBuffer cmdBuffer_; if (cmdBuffer == nullptr) { cmdBuffer_ = beginSingleTimeCommands(commandPool); } else { cmdBuffer_ = *cmdBuffer; } vk::DependencyInfo depI = getDepInfo(barrier); cmdBuffer_.pipelineBarrier2(depI); if (cmdBuffer == nullptr) { endSingleTimeCommands(cmdBuffer_, commandPool); } } // // STATIC: DEBUG LOG // gz::Log VulkanInstance::vLog(LogCreateInfo { .logfile = "vulkan.log", .showLog = true, .storeLog = true, .prefix = "Vulkan", .prefixColor = settings::VULKAN_MESSAGE_PREFIX_COLOR, .showTime = true, .timeColor = settings::VULKAN_MESSAGE_TIME_COLOR}); VKAPI_ATTR VkBool32 VKAPI_CALL VulkanInstance::debugLog( VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverety, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { // Set message color std::string type; std::vector colors; switch(messageType) { case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT: type = "General:"; colors.push_back(gz::Color::GREEN); break; case VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT: type = "Performance:"; colors.push_back(gz::Color::CYAN); break; case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT: type = "Validation:"; colors.push_back(gz::Color::MAGENTA); break; default: type = "None:"; colors.push_back(gz::Color::WHITE); } // color for second keyword / whole message if not validation message switch(messageSeverety) { case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: colors.push_back(gz::Color::RESET); break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: colors.push_back(gz::Color::WHITE); break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: colors.push_back(gz::Color::BG_YELLOW); break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: colors.push_back(gz::Color::BG_RED); break; default: colors.push_back(gz::Color::RESET); } std::string_view message(pCallbackData->pMessage); std::string_view messageIdName(pCallbackData->pMessageIdName); // extra differenciation between different validation messages // is validation or performance message: get type from brackets, print owner of any pointers if (messageType & (VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT)) { // if cant be found npos + 1 = 0 size_t startAt = message.find(']') + 1; if (messageIdName.starts_with("UNASSIGNED-DEBUG-PRINTF")) { type = "Shader:"; colors[0] = gz::Color::LI_CYAN; startAt = message.find_first_of('|'); if (startAt == std::string::npos) { startAt = 0; } else { startAt += 2; } // skip "| " } else if (messageIdName.starts_with("UNASSIGNED-BestPractices")) { type = "Best Practice:"; colors[0] = gz::Color::LI_GREEN; } else if (messageIdName.starts_with("SYNC-HAZARD")) { type = "Synchronization"; colors[0] = gz::Color::LI_RED; } message = std::string_view(message.begin() + startAt, message.end()); colors.push_back(colors[1]); // color for messageIdName, use also for brackets colors.push_back(colors[1]); // color for actual message if (lastColorWhite) { colors.push_back(gz::Color::RESET); } else { colors.push_back(gz::Color::WHITE); } // color for handleOwnerString colors.push_back(gz::Color::LI_CYAN); getHandleOwnerString(message); vLog.clog(colors, std::move(type), "[", messageIdName, "]", message, handleOwnerString); } else { vLog.clog(colors, std::move(type), message); } lastColorWhite = !lastColorWhite; if (settings::throwExceptionOnValidationError and messageSeverety == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { throw VkException("Validation error:" + std::string(message), "VulkanInstance::debugLog"); } return VK_FALSE; } // // PRIVATE MEMBERS // // INSTANCE void VulkanInstance::createInstance() { vk::ApplicationInfo appInfo { .pApplicationName = "Glowzwiebels Vulkan Project", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "Glowzwiebel Engine", .apiVersion = VK_API_VERSION_1_3, }; VkDebugUtilsMessengerCreateInfoEXT debugCI = static_cast(debugUtilsMessengerCI); vk::InstanceCreateInfo instanceCI { .pNext = &debugCI, .pApplicationInfo = &appInfo, }; if (enableValidationLayers) { if (!validationLayersSupported()) { throw VkException("Validation layers enabled but not available.", "VulkanInstance::createInstance"); } instanceCI.setPEnabledLayerNames(settings::validationLayers); } std::vector requiredInstanceExtensions = getRequiredInstanceExtensions(); instanceCI.setPEnabledExtensionNames(requiredInstanceExtensions); // log requiredExtensions std::string message; message.reserve(80); message += "Required extensions ("; message += std::to_string(instanceCI.enabledExtensionCount) + ')'; for (uint32_t i = 0; i < requiredInstanceExtensions.size(); i++) { message += "\n\t"; message += std::to_string(i + 1) + ": " + requiredInstanceExtensions[i]; } vLog.log2(message); vk::Result result = vk::createInstance(&instanceCI, NO_ALLOC, &instance); if (result != vk::Result::eSuccess) { vLog.error("Failed to create instance.", "VkResult:", result); throw getVkException(result, "Failed to create instance", "VulkanInstance::createInstance"); } auto [result2, extensions] = vk::enumerateInstanceExtensionProperties(); if (result2 != vk::Result::eSuccess) { throw getVkException(result, "Failed to enumerate instance extension properties", "VulkanInstance::createInstance"); } // log available extensions message = "Available extensions ("; message += gz::toString(extensions.size()) + "):"; for (uint32_t i = 0; i < extensions.size(); i++) { message += "\n\t"; message += gz::toString(i + 1) + ": " + gz::toString(extensions[i].extensionName); } vLog.log2(message); if (!areExtensionsAvailable(requiredInstanceExtensions, extensions)) { throw VkException("Not all required extensions are available.", "VulkanInstance::createInstance"); } } std::vector VulkanInstance::getRequiredInstanceExtensions() const { uint32_t glfwExtensionCount = 0; const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); std::vector requiredExtensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { requiredExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); requiredExtensions.push_back(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME); } return requiredExtensions; } // check if validation layers are supported/installed bool VulkanInstance::validationLayersSupported() { std::vector availableLayers; vk::Result result; std::tie(result, availableLayers) = vk::enumerateInstanceLayerProperties(); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to enumerate available validation layers", "VulkanInstance::validationLayersSupported"); } bool layerAvailable; for (const char* requiredLayer : settings::validationLayers) { layerAvailable = false; for (const auto& layerProperties : availableLayers) { if (strcmp(requiredLayer, layerProperties.layerName) == 0) { layerAvailable = true; break; } } if (!layerAvailable) { return false; } } return true; } // PHYSICAL DEVICE: SELECTION PROCESS // FEATURES PhysicalDeviceFeatures VulkanInstance::getRequiredDeviceFeatures() const { PhysicalDeviceFeatures requiredFeatures; requiredFeatures.f13.synchronization2 = VK_TRUE; return requiredFeatures; } void VulkanInstance::selectPhysicalDevice() { auto [result, devices] = instance.enumeratePhysicalDevices(); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to enumerate physical devices", "VulkanInstance::selectPhysicalDevice"); } if (devices.size() == 0) { vLog.error("Could not find any GPU."); throw VkException("Could not find any GPU.", "VulkanInstance::selectPhysicalDevice"); } // find best gpu std::vector deviceScores; for (const auto& device : devices) { deviceScores.push_back(rateDevice(device)); } unsigned int bestGPUIndex = std::max_element(deviceScores.begin(), deviceScores.end()) - deviceScores.begin(); if (deviceScores[bestGPUIndex] == 0) { vLog.error("Could not find suitable GPU."); throw std::runtime_error("Could not find suitable GPU."); } physicalDevice = devices[bestGPUIndex]; // initialize members phDevProperties = physicalDevice.getProperties2(); vkGetPhysicalDeviceFeatures2(physicalDevice, &phDevFeatures.f); /* phDevFeatures.f = physicalDevice.getFeatures2(&phDevFeatures.f); */ phDevMemProperties = physicalDevice.getMemoryProperties2(); qFamilyIndices = findQueueFamilies(physicalDevice); depthFormat = findSupportedFormat({ vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint }, vk::ImageTiling::eOptimal, vk::FormatFeatureFlagBits::eDepthStencilAttachment); vLog.log3("selectPhysicalDevice: Selected GPU:", phDevProperties.properties.deviceName); } const unsigned int SCORE_DISCRETE_GPU = 5000; const unsigned int SCORE_INTEGRATED_GPU = 2000; const unsigned int SCORE_VIRTUAL_GPU = 1000; const unsigned int SCORE_HAS_ALL_QUEUE_FAMILIES = 100; const unsigned int SCORE_HAS_FEATURE = 100; unsigned int VulkanInstance::rateDevice(vk::PhysicalDevice phDevice) { unsigned int score; // rate type vk::PhysicalDeviceProperties2 properties = phDevice.getProperties2(); switch(properties.properties.deviceType) { case vk::PhysicalDeviceType::eDiscreteGpu: score = SCORE_DISCRETE_GPU; break; case vk::PhysicalDeviceType::eIntegratedGpu: score = SCORE_INTEGRATED_GPU; break; case vk::PhysicalDeviceType::eVirtualGpu: score = SCORE_VIRTUAL_GPU; break; default: return 0; } qFamilyIndices = findQueueFamilies(phDevice); // has needed queue families if (qFamilyIndices.hasAllValues()) { score += SCORE_HAS_ALL_QUEUE_FAMILIES; } else if (!qFamilyIndices.hasNecessaryValues()) { return 0; } // supports all extensions (global var) vk::Result result; std::vector availableExtensions; std::tie(result, availableExtensions) = phDevice.enumerateDeviceExtensionProperties(); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to enumerate device extension properties", "VulkanInstance::rateDevice"); } if (!areExtensionsAvailable(settings::deviceExtensions, availableExtensions)) { return 0; } // supports all features PhysicalDeviceFeatures requiredFeatures = getRequiredDeviceFeatures(); PhysicalDeviceFeatures features; /* features.f = phDevice.getFeatures2(features.f); */ vkGetPhysicalDeviceFeatures2(phDevice, &features.f); if (!features.requiredFeaturesAvailable(requiredFeatures)) { return 0; } // optional features if (features.f.features.samplerAnisotropy == VK_TRUE) { score += SCORE_HAS_FEATURE; } // swap chain support SwapChainSupport scDetails = querySwapChainSupport(phDevice); if (scDetails.formats.empty() or scDetails.presentModes.empty()) { return 0; } // rate memory vk::PhysicalDeviceMemoryProperties2 memProperties = phDevice.getMemoryProperties2(); for (uint32_t i = 0; i < memProperties.memoryProperties.memoryHeapCount; i++) { score += memProperties.memoryProperties.memoryHeaps[i].size / 1e6; } vLog("rateDevice: GPU: ", properties.properties.deviceName, " - Score: ", score); return score; } vk::Format VulkanInstance::findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) { for (const vk::Format& format : candidates) { vk::FormatProperties2 formatProperties = physicalDevice.getFormatProperties2(format); if (tiling == vk::ImageTiling::eLinear and (formatProperties.formatProperties.linearTilingFeatures & features) == features) { return format; } else if (tiling == vk::ImageTiling::eOptimal and (formatProperties.formatProperties.optimalTilingFeatures & features) == features) { return format; } } throw VkException(std::string("Could not find a suitable format. tiling: ") + ::toString(tiling) + ".", "VulkanInstance::findSupportedFormat"); } // SETTINGS void VulkanInstance::setValidSettings() { // IMPORTANT: EDIT DOCUMENTATION WHEN ADDING/CHANGING VALUES // anisotropic filtering settings.setAllowedValues("anisotropy_enable", { false, vkBool2Bool(phDevFeatures.f.features.samplerAnisotropy) }, gz::SM_LIST); settings.setAllowedValues("max_anisotropy", { 1.0f, phDevProperties.properties.limits.maxSamplerAnisotropy }, gz::SM_RANGE); settings.setAllowedValues("max_frames_in_flight", { 1, 4 }, SM_RANGE); } // QUEUE QueueFamilyIndices VulkanInstance::findQueueFamilies(vk::PhysicalDevice phDevice) { QueueFamilyIndices indices; std::vector qFamilies = phDevice.getQueueFamilyProperties2(); vLog.log1("findQueueFamilies: found", qFamilies.size(), "queue families:"); vk::Bool32 presentSupport = false; vk::Result result; for (unsigned int i = 0; i < qFamilies.size(); i++) { // check for queue with graphic capabilities if (qFamilies[i].queueFamilyProperties.queueFlags & vk::QueueFlagBits::eGraphics) { indices.graphicsFamily = i; } else if (qFamilies[i].queueFamilyProperties.queueFlags & vk::QueueFlagBits::eTransfer) { // only transfer, not graphics indices.transferFamily = i; } // check for surface support std::tie(result, presentSupport) = phDevice.getSurfaceSupportKHR(i, surface); if (presentSupport == VK_TRUE) { indices.presentFamily = i; } vLog.log1("findQueueFamilies:", i, "-", qFamilies[i].queueFamilyProperties.queueFlags); if (indices.hasAllValues()) { vLog.log1("findQueueFamilies: Found all wanted families."); break; } } return indices; } // LOGICAL DEVICE void VulkanInstance::createLogicalDevice() { std::unordered_set qFamiliyIndicesSet = { // must be unique qFamilyIndices.graphicsFamily.value(), qFamilyIndices.presentFamily.value(), }; if (qFamilyIndices.transferFamily.has_value()) { qFamiliyIndicesSet.insert(qFamilyIndices.transferFamily.value()); } std::vector qPriorities; // make sure no reallocation happes as that would invalidate the pointers qPriorities.reserve(qFamiliyIndicesSet.size()); std::vector deviceQueueCI; for (uint32_t index : qFamiliyIndicesSet) { deviceQueueCI.push_back(vk::DeviceQueueCreateInfo()); deviceQueueCI.back().queueFamilyIndex = index; deviceQueueCI.back().queueCount = 1; qPriorities.emplace_back(1.0f); deviceQueueCI.back().pQueuePriorities = &(qPriorities.back()); } PhysicalDeviceFeatures enabledFeatures = getRequiredDeviceFeatures(); // optional features enabledFeatures.f.features.samplerAnisotropy = bool2VkBool(settings.getOr("anisotropy_enable", false)); vk::DeviceCreateInfo deviceCI { .pNext = &enabledFeatures.f, /* .pEnabledFeatures = &deviceFeatures.f, */ }; deviceCI.setQueueCreateInfos(deviceQueueCI); deviceCI.setPEnabledExtensionNames(settings::deviceExtensions); vk::Result result = physicalDevice.createDevice(&deviceCI, nullptr, &device); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create logical device.", "VulkanInstance::createLogicalDevice"); } // get handles uint32_t index = 0; graphicsQ = device.getQueue(qFamilyIndices.graphicsFamily.value(), index); presentQ = device.getQueue(qFamilyIndices.presentFamily.value(), index); if (qFamilyIndices.transferFamily.has_value()) { transferQ = device.getQueue(qFamilyIndices.transferFamily.value(), index); } else { transferQ = graphicsQ; } vLog.log0("createLogicalDevice: Created logical device."); } // SURFACE void VulkanInstance::createSurface() { VkSurfaceKHR surface2 = surface; VkResult result = glfwCreateWindowSurface(static_cast(instance), window, nullptr, &surface2); surface = surface2; if (result != VK_SUCCESS) { throw getVkException(vk::Result(result), "Failed to create window surface.", "VulkanInstance::createSurface"); } } // SWAP CHAIN SwapChainSupport VulkanInstance::querySwapChainSupport(vk::PhysicalDevice physicalDevice) { SwapChainSupport scDetails{}; vk::Result result; vk::PhysicalDeviceSurfaceInfo2KHR surfaceI { .surface = surface }; std::tie(result, scDetails.formats) = physicalDevice.getSurfaceFormats2KHR(surfaceI); if (result != vk::Result::eSuccess) { throw getVkException(vk::Result(result), "Failed get surface formats.", "VulkanInstance::querySwapChainSupport"); } std::tie(result, scDetails.presentModes) = physicalDevice.getSurfacePresentModesKHR(surface); if (result != vk::Result::eSuccess) { throw getVkException(vk::Result(result), "Failed get surface present modes.", "VulkanInstance::querySwapChainSupport"); } std::tie(result, scDetails.capabilities) = physicalDevice.getSurfaceCapabilities2KHR(surfaceI); if (result != vk::Result::eSuccess) { throw getVkException(vk::Result(result), "Failed get surface capabilities.", "VulkanInstance::querySwapChainSupport"); } return scDetails; } vk::SurfaceFormat2KHR VulkanInstance::selectSwapChainSurfaceFormat(const std::vector& availableFormats) { vLog.log0("selectSwapChainSurfaceFormat:", availableFormats.size(), "formats available."); // TODO REMOVE for (const auto& format : availableFormats) { vLog.log0("selectSwapChainSurfaceFormat: format:", format.surfaceFormat.format, "with color space:", format.surfaceFormat.colorSpace); } for (const auto& format : availableFormats) { if (format.surfaceFormat.format == vk::Format::eB8G8R8A8Srgb and format.surfaceFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { return format; } } return availableFormats.front(); } vk::PresentModeKHR VulkanInstance::selectSwapChainPresentMode(const std::vector& availableModes) { for (const auto& mode : availableModes) { if (mode == vk::PresentModeKHR::eMailbox) { return mode; } } return vk::PresentModeKHR::eFifo; } vk::Extent2D VulkanInstance::selectSwapExtent(const vk::SurfaceCapabilities2KHR& capabilities) { // if width = max(uint32_t) then surface size will be determined by a swapchain targeting it if (capabilities.surfaceCapabilities.currentExtent.width == std::numeric_limits::max()) { int width, height; glfwGetFramebufferSize(window, &width, &height); vk::Extent2D actualExtent { .width = std::clamp(static_cast(width), capabilities.surfaceCapabilities.minImageExtent.width, capabilities.surfaceCapabilities.maxImageExtent.width), .height = std::clamp(static_cast(height), capabilities.surfaceCapabilities.minImageExtent.height, capabilities.surfaceCapabilities.maxImageExtent.height), }; return actualExtent; } else { return capabilities.surfaceCapabilities.currentExtent; } } void VulkanInstance::createSwapChain() { SwapChainSupport scDetails = querySwapChainSupport(physicalDevice); vk::SurfaceFormat2KHR surfaceFormat = selectSwapChainSurfaceFormat(scDetails.formats); uint32_t imageCount = scDetails.capabilities.surfaceCapabilities.minImageCount + 1; // maxImageCount = 0 -> no max if (scDetails.capabilities.surfaceCapabilities.maxImageCount > 0 and imageCount < scDetails.capabilities.surfaceCapabilities.maxImageCount) { imageCount = scDetails.capabilities.surfaceCapabilities.maxImageCount; } scImageFormat = surfaceFormat.surfaceFormat.format; scExtent = selectSwapExtent(scDetails.capabilities); vk::SwapchainCreateInfoKHR swapChainCI { .surface = surface, .minImageCount = imageCount, .imageFormat = scImageFormat, .imageColorSpace = surfaceFormat.surfaceFormat.colorSpace, .imageExtent = scExtent, .imageArrayLayers = 1, // transferDst for copying rendered images to swap chain .imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst, .preTransform = scDetails.capabilities.surfaceCapabilities.currentTransform, // TODO .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, .presentMode = selectSwapChainPresentMode(scDetails.presentModes), .clipped = VK_TRUE, .oldSwapchain = VK_NULL_HANDLE, }; /* QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); */ // must live until vkCreateSwapchainKHR std::vector qIndices; if (qFamilyIndices.graphicsFamily.value() != qFamilyIndices.presentFamily.value()) { swapChainCI.imageSharingMode = vk::SharingMode::eConcurrent, qIndices = { qFamilyIndices.graphicsFamily.value(), qFamilyIndices.presentFamily.value() }; swapChainCI.setQueueFamilyIndices(qIndices); } else { swapChainCI.imageSharingMode = vk::SharingMode::eExclusive; } vk::Result result = device.createSwapchainKHR(&swapChainCI, nullptr, &swapChain); if (result != vk::Result::eSuccess) { vLog.error("createSwapChain: Couldn not create swap chain", "VkResult:", result); throw getVkException(result, "Couldn not create swap chain", "VulkanInstance::createSwapChain"); } // get image handles std::tie(result, scImages) = device.getSwapchainImagesKHR(swapChain); if (result != vk::Result::eSuccess) { throw getVkException(result, "Couldn not get swap chain images", "VulkanInstance::createSwapChain"); } // create image views scImageViews.resize(scImages.size()); for (unsigned int i = 0; i < scImageViews.size(); i++) { createImageView(scImageFormat, scImages[i], scImageViews[i], vk::ImageAspectFlagBits::eColor); } vLog.log0("createSwapChain: Created swap chain with", scImages.size(), "images with format", swapChainCI.imageFormat, "extent", swapChainCI.imageExtent, "and color space", swapChainCI.imageColorSpace); } void VulkanInstance::recreateSwapChain() { int width, height = 0; glfwGetFramebufferSize(window, &width, &height); while (width == 0 || height == 0) { // minimized glfwGetFramebufferSize(window, &width, &height); glfwWaitEvents(); } vLog.log0("recreateSwapChain: new framebuffer size:", width, height); vk::Result result = device.waitIdle(); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to wait for device idle", "VulkanInstance::recreateSwapChain"); } cleanupSwapChain(); createSwapChain(); createCommandBuffers(commandBuffersBegin); createCommandBuffers(commandBuffersEnd); // TODO: order? for (auto& f : scRecreateCallbacks) { f(); } vLog.log0("recreateSwapChain: Updating all ObjectsUsingVulkan"); for (auto& obj : VulkanInstance::objectsUsingVulkan) { obj->updateHandles(); } } void VulkanInstance::cleanupSwapChain() { for (auto& imageView : scImageViews) { device.destroyImageView(imageView, nullptr); } device.destroySwapchainKHR(swapChain, nullptr); } uint32_t VulkanInstance::findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { for (uint32_t i = 0; i < phDevMemProperties.memoryProperties.memoryTypeCount; i++) { // if ith bit of typefilter is set if ((typeFilter & (1 << i)) and (phDevMemProperties.memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { return i; } } throw VkException("Could not find suitable memory type", "VulkanInstance::findMemoryType"); } void VulkanInstance::frameBufferResizedCallback(GLFWwindow* window, int width, int height) { VulkanInstance* app = reinterpret_cast(glfwGetWindowUserPointer(window)); app->frameBufferResized = true; } // COMMAND POOLS void VulkanInstance::createCommandPools() { /* QueueFamilyIndicjes queueFamilyIndices = findQueueFamilies(physicalDevice); */ vk::CommandPoolCreateInfo commandPoolGraphicsCI { .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, .queueFamilyIndex = qFamilyIndices.graphicsFamily.value(), }; vk::Result result = device.createCommandPool(&commandPoolGraphicsCI, nullptr, &commandPoolGraphics); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create graphics command pool", "createCommandPool"); } if (qFamilyIndices.transferFamily.has_value()) { vk::CommandPoolCreateInfo commandPoolTransferCI { .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, .queueFamilyIndex = qFamilyIndices.transferFamily.value(), }; result = device.createCommandPool(&commandPoolTransferCI, nullptr, &commandPoolTransfer); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create transfer command pool", "createCommandPool"); } } else { commandPoolTransfer = commandPoolGraphics; } } // SYNCHRONIZATION void VulkanInstance::createSyncObjects() { imageAvailableSemaphores.resize(getMaxFramesInFlight()); renderFinishedSemaphores.resize(getMaxFramesInFlight()); inFlightFences.resize(getMaxFramesInFlight()); vk::SemaphoreCreateInfo semaphoreCI; vk::FenceCreateInfo fenceCI { .flags = vk::FenceCreateFlagBits::eSignaled, }; for (size_t i = 0; i < getMaxFramesInFlight(); i++) { vk::Result result = device.createSemaphore(&semaphoreCI, nullptr, &imageAvailableSemaphores[i]); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create imageAvailableSemaphores", "createSyncObjects"); } result = device.createSemaphore(&semaphoreCI, nullptr, &renderFinishedSemaphores[i]); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create renderFinishedSemaphores", "createSyncObjects"); } result = device.createFence(&fenceCI, nullptr, &inFlightFences[i]); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to create inFlightFences", "createSyncObjects"); } } } // DEBUG void VulkanInstance::setupDebugMessenger() { // get the address of the vkCreateDebugUtilsMessengerEXT function VkDebugUtilsMessengerCreateInfoEXT debugCI = static_cast(debugUtilsMessengerCI); vk::Result result = runVkResultFunction("vkCreateDebugUtilsMessengerEXT", instance, &debugCI, nullptr, &debugMessenger); if (result != vk::Result::eSuccess) { throw getVkException(result, "Failed to initialise debug messenger", "VulkanInstance::setupDebugMessenger"); } /* auto f = reinterpret_cast(vk::getInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); */ /* if (f == nullptr) { throw std::runtime_error("Failed to initialise debug messenger."); } */ /* else { */ /* f(instance, &debugUtilsMessengerCreateInfo, nullptr, &debugMessenger); */ /* } */ } void VulkanInstance::cleanupDebugMessenger() { runVkVoidFunction("vkDestroyDebugUtilsMessengerEXT", instance, debugMessenger, nullptr); /* throw std::runtime_error("Failed to destroy debug messenger."); */ } // WINDOW void VulkanInstance::createWindow() { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); glfwSetWindowUserPointer(window, this); glfwSetFramebufferSizeCallback(window, frameBufferResizedCallback); } // HANDLE OWNERSHIP bool VulkanInstance::lastColorWhite; std::vector> VulkanInstance::objectsUsingVulkan; std::set VulkanInstance::foundHandles; const std::regex reAddress(R"(handle = (0x[0-9a-f]+))"); std::string VulkanInstance::handleOwnerString(100, ' '); // reserve size 100 // void VulkanInstance::getHandleOwnerString(std::string_view message) { re::svmatch match; /* for (auto& obj : objectsUsingVulkan) { vLog(obj); }; */ handleOwnerString = "Handle ownerships: [ "; foundHandles.clear(); while (re::regex_search(message, match, reAddress)) { /* vLog("getHandleOwnerString: found", match.size(), "addresses"); */ uint64_t address; std::stringstream ss; ss << std::hex << match.str(1); ss >> address; // filter duplicates if (foundHandles.contains(address)) { goto stopOwnerSearch; } for (auto& obj : objectsUsingVulkan) { if (obj->contains(address)) { handleOwnerString += match.str(1) + " - " + obj->getName() + ", "; foundHandles.insert(address); address = 0; goto stopOwnerSearch; } } // if not found handleOwnerString += match.str(1) + " - Unknown, "; stopOwnerSearch: message = std::string_view(message.begin() + match.position() + match.length(), message.end()); } // while if (foundHandles.size() > 0) { handleOwnerString.erase(handleOwnerString.size() - 2); handleOwnerString += " ]"; } else { handleOwnerString.clear(); } } } // namespace gz::vlk