/** * @authors Artur Wasmut * @file src/vkcv/Core.cpp * @brief Handling of global states regarding dependencies */ #include <GLFW/glfw3.h> #include "vkcv/Core.hpp" #include "PassManager.hpp" #include "GraphicsPipelineManager.hpp" #include "ComputePipelineManager.hpp" #include "vkcv/BufferManager.hpp" #include "SamplerManager.hpp" #include "ImageManager.hpp" #include "DescriptorManager.hpp" #include "WindowManager.hpp" #include "CommandStreamManager.hpp" #include <cmath> #include "vkcv/Logger.hpp" #include "vkcv/BlitDownsampler.hpp" namespace vkcv { Core Core::create(const char *applicationName, uint32_t applicationVersion, const std::vector<vk::QueueFlagBits>& queueFlags, const Features& features, const std::vector<const char *>& instanceExtensions) { Context context = Context::create( applicationName, applicationVersion, queueFlags, features, instanceExtensions ); const auto& queueManager = context.getQueueManager(); const std::unordered_set<int> queueFamilySet = generateQueueFamilyIndexSet(queueManager); const auto commandResources = createCommandResources(context.getDevice(), queueFamilySet); const auto defaultSyncResources = createSyncResources(context.getDevice()); return Core(std::move(context) , commandResources, defaultSyncResources); } const Context &Core::getContext() const { return m_Context; } Core::Core(Context &&context, const CommandResources& commandResources, const SyncResources& syncResources) noexcept : m_Context(std::move(context)), m_PassManager{std::make_unique<PassManager>(m_Context.m_Device)}, m_PipelineManager{std::make_unique<GraphicsPipelineManager>(m_Context.m_Device, m_Context.m_PhysicalDevice)}, m_ComputePipelineManager{std::make_unique<ComputePipelineManager>(m_Context.m_Device)}, m_DescriptorManager(std::make_unique<DescriptorManager>(m_Context.m_Device)), m_BufferManager{std::unique_ptr<BufferManager>(new BufferManager())}, m_SamplerManager(std::unique_ptr<SamplerManager>(new SamplerManager(m_Context.m_Device))), m_ImageManager{std::unique_ptr<ImageManager>(new ImageManager(*m_BufferManager))}, m_CommandStreamManager{std::unique_ptr<CommandStreamManager>(new CommandStreamManager)}, m_WindowManager(std::make_unique<WindowManager>()), m_SwapchainManager(std::make_unique<SwapchainManager>()), m_CommandResources(commandResources), m_SyncResources(syncResources), m_downsampler(nullptr) { m_BufferManager->m_core = this; m_BufferManager->init(); m_CommandStreamManager->init(this); m_SwapchainManager->m_context = &m_Context; m_ImageManager->m_core = this; m_downsampler = std::unique_ptr<Downsampler>(new BlitDownsampler(*this, *m_ImageManager)); } Core::~Core() noexcept { m_Context.getDevice().waitIdle(); destroyCommandResources(m_Context.getDevice(), m_CommandResources); destroySyncResources(m_Context.getDevice(), m_SyncResources); } GraphicsPipelineHandle Core::createGraphicsPipeline(const GraphicsPipelineConfig &config) { return m_PipelineManager->createPipeline(config, *m_PassManager, *m_DescriptorManager); } ComputePipelineHandle Core::createComputePipeline(const ComputePipelineConfig &config) { std::vector<vk::DescriptorSetLayout> layouts; layouts.resize(config.m_DescriptorSetLayouts.size()); for (size_t i = 0; i < layouts.size(); i++) { layouts[i] = getDescriptorSetLayout(config.m_DescriptorSetLayouts[i]).vulkanHandle; } return m_ComputePipelineManager->createComputePipeline(config.m_ShaderProgram, layouts); } PassHandle Core::createPass(const PassConfig &config) { return m_PassManager->createPass(config); } Result Core::acquireSwapchainImage(const SwapchainHandle &swapchainHandle) { uint32_t imageIndex; vk::Result result; try { result = m_Context.getDevice().acquireNextImageKHR( m_SwapchainManager->getSwapchain(swapchainHandle).getSwapchain(), std::numeric_limits<uint64_t>::max(), m_SyncResources.swapchainImageAcquired, nullptr, &imageIndex, {} ); } catch (const vk::OutOfDateKHRError& e) { result = vk::Result::eErrorOutOfDateKHR; } catch (const vk::DeviceLostError& e) { result = vk::Result::eErrorDeviceLost; } if ((result != vk::Result::eSuccess) && (result != vk::Result::eSuboptimalKHR)) { vkcv_log(LogLevel::ERROR, "%s", vk::to_string(result).c_str()); return Result::ERROR; } else if (result == vk::Result::eSuboptimalKHR) { vkcv_log(LogLevel::WARNING, "Acquired image is suboptimal"); m_SwapchainManager->getSwapchain(swapchainHandle).signalSwapchainRecreation(); } m_currentSwapchainImageIndex = imageIndex; return Result::SUCCESS; } bool Core::beginFrame(uint32_t& width, uint32_t& height, const WindowHandle &windowHandle) { const SwapchainHandle swapchainHandle = m_WindowManager->getWindow(windowHandle).getSwapchainHandle(); if (m_SwapchainManager->getSwapchain(swapchainHandle).shouldUpdateSwapchain()) { m_Context.getDevice().waitIdle(); m_SwapchainManager->getSwapchain(swapchainHandle).updateSwapchain(m_Context, m_WindowManager->getWindow(windowHandle)); if (!m_SwapchainManager->getSwapchain(swapchainHandle).getSwapchain()) { return false; } setSwapchainImages(swapchainHandle); } const auto& extent = m_SwapchainManager->getSwapchain(swapchainHandle).getExtent(); width = extent.width; height = extent.height; if ((width < MIN_SURFACE_SIZE) || (height < MIN_SURFACE_SIZE)) { return false; } if (acquireSwapchainImage( swapchainHandle ) != Result::SUCCESS) { vkcv_log(LogLevel::ERROR, "Acquire failed"); m_currentSwapchainImageIndex = std::numeric_limits<uint32_t>::max(); } m_Context.getDevice().waitIdle(); // TODO: this is a sin against graphics programming, but its getting late - Alex m_ImageManager->setCurrentSwapchainImageIndex(m_currentSwapchainImageIndex); return (m_currentSwapchainImageIndex != std::numeric_limits<uint32_t>::max()); } std::array<uint32_t, 2> getWidthHeightFromRenderTargets( const std::vector<ImageHandle>& renderTargets, const Swapchain& swapchain, const ImageManager& imageManager) { std::array<uint32_t, 2> widthHeight; if (renderTargets.size() > 0) { const vkcv::ImageHandle firstImage = renderTargets[0]; if (firstImage.isSwapchainImage()) { const auto& swapchainExtent = swapchain.getExtent(); widthHeight[0] = swapchainExtent.width; widthHeight[1] = swapchainExtent.height; } else { widthHeight[0] = imageManager.getImageWidth(firstImage); widthHeight[1] = imageManager.getImageHeight(firstImage); } } else { widthHeight[0] = 1; widthHeight[1] = 1; } // TODO: validate that width/height match for all attachments return widthHeight; } vk::Framebuffer createFramebuffer( const std::vector<ImageHandle>& renderTargets, const ImageManager& imageManager, const Swapchain& swapchain, vk::RenderPass renderpass, vk::Device device) { std::vector<vk::ImageView> attachmentsViews; for (const ImageHandle& handle : renderTargets) { attachmentsViews.push_back(imageManager.getVulkanImageView(handle)); } const std::array<uint32_t, 2> widthHeight = getWidthHeightFromRenderTargets(renderTargets, swapchain, imageManager); const vk::FramebufferCreateInfo createInfo( {}, renderpass, static_cast<uint32_t>(attachmentsViews.size()), attachmentsViews.data(), widthHeight[0], widthHeight[1], 1); return device.createFramebuffer(createInfo); } void transitionRendertargetsToAttachmentLayout( const std::vector<ImageHandle>& renderTargets, ImageManager& imageManager, const vk::CommandBuffer cmdBuffer) { for (const ImageHandle& handle : renderTargets) { const bool isDepthImage = isDepthFormat(imageManager.getImageFormat(handle)); const vk::ImageLayout targetLayout = isDepthImage ? vk::ImageLayout::eDepthStencilAttachmentOptimal : vk::ImageLayout::eColorAttachmentOptimal; imageManager.recordImageLayoutTransition(handle, 0, 0, targetLayout, cmdBuffer); } } std::vector<vk::ClearValue> createAttachmentClearValues(const std::vector<AttachmentDescription>& attachments) { std::vector<vk::ClearValue> clearValues; for (const auto& attachment : attachments) { if (attachment.load_operation == AttachmentOperation::CLEAR) { float clear = 0.0f; if (isDepthFormat(attachment.format)) { clear = 1.0f; } clearValues.emplace_back(std::array<float, 4>{ clear, clear, clear, 1.f }); } } return clearValues; } void recordDynamicViewport(vk::CommandBuffer cmdBuffer, uint32_t width, uint32_t height) { vk::Viewport dynamicViewport( 0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f ); vk::Rect2D dynamicScissor({ 0, 0 }, { width, height }); cmdBuffer.setViewport(0, 1, &dynamicViewport); cmdBuffer.setScissor(0, 1, &dynamicScissor); } vk::IndexType getIndexType(IndexBitCount indexByteCount){ switch (indexByteCount) { case IndexBitCount::Bit8: return vk::IndexType::eUint8EXT; case IndexBitCount::Bit16: return vk::IndexType::eUint16; case IndexBitCount::Bit32: return vk::IndexType::eUint32; default: vkcv_log(LogLevel::ERROR, "unknown Enum"); return vk::IndexType::eNoneKHR; } } void recordDrawcall( const Core &core, const DrawcallInfo &drawcall, vk::CommandBuffer cmdBuffer, vk::PipelineLayout pipelineLayout, const PushConstants &pushConstants, const size_t drawcallIndex) { for (uint32_t i = 0; i < drawcall.mesh.vertexBufferBindings.size(); i++) { const auto& vertexBinding = drawcall.mesh.vertexBufferBindings[i]; cmdBuffer.bindVertexBuffers(i, vertexBinding.buffer, vertexBinding.offset); } for (const auto& descriptorUsage : drawcall.descriptorSets) { cmdBuffer.bindDescriptorSets( vk::PipelineBindPoint::eGraphics, pipelineLayout, descriptorUsage.setLocation, core.getDescriptorSet(descriptorUsage.descriptorSet).vulkanHandle, nullptr); } if (pushConstants.getSizePerDrawcall() > 0) { cmdBuffer.pushConstants( pipelineLayout, vk::ShaderStageFlagBits::eAll, 0, pushConstants.getSizePerDrawcall(), pushConstants.getDrawcallData(drawcallIndex)); } if (drawcall.mesh.indexBuffer) { cmdBuffer.bindIndexBuffer(drawcall.mesh.indexBuffer, 0, getIndexType(drawcall.mesh.indexBitCount)); cmdBuffer.drawIndexed(drawcall.mesh.indexCount, drawcall.instanceCount, 0, 0, {}); } else { cmdBuffer.draw(drawcall.mesh.indexCount, drawcall.instanceCount, 0, 0, {}); } } void Core::recordDrawcallsToCmdStream( const CommandStreamHandle& cmdStreamHandle, const PassHandle& renderpassHandle, const GraphicsPipelineHandle &pipelineHandle, const PushConstants &pushConstantData, const std::vector<DrawcallInfo> &drawcalls, const std::vector<ImageHandle> &renderTargets, const WindowHandle &windowHandle) { SwapchainHandle swapchainHandle = m_WindowManager->getWindow(windowHandle).getSwapchainHandle(); if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) { return; } const std::array<uint32_t, 2> widthHeight = getWidthHeightFromRenderTargets(renderTargets, m_SwapchainManager->getSwapchain(swapchainHandle), *m_ImageManager); const auto width = widthHeight[0]; const auto height = widthHeight[1]; const vk::RenderPass renderpass = m_PassManager->getVkPass(renderpassHandle); const PassConfig passConfig = m_PassManager->getPassConfig(renderpassHandle); const vk::Pipeline pipeline = m_PipelineManager->getVkPipeline(pipelineHandle); const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle); const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height)); vk::CommandBuffer cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle); transitionRendertargetsToAttachmentLayout(renderTargets, *m_ImageManager, cmdBuffer); const vk::Framebuffer framebuffer = createFramebuffer(renderTargets, *m_ImageManager, m_SwapchainManager->getSwapchain(swapchainHandle), renderpass, m_Context.m_Device); if (!framebuffer) { vkcv_log(LogLevel::ERROR, "Failed to create temporary framebuffer"); return; } SubmitInfo submitInfo; submitInfo.queueType = QueueType::Graphics; submitInfo.signalSemaphores = { m_SyncResources.renderFinished }; auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) { const std::vector<vk::ClearValue> clearValues = createAttachmentClearValues(passConfig.attachments); const vk::RenderPassBeginInfo beginInfo(renderpass, framebuffer, renderArea, clearValues.size(), clearValues.data()); cmdBuffer.beginRenderPass(beginInfo, {}, {}); cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {}); const GraphicsPipelineConfig &pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle); if (pipeConfig.m_UseDynamicViewport) { recordDynamicViewport(cmdBuffer, width, height); } for (size_t i = 0; i < drawcalls.size(); i++) { recordDrawcall(*this, drawcalls[i], cmdBuffer, pipelineLayout, pushConstantData, i); } cmdBuffer.endRenderPass(); }; auto finishFunction = [framebuffer, this]() { m_Context.m_Device.destroy(framebuffer); }; recordCommandsToStream(cmdStreamHandle, submitFunction, finishFunction); } void Core::recordIndexedIndirectDrawcallsToCmdStream( const CommandStreamHandle cmdStreamHandle, const PassHandle renderpassHandle, const GraphicsPipelineHandle &pipelineHandle, const PushConstants &pushConstantData, const vkcv::DescriptorSetHandle &compiledDescriptorSet, const vkcv::Mesh &compiledMesh, const std::vector<ImageHandle> &renderTargets, const vkcv::Buffer<vk::DrawIndexedIndirectCommand> &indirectBuffer, const uint32_t drawCount, const WindowHandle &windowHandle) { if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) { return; } SwapchainHandle swapchainHandle = m_WindowManager->getWindow(windowHandle).getSwapchainHandle(); const std::array<uint32_t, 2> widthHeight = getWidthHeightFromRenderTargets(renderTargets, m_SwapchainManager->getSwapchain(swapchainHandle), *m_ImageManager); const auto width = widthHeight[0]; const auto height = widthHeight[1]; const vk::RenderPass renderpass = m_PassManager->getVkPass(renderpassHandle); const PassConfig passConfig = m_PassManager->getPassConfig(renderpassHandle); const vk::Pipeline pipeline = m_PipelineManager->getVkPipeline(pipelineHandle); const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle); const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height)); vk::CommandBuffer cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle); transitionRendertargetsToAttachmentLayout(renderTargets, *m_ImageManager, cmdBuffer); const vk::Framebuffer framebuffer = createFramebuffer(renderTargets, *m_ImageManager, m_SwapchainManager->getSwapchain(swapchainHandle), renderpass, m_Context.m_Device); if (!framebuffer) { vkcv_log(LogLevel::ERROR, "Failed to create temporary framebuffer"); return; } SubmitInfo submitInfo; submitInfo.queueType = QueueType::Graphics; submitInfo.signalSemaphores = {m_SyncResources.renderFinished}; auto submitFunction = [&](const vk::CommandBuffer &cmdBuffer) { const std::vector<vk::ClearValue> clearValues = createAttachmentClearValues(passConfig.attachments); const vk::RenderPassBeginInfo beginInfo(renderpass, framebuffer, renderArea, clearValues.size(), clearValues.data()); cmdBuffer.beginRenderPass(beginInfo, {}, {}); cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {}); const GraphicsPipelineConfig &pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle); if (pipeConfig.m_UseDynamicViewport) { recordDynamicViewport(cmdBuffer, width, height); } if (pushConstantData.getSizePerDrawcall() > 0) { cmdBuffer.pushConstants( pipelineLayout, vk::ShaderStageFlagBits::eAll, 0, pushConstantData.getSizePerDrawcall(), pushConstantData.getDrawcallData(0)); } vkcv::DescriptorSet descSet = m_DescriptorManager->getDescriptorSet(compiledDescriptorSet); cmdBuffer.bindDescriptorSets( vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, descSet.vulkanHandle, nullptr); vk::DeviceSize deviceSize = 0; cmdBuffer.bindVertexBuffers(0, 1, &compiledMesh.vertexBufferBindings[0].buffer,&deviceSize); cmdBuffer.bindIndexBuffer(compiledMesh.indexBuffer, 0, getIndexType(compiledMesh.indexBitCount)); cmdBuffer.drawIndexedIndirect( indirectBuffer.getVulkanHandle(), 0, drawCount, sizeof(vk::DrawIndexedIndirectCommand)); cmdBuffer.endRenderPass(); }; auto finishFunction = [framebuffer, this]() { m_Context.m_Device.destroy(framebuffer); }; recordCommandsToStream(cmdStreamHandle, submitFunction, finishFunction); } void Core::recordMeshShaderDrawcalls( const CommandStreamHandle& cmdStreamHandle, const PassHandle& renderpassHandle, const GraphicsPipelineHandle &pipelineHandle, const PushConstants& pushConstantData, const std::vector<MeshShaderDrawcall>& drawcalls, const std::vector<ImageHandle>& renderTargets, const WindowHandle& windowHandle) { SwapchainHandle swapchainHandle = m_WindowManager->getWindow(windowHandle).getSwapchainHandle(); if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) { return; } const std::array<uint32_t, 2> widthHeight = getWidthHeightFromRenderTargets(renderTargets, m_SwapchainManager->getSwapchain(swapchainHandle), *m_ImageManager); const auto width = widthHeight[0]; const auto height = widthHeight[1]; const vk::RenderPass renderpass = m_PassManager->getVkPass(renderpassHandle); const PassConfig passConfig = m_PassManager->getPassConfig(renderpassHandle); const vk::Pipeline pipeline = m_PipelineManager->getVkPipeline(pipelineHandle); const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle); const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height)); vk::CommandBuffer cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle); transitionRendertargetsToAttachmentLayout(renderTargets, *m_ImageManager, cmdBuffer); const vk::Framebuffer framebuffer = createFramebuffer(renderTargets, *m_ImageManager, m_SwapchainManager->getSwapchain(swapchainHandle), renderpass, m_Context.m_Device); if (!framebuffer) { vkcv_log(LogLevel::ERROR, "Failed to create temporary framebuffer"); return; } SubmitInfo submitInfo; submitInfo.queueType = QueueType::Graphics; submitInfo.signalSemaphores = { m_SyncResources.renderFinished }; auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) { const std::vector<vk::ClearValue> clearValues = createAttachmentClearValues(passConfig.attachments); const vk::RenderPassBeginInfo beginInfo(renderpass, framebuffer, renderArea, clearValues.size(), clearValues.data()); cmdBuffer.beginRenderPass(beginInfo, {}, {}); cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {}); const GraphicsPipelineConfig& pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle); if (pipeConfig.m_UseDynamicViewport) { recordDynamicViewport(cmdBuffer, width, height); } for (size_t i = 0; i < drawcalls.size(); i++) { const uint32_t pushConstantOffset = i * pushConstantData.getSizePerDrawcall(); recordMeshShaderDrawcall( *this, cmdBuffer, pipelineLayout, pushConstantData, pushConstantOffset, drawcalls[i], 0 ); } cmdBuffer.endRenderPass(); }; auto finishFunction = [framebuffer, this]() { m_Context.m_Device.destroy(framebuffer); }; recordCommandsToStream(cmdStreamHandle, submitFunction, finishFunction); } void Core::recordRayGenerationToCmdStream( CommandStreamHandle cmdStreamHandle, vk::Pipeline rtxPipeline, vk::PipelineLayout rtxPipelineLayout, vk::StridedDeviceAddressRegionKHR rgenRegion, vk::StridedDeviceAddressRegionKHR rmissRegion, vk::StridedDeviceAddressRegionKHR rchitRegion, vk::StridedDeviceAddressRegionKHR rcallRegion, const std::vector<DescriptorSetUsage>& descriptorSetUsages, const PushConstants& pushConstants, const WindowHandle windowHandle) { auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) { cmdBuffer.bindPipeline(vk::PipelineBindPoint::eRayTracingKHR, rtxPipeline); for (const auto& usage : descriptorSetUsages) { cmdBuffer.bindDescriptorSets( vk::PipelineBindPoint::eRayTracingKHR, rtxPipelineLayout, usage.setLocation, { getDescriptorSet(usage.descriptorSet).vulkanHandle }, usage.dynamicOffsets ); } if (pushConstants.getSizePerDrawcall() > 0) { cmdBuffer.pushConstants( rtxPipelineLayout, (vk::ShaderStageFlagBits::eClosestHitKHR | vk::ShaderStageFlagBits::eMissKHR | vk::ShaderStageFlagBits::eRaygenKHR), // TODO: add Support for eAnyHitKHR, eCallableKHR, eIntersectionKHR 0, pushConstants.getSizePerDrawcall(), pushConstants.getData()); } auto m_rtxDispatcher = vk::DispatchLoaderDynamic((PFN_vkGetInstanceProcAddr)m_Context.getInstance().getProcAddr("vkGetInstanceProcAddr")); m_rtxDispatcher.init(m_Context.getInstance()); cmdBuffer.traceRaysKHR(&rgenRegion,&rmissRegion,&rchitRegion,&rcallRegion, getWindow(windowHandle).getWidth(), getWindow(windowHandle).getHeight(),1, m_rtxDispatcher); }; recordCommandsToStream(cmdStreamHandle, submitFunction, nullptr); } void Core::recordComputeDispatchToCmdStream( CommandStreamHandle cmdStreamHandle, ComputePipelineHandle computePipeline, const uint32_t dispatchCount[3], const std::vector<DescriptorSetUsage>& descriptorSetUsages, const PushConstants& pushConstants) { auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) { const auto pipelineLayout = m_ComputePipelineManager->getVkPipelineLayout(computePipeline); cmdBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, m_ComputePipelineManager->getVkPipeline(computePipeline)); for (const auto& usage : descriptorSetUsages) { cmdBuffer.bindDescriptorSets( vk::PipelineBindPoint::eCompute, pipelineLayout, usage.setLocation, { getDescriptorSet(usage.descriptorSet).vulkanHandle }, usage.dynamicOffsets ); } if (pushConstants.getSizePerDrawcall() > 0) { cmdBuffer.pushConstants( pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, pushConstants.getSizePerDrawcall(), pushConstants.getData()); } cmdBuffer.dispatch(dispatchCount[0], dispatchCount[1], dispatchCount[2]); }; recordCommandsToStream(cmdStreamHandle, submitFunction, nullptr); } void Core::recordBeginDebugLabel(const CommandStreamHandle &cmdStream, const std::string& label, const std::array<float, 4>& color) { #ifdef VULKAN_DEBUG_LABELS static PFN_vkCmdBeginDebugUtilsLabelEXT beginDebugLabel = reinterpret_cast<PFN_vkCmdBeginDebugUtilsLabelEXT>( m_Context.getDevice().getProcAddr("vkCmdBeginDebugUtilsLabelEXT") ); if (!beginDebugLabel) { return; } auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) { const vk::DebugUtilsLabelEXT debug ( label.c_str(), color ); beginDebugLabel(static_cast<VkCommandBuffer>(cmdBuffer), &(static_cast<const VkDebugUtilsLabelEXT&>(debug))); }; recordCommandsToStream(cmdStream, submitFunction, nullptr); #endif } void Core::recordEndDebugLabel(const CommandStreamHandle &cmdStream) { #ifdef VULKAN_DEBUG_LABELS static PFN_vkCmdEndDebugUtilsLabelEXT endDebugLabel = reinterpret_cast<PFN_vkCmdEndDebugUtilsLabelEXT>( m_Context.getDevice().getProcAddr("vkCmdEndDebugUtilsLabelEXT") ); if (!endDebugLabel) { return; } auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) { endDebugLabel(static_cast<VkCommandBuffer>(cmdBuffer)); }; recordCommandsToStream(cmdStream, submitFunction, nullptr); #endif } void Core::recordComputeIndirectDispatchToCmdStream( const CommandStreamHandle cmdStream, const ComputePipelineHandle computePipeline, const vkcv::BufferHandle buffer, const size_t bufferArgOffset, const std::vector<DescriptorSetUsage>& descriptorSetUsages, const PushConstants& pushConstants) { auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) { const auto pipelineLayout = m_ComputePipelineManager->getVkPipelineLayout(computePipeline); cmdBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, m_ComputePipelineManager->getVkPipeline(computePipeline)); for (const auto& usage : descriptorSetUsages) { cmdBuffer.bindDescriptorSets( vk::PipelineBindPoint::eCompute, pipelineLayout, usage.setLocation, { getDescriptorSet(usage.descriptorSet).vulkanHandle }, usage.dynamicOffsets ); } if (pushConstants.getSizePerDrawcall() > 0) { cmdBuffer.pushConstants( pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, pushConstants.getSizePerDrawcall(), pushConstants.getData()); } cmdBuffer.dispatchIndirect(m_BufferManager->getBuffer(buffer), bufferArgOffset); }; recordCommandsToStream(cmdStream, submitFunction, nullptr); } void Core::endFrame(const WindowHandle& windowHandle) { SwapchainHandle swapchainHandle = m_WindowManager->getWindow(windowHandle).getSwapchainHandle(); if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) { return; } std::array<vk::Semaphore, 2> waitSemaphores{ m_SyncResources.renderFinished, m_SyncResources.swapchainImageAcquired }; const vk::SwapchainKHR& swapchain = m_SwapchainManager->getSwapchain(swapchainHandle).getSwapchain(); const vk::PresentInfoKHR presentInfo( waitSemaphores, swapchain, m_currentSwapchainImageIndex ); vk::Result result; try { result = m_Context.getDevice().getQueue(m_SwapchainManager->getSwapchain(swapchainHandle).getPresentQueueIndex(),0).presentKHR(presentInfo); } catch (const vk::OutOfDateKHRError& e) { result = vk::Result::eErrorOutOfDateKHR; } catch (const vk::DeviceLostError& e) { result = vk::Result::eErrorDeviceLost; } if ((result != vk::Result::eSuccess) && (result != vk::Result::eSuboptimalKHR)) { vkcv_log(LogLevel::ERROR, "Swapchain presentation failed (%s)", vk::to_string(result).c_str()); } else if (result == vk::Result::eSuboptimalKHR) { vkcv_log(LogLevel::WARNING, "Swapchain presentation is suboptimal"); m_SwapchainManager->signalRecreation(swapchainHandle); } } void Core::recordAndSubmitCommandsImmediate( const SubmitInfo &submitInfo, const RecordCommandFunction &record, const FinishCommandFunction &finish) { const vk::Device& device = m_Context.getDevice(); const vkcv::Queue queue = getQueueForSubmit(submitInfo.queueType, m_Context.getQueueManager()); const vk::CommandPool cmdPool = chooseCmdPool(queue, m_CommandResources); const vk::CommandBuffer cmdBuffer = allocateCommandBuffer(device, cmdPool); beginCommandBuffer(cmdBuffer, vk::CommandBufferUsageFlagBits::eOneTimeSubmit); record(cmdBuffer); cmdBuffer.end(); vk::Fence waitFence = createFence(device); submitCommandBufferToQueue(queue.handle, cmdBuffer, waitFence, submitInfo.waitSemaphores, submitInfo.signalSemaphores); waitForFence(device, waitFence); device.destroyFence(waitFence); device.freeCommandBuffers(cmdPool, cmdBuffer); if (finish) { finish(); } } CommandStreamHandle Core::createCommandStream(QueueType queueType) { const vkcv::Queue queue = getQueueForSubmit(queueType, m_Context.getQueueManager()); const vk::CommandPool cmdPool = chooseCmdPool(queue, m_CommandResources); return m_CommandStreamManager->createCommandStream(queue.handle, cmdPool); } void Core::recordCommandsToStream( const CommandStreamHandle cmdStreamHandle, const RecordCommandFunction &record, const FinishCommandFunction &finish) { if (record) { m_CommandStreamManager->recordCommandsToStream(cmdStreamHandle, record); } if (finish) { m_CommandStreamManager->addFinishCallbackToStream(cmdStreamHandle, finish); } } void Core::submitCommandStream(const CommandStreamHandle& handle, bool signalRendering) { std::vector<vk::Semaphore> waitSemaphores; // FIXME: add proper user controllable sync std::vector<vk::Semaphore> signalSemaphores; if (signalRendering) { signalSemaphores.push_back(m_SyncResources.renderFinished); } m_CommandStreamManager->submitCommandStreamSynchronous(handle, waitSemaphores, signalSemaphores); } SamplerHandle Core::createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter, SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode, float mipLodBias, SamplerBorderColor borderColor) { return m_SamplerManager->createSampler(magFilter, minFilter, mipmapMode, addressMode, mipLodBias, borderColor); } Image Core::createImage( vk::Format format, uint32_t width, uint32_t height, uint32_t depth, bool createMipChain, bool supportStorage, bool supportColorAttachment, Multisampling multisampling) { uint32_t mipCount = 1; if (createMipChain) { mipCount = 1 + (uint32_t)std::floor(std::log2(std::max(width, std::max(height, depth)))); } return Image::create( m_ImageManager.get(), format, width, height, depth, mipCount, supportStorage, supportColorAttachment, multisampling); } Downsampler &Core::getDownsampler() { return *m_downsampler; } WindowHandle Core::createWindow( const char *applicationName, uint32_t windowWidth, uint32_t windowHeight, bool resizeable) { WindowHandle windowHandle = m_WindowManager->createWindow(*m_SwapchainManager ,applicationName, windowWidth, windowHeight, resizeable); SwapchainHandle swapchainHandle = m_WindowManager->getWindow(windowHandle).getSwapchainHandle(); setSwapchainImages( swapchainHandle ); return windowHandle; } Window& Core::getWindow(const WindowHandle& handle) { return m_WindowManager->getWindow(handle); } uint32_t Core::getImageWidth(const ImageHandle& image) { return m_ImageManager->getImageWidth(image); } uint32_t Core::getImageHeight(const ImageHandle& image) { return m_ImageManager->getImageHeight(image); } uint32_t Core::getImageDepth(const ImageHandle& image) { return m_ImageManager->getImageDepth(image); } vk::Format Core::getImageFormat(const ImageHandle& image) { return m_ImageManager->getImageFormat(image); } bool Core::isImageSupportingStorage(const ImageHandle &image) { return m_ImageManager->isImageSupportingStorage(image); } uint32_t Core::getImageMipLevels(const ImageHandle &image) { return m_ImageManager->getImageMipCount(image); } uint32_t Core::getImageArrayLayers(const ImageHandle &image) { return m_ImageManager->getImageArrayLayers(image); } Swapchain& Core::getSwapchainOfCurrentWindow() { return m_SwapchainManager->getSwapchain(Window::getFocusedWindow().getSwapchainHandle()); } Swapchain& Core::getSwapchain(const SwapchainHandle& handle) { return m_SwapchainManager->getSwapchain(handle); } Swapchain& Core::getSwapchain(const WindowHandle& handle) { SwapchainHandle swapchainHandle = m_WindowManager->getWindow(handle).getSwapchainHandle(); return getSwapchain(swapchainHandle); } DescriptorSetLayoutHandle Core::createDescriptorSetLayout(const DescriptorBindings &bindings) { return m_DescriptorManager->createDescriptorSetLayout(bindings); } DescriptorSetLayout Core::getDescriptorSetLayout(const DescriptorSetLayoutHandle handle) const { return m_DescriptorManager->getDescriptorSetLayout(handle); } DescriptorSetHandle Core::createDescriptorSet(const DescriptorSetLayoutHandle &layout) { return m_DescriptorManager->createDescriptorSet(layout); } void Core::writeDescriptorSet(DescriptorSetHandle handle, const DescriptorWrites &writes) { m_DescriptorManager->writeDescriptorSet( handle, writes, *m_ImageManager, *m_BufferManager, *m_SamplerManager); } DescriptorSet Core::getDescriptorSet(const DescriptorSetHandle handle) const { return m_DescriptorManager->getDescriptorSet(handle); } void Core::prepareSwapchainImageForPresent(const CommandStreamHandle& cmdStream) { auto swapchainHandle = ImageHandle::createSwapchainImageHandle(); recordCommandsToStream(cmdStream, [swapchainHandle, this](const vk::CommandBuffer cmdBuffer) { m_ImageManager->recordImageLayoutTransition( swapchainHandle, 0, 0, vk::ImageLayout::ePresentSrcKHR, cmdBuffer ); }, nullptr); } void Core::prepareImageForSampling(const CommandStreamHandle& cmdStream, const ImageHandle& image, uint32_t mipLevelCount, uint32_t mipLevelOffset) { recordCommandsToStream(cmdStream, [image, mipLevelCount, mipLevelOffset, this](const vk::CommandBuffer cmdBuffer) { m_ImageManager->recordImageLayoutTransition( image, mipLevelCount, mipLevelOffset, vk::ImageLayout::eShaderReadOnlyOptimal, cmdBuffer ); }, nullptr); } void Core::prepareImageForStorage(const CommandStreamHandle& cmdStream, const ImageHandle& image, uint32_t mipLevelCount, uint32_t mipLevelOffset) { recordCommandsToStream(cmdStream, [image, mipLevelCount, mipLevelOffset, this](const vk::CommandBuffer cmdBuffer) { m_ImageManager->recordImageLayoutTransition( image, mipLevelCount, mipLevelOffset, vk::ImageLayout::eGeneral, cmdBuffer ); }, nullptr); } void Core::prepareImageForAttachmentManually(const vk::CommandBuffer& cmdBuffer, const ImageHandle& image) { transitionRendertargetsToAttachmentLayout({ image }, *m_ImageManager, cmdBuffer); } void Core::updateImageLayoutManual(const vkcv::ImageHandle& image, const vk::ImageLayout layout) { m_ImageManager->updateImageLayoutManual(image, layout); } void Core::recordImageMemoryBarrier(const CommandStreamHandle& cmdStream, const ImageHandle& image) { recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) { m_ImageManager->recordImageMemoryBarrier(image, cmdBuffer); }, nullptr); } void Core::recordBufferMemoryBarrier(const CommandStreamHandle& cmdStream, const BufferHandle& buffer) { recordCommandsToStream(cmdStream, [buffer, this](const vk::CommandBuffer cmdBuffer) { m_BufferManager->recordBufferMemoryBarrier(buffer, cmdBuffer); }, nullptr); } void Core::resolveMSAAImage(const CommandStreamHandle& cmdStream, const ImageHandle& src, const ImageHandle& dst) { recordCommandsToStream(cmdStream, [src, dst, this](const vk::CommandBuffer cmdBuffer) { m_ImageManager->recordMSAAResolve(cmdBuffer, src, dst); }, nullptr); } vk::ImageView Core::getSwapchainImageView() const { return m_ImageManager->getVulkanImageView(vkcv::ImageHandle::createSwapchainImageHandle()); } void Core::recordMemoryBarrier(const CommandStreamHandle& cmdStream) { recordCommandsToStream(cmdStream, [](const vk::CommandBuffer cmdBuffer) { vk::MemoryBarrier barrier ( vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead ); cmdBuffer.pipelineBarrier( vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlags(), 1, &barrier, 0, nullptr, 0, nullptr ); }, nullptr); } void Core::recordBlitImage(const CommandStreamHandle& cmdStream, const ImageHandle& src, const ImageHandle& dst, SamplerFilterType filterType) { recordCommandsToStream(cmdStream, [&](const vk::CommandBuffer cmdBuffer) { m_ImageManager->recordImageLayoutTransition( src, 0, 0, vk::ImageLayout::eTransferSrcOptimal, cmdBuffer ); m_ImageManager->recordImageLayoutTransition( dst, 0, 0, vk::ImageLayout::eTransferDstOptimal, cmdBuffer ); const std::array<vk::Offset3D, 2> srcOffsets = { vk::Offset3D(0, 0, 0), vk::Offset3D( m_ImageManager->getImageWidth(src), m_ImageManager->getImageHeight(src), 1 ) }; const std::array<vk::Offset3D, 2> dstOffsets = { vk::Offset3D(0, 0, 0), vk::Offset3D( m_ImageManager->getImageWidth(dst), m_ImageManager->getImageHeight(dst), 1 ) }; const bool srcDepth = isDepthFormat(m_ImageManager->getImageFormat(src)); const bool dstDepth = isDepthFormat(m_ImageManager->getImageFormat(dst)); const vk::ImageBlit blit = vk::ImageBlit( vk::ImageSubresourceLayers( srcDepth? vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eColor, 0, 0, 1 ), srcOffsets, vk::ImageSubresourceLayers( dstDepth? vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eColor, 0, 0, 1 ), dstOffsets ); cmdBuffer.blitImage( m_ImageManager->getVulkanImage(src), vk::ImageLayout::eTransferSrcOptimal, m_ImageManager->getVulkanImage(dst), vk::ImageLayout::eTransferDstOptimal, 1, &blit, filterType == SamplerFilterType::LINEAR? vk::Filter::eLinear : vk::Filter::eNearest ); }, nullptr); } void Core::setSwapchainImages( SwapchainHandle handle ) { Swapchain swapchain = m_SwapchainManager->getSwapchain(handle); const auto swapchainImages = m_SwapchainManager->getSwapchainImages(handle); const auto swapchainImageViews = m_SwapchainManager->createSwapchainImageViews(handle); m_ImageManager->setSwapchainImages( swapchainImages, swapchainImageViews, swapchain.getExtent().width, swapchain.getExtent().height, swapchain.getFormat() ); } static void setDebugObjectLabel(const vk::Device& device, const vk::ObjectType& type, uint64_t handle, const std::string& label) { #ifdef VULKAN_DEBUG_LABELS static PFN_vkSetDebugUtilsObjectNameEXT setDebugLabel = reinterpret_cast<PFN_vkSetDebugUtilsObjectNameEXT>( device.getProcAddr("vkSetDebugUtilsObjectNameEXT") ); if (!setDebugLabel) { return; } const vk::DebugUtilsObjectNameInfoEXT debug ( type, handle, label.c_str() ); setDebugLabel(static_cast<VkDevice>(device), &(static_cast<const VkDebugUtilsObjectNameInfoEXT&>(debug))); #endif } void Core::setDebugLabel(const BufferHandle &handle, const std::string &label) { if (!handle) { vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); return; } setDebugObjectLabel( m_Context.getDevice(), vk::ObjectType::eBuffer, uint64_t(static_cast<VkBuffer>( m_BufferManager->getBuffer(handle) )), label ); } void Core::setDebugLabel(const PassHandle &handle, const std::string &label) { if (!handle) { vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); return; } setDebugObjectLabel( m_Context.getDevice(), vk::ObjectType::eRenderPass, uint64_t(static_cast<VkRenderPass>( m_PassManager->getVkPass(handle) )), label ); } void Core::setDebugLabel(const GraphicsPipelineHandle &handle, const std::string &label) { if (!handle) { vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); return; } setDebugObjectLabel( m_Context.getDevice(), vk::ObjectType::ePipeline, uint64_t(static_cast<VkPipeline>( m_PipelineManager->getVkPipeline(handle) )), label ); } void Core::setDebugLabel(const ComputePipelineHandle &handle, const std::string &label) { if (!handle) { vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); return; } setDebugObjectLabel( m_Context.getDevice(), vk::ObjectType::ePipeline, uint64_t(static_cast<VkPipeline>( m_ComputePipelineManager->getVkPipeline(handle) )), label ); } void Core::setDebugLabel(const DescriptorSetHandle &handle, const std::string &label) { if (!handle) { vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); return; } setDebugObjectLabel( m_Context.getDevice(), vk::ObjectType::eDescriptorSet, uint64_t(static_cast<VkDescriptorSet>( m_DescriptorManager->getDescriptorSet(handle).vulkanHandle )), label ); } void Core::setDebugLabel(const SamplerHandle &handle, const std::string &label) { if (!handle) { vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); return; } setDebugObjectLabel( m_Context.getDevice(), vk::ObjectType::eSampler, uint64_t(static_cast<VkSampler>( m_SamplerManager->getVulkanSampler(handle) )), label ); } void Core::setDebugLabel(const ImageHandle &handle, const std::string &label) { if (!handle) { vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); return; } else if (handle.isSwapchainImage()) { vkcv_log(LogLevel::WARNING, "Can't set debug label to swapchain image"); return; } setDebugObjectLabel( m_Context.getDevice(), vk::ObjectType::eImage, uint64_t(static_cast<VkImage>( m_ImageManager->getVulkanImage(handle) )), label ); } void Core::setDebugLabel(const CommandStreamHandle &handle, const std::string &label) { if (!handle) { vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); return; } setDebugObjectLabel( m_Context.getDevice(), vk::ObjectType::eCommandBuffer, uint64_t(static_cast<VkCommandBuffer>( m_CommandStreamManager->getStreamCommandBuffer(handle) )), label ); } }