vulkan-project/src/vulkan_instance.cpp
2022-11-10 16:58:59 +01:00

1804 lines
77 KiB
C++

#include "vulkan_instance.hpp"
#include "exceptions.hpp"
/* #include "font.hpp" */
#include "vulkan_util.hpp"
#include <gz-util/log.hpp>
#include <gz-util/regex.hpp>
#include <gz-util/string/conversion.hpp>
#include <iterator>
#include <memory>
#include <stop_token>
#include <utility>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define TINYOBJLOADER_IMPLEMENTATION
#include "tiny_obj_loader.h"
#include <fstream>
#include <unordered_set>
#include <unordered_map>
#include <cstring>
#include <algorithm>
#define VULKAN_HPP_NO_CONSTRUCTORS
#include <vulkan/vulkan.hpp>
#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<uint32_t>("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<uint32_t>(commandBuffers2D.size()), commandBuffers2D.data()); */
/* device.freeCommandBuffers(commandPoolGraphics, static_cast<uint32_t>(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<void()> callbackF) {
scRecreateCallbacks.push_back(callbackF);
}
void VulkanInstance::registerCleanupCallback(std::function<void()> 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<vk::CommandBuffer>& commandBuffers) {
commandBuffers.resize(getMaxFramesInFlight());
vk::CommandBufferAllocateInfo commandBufferAI {
.commandPool = commandPoolGraphics,
.level = vk::CommandBufferLevel::ePrimary,
.commandBufferCount = static_cast<uint32_t>(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<vk::CommandBuffer>& commandBuffers) {
device.freeCommandBuffers(commandPoolGraphics, commandBuffers);
commandBuffers.resize(0);
}
// GRAPHICS PIPELINE
template<VertexType VertexT>
void VulkanInstance::createGraphicsPipeline(vk::GraphicsPipelineCreateInfo&& pipelineCI, const std::vector<vk::DescriptorSetLayout>& descriptorSetLayouts, const std::vector<vk::PushConstantRange>& pushConstantRanges, bool useDepthStencil, Pipeline& pipeline) {
// 1) sanity checks
if (pipelineCI.pStages == nullptr) {
throw VkUserError("pStages is nullptr", "VulkanInstance::createGraphicsPipeline");
}
if (static_cast<VkRenderPass>(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<VkPipelineLayoutCreateInfo>(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<vk::VertexInputAttributeDescription>
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<float>(scExtent.width),
.height = static_cast<float>(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<vk::DynamicState> dynamicStates = { */
/* vk::DynamicState::eViewport, */
/* vk::DynamicState::eLineWidth */
/* }; */
/* vk::PipelineDynamicStateCreateInfo dynamicState { */
/* dynamicState.dynamicStateCount = static_cast<uint32_t>(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<pipeline>
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<Vertex2D>(vk::GraphicsPipelineCreateInfo&& pipelineCI, const std::vector<vk::DescriptorSetLayout>& descriptorSetLayouts, const std::vector<vk::PushConstantRange>& pushConstantRanges, bool useDepthStencil, Pipeline& pipeline);
template void VulkanInstance::createGraphicsPipeline<Vertex3D>(vk::GraphicsPipelineCreateInfo&& pipelineCI, const std::vector<vk::DescriptorSetLayout>& descriptorSetLayouts, const std::vector<vk::PushConstantRange>& pushConstantRanges, bool useDepthStencil, Pipeline& pipeline);
// SHADER MODULE
vk::ShaderModule VulkanInstance::createShaderModule(const std::vector<char>& code) {
vk::ShaderModuleCreateInfo shaderModuleCI {
.codeSize = code.size(),
.pCode = reinterpret_cast<const uint32_t*>(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<std::pair<std::string, vk::SpecializationInfo*>>& shaders, std::vector<vk::PipelineShaderStageCreateInfo>& 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<uint32_t>* qFamiliesWithAccess) {
vk::BufferCreateInfo bufferCI {
.size = size,
.usage = usage,
.sharingMode = sharingMode,
};
/* std::vector<uint32_t> 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<VertexType VertexT>
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<uint32_t> 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<Vertex2D>(size_t vertexCount, vk::Buffer& vertexBuffer, MemoryInfo& vertexBufferMemory, vk::DeviceSize& vertexBufferSize);
template void VulkanInstance::createVertexBuffer<Vertex3D>(size_t vertexCount, vk::Buffer& vertexBuffer, MemoryInfo& vertexBufferMemory, vk::DeviceSize& vertexBufferSize);
// INDEX BUFFER
template<SupportedIndexType T>
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<uint32_t> 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<uint16_t>(size_t indexCount, vk::Buffer& indexBuffer, MemoryInfo& indexBufferMemory, vk::DeviceSize& indexBufferSize);
template void VulkanInstance::createIndexBuffer<uint32_t>(size_t indexCount, vk::Buffer& indexBuffer, MemoryInfo& indexBufferMemory, vk::DeviceSize& indexBufferSize);
// FRAMEBUFFERS
void VulkanInstance::createFramebuffers(std::vector<vk::Framebuffer>& framebuffers, std::vector<vk::ImageView>& 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<vk::ImageView> attachments;
for (size_t i = 0; i < framebuffers.size(); i++) {
if (static_cast<VkImageView>(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<vk::Framebuffer>& 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<bool>("anisotropy_enable")),
.maxAnisotropy = settings.get<float>("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<vk::DescriptorSetLayoutBinding>& 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<vk::DescriptorPoolSize>& 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<vk::DescriptorSetLayout>& layouts, const vk::DescriptorPool& pool, std::vector<vk::DescriptorSet>& 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<Color> 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<VkDebugUtilsMessengerCreateInfoEXT>(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<const char*> 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<const char*> VulkanInstance::getRequiredInstanceExtensions() const {
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char*> 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<vk::LayerProperties> 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<unsigned int> 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<vk::ExtensionProperties> 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<vk::Format>& 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<bool>("anisotropy_enable", { false, vkBool2Bool(phDevFeatures.f.features.samplerAnisotropy) }, gz::SM_LIST);
settings.setAllowedValues<float>("max_anisotropy", { 1.0f, phDevProperties.properties.limits.maxSamplerAnisotropy }, gz::SM_RANGE);
settings.setAllowedValues<uint32_t>("max_frames_in_flight", { 1, 4 }, SM_RANGE);
}
// QUEUE
QueueFamilyIndices VulkanInstance::findQueueFamilies(vk::PhysicalDevice phDevice) {
QueueFamilyIndices indices;
std::vector<vk::QueueFamilyProperties2> 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<uint32_t> qFamiliyIndicesSet = { // must be unique
qFamilyIndices.graphicsFamily.value(), qFamilyIndices.presentFamily.value(),
};
if (qFamilyIndices.transferFamily.has_value()) {
qFamiliyIndicesSet.insert(qFamilyIndices.transferFamily.value());
}
std::vector<float> qPriorities;
// make sure no reallocation happes as that would invalidate the pointers
qPriorities.reserve(qFamiliyIndicesSet.size());
std::vector<vk::DeviceQueueCreateInfo> 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<bool>("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<VkInstance>(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<vk::SurfaceFormat2KHR>& 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<vk::PresentModeKHR>& 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<uint32_t>::max()) {
int width, height;
glfwGetFramebufferSize(window, &width, &height);
vk::Extent2D actualExtent {
.width = std::clamp(static_cast<uint32_t>(width), capabilities.surfaceCapabilities.minImageExtent.width, capabilities.surfaceCapabilities.maxImageExtent.width),
.height = std::clamp(static_cast<uint32_t>(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<uint32_t> 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<VulkanInstance*>(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<VkDebugUtilsMessengerCreateInfoEXT>(debugUtilsMessengerCI);
vk::Result result = runVkResultFunction<PFN_vkCreateDebugUtilsMessengerEXT>("vkCreateDebugUtilsMessengerEXT", instance, &debugCI, nullptr, &debugMessenger);
if (result != vk::Result::eSuccess) {
throw getVkException(result, "Failed to initialise debug messenger", "VulkanInstance::setupDebugMessenger");
}
/* auto f = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(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<PFN_vkDestroyDebugUtilsMessengerEXT>("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<std::unique_ptr<ObjectUsingVulkanBase>> VulkanInstance::objectsUsingVulkan;
std::set<uint64_t> 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