mirror of https://git.suyu.dev/suyu/suyu
vk_swapchain: Implement a swapchain manager
This commit is contained in:
parent
f770c17d01
commit
746dab407e
|
@ -128,7 +128,9 @@ if (ENABLE_VULKAN)
|
||||||
renderer_vulkan/vk_scheduler.cpp
|
renderer_vulkan/vk_scheduler.cpp
|
||||||
renderer_vulkan/vk_scheduler.h
|
renderer_vulkan/vk_scheduler.h
|
||||||
renderer_vulkan/vk_stream_buffer.cpp
|
renderer_vulkan/vk_stream_buffer.cpp
|
||||||
renderer_vulkan/vk_stream_buffer.h)
|
renderer_vulkan/vk_stream_buffer.h
|
||||||
|
renderer_vulkan/vk_swapchain.cpp
|
||||||
|
renderer_vulkan/vk_swapchain.h)
|
||||||
|
|
||||||
target_include_directories(video_core PRIVATE ../../externals/Vulkan-Headers/include)
|
target_include_directories(video_core PRIVATE ../../externals/Vulkan-Headers/include)
|
||||||
target_compile_definitions(video_core PRIVATE HAS_VULKAN)
|
target_compile_definitions(video_core PRIVATE HAS_VULKAN)
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
// Copyright 2019 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <limits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/frontend/framebuffer_layout.h"
|
||||||
|
#include "video_core/renderer_vulkan/declarations.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_device.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_resource_manager.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
vk::SurfaceFormatKHR ChooseSwapSurfaceFormat(const std::vector<vk::SurfaceFormatKHR>& formats) {
|
||||||
|
if (formats.size() == 1 && formats[0].format == vk::Format::eUndefined) {
|
||||||
|
return {vk::Format::eB8G8R8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear};
|
||||||
|
}
|
||||||
|
const auto& found = std::find_if(formats.begin(), formats.end(), [](const auto& format) {
|
||||||
|
return format.format == vk::Format::eB8G8R8A8Unorm &&
|
||||||
|
format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear;
|
||||||
|
});
|
||||||
|
return found != formats.end() ? *found : formats[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::PresentModeKHR ChooseSwapPresentMode(const std::vector<vk::PresentModeKHR>& modes) {
|
||||||
|
// Mailbox doesn't lock the application like fifo (vsync), prefer it
|
||||||
|
const auto& found = std::find_if(modes.begin(), modes.end(), [](const auto& mode) {
|
||||||
|
return mode == vk::PresentModeKHR::eMailbox;
|
||||||
|
});
|
||||||
|
return found != modes.end() ? *found : vk::PresentModeKHR::eFifo;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Extent2D ChooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities, u32 width,
|
||||||
|
u32 height) {
|
||||||
|
constexpr auto undefined_size{std::numeric_limits<u32>::max()};
|
||||||
|
if (capabilities.currentExtent.width != undefined_size) {
|
||||||
|
return capabilities.currentExtent;
|
||||||
|
}
|
||||||
|
vk::Extent2D extent = {width, height};
|
||||||
|
extent.width = std::max(capabilities.minImageExtent.width,
|
||||||
|
std::min(capabilities.maxImageExtent.width, extent.width));
|
||||||
|
extent.height = std::max(capabilities.minImageExtent.height,
|
||||||
|
std::min(capabilities.maxImageExtent.height, extent.height));
|
||||||
|
return extent;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
VKSwapchain::VKSwapchain(vk::SurfaceKHR surface, const VKDevice& device)
|
||||||
|
: surface{surface}, device{device} {}
|
||||||
|
|
||||||
|
VKSwapchain::~VKSwapchain() = default;
|
||||||
|
|
||||||
|
void VKSwapchain::Create(u32 width, u32 height) {
|
||||||
|
const auto dev = device.GetLogical();
|
||||||
|
const auto& dld = device.GetDispatchLoader();
|
||||||
|
const auto physical_device = device.GetPhysical();
|
||||||
|
|
||||||
|
const vk::SurfaceCapabilitiesKHR capabilities{
|
||||||
|
physical_device.getSurfaceCapabilitiesKHR(surface, dld)};
|
||||||
|
if (capabilities.maxImageExtent.width == 0 || capabilities.maxImageExtent.height == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev.waitIdle(dld);
|
||||||
|
Destroy();
|
||||||
|
|
||||||
|
CreateSwapchain(capabilities, width, height);
|
||||||
|
CreateSemaphores();
|
||||||
|
CreateImageViews();
|
||||||
|
|
||||||
|
fences.resize(image_count, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKSwapchain::AcquireNextImage() {
|
||||||
|
const auto dev{device.GetLogical()};
|
||||||
|
const auto& dld{device.GetDispatchLoader()};
|
||||||
|
dev.acquireNextImageKHR(*swapchain, std::numeric_limits<u64>::max(),
|
||||||
|
*present_semaphores[frame_index], {}, &image_index, dld);
|
||||||
|
|
||||||
|
if (auto& fence = fences[image_index]; fence) {
|
||||||
|
fence->Wait();
|
||||||
|
fence->Release();
|
||||||
|
fence = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VKSwapchain::Present(vk::Semaphore render_semaphore, VKFence& fence) {
|
||||||
|
const vk::Semaphore present_semaphore{*present_semaphores[frame_index]};
|
||||||
|
const std::array<vk::Semaphore, 2> semaphores{present_semaphore, render_semaphore};
|
||||||
|
const u32 wait_semaphore_count{render_semaphore ? 2U : 1U};
|
||||||
|
const auto& dld{device.GetDispatchLoader()};
|
||||||
|
const auto present_queue{device.GetPresentQueue()};
|
||||||
|
bool recreated = false;
|
||||||
|
|
||||||
|
const vk::PresentInfoKHR present_info(wait_semaphore_count, semaphores.data(), 1,
|
||||||
|
&swapchain.get(), &image_index, {});
|
||||||
|
switch (const auto result = present_queue.presentKHR(&present_info, dld); result) {
|
||||||
|
case vk::Result::eSuccess:
|
||||||
|
break;
|
||||||
|
case vk::Result::eErrorOutOfDateKHR:
|
||||||
|
if (current_width > 0 && current_height > 0) {
|
||||||
|
Create(current_width, current_height);
|
||||||
|
recreated = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_CRITICAL(Render_Vulkan, "Vulkan failed to present swapchain due to {}!",
|
||||||
|
vk::to_string(result));
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(fences[image_index] == nullptr);
|
||||||
|
fences[image_index] = &fence;
|
||||||
|
frame_index = (frame_index + 1) % image_count;
|
||||||
|
return recreated;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VKSwapchain::HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const {
|
||||||
|
// TODO(Rodrigo): Handle framebuffer pixel format changes
|
||||||
|
return framebuffer.width != current_width || framebuffer.height != current_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKSwapchain::CreateSwapchain(const vk::SurfaceCapabilitiesKHR& capabilities, u32 width,
|
||||||
|
u32 height) {
|
||||||
|
const auto dev{device.GetLogical()};
|
||||||
|
const auto& dld{device.GetDispatchLoader()};
|
||||||
|
const auto physical_device{device.GetPhysical()};
|
||||||
|
|
||||||
|
const std::vector<vk::SurfaceFormatKHR> formats{
|
||||||
|
physical_device.getSurfaceFormatsKHR(surface, dld)};
|
||||||
|
|
||||||
|
const std::vector<vk::PresentModeKHR> present_modes{
|
||||||
|
physical_device.getSurfacePresentModesKHR(surface, dld)};
|
||||||
|
|
||||||
|
const vk::SurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats)};
|
||||||
|
const vk::PresentModeKHR present_mode{ChooseSwapPresentMode(present_modes)};
|
||||||
|
extent = ChooseSwapExtent(capabilities, width, height);
|
||||||
|
|
||||||
|
current_width = extent.width;
|
||||||
|
current_height = extent.height;
|
||||||
|
|
||||||
|
u32 requested_image_count{capabilities.minImageCount + 1};
|
||||||
|
if (capabilities.maxImageCount > 0 && requested_image_count > capabilities.maxImageCount) {
|
||||||
|
requested_image_count = capabilities.maxImageCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::SwapchainCreateInfoKHR swapchain_ci(
|
||||||
|
{}, surface, requested_image_count, surface_format.format, surface_format.colorSpace,
|
||||||
|
extent, 1, vk::ImageUsageFlagBits::eColorAttachment, {}, {}, {},
|
||||||
|
capabilities.currentTransform, vk::CompositeAlphaFlagBitsKHR::eOpaque, present_mode, false,
|
||||||
|
{});
|
||||||
|
|
||||||
|
const u32 graphics_family{device.GetGraphicsFamily()};
|
||||||
|
const u32 present_family{device.GetPresentFamily()};
|
||||||
|
const std::array<u32, 2> queue_indices{graphics_family, present_family};
|
||||||
|
if (graphics_family != present_family) {
|
||||||
|
swapchain_ci.imageSharingMode = vk::SharingMode::eConcurrent;
|
||||||
|
swapchain_ci.queueFamilyIndexCount = static_cast<u32>(queue_indices.size());
|
||||||
|
swapchain_ci.pQueueFamilyIndices = queue_indices.data();
|
||||||
|
} else {
|
||||||
|
swapchain_ci.imageSharingMode = vk::SharingMode::eExclusive;
|
||||||
|
}
|
||||||
|
|
||||||
|
swapchain = dev.createSwapchainKHRUnique(swapchain_ci, nullptr, dld);
|
||||||
|
|
||||||
|
images = dev.getSwapchainImagesKHR(*swapchain, dld);
|
||||||
|
image_count = static_cast<u32>(images.size());
|
||||||
|
image_format = surface_format.format;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKSwapchain::CreateSemaphores() {
|
||||||
|
const auto dev{device.GetLogical()};
|
||||||
|
const auto& dld{device.GetDispatchLoader()};
|
||||||
|
|
||||||
|
present_semaphores.resize(image_count);
|
||||||
|
for (std::size_t i = 0; i < image_count; i++) {
|
||||||
|
present_semaphores[i] = dev.createSemaphoreUnique({}, nullptr, dld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKSwapchain::CreateImageViews() {
|
||||||
|
const auto dev{device.GetLogical()};
|
||||||
|
const auto& dld{device.GetDispatchLoader()};
|
||||||
|
|
||||||
|
image_views.resize(image_count);
|
||||||
|
for (std::size_t i = 0; i < image_count; i++) {
|
||||||
|
const vk::ImageViewCreateInfo image_view_ci({}, images[i], vk::ImageViewType::e2D,
|
||||||
|
image_format, {},
|
||||||
|
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
|
||||||
|
image_views[i] = dev.createImageViewUnique(image_view_ci, nullptr, dld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKSwapchain::Destroy() {
|
||||||
|
frame_index = 0;
|
||||||
|
present_semaphores.clear();
|
||||||
|
framebuffers.clear();
|
||||||
|
image_views.clear();
|
||||||
|
swapchain.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright 2019 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "video_core/renderer_vulkan/declarations.h"
|
||||||
|
|
||||||
|
namespace Layout {
|
||||||
|
struct FramebufferLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
class VKDevice;
|
||||||
|
class VKFence;
|
||||||
|
|
||||||
|
class VKSwapchain {
|
||||||
|
public:
|
||||||
|
explicit VKSwapchain(vk::SurfaceKHR surface, const VKDevice& device);
|
||||||
|
~VKSwapchain();
|
||||||
|
|
||||||
|
/// Creates (or recreates) the swapchain with a given size.
|
||||||
|
void Create(u32 width, u32 height);
|
||||||
|
|
||||||
|
/// Acquires the next image in the swapchain, waits as needed.
|
||||||
|
void AcquireNextImage();
|
||||||
|
|
||||||
|
/// Presents the rendered image to the swapchain. Returns true when the swapchains had to be
|
||||||
|
/// recreated. Takes responsability for the ownership of fence.
|
||||||
|
bool Present(vk::Semaphore render_semaphore, VKFence& fence);
|
||||||
|
|
||||||
|
/// Returns true when the framebuffer layout has changed.
|
||||||
|
bool HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const;
|
||||||
|
|
||||||
|
const vk::Extent2D& GetSize() const {
|
||||||
|
return extent;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetImageCount() const {
|
||||||
|
return image_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetImageIndex() const {
|
||||||
|
return image_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Image GetImageIndex(u32 index) const {
|
||||||
|
return images[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::ImageView GetImageViewIndex(u32 index) const {
|
||||||
|
return *image_views[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Format GetImageFormat() const {
|
||||||
|
return image_format;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void CreateSwapchain(const vk::SurfaceCapabilitiesKHR& capabilities, u32 width, u32 height);
|
||||||
|
void CreateSemaphores();
|
||||||
|
void CreateImageViews();
|
||||||
|
|
||||||
|
void Destroy();
|
||||||
|
|
||||||
|
const vk::SurfaceKHR surface;
|
||||||
|
const VKDevice& device;
|
||||||
|
|
||||||
|
UniqueSwapchainKHR swapchain;
|
||||||
|
|
||||||
|
u32 image_count{};
|
||||||
|
std::vector<vk::Image> images;
|
||||||
|
std::vector<UniqueImageView> image_views;
|
||||||
|
std::vector<UniqueFramebuffer> framebuffers;
|
||||||
|
std::vector<VKFence*> fences;
|
||||||
|
std::vector<UniqueSemaphore> present_semaphores;
|
||||||
|
|
||||||
|
u32 image_index{};
|
||||||
|
u32 frame_index{};
|
||||||
|
|
||||||
|
vk::Format image_format{};
|
||||||
|
vk::Extent2D extent{};
|
||||||
|
|
||||||
|
u32 current_width{};
|
||||||
|
u32 current_height{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
Loading…
Reference in New Issue