/** * @authors Artur Wasmut * @file src/vkcv/Core.cpp * @brief Handling of global states regarding dependencies */ #include "vkcv/Core.hpp" namespace vkcv { static vk::ImageLayout getVkLayoutFromAttachLayout(AttachmentLayout layout) { switch(layout) { case AttachmentLayout::GENERAL: return vk::ImageLayout::eGeneral; case AttachmentLayout::COLOR_ATTACHMENT: return vk::ImageLayout::eColorAttachmentOptimal; case AttachmentLayout::SHADER_READ_ONLY: return vk::ImageLayout::eShaderReadOnlyOptimal; case AttachmentLayout::DEPTH_STENCIL_ATTACHMENT: return vk::ImageLayout::eDepthStencilAttachmentOptimal; case AttachmentLayout::DEPTH_STENCIL_READ_ONLY: return vk::ImageLayout::eDepthStencilReadOnlyOptimal; case AttachmentLayout::PRESENTATION: return vk::ImageLayout::ePresentSrcKHR; default: return vk::ImageLayout::eUndefined; } } static vk::AttachmentStoreOp getVkStoreOpFromAttachOp(AttachmentOperation op) { switch(op) { case AttachmentOperation::STORE: return vk::AttachmentStoreOp::eStore; default: return vk::AttachmentStoreOp::eDontCare; } } static vk::AttachmentLoadOp getVKLoadOpFromAttachOp(AttachmentOperation op) { switch(op) { case AttachmentOperation::LOAD: return vk::AttachmentLoadOp::eLoad; case AttachmentOperation::CLEAR: return vk::AttachmentLoadOp::eClear; default: return vk::AttachmentLoadOp::eDontCare; } } /** * @brief The physical device is evaluated by three categories: * discrete GPU vs. integrated GPU, amount of queues and its abilities, and VRAM.physicalDevice. * @param physicalDevice The physical device * @return Device score as integer */ int deviceScore(const vk::PhysicalDevice& physicalDevice) { int score = 0; vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); std::vector<vk::QueueFamilyProperties> qFamilyProperties = physicalDevice.getQueueFamilyProperties(); // for every queue family compute queue flag bits and the amount of queues for (const auto& qFamily : qFamilyProperties) { uint32_t qCount = qFamily.queueCount; uint32_t bitCount = (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eCompute) != 0) + (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eGraphics) != 0) + (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eTransfer) != 0) + (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eSparseBinding) != 0); score += static_cast<int>(qCount * bitCount); } // compute the VRAM of the physical device vk::PhysicalDeviceMemoryProperties memoryProperties = physicalDevice.getMemoryProperties(); auto vram = static_cast<int>(memoryProperties.memoryHeaps[0].size / static_cast<uint32_t>(1E9)); score *= vram; if (properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) { score *= 2; } else if (properties.deviceType != vk::PhysicalDeviceType::eIntegratedGpu) { score = -1; } return score; } /** * @brief All existing physical devices will be evaluated by deviceScore. * @param instance The instance * @return The optimal physical device * @see Context.deviceScore */ vk::PhysicalDevice pickPhysicalDevice(vk::Instance& instance) { vk::PhysicalDevice phyDevice; std::vector<vk::PhysicalDevice> devices = instance.enumeratePhysicalDevices(); if (devices.empty()) { throw std::runtime_error("failed to find GPUs with Vulkan support!"); } int max_score = -1; for (const auto& device : devices) { int score = deviceScore(device); if (score > max_score) { max_score = score; phyDevice = device; } } if (max_score == -1) { throw std::runtime_error("failed to find a suitable GPU!"); } return phyDevice; } /** * @brief Creates a candidate list of queues that all meet the desired flags and then creates the maximum possible number * of queues. If the number of desired queues is not sufficient, the remaining queues are created from the next * candidate from the list. * @param physicalDevice The physical device * @param queueCount The amount of queues to be created * @param qPriorities * @param queueFlags The abilities which have to be supported by any created queue * @return */ std::vector<vk::DeviceQueueCreateInfo> getQueueCreateInfos(vk::PhysicalDevice& physicalDevice, uint32_t queueCount, std::vector<float> &qPriorities, std::vector<vk::QueueFlagBits>& queueFlags) { std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos; std::vector<vk::QueueFamilyProperties> qFamilyProperties = physicalDevice.getQueueFamilyProperties(); std::vector<vk::QueueFamilyProperties> qFamilyCandidates; // search for queue families which support the desired queue flag bits for (auto& qFamily : qFamilyProperties) { bool supported = true; for (auto qFlag : queueFlags) { supported = supported && (static_cast<uint32_t>(qFlag & qFamily.queueFlags) != 0); } if (supported) { qFamilyCandidates.push_back(qFamily); } } uint32_t create = queueCount; for (uint32_t i = 0; i < qFamilyCandidates.size() && create > 0; i++) { const uint32_t maxCreatableQueues = std::min(create, qFamilyCandidates[i].queueCount); vk::DeviceQueueCreateInfo qCreateInfo( vk::DeviceQueueCreateFlags(), i, maxCreatableQueues, qPriorities.data() ); queueCreateInfos.push_back(qCreateInfo); create -= maxCreatableQueues; } return queueCreateInfos; } /** * @brief With the help of the reference "supported" all elements in "check" checked, * if they are supported by the physical device. * @param supported The reference that can be used to check "check" * @param check The elements to be checked * @return True, if all elements in "check" are supported */ bool checkSupport(std::vector<const char*>& supported, std::vector<const char*>& check) { for (auto checkElem : check) { bool found = false; for (auto supportedElem : supported) { if (strcmp(supportedElem, checkElem) == 0) { found = true; break; } } if (!found) return false; } return true; } std::vector<const char*> getRequiredExtensions() { uint32_t glfwExtensionCount = 0; const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); #ifndef NDEBUG extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); #endif return extensions; } /** * @brief finds an queue family index that fits with the given queue flags to create a queue handle * @param flag The given flag that specifies as which queue type the accessed queue should be treated * @param createInfos The createInfos of the created queues depending on the logical device * @param device The physical with which the queue families can be accessed * @return a fitting queue family index */ int findQueueFamilyIndex(vk::QueueFlagBits flag, std::vector<vk::DeviceQueueCreateInfo> &createInfos, vk::PhysicalDevice &device){ std::vector<vk::QueueFamilyProperties> queueFamilyProperties = device.getQueueFamilyProperties(); for (auto i = createInfos.begin(); i != createInfos.end(); ++i ) { auto createInfo = *i; int index = createInfo.queueFamilyIndex; if(static_cast<uint32_t>(queueFamilyProperties[index].queueFlags & flag) != 0){ return index; } } return -1; } Core Core::create(const Window &window, const char *applicationName, uint32_t applicationVersion, uint32_t queueCount, std::vector<vk::QueueFlagBits> queueFlags, std::vector<const char *> instanceExtensions, std::vector<const char *> deviceExtensions) { // check for layer support const std::vector<vk::LayerProperties>& layerProperties = vk::enumerateInstanceLayerProperties(); std::vector<const char*> supportedLayers; supportedLayers.reserve(layerProperties.size()); for (auto& elem : layerProperties) { supportedLayers.push_back(elem.layerName); } // if in debug mode, check if validation layers are supported. Enable them if supported #ifndef NDEBUG std::vector<const char*> validationLayers = { "VK_LAYER_KHRONOS_validation" }; if (!checkSupport(supportedLayers, validationLayers)) { throw std::runtime_error("Validation layers requested but not available!"); } #endif // check for extension support std::vector<vk::ExtensionProperties> instanceExtensionProperties = vk::enumerateInstanceExtensionProperties(); std::vector<const char*> supportedExtensions; supportedExtensions.reserve(instanceExtensionProperties.size()); for (auto& elem : instanceExtensionProperties) { supportedExtensions.push_back(elem.extensionName); } if (!checkSupport(supportedExtensions, instanceExtensions)) { throw std::runtime_error("The requested instance extensions are not supported!"); } // for GLFW: get all required extensions std::vector<const char*> requiredExtensions = getRequiredExtensions(); instanceExtensions.insert(instanceExtensions.end(), requiredExtensions.begin(), requiredExtensions.end()); const vk::ApplicationInfo applicationInfo( applicationName, applicationVersion, "vkCV", VK_MAKE_VERSION(0, 0, 1), VK_HEADER_VERSION_COMPLETE ); vk::InstanceCreateInfo instanceCreateInfo( vk::InstanceCreateFlags(), &applicationInfo, 0, nullptr, static_cast<uint32_t>(instanceExtensions.size()), instanceExtensions.data() ); #ifndef NDEBUG instanceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); instanceCreateInfo.ppEnabledLayerNames = validationLayers.data(); #endif vk::Instance instance = vk::createInstance(instanceCreateInfo); std::vector<vk::PhysicalDevice> physicalDevices = instance.enumeratePhysicalDevices(); vk::PhysicalDevice physicalDevice = pickPhysicalDevice(instance); // check for physical device extension support std::vector<vk::ExtensionProperties> deviceExtensionProperties = physicalDevice.enumerateDeviceExtensionProperties(); supportedExtensions.clear(); for (auto& elem : deviceExtensionProperties) { supportedExtensions.push_back(elem.extensionName); } if (!checkSupport(supportedExtensions, deviceExtensions)) { throw std::runtime_error("The requested device extensions are not supported by the physical device!"); } //vector to define the queue priorities std::vector<float> qPriorities; qPriorities.resize(queueCount, 1.f); // all queues have the same priorities // create required queues std::vector<vk::DeviceQueueCreateInfo> qCreateInfos = getQueueCreateInfos(physicalDevice, queueCount, qPriorities,queueFlags); vk::DeviceCreateInfo deviceCreateInfo( vk::DeviceCreateFlags(), qCreateInfos.size(), qCreateInfos.data(), 0, nullptr, deviceExtensions.size(), deviceExtensions.data(), nullptr // Should our device use some features??? If yes: TODO ); #ifndef NDEBUG deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); deviceCreateInfo.ppEnabledLayerNames = validationLayers.data(); #endif vk::Device device = physicalDevice.createDevice(deviceCreateInfo); uint32_t graphicsQueueFamilyIndex = findQueueFamilyIndex({vk::QueueFlagBits::eGraphics}, qCreateInfos, physicalDevice); if(graphicsQueueFamilyIndex == -1){ throw std::runtime_error("It is not possible to access another queue as a graphics queue."); } uint32_t computeQueueFamilyIndex = findQueueFamilyIndex({vk::QueueFlagBits::eCompute}, qCreateInfos, physicalDevice); if(computeQueueFamilyIndex == -1){ throw std::runtime_error("It is not possible to access another queue as a compute queue."); } uint32_t transferQueueFamilyIndex = findQueueFamilyIndex({vk::QueueFlagBits::eTransfer}, qCreateInfos, physicalDevice); if(transferQueueFamilyIndex == -1){ throw std::runtime_error("It is not possible to access another queue as a transfer queue."); } vk::Queue graphicsQueue = device.getQueue( graphicsQueueFamilyIndex, 0 ); vk::Queue computeQueue = device.getQueue(computeQueueFamilyIndex,1); vk::Queue transferQueue = device.getQueue(transferQueueFamilyIndex,2); Context context(instance, physicalDevice, device); SwapChain swapChain = SwapChain::create(window, context); std::vector<vk::Image> swapChainImages = device.getSwapchainImagesKHR(swapChain.getSwapchain()); std::vector<vk::ImageView> imageViews; imageViews.reserve( swapChainImages.size() ); //here can be swizzled with vk::ComponentSwizzle if needed // ToDo: we need the format from the surface object vk::ComponentMapping componentMapping( vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG, vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA ); vk::ImageSubresourceRange subResourceRange( vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 ); for ( auto image : swapChainImages ) { vk::ImageViewCreateInfo imageViewCreateInfo( vk::ImageViewCreateFlags(), image, vk::ImageViewType::e2D, swapChain.getSurfaceFormat().format, componentMapping, subResourceRange ); imageViews.push_back( device.createImageView( imageViewCreateInfo ) ); } return Core(std::move(context) , window, swapChain, imageViews); } const Context &Core::getContext() const { return m_Context; } Core::Core(Context &&context, const Window &window , SwapChain swapChain, std::vector<vk::ImageView> imageViews) noexcept : m_Context(std::move(context)), m_window(window), m_swapchain(swapChain), m_swapchainImageViews(imageViews), m_NextPipelineId(0), m_Pipelines{}, m_PipelineLayouts{}, m_NextRenderpassId(0), m_NextPassId(0), m_Renderpasses{} {} Core::~Core() { std::cout << " Core " << std::endl; for (auto image : m_swapchainImageViews) { m_Context.getDevice().destroyImageView(image); } for (const auto& pass : m_Renderpasses) m_Context.m_Device.destroy(pass); m_Renderpasses.clear(); m_NextPassId = 0; m_Context.getDevice().destroySwapchainKHR(m_swapchain.getSwapchain()); m_Context.getInstance().destroySurfaceKHR(m_swapchain.getSurface()); } bool Core::createGraphicsPipeline(const Pipeline& pipeline, PipelineHandle& handle) { // vertex shader stage vk::ShaderModuleCreateInfo vertexModuleInfo({}, pipeline.m_vertexCode.size(), pipeline.m_vertexCode.data()); vk::ShaderModule vertexModule{}; if (m_Context.m_Device.createShaderModule(&vertexModuleInfo, nullptr, &vertexModule) != vk::Result::eSuccess) return false; vk::PipelineShaderStageCreateInfo pipelineVertexShaderStageInfo( {}, vk::ShaderStageFlagBits::eVertex, vertexModule, "main", nullptr ); // fragment shader stage vk::ShaderModuleCreateInfo fragmentModuleInfo({}, pipeline.m_fragCode.size(), pipeline.m_fragCode.data()); vk::ShaderModule fragmentModule{}; if (m_Context.m_Device.createShaderModule(&fragmentModuleInfo, nullptr, &fragmentModule) != vk::Result::eSuccess) return false; vk::PipelineShaderStageCreateInfo pipelineFragmentShaderStageInfo( {}, vk::ShaderStageFlagBits::eFragment, fragmentModule, "main", nullptr ); // vertex input state vk::VertexInputBindingDescription vertexInputBindingDescription(0, 12, vk::VertexInputRate::eVertex); vk::VertexInputAttributeDescription vertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, 0); vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo( {}, 1, &vertexInputBindingDescription, 1, &vertexInputAttributeDescription ); // input assembly state vk::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo( {}, vk::PrimitiveTopology::eTriangleList, false ); // viewport state vk::Viewport viewport(0.f, 0.f, static_cast<float>(pipeline.m_width), static_cast<float>(pipeline.m_height), 0.f, 1.f); vk::Rect2D scissor({ 0,0 }, { pipeline.m_width, pipeline.m_height }); vk::PipelineViewportStateCreateInfo pipelineViewportStateCreateInfo({}, 1, &viewport, 1, &scissor); // rasterization state vk::PipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo( {}, false, false, vk::PolygonMode::eFill, vk::CullModeFlagBits::eNone, vk::FrontFace::eCounterClockwise, false, 0.f, 0.f, 0.f, 1.f ); // multisample state vk::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo( {}, vk::SampleCountFlagBits::e1, false, 0.f, nullptr, false, false ); // color blend state vk::ColorComponentFlags colorWriteMask(VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT); vk::PipelineColorBlendAttachmentState colorBlendAttachmentState( false, vk::BlendFactor::eOne, vk::BlendFactor::eOne, vk::BlendOp::eAdd, vk::BlendFactor::eOne, vk::BlendFactor::eOne, vk::BlendOp::eAdd, colorWriteMask ); vk::PipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo( {}, false, vk::LogicOp::eClear, 0, &colorBlendAttachmentState, { 1.f,1.f,1.f,1.f } ); // pipeline layout vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo( {}, 0, {}, 0, {} ); vk::PipelineLayout vkPipelineLayout{}; if (m_Context.m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &vkPipelineLayout) != vk::Result::eSuccess) return false; // graphics pipeline create std::vector<vk::PipelineShaderStageCreateInfo> shaderStages = { pipelineVertexShaderStageInfo, pipelineFragmentShaderStageInfo }; vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo( {}, static_cast<uint32_t>(shaderStages.size()), shaderStages.data(), &pipelineVertexInputStateCreateInfo, &pipelineInputAssemblyStateCreateInfo, nullptr, &pipelineViewportStateCreateInfo, &pipelineRasterizationStateCreateInfo, &pipelineMultisampleStateCreateInfo, nullptr, &pipelineColorBlendStateCreateInfo, nullptr, vkPipelineLayout, m_Renderpasses[pipeline.m_passHandle.id], 0, {}, 0 ); vk::Pipeline vkPipeline{}; if (m_Context.m_Device.createGraphicsPipelines(nullptr, 1, &graphicsPipelineCreateInfo, nullptr, &vkPipeline) != vk::Result::eSuccess) return false; m_Pipelines.push_back(vkPipeline); m_PipelineLayouts.push_back(vkPipelineLayout); handle.id = m_NextPipelineId++; } bool Core::createRenderpass(const Renderpass &pass, RenderpassHandle &handle) { // description of all {color, input, depth/stencil} attachments of the render pass std::vector<vk::AttachmentDescription> attachmentDescriptions{}; // individual references to color attachments (of a subpass) std::vector<vk::AttachmentReference> colorAttachmentReferences{}; // individual reference to depth attachment (of a subpass) vk::AttachmentReference depthAttachmentReference{}; vk::AttachmentReference *pDepthAttachment = nullptr; //stays nullptr if no depth attachment used for(uint32_t i = 0; i < pass.attachments.size(); i++) { // TODO: Renderpass struct should hold proper format information vk::Format format; if(pass.attachments[i].layout_in_pass == AttachmentLayout::DEPTH_STENCIL_ATTACHMENT) { format = vk::Format::eD16Unorm; // depth attachments; depthAttachmentReference.attachment = i; depthAttachmentReference.layout = getVkLayoutFromAttachLayout(pass.attachments[i].layout_in_pass); pDepthAttachment = &depthAttachmentReference; } else { format = vk::Format::eB8G8R8A8Srgb; // color attachments, compatible with swapchain vk::AttachmentReference attachmentRef(i, getVkLayoutFromAttachLayout(pass.attachments[i].layout_in_pass)); colorAttachmentReferences.push_back(attachmentRef); } vk::AttachmentDescription attachmentDesc({}, format, vk::SampleCountFlagBits::e1, getVKLoadOpFromAttachOp(pass.attachments[i].load_operation), getVkStoreOpFromAttachOp(pass.attachments[i].load_operation), vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare, getVkLayoutFromAttachLayout(pass.attachments[i].layout_initial), getVkLayoutFromAttachLayout(pass.attachments[i].layout_final)); attachmentDescriptions.push_back(attachmentDesc); } vk::SubpassDescription subpassDescription({}, vk::PipelineBindPoint::eGraphics, 0, {}, static_cast<uint32_t>(colorAttachmentReferences.size()), colorAttachmentReferences.data(), {}, pDepthAttachment, 0, {}); vk::RenderPassCreateInfo passInfo({}, static_cast<uint32_t>(attachmentDescriptions.size()), attachmentDescriptions.data(), 1, &subpassDescription, 0, {}); vk::RenderPass vkObject{nullptr}; if(m_Context.m_Device.createRenderPass(&passInfo, nullptr, &vkObject) != vk::Result::eSuccess) return false; m_Renderpasses.push_back(vkObject); handle.id = m_NextPassId++; return true; } }