renderer_vulkan: Use separate present thread

This commit is contained in:
GPUCode
2023-02-05 18:29:49 +02:00
parent fd09f1471e
commit faefc5cfe1
9 changed files with 267 additions and 285 deletions

View File

@ -313,23 +313,9 @@ private:
class VulkanRenderWidget : public RenderWidget {
public:
explicit VulkanRenderWidget(GRenderWindow* parent, bool is_secondary)
: RenderWidget(parent), is_secondary(is_secondary) {
explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
windowHandle()->setSurfaceType(QWindow::VulkanSurface);
}
void Present() override {
if (!isVisible()) {
return;
}
if (!Core::System::GetInstance().IsPoweredOn()) {
return;
}
VideoCore::g_renderer->TryPresent(100, is_secondary);
}
private:
bool is_secondary;
};
static Frontend::WindowSystemType GetWindowSystemType() {
@ -677,7 +663,7 @@ bool GRenderWindow::InitializeOpenGL() {
}
bool GRenderWindow::InitializeVulkan() {
auto child = new VulkanRenderWidget(this, is_secondary);
auto child = new VulkanRenderWidget(this);
child_widget = child;
child_widget->windowHandle()->create();
main_context = std::make_unique<DummyContext>();

View File

@ -13,8 +13,10 @@
#include <mutex>
#include <utility>
#include "common/polyfill_thread.h"
namespace Common {
template <typename T>
template <typename T, bool with_stop_token = false>
class SPSCQueue {
public:
SPSCQueue() {
@ -40,21 +42,19 @@ public:
template <typename Arg>
void Push(Arg&& t) {
// create the element, add it to the queue
write_ptr->current = std::forward<Arg>(t);
write_ptr->current = std::move(t);
// set the next pointer to a new element ptr
// then advance the write pointer
ElementPtr* new_ptr = new ElementPtr();
write_ptr->next.store(new_ptr, std::memory_order_release);
write_ptr = new_ptr;
++size;
const size_t previous_size{size++};
// Acquire the mutex and then immediately release it as a fence.
// cv_mutex must be held or else there will be a missed wakeup if the other thread is in the
// line before cv.wait
// TODO(bunnei): This can be replaced with C++20 waitable atomics when properly supported.
// See discussion on https://github.com/yuzu-emu/yuzu/pull/3173 for details.
if (previous_size == 0) {
std::lock_guard lock{cv_mutex};
}
std::scoped_lock lock{cv_mutex};
cv.notify_one();
}
@ -83,10 +83,27 @@ public:
return true;
}
T PopWait() {
void Wait() {
if (Empty()) {
std::unique_lock lock{cv_mutex};
cv.wait(lock, [this]() { return !Empty(); });
cv.wait(lock, [this] { return !Empty(); });
}
}
T PopWait() {
Wait();
T t;
Pop(t);
return t;
}
T PopWait(std::stop_token stop_token) {
if (Empty()) {
std::unique_lock lock{cv_mutex};
Common::CondvarWait(cv, lock, stop_token, [this] { return !Empty(); });
}
if (stop_token.stop_requested()) {
return T{};
}
T t;
Pop(t);
@ -105,7 +122,7 @@ private:
// and a pointer to the next ElementPtr
class ElementPtr {
public:
ElementPtr() = default;
ElementPtr() {}
~ElementPtr() {
ElementPtr* next_ptr = next.load();
@ -121,13 +138,13 @@ private:
ElementPtr* read_ptr;
std::atomic_size_t size{0};
std::mutex cv_mutex;
std::condition_variable cv;
std::conditional_t<with_stop_token, std::condition_variable_any, std::condition_variable> cv;
};
// a simple thread-safe,
// single reader, multiple writer queue
template <typename T>
template <typename T, bool with_stop_token = false>
class MPSCQueue {
public:
[[nodiscard]] std::size_t Size() const {
@ -144,7 +161,7 @@ public:
template <typename Arg>
void Push(Arg&& t) {
std::lock_guard lock{write_lock};
std::scoped_lock lock{write_lock};
spsc_queue.Push(t);
}
@ -156,17 +173,25 @@ public:
return spsc_queue.Pop(t);
}
void Wait() {
spsc_queue.Wait();
}
T PopWait() {
return spsc_queue.PopWait();
}
T PopWait(std::stop_token stop_token) {
return spsc_queue.PopWait(stop_token);
}
// not thread-safe
void Clear() {
spsc_queue.Clear();
}
private:
SPSCQueue<T> spsc_queue;
SPSCQueue<T, with_stop_token> spsc_queue;
std::mutex write_lock;
};
} // namespace Common

View File

@ -11,7 +11,7 @@ namespace OpenGL {
StreamBuffer::StreamBuffer(GLenum target, size_t size_)
: gl_target{target}, buffer_size{size_}, slot_size{buffer_size / SYNC_POINTS},
buffer_storage{bool(GLAD_GL_ARB_buffer_storage)} {
for (int i = 0; i < SYNC_POINTS; i++) {
for (u64 i = 0; i < SYNC_POINTS; i++) {
fences[i].Create();
}
@ -44,13 +44,13 @@ std::tuple<u8*, u64, bool> StreamBuffer::Map(u64 size, u64 alignment) {
}
// Insert waiting slots for used memory
for (u32 i = Slot(used_iterator); i < Slot(iterator); i++) {
for (u64 i = Slot(used_iterator); i < Slot(iterator); i++) {
fences[i].Create();
}
used_iterator = iterator;
// Wait for new slots to end of buffer
for (u32 i = Slot(free_iterator) + 1; i <= Slot(iterator + size) && i < SYNC_POINTS; i++) {
for (u64 i = Slot(free_iterator) + 1; i <= Slot(iterator + size) && i < SYNC_POINTS; i++) {
glClientWaitSync(fences[i].handle, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED);
fences[i].Release();
}
@ -69,7 +69,7 @@ std::tuple<u8*, u64, bool> StreamBuffer::Map(u64 size, u64 alignment) {
invalidate = true;
// Insert waiting slots in unused space at the end of the buffer
for (int i = Slot(used_iterator); i < SYNC_POINTS; i++) {
for (u64 i = Slot(used_iterator); i < SYNC_POINTS; i++) {
fences[i].Create();
}
@ -77,7 +77,7 @@ std::tuple<u8*, u64, bool> StreamBuffer::Map(u64 size, u64 alignment) {
used_iterator = iterator = 0; // offset 0 is always aligned
// Wait for space at the start
for (int i = 0; i <= Slot(iterator + size); i++) {
for (u64 i = 0; i <= Slot(iterator + size); i++) {
glClientWaitSync(fences[i].handle, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED);
fences[i].Release();
}

View File

@ -28,7 +28,6 @@
#include <vk_mem_alloc.h>
MICROPROFILE_DEFINE(Vulkan_RenderFrame, "Vulkan", "Render Frame", MP_RGB(128, 128, 64));
MICROPROFILE_DEFINE(Vulkan_SwapchainCopy, "Vulkan", "Swapchain Copy", MP_RGB(64, 64, 0));
namespace Vulkan {
@ -117,7 +116,7 @@ RendererVulkan::RendererVulkan(Memory::MemorySystem& memory_, Frontend::EmuWindo
CompileShaders();
BuildLayouts();
BuildPipelines();
window.mailbox = std::make_unique<TextureMailbox>(instance, swapchain, renderpass_cache);
mailbox = std::make_unique<PresentMailbox>(instance, swapchain, scheduler, renderpass_cache);
}
RendererVulkan::~RendererVulkan() {
@ -186,14 +185,13 @@ void RendererVulkan::PrepareRendertarget() {
}
void RendererVulkan::RenderToMailbox(const Layout::FramebufferLayout& layout,
std::unique_ptr<Frontend::TextureMailbox>& mailbox,
bool flipped) {
Frontend::Frame* frame = mailbox->GetRenderFrame();
std::unique_ptr<PresentMailbox>& mailbox, bool flipped) {
Frame* frame = mailbox->GetRenderFrame();
MICROPROFILE_SCOPE(Vulkan_RenderFrame);
const auto [width, height] = swapchain.GetExtent();
if (width != frame->width || height != frame->height) {
mailbox->ReloadRenderFrame(frame, width, height);
mailbox->ReloadFrame(frame, width, height);
}
scheduler.Record([layout](vk::CommandBuffer cmdbuf) {
@ -237,7 +235,7 @@ void RendererVulkan::RenderToMailbox(const Layout::FramebufferLayout& layout,
DrawScreens(layout, flipped);
scheduler.Flush(frame->render_ready);
scheduler.Record([&mailbox, frame](vk::CommandBuffer) { mailbox->ReleaseRenderFrame(frame); });
scheduler.Record([&mailbox, frame](vk::CommandBuffer) { mailbox->Present(frame); });
scheduler.DispatchWork();
}
@ -956,165 +954,12 @@ void RendererVulkan::DrawScreens(const Layout::FramebufferLayout& layout, bool f
scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.endRenderPass(); });
}
void RendererVulkan::TryPresent(int timeout_ms, bool is_secondary) {
Frontend::Frame* frame = render_window.mailbox->TryGetPresentFrame(timeout_ms);
if (!frame) {
LOG_DEBUG(Render_Vulkan, "TryGetPresentFrame returned no frame to present");
return;
}
#if ANDROID
// On Android swapchain invalidations are always due to surface changes.
// These are processed on the main thread so wait for it to recreate
// the swapchain for us.
std::unique_lock lock{swapchain_mutex};
swapchain_cv.wait(lock, [this]() { return !swapchain.NeedsRecreation(); });
#endif
while (!swapchain.AcquireNextImage()) {
#if ANDROID
swapchain_cv.wait(lock, [this]() { return !swapchain.NeedsRecreation(); });
#else
std::scoped_lock lock{scheduler.QueueMutex()};
instance.GetGraphicsQueue().waitIdle();
swapchain.Create();
#endif
}
{
MICROPROFILE_SCOPE(Vulkan_SwapchainCopy);
const vk::Image swapchain_image = swapchain.Image();
const vk::CommandBufferBeginInfo begin_info = {
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit,
};
const vk::CommandBuffer cmdbuf = frame->cmdbuf;
cmdbuf.begin(begin_info);
const auto [width, height] = swapchain.GetExtent();
const u32 copy_width = std::min(width, frame->width);
const u32 copy_height = std::min(height, frame->height);
const vk::ImageCopy image_copy = {
.srcSubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.srcOffset = {0, 0, 0},
.dstSubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.dstOffset = {0, 0, 0},
.extent = {copy_width, copy_height, 1},
};
const std::array pre_barriers{
vk::ImageMemoryBarrier{
.srcAccessMask = vk::AccessFlagBits::eNone,
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
.oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eTransferDstOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = swapchain_image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
},
vk::ImageMemoryBarrier{
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
.dstAccessMask = vk::AccessFlagBits::eTransferRead,
.oldLayout = vk::ImageLayout::eTransferSrcOptimal,
.newLayout = vk::ImageLayout::eTransferSrcOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = frame->image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
},
};
const vk::ImageMemoryBarrier post_barrier{
.srcAccessMask = vk::AccessFlagBits::eTransferWrite,
.dstAccessMask = vk::AccessFlagBits::eNone,
.oldLayout = vk::ImageLayout::eTransferDstOptimal,
.newLayout = vk::ImageLayout::ePresentSrcKHR,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = swapchain_image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eTransfer,
vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers);
cmdbuf.copyImage(frame->image, vk::ImageLayout::eTransferSrcOptimal, swapchain_image,
vk::ImageLayout::eTransferDstOptimal, image_copy);
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eBottomOfPipe,
vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier);
cmdbuf.end();
static constexpr std::array<vk::PipelineStageFlags, 2> wait_stage_masks = {
vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eAllCommands,
};
const vk::Semaphore present_ready = swapchain.GetPresentReadySemaphore();
const vk::Semaphore image_acquired = swapchain.GetImageAcquiredSemaphore();
const std::array wait_semaphores = {image_acquired, frame->render_ready};
vk::SubmitInfo submit_info = {
.waitSemaphoreCount = static_cast<u32>(wait_semaphores.size()),
.pWaitSemaphores = wait_semaphores.data(),
.pWaitDstStageMask = wait_stage_masks.data(),
.commandBufferCount = 1u,
.pCommandBuffers = &cmdbuf,
.signalSemaphoreCount = 1,
.pSignalSemaphores = &present_ready,
};
try {
std::scoped_lock lock{scheduler.QueueMutex(), frame->fence_mutex};
instance.GetGraphicsQueue().submit(submit_info, frame->present_done);
} catch (vk::DeviceLostError& err) {
LOG_CRITICAL(Render_Vulkan, "Device lost during present submit: {}", err.what());
UNREACHABLE();
}
}
swapchain.Present();
render_window.mailbox->ReleasePresentFrame(frame);
}
void RendererVulkan::SwapBuffers() {
const auto& layout = render_window.GetFramebufferLayout();
PrepareRendertarget();
RenderScreenshot();
RenderToMailbox(layout, render_window.mailbox, false);
RenderToMailbox(layout, mailbox, false);
m_current_frame++;
@ -1180,11 +1025,10 @@ void RendererVulkan::RenderScreenshot() {
}
vk::Image staging_image{unsafe_image};
Frontend::Frame frame{};
render_window.mailbox->ReloadRenderFrame(&frame, width, height);
Frame frame{};
mailbox->ReloadFrame(&frame, width, height);
renderpass_cache.ExitRenderpass();
scheduler.Record([this, framebuffer = frame.framebuffer, width = frame.width,
height = frame.height](vk::CommandBuffer cmdbuf) {
const vk::ClearValue clear{.color = clear_color};
@ -1343,12 +1187,7 @@ void RendererVulkan::RenderScreenshot() {
void RendererVulkan::NotifySurfaceChanged() {
scheduler.Finish();
vk::SurfaceKHR new_surface = CreateSurface(instance.GetInstance(), render_window);
{
std::scoped_lock lock{swapchain_mutex};
swapchain.SetNeedsRecreation(true);
swapchain.Create(new_surface);
swapchain_cv.notify_one();
}
mailbox->UpdateSurface(new_surface);
}
void RendererVulkan::Report() const {

View File

@ -54,7 +54,7 @@ struct ScreenInfo {
vk::ImageView image_view;
};
class RasterizerVulkan;
class PresentMailbox;
class RendererVulkan : public VideoCore::RendererBase {
static constexpr std::size_t PRESENT_PIPELINES = 3;
@ -70,7 +70,7 @@ public:
void SwapBuffers() override;
void NotifySurfaceChanged() override;
void TryPresent(int timeout_ms, bool is_secondary) override;
void TryPresent(int timeout_ms, bool is_secondary) override {}
void PrepareVideoDumping() override {}
void CleanupVideoDumping() override {}
void Sync() override;
@ -88,7 +88,7 @@ private:
void PrepareRendertarget();
void RenderScreenshot();
void RenderToMailbox(const Layout::FramebufferLayout& layout,
std::unique_ptr<Frontend::TextureMailbox>& mailbox, bool flipped);
std::unique_ptr<PresentMailbox>& mailbox, bool flipped);
void BeginRendering();
/**
@ -135,8 +135,7 @@ private:
Swapchain swapchain;
StreamBuffer vertex_buffer;
RasterizerVulkan rasterizer;
std::mutex swapchain_mutex;
std::condition_variable swapchain_cv;
std::unique_ptr<PresentMailbox> mailbox;
/// Present pipelines (Normal, Anaglyph, Interlaced)
vk::PipelineLayout present_pipeline_layout;

View File

@ -5,6 +5,7 @@
#include <utility>
#include "common/microprofile.h"
#include "common/settings.h"
#include "common/thread.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
@ -79,6 +80,7 @@ void Scheduler::DispatchWork() {
}
void Scheduler::WorkerThread(std::stop_token stop_token) {
Common::SetCurrentThreadName("VulkanWorker");
do {
std::unique_ptr<CommandChunk> work;
{

View File

@ -30,6 +30,7 @@ Swapchain::~Swapchain() {
}
void Swapchain::Create(vk::SurfaceKHR new_surface) {
needs_recreation = true; ///< Set this for the present thread to wait on
Destroy();
if (new_surface) {

View File

@ -3,20 +3,24 @@
// Refer to the license.txt file included.
#include "common/microprofile.h"
#include "common/thread.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
#include "video_core/renderer_vulkan/vk_texture_mailbox.h"
#include <vk_mem_alloc.h>
MICROPROFILE_DEFINE(Vulkan_WaitPresent, "Vulkan", "Wait For Present", MP_RGB(128, 128, 128));
MICROPROFILE_DEFINE(Vulkan_PresentFrame, "Vulkan", "Present Frame", MP_RGB(64, 64, 0));
namespace Vulkan {
TextureMailbox::TextureMailbox(const Instance& instance_, const Swapchain& swapchain_,
const RenderpassCache& renderpass_cache_)
: instance{instance_}, swapchain{swapchain_}, renderpass_cache{renderpass_cache_} {
PresentMailbox::PresentMailbox(const Instance& instance_, Swapchain& swapchain_,
Scheduler& scheduler_, RenderpassCache& renderpass_cache_)
: instance{instance_}, swapchain{swapchain_}, scheduler{scheduler_},
renderpass_cache{renderpass_cache_}, graphics_queue{instance.GetGraphicsQueue()} {
const vk::Device device = instance.GetDevice();
const vk::CommandPoolCreateInfo pool_info = {
@ -34,20 +38,19 @@ TextureMailbox::TextureMailbox(const Instance& instance_, const Swapchain& swapc
const std::vector command_buffers = device.allocateCommandBuffers(alloc_info);
for (u32 i = 0; i < SWAP_CHAIN_SIZE; i++) {
Frontend::Frame& frame = swap_chain[i];
Frame& frame = swap_chain[i];
frame.cmdbuf = command_buffers[i];
frame.render_ready = device.createSemaphore({});
frame.present_done = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled});
free_queue.push(&frame);
free_queue.Push(&frame);
}
present_thread = std::jthread([this](std::stop_token token) { PresentThread(token); });
}
TextureMailbox::~TextureMailbox() {
std::scoped_lock lock{present_mutex, free_mutex};
free_queue = {};
present_queue = {};
present_cv.notify_all();
free_cv.notify_all();
PresentMailbox::~PresentMailbox() {
free_queue.Clear();
present_queue.Clear();
const vk::Device device = instance.GetDevice();
device.destroyCommandPool(command_pool);
@ -60,7 +63,7 @@ TextureMailbox::~TextureMailbox() {
}
}
void TextureMailbox::ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) {
void PresentMailbox::ReloadFrame(Frame* frame, u32 width, u32 height) {
vk::Device device = instance.GetDevice();
if (frame->framebuffer) {
device.destroyFramebuffer(frame->framebuffer);
@ -131,26 +134,16 @@ void TextureMailbox::ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 he
frame->height = height;
}
Frontend::Frame* TextureMailbox::GetRenderFrame() {
Frame* PresentMailbox::GetRenderFrame() {
MICROPROFILE_SCOPE(Vulkan_WaitPresent);
Frontend::Frame* frame{};
{
std::unique_lock lock{free_mutex};
if (free_queue.empty()) {
free_cv.wait(lock, [&] { return !free_queue.empty(); });
}
frame = free_queue.front();
free_queue.pop();
}
std::scoped_lock lock{frame->fence_mutex};
Frame* frame = free_queue.PopWait();
vk::Device device = instance.GetDevice();
vk::Result result{};
const auto Wait = [&]() {
std::scoped_lock lock{frame->fence_mutex};
result = device.waitForFences(frame->present_done, false, std::numeric_limits<u64>::max());
return result;
};
@ -173,31 +166,168 @@ Frontend::Frame* TextureMailbox::GetRenderFrame() {
return frame;
}
void TextureMailbox::ReleaseRenderFrame(Frontend::Frame* frame) {
std::unique_lock lock{present_mutex};
present_queue.push(frame);
present_cv.notify_one();
void PresentMailbox::UpdateSurface(vk::SurfaceKHR surface) {
std::scoped_lock lock{swapchain_mutex};
swapchain.Create(surface);
swapchain_cv.notify_one();
}
void TextureMailbox::ReleasePresentFrame(Frontend::Frame* frame) {
std::unique_lock lock{free_mutex};
free_queue.push(frame);
free_cv.notify_one();
void PresentMailbox::Present(Frame* frame) {
present_queue.Push(frame);
}
Frontend::Frame* TextureMailbox::TryGetPresentFrame(int timeout_ms) {
std::unique_lock lock{present_mutex};
// Wait for new entries in the present_queue
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
[&] { return !present_queue.empty(); });
if (present_queue.empty()) {
LOG_DEBUG(Render_Vulkan, "Timed out waiting present frame");
return nullptr;
void PresentMailbox::PresentThread(std::stop_token token) {
Common::SetCurrentThreadName("VulkanPresent");
do {
Frame* frame = present_queue.PopWait(token);
CopyToSwapchain(frame);
free_queue.Push(frame);
} while (!token.stop_requested());
}
void PresentMailbox::CopyToSwapchain(Frame* frame) {
MICROPROFILE_SCOPE(Vulkan_PresentFrame);
#if ANDROID
// On Android swapchain invalidations are always due to surface changes.
// These are processed on the main thread so wait for it to recreate
// the swapchain for us.
std::unique_lock lock{swapchain_mutex};
swapchain_cv.wait(lock, [this]() { return !swapchain.NeedsRecreation(); });
#endif
while (!swapchain.AcquireNextImage()) {
#if ANDROID
swapchain_cv.wait(lock, [this]() { return !swapchain.NeedsRecreation(); });
#else
std::scoped_lock lock{scheduler.QueueMutex()};
graphics_queue.waitIdle();
swapchain.Create();
#endif
}
Frontend::Frame* frame = present_queue.front();
present_queue.pop();
return frame;
const vk::Image swapchain_image = swapchain.Image();
const vk::CommandBufferBeginInfo begin_info = {
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit,
};
const vk::CommandBuffer cmdbuf = frame->cmdbuf;
cmdbuf.begin(begin_info);
const auto [width, height] = swapchain.GetExtent();
const u32 copy_width = std::min(width, frame->width);
const u32 copy_height = std::min(height, frame->height);
const vk::ImageCopy image_copy = {
.srcSubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.srcOffset = {0, 0, 0},
.dstSubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.dstOffset = {0, 0, 0},
.extent = {copy_width, copy_height, 1},
};
const std::array pre_barriers{
vk::ImageMemoryBarrier{
.srcAccessMask = vk::AccessFlagBits::eNone,
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
.oldLayout = vk::ImageLayout::eUndefined,
.newLayout = vk::ImageLayout::eTransferDstOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = swapchain_image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
},
vk::ImageMemoryBarrier{
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
.dstAccessMask = vk::AccessFlagBits::eTransferRead,
.oldLayout = vk::ImageLayout::eTransferSrcOptimal,
.newLayout = vk::ImageLayout::eTransferSrcOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = frame->image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
},
};
const vk::ImageMemoryBarrier post_barrier{
.srcAccessMask = vk::AccessFlagBits::eTransferWrite,
.dstAccessMask = vk::AccessFlagBits::eNone,
.oldLayout = vk::ImageLayout::eTransferDstOptimal,
.newLayout = vk::ImageLayout::ePresentSrcKHR,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = swapchain_image,
.subresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion,
{}, {}, pre_barriers);
cmdbuf.copyImage(frame->image, vk::ImageLayout::eTransferSrcOptimal, swapchain_image,
vk::ImageLayout::eTransferDstOptimal, image_copy);
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eBottomOfPipe,
vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier);
cmdbuf.end();
static constexpr std::array<vk::PipelineStageFlags, 2> wait_stage_masks = {
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eAllGraphics,
};
const vk::Semaphore present_ready = swapchain.GetPresentReadySemaphore();
const vk::Semaphore image_acquired = swapchain.GetImageAcquiredSemaphore();
const std::array wait_semaphores = {image_acquired, frame->render_ready};
vk::SubmitInfo submit_info = {
.waitSemaphoreCount = static_cast<u32>(wait_semaphores.size()),
.pWaitSemaphores = wait_semaphores.data(),
.pWaitDstStageMask = wait_stage_masks.data(),
.commandBufferCount = 1u,
.pCommandBuffers = &cmdbuf,
.signalSemaphoreCount = 1,
.pSignalSemaphores = &present_ready,
};
try {
std::scoped_lock lock{scheduler.QueueMutex(), frame->fence_mutex};
graphics_queue.submit(submit_info, frame->present_done);
} catch (vk::DeviceLostError& err) {
LOG_CRITICAL(Render_Vulkan, "Device lost during present submit: {}", err.what());
UNREACHABLE();
}
swapchain.Present();
}
} // namespace Vulkan

View File

@ -5,12 +5,18 @@
#include <condition_variable>
#include <mutex>
#include <queue>
#include "core/frontend/emu_window.h"
#include "common/polyfill_thread.h"
#include "common/threadsafe_queue.h"
#include "video_core/renderer_vulkan/vk_common.h"
VK_DEFINE_HANDLE(VmaAllocation)
namespace Frontend {
namespace Vulkan {
class Instance;
class Swapchain;
class Scheduler;
class RenderpassCache;
struct Frame {
u32 width{};
@ -25,42 +31,36 @@ struct Frame {
vk::CommandBuffer cmdbuf{};
};
} // namespace Frontend
namespace Vulkan {
class Instance;
class Swapchain;
class RenderpassCache;
class TextureMailbox final : public Frontend::TextureMailbox {
static constexpr std::size_t SWAP_CHAIN_SIZE = 8;
class PresentMailbox final {
static constexpr std::size_t SWAP_CHAIN_SIZE = 6;
public:
TextureMailbox(const Instance& instance, const Swapchain& swapchain,
const RenderpassCache& renderpass_cache);
~TextureMailbox() override;
PresentMailbox(const Instance& instance, Swapchain& swapchain, Scheduler& scheduler,
RenderpassCache& renderpass_cache);
~PresentMailbox();
void ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) override;
Frame* GetRenderFrame();
void UpdateSurface(vk::SurfaceKHR surface);
void ReloadFrame(Frame* frame, u32 width, u32 height);
void Present(Frame* frame);
Frontend::Frame* GetRenderFrame() override;
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override;
void ReleaseRenderFrame(Frontend::Frame* frame) override;
void ReleasePresentFrame(Frontend::Frame* frame) override;
private:
void PresentThread(std::stop_token token);
void CopyToSwapchain(Frame* frame);
private:
const Instance& instance;
const Swapchain& swapchain;
const RenderpassCache& renderpass_cache;
Swapchain& swapchain;
Scheduler& scheduler;
RenderpassCache& renderpass_cache;
vk::CommandPool command_pool;
std::mutex free_mutex;
std::mutex present_mutex;
std::condition_variable free_cv;
std::condition_variable present_cv;
std::array<Frontend::Frame, SWAP_CHAIN_SIZE> swap_chain{};
std::queue<Frontend::Frame*> free_queue{};
std::queue<Frontend::Frame*> present_queue{};
vk::Queue graphics_queue;
std::jthread present_thread;
std::array<Frame, SWAP_CHAIN_SIZE> swap_chain{};
Common::SPSCQueue<Frame*> free_queue{};
Common::SPSCQueue<Frame*, true> present_queue{};
std::mutex swapchain_mutex;
std::condition_variable swapchain_cv;
};
} // namespace Vulkan