mirror of https://git.suyu.dev/suyu/suyu
Merge pull request #9973 from GPUCode/async-present
Implement asynchronous presentationmerge-requests/60/head
commit
8f43b05d6b
@ -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
|
Loading…
Reference in New Issue