#include "common/assert.h" #include "common/common.h" #include "common/emulatorConfig.h" #include "common/logging/log.h" #include "common/profiler.h" #include "common/threads.h" #include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/vulkanCommon.h" #include "graphics/presentation/presenter.h" #include "graphics/presentation/systemOverlay.h" #include "graphics/presentation/videoOut.h" #include "graphics/presentation/window/windowInternal.h" #include #include #include #include #include #include // IWYU pragma: no_include #define KYTY_ENABLE_DEBUG_PRINTF #define KYTY_DBG_INPUT namespace Libs::Graphics { struct Presenter::Frame { VulkanImage image; uint64_t present_tick = 0; bool busy = false; bool reusing_last = false; void Configure(GraphicContext& graphics, vk::Extent2D extent, vk::Format format); void Transit(vk::CommandBuffer command, vk::ImageLayout layout, vk::AccessFlags2 access); void CopyFrom(CommandBuffer& command, Image& source); void Clear(CommandBuffer& command, const vk::ClearColorValue& color); }; class FramePool final { public: FramePool(WindowContext& window, CommandScheduler& scheduler) : m_window(window), m_scheduler(scheduler) {} ~FramePool() { m_scheduler.Wait(m_scheduler.CurrentTick() - 1); for (auto& frame: m_frames) { if (frame->image.image != nullptr) { m_window.graphic_ctx.DeleteImage(frame->image); } } } KYTY_CLASS_NO_COPY(FramePool); void Initialize(uint32_t count, vk::Format format) { if (count == 0 || format == vk::Format::eUndefined) { EXIT("prepared-frame pool requires at least one frame\n"); } Common::LockGuard lock(m_mutex); if (!m_frames.empty()) { EXIT("prepared-frame pool was initialized twice\n"); } m_format = format; m_frames.reserve(count); for (uint32_t i = 0; i < count; i++) { auto frame = std::make_unique(); m_free.push_back(frame.get()); m_frames.push_back(std::move(frame)); } } void SetFormat(vk::Format format) { if (format == vk::Format::eUndefined) { EXIT("prepared-frame pool requires a presentation format\n"); } Common::LockGuard lock(m_mutex); m_format = format; } vk::Format GetFormat() { Common::LockGuard lock(m_mutex); if (m_format == vk::Format::eUndefined) { EXIT("prepared-frame pool has no presentation format\n"); } return m_format; } Presenter::Frame* Acquire() { m_mutex.Lock(); if (m_frames.empty()) { EXIT("prepared-frame pool was used before swapchain initialization\n"); } while (m_free.empty()) { m_available.Wait(&m_mutex); } auto* frame = m_free.front(); m_free.pop_front(); if (frame->busy) { EXIT("prepared-frame pool returned an invalid frame\n"); } if (m_last_frame == frame) { m_last_frame = nullptr; } frame->busy = true; frame->reusing_last = false; m_mutex.Unlock(); WaitForFrame(*frame); return frame; } Presenter::Frame* AcquireLast() { m_mutex.Lock(); auto* frame = m_last_frame; if (frame == nullptr) { m_mutex.Unlock(); return nullptr; } auto free = std::find(m_free.begin(), m_free.end(), frame); if (free == m_free.end() || frame->busy) { m_mutex.Unlock(); EXIT("last submitted frame is not available for reuse\n"); } m_free.erase(free); m_last_frame = nullptr; frame->busy = true; frame->reusing_last = true; m_mutex.Unlock(); WaitForFrame(*frame); return frame; } void ValidateForPresent(Presenter::Frame* frame, bool reuse) { Common::LockGuard lock(m_mutex); if (frame == nullptr || !frame->busy || frame->reusing_last != reuse) { EXIT("prepared frame has invalid presentation ownership\n"); } } void Release(Presenter::Frame* frame, bool make_last = false) { if (frame == nullptr) { EXIT("cannot release a null prepared frame\n"); } Common::LockGuard lock(m_mutex); if (!frame->busy) { EXIT("prepared frame was released twice\n"); } frame->busy = false; frame->reusing_last = false; if (make_last) { m_last_frame = frame; } m_free.push_back(frame); m_available.Signal(); } private: void WaitForFrame(Presenter::Frame& frame) { m_scheduler.Wait(frame.present_tick); } WindowContext& m_window; CommandScheduler& m_scheduler; Common::Mutex m_mutex; Common::CondVar m_available; std::vector> m_frames; std::deque m_free; Presenter::Frame* m_last_frame = nullptr; vk::Format m_format = vk::Format::eUndefined; }; void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent, vk::Format format) { if (extent.width == 0 || extent.height == 0 || format == vk::Format::eUndefined) { EXIT("unsupported prepared frame, extent=%ux%u format=%d\n", extent.width, extent.height, static_cast(format)); } const auto features = graphics.GetFormatProperties(format).optimalTilingFeatures; const auto required = vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eSampledImageFilterLinear | vk::FormatFeatureFlagBits::eTransferSrc | vk::FormatFeatureFlagBits::eTransferDst; if ((features & required) != required) { EXIT("prepared presentation format lacks optimal blit support: format=%d features=0x%x\n", static_cast(format), static_cast(features)); } auto& dst = image; const bool compatible = dst.image != nullptr && dst.extent.width == extent.width && dst.extent.height == extent.height && dst.format == format; if (compatible) { return; } if (dst.image != nullptr) { graphics.DeleteImage(dst); dst.memory = {}; } dst.extent = {extent.width, extent.height, 1}; dst.format = format; dst.layers = 1; dst.mip_levels = 1; dst.state = {}; dst.subresource_states.clear(); dst.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal; vk::ImageCreateInfo create {}; create.sType = vk::StructureType::eImageCreateInfo; create.imageType = vk::ImageType::e2D; create.extent = {dst.extent.width, dst.extent.height, 1}; create.mipLevels = 1; create.arrayLayers = 1; create.format = dst.format; create.tiling = vk::ImageTiling::eOptimal; create.initialLayout = vk::ImageLayout::eUndefined; create.usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst; create.sharingMode = vk::SharingMode::eExclusive; create.samples = vk::SampleCountFlagBits::e1; if (!graphics.CreateImage(create, dst)) { EXIT("failed to allocate prepared presentation image, extent=%ux%u format=%d\n", dst.extent.width, dst.extent.height, static_cast(dst.format)); } } void Presenter::Frame::Transit(vk::CommandBuffer command, vk::ImageLayout layout, vk::AccessFlags2 access) { const auto stage = access == vk::AccessFlagBits2::eTransferRead || access == vk::AccessFlagBits2::eTransferWrite ? vk::PipelineStageFlagBits2::eTransfer : vk::PipelineStageFlagBits2::eAllCommands; constexpr auto writes = vk::AccessFlagBits2::eTransferWrite | vk::AccessFlagBits2::eShaderWrite | vk::AccessFlagBits2::eMemoryWrite; if (image.state.layout == layout && image.state.access_mask == access && !static_cast(image.state.access_mask & writes)) { return; } vk::ImageMemoryBarrier2 barrier {}; barrier.srcStageMask = image.state.pl_stage; barrier.srcAccessMask = image.state.access_mask; barrier.dstStageMask = stage; barrier.dstAccessMask = access; barrier.oldLayout = image.state.layout; barrier.newLayout = layout; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = image.image; barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; barrier.subresourceRange.baseMipLevel = 0; barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; vk::DependencyInfo dependency {}; dependency.imageMemoryBarrierCount = 1; dependency.pImageMemoryBarriers = &barrier; command.pipelineBarrier2(dependency); image.state = {stage, access, layout}; image.subresource_states.clear(); } void Presenter::Frame::CopyFrom(CommandBuffer& command_buffer, Image& source) { command_buffer.EndRendering(); auto command = command_buffer.Handle(); source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}, command); Transit(command, vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite); vk::ImageCopy copy {}; copy.srcSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, source.backing.layers}; copy.dstSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, image.layers}; copy.extent = {std::min(source.backing.extent.width, image.extent.width), std::min(source.backing.extent.height, image.extent.height), 1}; EXIT_IF(copy.srcSubresource.layerCount != copy.dstSubresource.layerCount); command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, image.image, vk::ImageLayout::eTransferDstOptimal, copy); Transit(command, vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead); } void Presenter::Frame::Clear(CommandBuffer& command_buffer, const vk::ClearColorValue& color) { command_buffer.EndRendering(); auto command = command_buffer.Handle(); Transit(command, vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite); const vk::ImageSubresourceRange range {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}; command.clearColorImage(image.image, vk::ImageLayout::eTransferDstOptimal, &color, 1, &range); Transit(command, vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead); } class Swapchain final { public: enum class Status : uint8_t { Success, Recreate, SurfaceLost }; explicit Swapchain(WindowContext& window): m_window(window) {} ~Swapchain(); KYTY_CLASS_NO_COPY(Swapchain); void Create(); void Recreate(bool surface_lost = false); [[nodiscard]] Status AcquireNextImage(); [[nodiscard]] bool PrepareSystemOverlay(); void RecordPresentCommands(CommandBuffer& command, VulkanImage& source, bool draw_system_overlay); uint64_t Submit(CommandScheduler& scheduler); [[nodiscard]] Status Present(); [[nodiscard]] uint32_t ImageCount() const noexcept { return static_cast(m_images.size()); } [[nodiscard]] vk::Format Format() const noexcept { return m_format; } private: void Destroy(); WindowContext& m_window; vk::SwapchainKHR m_handle = nullptr; vk::Format m_format = vk::Format::eUndefined; vk::Extent2D m_extent {}; std::vector m_images; std::vector m_image_views; std::vector m_image_acquired; std::vector m_render_complete; std::unique_ptr m_system_overlay; uint32_t m_image_index = static_cast(-1); uint32_t m_frame_index = 0; }; struct Presenter::Impl { explicit Impl(WindowContext& owner) : renderer(*owner.render_context), window(owner), swapchain(owner), present_scheduler(renderer, owner.graphic_ctx), frames(owner, present_scheduler) { EXIT_IF(owner.render_context == nullptr); swapchain.Create(); frames.Initialize(swapchain.ImageCount(), swapchain.Format()); } void RecoverSwapchain(Swapchain::Status status) { LOGF("Recovering Vulkan swapchain%s\n", status == Swapchain::Status::SurfaceLost ? " and surface" : ""); swapchain.Recreate(status == Swapchain::Status::SurfaceLost); frames.SetFormat(swapchain.Format()); } Image& ResolveSurface(const ImageInfo& info) { TextureCache::ImageDesc desc {}; desc.info = info; desc.view_info.format = info.pixel_format; desc.view_info.type = vk::ImageViewType::e2D; desc.view_info.aspect = vk::ImageAspectFlagBits::eColor; desc.view_info.base_level = 0; desc.view_info.level_count = 1; desc.view_info.base_layer = 0; desc.view_info.layer_count = 1; desc.view_info.usage = vk::ImageUsageFlagBits::eTransferSrc; desc.type = TextureCache::BindingType::VideoOut; auto& cache = renderer.GetTextureCache(); const auto image_id = cache.FindImage(desc); auto& image = cache.GetImage(image_id); image.usage.video_out = true; cache.UpdateImage(image_id); return image; } RenderContext& renderer; WindowContext& window; Swapchain swapchain; CommandScheduler present_scheduler; FramePool frames; std::atomic presented_overlay_revision {0}; }; void Swapchain::Create() { auto& graphics = m_window.graphic_ctx; EXIT_IF(graphics.device == nullptr); EXIT_IF(m_window.surface == nullptr); Common::LockGuard lock(m_window.mutex); EXIT_IF(graphics.screen_width == 0); EXIT_IF(graphics.screen_height == 0); const auto& surface = m_window.surface_capabilities; EXIT_NOT_IMPLEMENTED(surface.formats.empty()); m_extent = surface.capabilities.currentExtent; if (m_extent.width == std::numeric_limits::max()) { m_extent.width = std::clamp(graphics.screen_width, surface.capabilities.minImageExtent.width, surface.capabilities.maxImageExtent.width); m_extent.height = std::clamp(graphics.screen_height, surface.capabilities.minImageExtent.height, surface.capabilities.maxImageExtent.height); } uint32_t image_count = surface.capabilities.minImageCount + 1; if (surface.capabilities.maxImageCount != 0) { image_count = std::min(image_count, surface.capabilities.maxImageCount); } const auto transform = surface.capabilities.supportedTransforms & vk::SurfaceTransformFlagBitsKHR::eIdentity ? vk::SurfaceTransformFlagBitsKHR::eIdentity : surface.capabilities.currentTransform; const auto composite = surface.capabilities.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::eOpaque ? vk::CompositeAlphaFlagBitsKHR::eOpaque : vk::CompositeAlphaFlagBitsKHR::eInherit; vk::SurfaceFormatKHR format {vk::Format::eR8G8B8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear}; if (surface.formats.size() != 1 || surface.formats.front().format != vk::Format::eUndefined) { const auto it = std::find_if(surface.formats.begin(), surface.formats.end(), [](const vk::SurfaceFormatKHR& candidate) { return candidate.format == vk::Format::eB8G8R8A8Unorm || candidate.format == vk::Format::eR8G8B8A8Unorm; }); if (it == surface.formats.end()) { EXIT("no supported UNORM swapchain format\n"); } format = *it; } m_format = format.format; const auto swapchain_features = graphics.GetFormatProperties(m_format).optimalTilingFeatures; if (!static_cast(swapchain_features & vk::FormatFeatureFlagBits::eBlitDst)) { EXIT("swapchain format cannot be a blit destination: format=%d\n", static_cast(m_format)); } vk::SwapchainCreateInfoKHR create_info {}; create_info.sType = vk::StructureType::eSwapchainCreateInfoKHR; create_info.surface = m_window.surface; create_info.minImageCount = image_count; create_info.imageFormat = format.format; create_info.imageColorSpace = format.colorSpace; create_info.imageExtent = m_extent; create_info.imageArrayLayers = 1; create_info.imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst; create_info.imageSharingMode = vk::SharingMode::eExclusive; create_info.preTransform = transform; create_info.compositeAlpha = composite; switch (Config::GetPresentMode()) { case Config::PresentMode::Mailbox: create_info.presentMode = vk::PresentModeKHR::eMailbox; break; case Config::PresentMode::Immediate: create_info.presentMode = vk::PresentModeKHR::eImmediate; break; case Config::PresentMode::Fifo: default: create_info.presentMode = vk::PresentModeKHR::eFifo; break; } if (std::find(surface.present_modes.begin(), surface.present_modes.end(), create_info.presentMode) == surface.present_modes.end()) { LOGF("warning: requested present mode is unavailable; falling back to Fifo\n"); create_info.presentMode = vk::PresentModeKHR::eFifo; } create_info.clipped = VK_TRUE; RequireVulkanSuccess(graphics.device.createSwapchainKHR(&create_info, nullptr, &m_handle), "vkCreateSwapchainKHR"); EXIT_IF(m_handle == nullptr); m_images = EnumerateVulkan( "vkGetSwapchainImagesKHR", [&](uint32_t* count, vk::Image* images) { return graphics.device.getSwapchainImagesKHR(m_handle, count, images); }); EXIT_NOT_IMPLEMENTED(m_images.empty()); m_image_views.resize(m_images.size()); for (size_t i = 0; i < m_images.size(); i++) { vk::ImageViewCreateInfo view {}; view.sType = vk::StructureType::eImageViewCreateInfo; view.image = m_images[i]; view.viewType = vk::ImageViewType::e2D; view.format = m_format; view.components = {}; view.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; view.subresourceRange.baseArrayLayer = 0; view.subresourceRange.baseMipLevel = 0; view.subresourceRange.layerCount = 1; view.subresourceRange.levelCount = 1; RequireVulkanSuccess(graphics.device.createImageView(&view, nullptr, &m_image_views[i]), "vkCreateImageView"); EXIT_IF(m_image_views[i] == nullptr); } vk::SemaphoreCreateInfo semaphore_info {}; semaphore_info.sType = vk::StructureType::eSemaphoreCreateInfo; m_image_acquired.resize(m_images.size()); m_render_complete.resize(m_images.size()); for (size_t i = 0; i < m_images.size(); i++) { RequireVulkanSuccess( graphics.device.createSemaphore(&semaphore_info, nullptr, &m_image_acquired[i]), "create swapchain image-acquired semaphore"); RequireVulkanSuccess( graphics.device.createSemaphore(&semaphore_info, nullptr, &m_render_complete[i]), "create swapchain render-complete semaphore"); } m_image_index = static_cast(-1); m_frame_index = 0; } Swapchain::~Swapchain() { Destroy(); } void Swapchain::Destroy() { if (m_handle == nullptr && m_image_acquired.empty() && m_render_complete.empty() && m_image_views.empty()) { return; } auto& graphics = m_window.graphic_ctx; { Common::LockGuard queue_lock(graphics.queue_mutex); RequireVulkanSuccess(graphics.queue.waitIdle(), "wait for swapchain queue"); } if (m_system_overlay != nullptr) { m_system_overlay->ReleaseVulkan(); } for (const auto semaphore: m_image_acquired) { if (semaphore != nullptr) { graphics.device.destroySemaphore(semaphore, nullptr); } } for (const auto semaphore: m_render_complete) { if (semaphore != nullptr) { graphics.device.destroySemaphore(semaphore, nullptr); } } for (const auto view: m_image_views) { if (view != nullptr) { graphics.device.destroyImageView(view, nullptr); } } if (m_handle != nullptr) { graphics.device.destroySwapchainKHR(m_handle, nullptr); } m_handle = nullptr; m_format = vk::Format::eUndefined; m_extent = {}; m_image_index = static_cast(-1); m_frame_index = 0; m_images.clear(); m_image_views.clear(); m_image_acquired.clear(); m_render_complete.clear(); } void Swapchain::Recreate(bool surface_lost) { Destroy(); if (surface_lost) { #if defined(__APPLE__) // Surface recreation goes through SDL_Vulkan_CreateSurface, which touches the // window's view/layer and must run on the main thread on macOS. m_window.RunOnMainThread([this] { m_window.RecreateSurface(); }); #else m_window.RecreateSurface(); #endif } m_window.RefreshSurfaceCapabilities(); Create(); } Swapchain::Status Swapchain::AcquireNextImage() { EXIT_IF(m_handle == nullptr || m_frame_index >= m_image_acquired.size()); m_image_index = static_cast(-1); const auto result = m_window.graphic_ctx.device.acquireNextImageKHR( m_handle, std::numeric_limits::max(), m_image_acquired[m_frame_index], nullptr, &m_image_index); switch (result) { case vk::Result::eSuccess: break; case vk::Result::eSuboptimalKHR: LOGF("vkAcquireNextImageKHR returned vk::Result::eSuboptimalKHR\n"); return Status::Recreate; case vk::Result::eErrorOutOfDateKHR: LOGF("vkAcquireNextImageKHR returned vk::Result::eErrorOutOfDateKHR\n"); return Status::Recreate; case vk::Result::eErrorUnknown: LOGF("vkAcquireNextImageKHR returned vk::Result::eErrorUnknown\n"); return Status::Recreate; case vk::Result::eErrorSurfaceLostKHR: LOGF("vkAcquireNextImageKHR returned vk::Result::eErrorSurfaceLostKHR\n"); return Status::SurfaceLost; default: EXIT("vkAcquireNextImageKHR failed: %s\n", vk::to_string(result).c_str()); } EXIT_IF(m_image_index >= m_images.size()); return Status::Success; } bool Swapchain::PrepareSystemOverlay() { if (m_system_overlay == nullptr) { m_system_overlay = std::make_unique(m_window.graphic_ctx); } return m_system_overlay->PrepareFrame(m_extent, m_format, ImageCount()); } void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& source, bool draw_system_overlay) { if (source.state.layout != vk::ImageLayout::eTransferSrcOptimal) { EXIT("invalid prepared presentation image, vk_image=%p layout=%d\n", static_cast(source.image), static_cast(source.state.layout)); } EXIT_IF(m_image_index >= m_images.size()); auto vk_command = command.Handle(); vk::ImageMemoryBarrier to_transfer {}; to_transfer.sType = vk::StructureType::eImageMemoryBarrier; to_transfer.dstAccessMask = vk::AccessFlagBits::eTransferWrite; to_transfer.oldLayout = vk::ImageLayout::eUndefined; to_transfer.newLayout = vk::ImageLayout::eTransferDstOptimal; to_transfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; to_transfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; to_transfer.image = m_images[m_image_index]; to_transfer.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; to_transfer.subresourceRange.baseMipLevel = 0; to_transfer.subresourceRange.levelCount = 1; to_transfer.subresourceRange.baseArrayLayer = 0; to_transfer.subresourceRange.layerCount = 1; // Match the acquire wait stage so the layout transition cannot precede acquisition. vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &to_transfer); vk::ImageBlit region {}; region.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; region.srcSubresource.mipLevel = 0; region.srcSubresource.baseArrayLayer = 0; region.srcSubresource.layerCount = 1; region.srcOffsets[1].x = static_cast(source.extent.width); region.srcOffsets[1].y = static_cast(source.extent.height); region.srcOffsets[1].z = 1; region.dstSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; region.dstSubresource.mipLevel = 0; region.dstSubresource.baseArrayLayer = 0; region.dstSubresource.layerCount = 1; region.dstOffsets[1].x = static_cast(m_extent.width); region.dstOffsets[1].y = static_cast(m_extent.height); region.dstOffsets[1].z = 1; vk_command.blitImage(source.image, vk::ImageLayout::eTransferSrcOptimal, m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal, 1, ®ion, vk::Filter::eLinear); vk::ImageMemoryBarrier to_present {}; to_present.sType = vk::StructureType::eImageMemoryBarrier; to_present.srcAccessMask = vk::AccessFlagBits::eTransferWrite; to_present.dstAccessMask = draw_system_overlay ? vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite : vk::AccessFlagBits::eMemoryRead; to_present.oldLayout = vk::ImageLayout::eTransferDstOptimal; to_present.newLayout = draw_system_overlay ? vk::ImageLayout::eColorAttachmentOptimal : vk::ImageLayout::ePresentSrcKHR; to_present.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; to_present.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; to_present.image = m_images[m_image_index]; to_present.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; to_present.subresourceRange.baseMipLevel = 0; to_present.subresourceRange.levelCount = 1; to_present.subresourceRange.baseArrayLayer = 0; to_present.subresourceRange.layerCount = 1; vk_command.pipelineBarrier( vk::PipelineStageFlagBits::eTransfer, draw_system_overlay ? vk::PipelineStageFlagBits::eColorAttachmentOutput : vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1, &to_present); if (draw_system_overlay) { m_system_overlay->Record(vk_command, m_image_views[m_image_index]); to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; to_present.dstAccessMask = vk::AccessFlagBits::eMemoryRead; to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal; to_present.newLayout = vk::ImageLayout::ePresentSrcKHR; vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1, &to_present); } } uint64_t Swapchain::Submit(CommandScheduler& scheduler) { EXIT_IF(m_frame_index >= m_image_acquired.size() || m_image_index >= m_render_complete.size()); SubmitInfo submit; submit.AddWait(m_image_acquired[m_frame_index], 1, vk::PipelineStageFlagBits::eTransfer); submit.AddSignal(m_render_complete[m_image_index]); return scheduler.Submit(submit); } Swapchain::Status Swapchain::Present() { EXIT_IF(m_image_index >= m_render_complete.size()); const auto ready = m_render_complete[m_image_index]; vk::PresentInfoKHR present {}; present.sType = vk::StructureType::ePresentInfoKHR; present.swapchainCount = 1; present.pSwapchains = &m_handle; present.pImageIndices = &m_image_index; present.pWaitSemaphores = &ready; present.waitSemaphoreCount = 1; vk::Result result; { Common::LockGuard lock(m_window.graphic_ctx.queue_mutex); result = m_window.graphic_ctx.queue.presentKHR(&present); } switch (result) { case vk::Result::eSuccess: break; case vk::Result::eSuboptimalKHR: LOGF("vkQueuePresentKHR returned vk::Result::eSuboptimalKHR\n"); return Status::Recreate; case vk::Result::eErrorOutOfDateKHR: LOGF("vkQueuePresentKHR returned vk::Result::eErrorOutOfDateKHR\n"); return Status::Recreate; case vk::Result::eErrorSurfaceLostKHR: LOGF("vkQueuePresentKHR returned vk::Result::eErrorSurfaceLostKHR\n"); return Status::SurfaceLost; default: EXIT("vkQueuePresentKHR failed: %s\n", vk::to_string(result).c_str()); } m_frame_index = (m_frame_index + 1u) % static_cast(m_images.size()); return Status::Success; } Presenter::Presenter(WindowContext& window): m_impl(std::make_unique(window)) {} Presenter::~Presenter() = default; Presenter::Frame& Presenter::PrepareFrame(CommandBuffer& buffer, const ImageInfo& info) { KYTY_PROFILER_FUNCTION(); EXIT_IF(buffer.IsInvalid()); auto* frame = m_impl->frames.Acquire(); Common::LockGuard render_lock(m_impl->renderer.GetMutex()); auto& image = m_impl->ResolveSurface(info); if (image.backing.format == vk::Format::eUndefined) { EXIT("unsupported presentation source, image=%p\n", static_cast(&image)); } auto frame_format = info.pixel_format; switch (frame_format) { case vk::Format::eR8G8B8A8Srgb: frame_format = vk::Format::eR8G8B8A8Unorm; break; case vk::Format::eB8G8R8A8Srgb: frame_format = vk::Format::eB8G8R8A8Unorm; break; default: break; } frame->Configure(m_impl->window.graphic_ctx, {image.backing.extent.width, image.backing.extent.height}, frame_format); frame->CopyFrom(buffer, image); return *frame; } Presenter::Frame& Presenter::PrepareBlankFrame(uint32_t width, uint32_t height, bool opaque, CommandBuffer* producer) { KYTY_PROFILER_FUNCTION(); auto format = m_impl->frames.GetFormat(); auto* frame = m_impl->frames.Acquire(); Common::LockGuard render_lock(m_impl->renderer.GetMutex()); frame->Configure(m_impl->window.graphic_ctx, {width, height}, format); vk::ClearColorValue clear {}; clear.float32[3] = opaque ? 1.0f : 0.0f; if (producer != nullptr) { EXIT_IF(producer->IsInvalid()); frame->Clear(*producer, clear); } else { auto& command = m_impl->present_scheduler.BeginCommand(); frame->Clear(command, clear); frame->present_tick = m_impl->present_scheduler.Submit(); } return *frame; } Presenter::Frame* Presenter::PrepareLastFrame() { return m_impl->frames.AcquireLast(); } bool Presenter::IsGuestPaused() const noexcept { return m_impl->window.loop.paused.load(std::memory_order_acquire); } bool Presenter::NeedsSystemOverlayRefresh() const noexcept { const auto visual = GetSystemOverlayVisualState(); return visual.active || visual.revision != m_impl->presented_overlay_revision.load(std::memory_order_acquire); } RenderContext& Presenter::Renderer() const noexcept { return m_impl->renderer; } void Presenter::Present(Frame& frame, bool reuse) { KYTY_PROFILER_FUNCTION(); m_impl->frames.ValidateForPresent(&frame, reuse); const auto overlay_visual = GetSystemOverlayVisualState(); auto& swapchain = m_impl->swapchain; for (uint32_t attempt = 0; attempt < 2; attempt++) { auto status = swapchain.AcquireNextImage(); if (status != Swapchain::Status::Success) { m_impl->RecoverSwapchain(status); continue; } { Common::LockGuard render_lock(m_impl->renderer.GetMutex()); auto& command = m_impl->present_scheduler.BeginCommand(); const bool draw_system_overlay = overlay_visual.active && swapchain.PrepareSystemOverlay(); swapchain.RecordPresentCommands(command, frame.image, draw_system_overlay); frame.present_tick = swapchain.Submit(m_impl->present_scheduler); } status = swapchain.Present(); if (status != Swapchain::Status::Success) { m_impl->RecoverSwapchain(status); continue; } m_impl->presented_overlay_revision.store(overlay_visual.revision, std::memory_order_release); m_impl->window.UpdateTitle(); m_impl->frames.Release(&frame, true); return; } LOGF("Vulkan presentation retry exhausted; dropping frame\n"); m_impl->frames.Release(&frame, reuse); } void Presenter::Discard(Frame& frame) { m_impl->frames.Release(&frame); } WindowContext::WindowContext() = default; } // namespace Libs::Graphics