Browse Source
Merge pull request #9973 from GPUCode/async-present
Merge pull request #9973 from GPUCode/async-present
Implement asynchronous presentationpull/15/merge
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 772 additions and 226 deletions
-
1src/common/settings.cpp
-
1src/common/settings.h
-
2src/video_core/CMakeLists.txt
-
51src/video_core/renderer_vulkan/renderer_vulkan.cpp
-
2src/video_core/renderer_vulkan/renderer_vulkan.h
-
224src/video_core/renderer_vulkan/vk_blit_screen.cpp
-
34src/video_core/renderer_vulkan/vk_blit_screen.h
-
454src/video_core/renderer_vulkan/vk_present_manager.cpp
-
83src/video_core/renderer_vulkan/vk_present_manager.h
-
9src/video_core/renderer_vulkan/vk_scheduler.cpp
-
6src/video_core/renderer_vulkan/vk_scheduler.h
-
49src/video_core/renderer_vulkan/vk_swapchain.cpp
-
31src/video_core/renderer_vulkan/vk_swapchain.h
-
11src/video_core/renderer_vulkan/vk_update_descriptor.cpp
-
10src/video_core/renderer_vulkan/vk_update_descriptor.h
-
2src/yuzu/configuration/config.cpp
-
7src/yuzu/configuration/configure_graphics_advanced.cpp
-
1src/yuzu/configuration/configure_graphics_advanced.h
-
15src/yuzu/configuration/configure_graphics_advanced.ui
-
1src/yuzu_cmd/config.cpp
-
4src/yuzu_cmd/default_ini.h
@ -0,0 +1,454 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "common/microprofile.h"
|
||||
|
#include "common/settings.h"
|
||||
|
#include "common/thread.h"
|
||||
|
#include "video_core/renderer_vulkan/vk_present_manager.h"
|
||||
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
|
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
||||
|
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
|
||||
|
namespace Vulkan { |
||||
|
|
||||
|
MICROPROFILE_DEFINE(Vulkan_WaitPresent, "Vulkan", "Wait For Present", MP_RGB(128, 128, 128)); |
||||
|
MICROPROFILE_DEFINE(Vulkan_CopyToSwapchain, "Vulkan", "Copy to swapchain", MP_RGB(192, 255, 192)); |
||||
|
|
||||
|
namespace { |
||||
|
|
||||
|
bool CanBlitToSwapchain(const vk::PhysicalDevice& physical_device, VkFormat format) { |
||||
|
const VkFormatProperties props{physical_device.GetFormatProperties(format)}; |
||||
|
return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT); |
||||
|
} |
||||
|
|
||||
|
[[nodiscard]] VkImageSubresourceLayers MakeImageSubresourceLayers() { |
||||
|
return VkImageSubresourceLayers{ |
||||
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, |
||||
|
.mipLevel = 0, |
||||
|
.baseArrayLayer = 0, |
||||
|
.layerCount = 1, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
[[nodiscard]] VkImageBlit MakeImageBlit(s32 frame_width, s32 frame_height, s32 swapchain_width, |
||||
|
s32 swapchain_height) { |
||||
|
return VkImageBlit{ |
||||
|
.srcSubresource = MakeImageSubresourceLayers(), |
||||
|
.srcOffsets = |
||||
|
{ |
||||
|
{ |
||||
|
.x = 0, |
||||
|
.y = 0, |
||||
|
.z = 0, |
||||
|
}, |
||||
|
{ |
||||
|
.x = frame_width, |
||||
|
.y = frame_height, |
||||
|
.z = 1, |
||||
|
}, |
||||
|
}, |
||||
|
.dstSubresource = MakeImageSubresourceLayers(), |
||||
|
.dstOffsets = |
||||
|
{ |
||||
|
{ |
||||
|
.x = 0, |
||||
|
.y = 0, |
||||
|
.z = 0, |
||||
|
}, |
||||
|
{ |
||||
|
.x = swapchain_width, |
||||
|
.y = swapchain_height, |
||||
|
.z = 1, |
||||
|
}, |
||||
|
}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
[[nodiscard]] VkImageCopy MakeImageCopy(u32 frame_width, u32 frame_height, u32 swapchain_width, |
||||
|
u32 swapchain_height) { |
||||
|
return VkImageCopy{ |
||||
|
.srcSubresource = MakeImageSubresourceLayers(), |
||||
|
.srcOffset = |
||||
|
{ |
||||
|
.x = 0, |
||||
|
.y = 0, |
||||
|
.z = 0, |
||||
|
}, |
||||
|
.dstSubresource = MakeImageSubresourceLayers(), |
||||
|
.dstOffset = |
||||
|
{ |
||||
|
.x = 0, |
||||
|
.y = 0, |
||||
|
.z = 0, |
||||
|
}, |
||||
|
.extent = |
||||
|
{ |
||||
|
.width = std::min(frame_width, swapchain_width), |
||||
|
.height = std::min(frame_height, swapchain_height), |
||||
|
.depth = 1, |
||||
|
}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
} // Anonymous namespace
|
||||
|
|
||||
|
PresentManager::PresentManager(Core::Frontend::EmuWindow& render_window_, const Device& device_, |
||||
|
MemoryAllocator& memory_allocator_, Scheduler& scheduler_, |
||||
|
Swapchain& swapchain_) |
||||
|
: render_window{render_window_}, device{device_}, |
||||
|
memory_allocator{memory_allocator_}, scheduler{scheduler_}, swapchain{swapchain_}, |
||||
|
blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())}, |
||||
|
use_present_thread{Settings::values.async_presentation.GetValue()}, |
||||
|
image_count{swapchain.GetImageCount()} { |
||||
|
|
||||
|
auto& dld = device.GetLogical(); |
||||
|
cmdpool = dld.CreateCommandPool({ |
||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, |
||||
|
.pNext = nullptr, |
||||
|
.flags = |
||||
|
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, |
||||
|
.queueFamilyIndex = device.GetGraphicsFamily(), |
||||
|
}); |
||||
|
auto cmdbuffers = cmdpool.Allocate(image_count); |
||||
|
|
||||
|
frames.resize(image_count); |
||||
|
for (u32 i = 0; i < frames.size(); i++) { |
||||
|
Frame& frame = frames[i]; |
||||
|
frame.cmdbuf = vk::CommandBuffer{cmdbuffers[i], device.GetDispatchLoader()}; |
||||
|
frame.render_ready = dld.CreateSemaphore({ |
||||
|
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, |
||||
|
.pNext = nullptr, |
||||
|
.flags = 0, |
||||
|
}); |
||||
|
frame.present_done = dld.CreateFence({ |
||||
|
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, |
||||
|
.pNext = nullptr, |
||||
|
.flags = VK_FENCE_CREATE_SIGNALED_BIT, |
||||
|
}); |
||||
|
free_queue.push(&frame); |
||||
|
} |
||||
|
|
||||
|
if (use_present_thread) { |
||||
|
present_thread = std::jthread([this](std::stop_token token) { PresentThread(token); }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
PresentManager::~PresentManager() = default; |
||||
|
|
||||
|
Frame* PresentManager::GetRenderFrame() { |
||||
|
MICROPROFILE_SCOPE(Vulkan_WaitPresent); |
||||
|
|
||||
|
// Wait for free presentation frames
|
||||
|
std::unique_lock lock{free_mutex}; |
||||
|
free_cv.wait(lock, [this] { return !free_queue.empty(); }); |
||||
|
|
||||
|
// Take the frame from the queue
|
||||
|
Frame* frame = free_queue.front(); |
||||
|
free_queue.pop(); |
||||
|
|
||||
|
// Wait for the presentation to be finished so all frame resources are free
|
||||
|
frame->present_done.Wait(); |
||||
|
frame->present_done.Reset(); |
||||
|
|
||||
|
return frame; |
||||
|
} |
||||
|
|
||||
|
void PresentManager::PushFrame(Frame* frame) { |
||||
|
if (!use_present_thread) { |
||||
|
CopyToSwapchain(frame); |
||||
|
free_queue.push(frame); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
std::unique_lock lock{queue_mutex}; |
||||
|
present_queue.push(frame); |
||||
|
frame_cv.notify_one(); |
||||
|
} |
||||
|
|
||||
|
void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, bool is_srgb, |
||||
|
VkFormat image_view_format, VkRenderPass rd) { |
||||
|
auto& dld = device.GetLogical(); |
||||
|
|
||||
|
frame->width = width; |
||||
|
frame->height = height; |
||||
|
frame->is_srgb = is_srgb; |
||||
|
|
||||
|
frame->image = dld.CreateImage({ |
||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, |
||||
|
.pNext = nullptr, |
||||
|
.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, |
||||
|
.imageType = VK_IMAGE_TYPE_2D, |
||||
|
.format = swapchain.GetImageFormat(), |
||||
|
.extent = |
||||
|
{ |
||||
|
.width = width, |
||||
|
.height = height, |
||||
|
.depth = 1, |
||||
|
}, |
||||
|
.mipLevels = 1, |
||||
|
.arrayLayers = 1, |
||||
|
.samples = VK_SAMPLE_COUNT_1_BIT, |
||||
|
.tiling = VK_IMAGE_TILING_OPTIMAL, |
||||
|
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, |
||||
|
.sharingMode = VK_SHARING_MODE_EXCLUSIVE, |
||||
|
.queueFamilyIndexCount = 0, |
||||
|
.pQueueFamilyIndices = nullptr, |
||||
|
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, |
||||
|
}); |
||||
|
|
||||
|
frame->image_commit = memory_allocator.Commit(frame->image, MemoryUsage::DeviceLocal); |
||||
|
|
||||
|
frame->image_view = dld.CreateImageView({ |
||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, |
||||
|
.pNext = nullptr, |
||||
|
.flags = 0, |
||||
|
.image = *frame->image, |
||||
|
.viewType = VK_IMAGE_VIEW_TYPE_2D, |
||||
|
.format = image_view_format, |
||||
|
.components = |
||||
|
{ |
||||
|
.r = VK_COMPONENT_SWIZZLE_IDENTITY, |
||||
|
.g = VK_COMPONENT_SWIZZLE_IDENTITY, |
||||
|
.b = VK_COMPONENT_SWIZZLE_IDENTITY, |
||||
|
.a = VK_COMPONENT_SWIZZLE_IDENTITY, |
||||
|
}, |
||||
|
.subresourceRange = |
||||
|
{ |
||||
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, |
||||
|
.baseMipLevel = 0, |
||||
|
.levelCount = 1, |
||||
|
.baseArrayLayer = 0, |
||||
|
.layerCount = 1, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const VkImageView image_view{*frame->image_view}; |
||||
|
frame->framebuffer = dld.CreateFramebuffer({ |
||||
|
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, |
||||
|
.pNext = nullptr, |
||||
|
.flags = 0, |
||||
|
.renderPass = rd, |
||||
|
.attachmentCount = 1, |
||||
|
.pAttachments = &image_view, |
||||
|
.width = width, |
||||
|
.height = height, |
||||
|
.layers = 1, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
void PresentManager::WaitPresent() { |
||||
|
if (!use_present_thread) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Wait for the present queue to be empty
|
||||
|
{ |
||||
|
std::unique_lock queue_lock{queue_mutex}; |
||||
|
frame_cv.wait(queue_lock, [this] { return present_queue.empty(); }); |
||||
|
} |
||||
|
|
||||
|
// The above condition will be satisfied when the last frame is taken from the queue.
|
||||
|
// To ensure that frame has been presented as well take hold of the swapchain
|
||||
|
// mutex.
|
||||
|
std::scoped_lock swapchain_lock{swapchain_mutex}; |
||||
|
} |
||||
|
|
||||
|
void PresentManager::PresentThread(std::stop_token token) { |
||||
|
Common::SetCurrentThreadName("VulkanPresent"); |
||||
|
while (!token.stop_requested()) { |
||||
|
std::unique_lock lock{queue_mutex}; |
||||
|
|
||||
|
// Wait for presentation frames
|
||||
|
Common::CondvarWait(frame_cv, lock, token, [this] { return !present_queue.empty(); }); |
||||
|
if (token.stop_requested()) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Take the frame and notify anyone waiting
|
||||
|
Frame* frame = present_queue.front(); |
||||
|
present_queue.pop(); |
||||
|
frame_cv.notify_one(); |
||||
|
|
||||
|
// By exchanging the lock ownership we take the swapchain lock
|
||||
|
// before the queue lock goes out of scope. This way the swapchain
|
||||
|
// lock in WaitPresent is guaranteed to occur after here.
|
||||
|
std::exchange(lock, std::unique_lock{swapchain_mutex}); |
||||
|
|
||||
|
CopyToSwapchain(frame); |
||||
|
|
||||
|
// Free the frame for reuse
|
||||
|
std::scoped_lock fl{free_mutex}; |
||||
|
free_queue.push(frame); |
||||
|
free_cv.notify_one(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void PresentManager::CopyToSwapchain(Frame* frame) { |
||||
|
MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain); |
||||
|
|
||||
|
const auto recreate_swapchain = [&] { |
||||
|
swapchain.Create(frame->width, frame->height, frame->is_srgb); |
||||
|
image_count = swapchain.GetImageCount(); |
||||
|
}; |
||||
|
|
||||
|
// If the size or colorspace of the incoming frames has changed, recreate the swapchain
|
||||
|
// to account for that.
|
||||
|
const bool srgb_changed = swapchain.NeedsRecreation(frame->is_srgb); |
||||
|
const bool size_changed = |
||||
|
swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height; |
||||
|
if (srgb_changed || size_changed) { |
||||
|
recreate_swapchain(); |
||||
|
} |
||||
|
|
||||
|
while (swapchain.AcquireNextImage()) { |
||||
|
recreate_swapchain(); |
||||
|
} |
||||
|
|
||||
|
const vk::CommandBuffer cmdbuf{frame->cmdbuf}; |
||||
|
cmdbuf.Begin({ |
||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, |
||||
|
.pNext = nullptr, |
||||
|
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, |
||||
|
.pInheritanceInfo = nullptr, |
||||
|
}); |
||||
|
|
||||
|
const VkImage image{swapchain.CurrentImage()}; |
||||
|
const VkExtent2D extent = swapchain.GetExtent(); |
||||
|
const std::array pre_barriers{ |
||||
|
VkImageMemoryBarrier{ |
||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, |
||||
|
.pNext = nullptr, |
||||
|
.srcAccessMask = 0, |
||||
|
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, |
||||
|
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, |
||||
|
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, |
||||
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
||||
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
||||
|
.image = image, |
||||
|
.subresourceRange{ |
||||
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, |
||||
|
.baseMipLevel = 0, |
||||
|
.levelCount = 1, |
||||
|
.baseArrayLayer = 0, |
||||
|
.layerCount = VK_REMAINING_ARRAY_LAYERS, |
||||
|
}, |
||||
|
}, |
||||
|
VkImageMemoryBarrier{ |
||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, |
||||
|
.pNext = nullptr, |
||||
|
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, |
||||
|
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, |
||||
|
.oldLayout = VK_IMAGE_LAYOUT_GENERAL, |
||||
|
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, |
||||
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
||||
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
||||
|
.image = *frame->image, |
||||
|
.subresourceRange{ |
||||
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, |
||||
|
.baseMipLevel = 0, |
||||
|
.levelCount = 1, |
||||
|
.baseArrayLayer = 0, |
||||
|
.layerCount = VK_REMAINING_ARRAY_LAYERS, |
||||
|
}, |
||||
|
}, |
||||
|
}; |
||||
|
const std::array post_barriers{ |
||||
|
VkImageMemoryBarrier{ |
||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, |
||||
|
.pNext = nullptr, |
||||
|
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, |
||||
|
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, |
||||
|
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, |
||||
|
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, |
||||
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
||||
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
||||
|
.image = image, |
||||
|
.subresourceRange{ |
||||
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, |
||||
|
.baseMipLevel = 0, |
||||
|
.levelCount = 1, |
||||
|
.baseArrayLayer = 0, |
||||
|
.layerCount = VK_REMAINING_ARRAY_LAYERS, |
||||
|
}, |
||||
|
}, |
||||
|
VkImageMemoryBarrier{ |
||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, |
||||
|
.pNext = nullptr, |
||||
|
.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, |
||||
|
.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, |
||||
|
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, |
||||
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL, |
||||
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
||||
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
||||
|
.image = *frame->image, |
||||
|
.subresourceRange{ |
||||
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, |
||||
|
.baseMipLevel = 0, |
||||
|
.levelCount = 1, |
||||
|
.baseArrayLayer = 0, |
||||
|
.layerCount = VK_REMAINING_ARRAY_LAYERS, |
||||
|
}, |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, {}, |
||||
|
{}, {}, pre_barriers); |
||||
|
|
||||
|
if (blit_supported) { |
||||
|
cmdbuf.BlitImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, |
||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, |
||||
|
MakeImageBlit(frame->width, frame->height, extent.width, extent.height), |
||||
|
VK_FILTER_LINEAR); |
||||
|
} else { |
||||
|
cmdbuf.CopyImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, |
||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, |
||||
|
MakeImageCopy(frame->width, frame->height, extent.width, extent.height)); |
||||
|
} |
||||
|
|
||||
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, {}, |
||||
|
{}, {}, post_barriers); |
||||
|
|
||||
|
cmdbuf.End(); |
||||
|
|
||||
|
const VkSemaphore present_semaphore = swapchain.CurrentPresentSemaphore(); |
||||
|
const VkSemaphore render_semaphore = swapchain.CurrentRenderSemaphore(); |
||||
|
const std::array wait_semaphores = {present_semaphore, *frame->render_ready}; |
||||
|
|
||||
|
static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{ |
||||
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, |
||||
|
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, |
||||
|
}; |
||||
|
|
||||
|
const VkSubmitInfo submit_info{ |
||||
|
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, |
||||
|
.pNext = nullptr, |
||||
|
.waitSemaphoreCount = 2U, |
||||
|
.pWaitSemaphores = wait_semaphores.data(), |
||||
|
.pWaitDstStageMask = wait_stage_masks.data(), |
||||
|
.commandBufferCount = 1, |
||||
|
.pCommandBuffers = cmdbuf.address(), |
||||
|
.signalSemaphoreCount = 1U, |
||||
|
.pSignalSemaphores = &render_semaphore, |
||||
|
}; |
||||
|
|
||||
|
// Submit the image copy/blit to the swapchain
|
||||
|
{ |
||||
|
std::scoped_lock lock{scheduler.submit_mutex}; |
||||
|
switch (const VkResult result = |
||||
|
device.GetGraphicsQueue().Submit(submit_info, *frame->present_done)) { |
||||
|
case VK_SUCCESS: |
||||
|
break; |
||||
|
case VK_ERROR_DEVICE_LOST: |
||||
|
device.ReportLoss(); |
||||
|
[[fallthrough]]; |
||||
|
default: |
||||
|
vk::Check(result); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Present
|
||||
|
swapchain.Present(render_semaphore); |
||||
|
} |
||||
|
|
||||
|
} // namespace Vulkan
|
||||
@ -0,0 +1,83 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <condition_variable> |
||||
|
#include <mutex> |
||||
|
#include <queue> |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
#include "common/polyfill_thread.h" |
||||
|
#include "video_core/vulkan_common/vulkan_memory_allocator.h" |
||||
|
#include "video_core/vulkan_common/vulkan_wrapper.h" |
||||
|
|
||||
|
namespace Core::Frontend { |
||||
|
class EmuWindow; |
||||
|
} // namespace Core::Frontend |
||||
|
|
||||
|
namespace Vulkan { |
||||
|
|
||||
|
class Device; |
||||
|
class Scheduler; |
||||
|
class Swapchain; |
||||
|
|
||||
|
struct Frame { |
||||
|
u32 width; |
||||
|
u32 height; |
||||
|
bool is_srgb; |
||||
|
vk::Image image; |
||||
|
vk::ImageView image_view; |
||||
|
vk::Framebuffer framebuffer; |
||||
|
MemoryCommit image_commit; |
||||
|
vk::CommandBuffer cmdbuf; |
||||
|
vk::Semaphore render_ready; |
||||
|
vk::Fence present_done; |
||||
|
}; |
||||
|
|
||||
|
class PresentManager { |
||||
|
public: |
||||
|
PresentManager(Core::Frontend::EmuWindow& render_window, const Device& device, |
||||
|
MemoryAllocator& memory_allocator, Scheduler& scheduler, Swapchain& swapchain); |
||||
|
~PresentManager(); |
||||
|
|
||||
|
/// Returns the last used presentation frame |
||||
|
Frame* GetRenderFrame(); |
||||
|
|
||||
|
/// Pushes a frame for presentation |
||||
|
void PushFrame(Frame* frame); |
||||
|
|
||||
|
/// Recreates the present frame to match the provided parameters |
||||
|
void RecreateFrame(Frame* frame, u32 width, u32 height, bool is_srgb, |
||||
|
VkFormat image_view_format, VkRenderPass rd); |
||||
|
|
||||
|
/// Waits for the present thread to finish presenting all queued frames. |
||||
|
void WaitPresent(); |
||||
|
|
||||
|
private: |
||||
|
void PresentThread(std::stop_token token); |
||||
|
|
||||
|
void CopyToSwapchain(Frame* frame); |
||||
|
|
||||
|
private: |
||||
|
Core::Frontend::EmuWindow& render_window; |
||||
|
const Device& device; |
||||
|
MemoryAllocator& memory_allocator; |
||||
|
Scheduler& scheduler; |
||||
|
Swapchain& swapchain; |
||||
|
vk::CommandPool cmdpool; |
||||
|
std::vector<Frame> frames; |
||||
|
std::queue<Frame*> present_queue; |
||||
|
std::queue<Frame*> free_queue; |
||||
|
std::condition_variable_any frame_cv; |
||||
|
std::condition_variable free_cv; |
||||
|
std::mutex swapchain_mutex; |
||||
|
std::mutex queue_mutex; |
||||
|
std::mutex free_mutex; |
||||
|
std::jthread present_thread; |
||||
|
bool blit_supported; |
||||
|
bool use_present_thread; |
||||
|
std::size_t image_count; |
||||
|
}; |
||||
|
|
||||
|
} // namespace Vulkan |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue