2023-09-13 00:28:50 +02:00
|
|
|
// Copyright 2023 Citra Emulator Project
|
|
|
|
// Licensed under GPLv2 or any later version
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <limits>
|
|
|
|
#include "common/logging/log.h"
|
|
|
|
#include "common/microprofile.h"
|
|
|
|
#include "common/settings.h"
|
|
|
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
|
|
|
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
|
|
|
|
|
|
|
MICROPROFILE_DEFINE(Vulkan_Acquire, "Vulkan", "Swapchain Acquire", MP_RGB(185, 66, 245));
|
|
|
|
MICROPROFILE_DEFINE(Vulkan_Present, "Vulkan", "Swapchain Present", MP_RGB(66, 185, 245));
|
|
|
|
|
|
|
|
namespace Vulkan {
|
|
|
|
|
|
|
|
Swapchain::Swapchain(const Instance& instance_, u32 width, u32 height, vk::SurfaceKHR surface_)
|
|
|
|
: instance{instance_}, surface{surface_} {
|
|
|
|
FindPresentFormat();
|
|
|
|
SetPresentMode();
|
|
|
|
Create(width, height, surface);
|
|
|
|
}
|
|
|
|
|
|
|
|
Swapchain::~Swapchain() {
|
|
|
|
Destroy();
|
|
|
|
instance.GetInstance().destroySurfaceKHR(surface);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) {
|
|
|
|
width = width_;
|
|
|
|
height = height_;
|
|
|
|
surface = surface_;
|
|
|
|
needs_recreation = false;
|
|
|
|
|
|
|
|
Destroy();
|
|
|
|
|
|
|
|
SetPresentMode();
|
|
|
|
SetSurfaceProperties();
|
|
|
|
|
|
|
|
const std::array queue_family_indices = {
|
|
|
|
instance.GetGraphicsQueueFamilyIndex(),
|
|
|
|
instance.GetPresentQueueFamilyIndex(),
|
|
|
|
};
|
|
|
|
|
|
|
|
const bool exclusive = queue_family_indices[0] == queue_family_indices[1];
|
|
|
|
const u32 queue_family_indices_count = exclusive ? 1u : 2u;
|
|
|
|
const vk::SharingMode sharing_mode =
|
|
|
|
exclusive ? vk::SharingMode::eExclusive : vk::SharingMode::eConcurrent;
|
|
|
|
const vk::SwapchainCreateInfoKHR swapchain_info = {
|
|
|
|
.surface = surface,
|
|
|
|
.minImageCount = image_count,
|
|
|
|
.imageFormat = surface_format.format,
|
|
|
|
.imageColorSpace = surface_format.colorSpace,
|
|
|
|
.imageExtent = extent,
|
|
|
|
.imageArrayLayers = 1,
|
|
|
|
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment |
|
|
|
|
vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst,
|
|
|
|
.imageSharingMode = sharing_mode,
|
|
|
|
.queueFamilyIndexCount = queue_family_indices_count,
|
|
|
|
.pQueueFamilyIndices = queue_family_indices.data(),
|
|
|
|
.preTransform = transform,
|
|
|
|
.compositeAlpha = composite_alpha,
|
|
|
|
.presentMode = present_mode,
|
|
|
|
.clipped = true,
|
|
|
|
.oldSwapchain = nullptr,
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
swapchain = instance.GetDevice().createSwapchainKHR(swapchain_info);
|
|
|
|
} catch (vk::SystemError& err) {
|
|
|
|
LOG_CRITICAL(Render_Vulkan, "{}", err.what());
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
|
|
|
SetupImages();
|
|
|
|
RefreshSemaphores();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Swapchain::AcquireNextImage() {
|
|
|
|
MICROPROFILE_SCOPE(Vulkan_Acquire);
|
|
|
|
vk::Device device = instance.GetDevice();
|
|
|
|
vk::Result result =
|
|
|
|
device.acquireNextImageKHR(swapchain, std::numeric_limits<u64>::max(),
|
|
|
|
image_acquired[frame_index], VK_NULL_HANDLE, &image_index);
|
|
|
|
|
|
|
|
switch (result) {
|
|
|
|
case vk::Result::eSuccess:
|
|
|
|
break;
|
|
|
|
case vk::Result::eSuboptimalKHR:
|
|
|
|
case vk::Result::eErrorSurfaceLostKHR:
|
|
|
|
case vk::Result::eErrorOutOfDateKHR:
|
|
|
|
needs_recreation = true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
LOG_CRITICAL(Render_Vulkan, "Swapchain acquire returned unknown result {}", result);
|
|
|
|
UNREACHABLE();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return !needs_recreation;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Swapchain::Present() {
|
|
|
|
if (needs_recreation) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const vk::PresentInfoKHR present_info = {
|
|
|
|
.waitSemaphoreCount = 1,
|
|
|
|
.pWaitSemaphores = &present_ready[image_index],
|
|
|
|
.swapchainCount = 1,
|
|
|
|
.pSwapchains = &swapchain,
|
|
|
|
.pImageIndices = &image_index,
|
|
|
|
};
|
|
|
|
|
|
|
|
MICROPROFILE_SCOPE(Vulkan_Present);
|
|
|
|
try {
|
|
|
|
[[maybe_unused]] vk::Result result = instance.GetPresentQueue().presentKHR(present_info);
|
|
|
|
} catch (vk::OutOfDateKHRError&) {
|
|
|
|
needs_recreation = true;
|
|
|
|
} catch (const vk::SystemError& err) {
|
|
|
|
LOG_CRITICAL(Render_Vulkan, "Swapchain presentation failed {}", err.what());
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
|
|
|
frame_index = (frame_index + 1) % image_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Swapchain::FindPresentFormat() {
|
|
|
|
const auto formats = instance.GetPhysicalDevice().getSurfaceFormatsKHR(surface);
|
|
|
|
|
|
|
|
// If there is a single undefined surface format, the device doesn't care, so we'll just use
|
|
|
|
// RGBA.
|
|
|
|
if (formats[0].format == vk::Format::eUndefined) {
|
|
|
|
surface_format.format = vk::Format::eR8G8B8A8Unorm;
|
|
|
|
surface_format.colorSpace = vk::ColorSpaceKHR::eSrgbNonlinear;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to find a suitable format.
|
|
|
|
for (const vk::SurfaceFormatKHR& sformat : formats) {
|
|
|
|
vk::Format format = sformat.format;
|
|
|
|
if (format != vk::Format::eR8G8B8A8Unorm && format != vk::Format::eB8G8R8A8Unorm) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
surface_format.format = format;
|
|
|
|
surface_format.colorSpace = sformat.colorSpace;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-10-22 21:53:14 +02:00
|
|
|
UNREACHABLE_MSG("Unable to find required swapchain format!");
|
2023-09-13 00:28:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Swapchain::SetPresentMode() {
|
|
|
|
const auto modes = instance.GetPhysicalDevice().getSurfacePresentModesKHR(surface);
|
|
|
|
const bool use_vsync = Settings::values.use_vsync_new.GetValue();
|
|
|
|
const auto find_mode = [&modes](vk::PresentModeKHR requested) {
|
|
|
|
const auto it =
|
|
|
|
std::find_if(modes.begin(), modes.end(),
|
|
|
|
[&requested](vk::PresentModeKHR mode) { return mode == requested; });
|
|
|
|
|
|
|
|
return it != modes.end();
|
|
|
|
};
|
|
|
|
|
|
|
|
present_mode = vk::PresentModeKHR::eFifo;
|
|
|
|
const bool has_immediate = find_mode(vk::PresentModeKHR::eImmediate);
|
|
|
|
const bool has_mailbox = find_mode(vk::PresentModeKHR::eMailbox);
|
|
|
|
if (!has_immediate && !has_mailbox) {
|
|
|
|
LOG_WARNING(Render_Vulkan, "Forcing Fifo present mode as no alternatives are available");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the user has disabled vsync use immediate mode for the least latency.
|
|
|
|
// This may have screen tearing.
|
|
|
|
if (!use_vsync) {
|
|
|
|
present_mode =
|
|
|
|
has_immediate ? vk::PresentModeKHR::eImmediate : vk::PresentModeKHR::eMailbox;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// If vsync is enabled attempt to use mailbox mode in case the user wants to speedup/slowdown
|
|
|
|
// the game. If mailbox is not available use immediate and warn about it.
|
|
|
|
if (use_vsync && Settings::values.frame_limit.GetValue() > 100) {
|
|
|
|
present_mode = has_mailbox ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eImmediate;
|
|
|
|
if (!has_mailbox) {
|
|
|
|
LOG_WARNING(
|
|
|
|
Render_Vulkan,
|
|
|
|
"Vsync enabled while frame limiting and no mailbox support, expect tearing");
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Swapchain::SetSurfaceProperties() {
|
|
|
|
const vk::SurfaceCapabilitiesKHR capabilities =
|
|
|
|
instance.GetPhysicalDevice().getSurfaceCapabilitiesKHR(surface);
|
|
|
|
|
|
|
|
extent = capabilities.currentExtent;
|
|
|
|
if (capabilities.currentExtent.width == std::numeric_limits<u32>::max()) {
|
|
|
|
extent.width = std::max(capabilities.minImageExtent.width,
|
|
|
|
std::min(capabilities.maxImageExtent.width, width));
|
|
|
|
extent.height = std::max(capabilities.minImageExtent.height,
|
|
|
|
std::min(capabilities.maxImageExtent.height, height));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Select number of images in swap chain, we prefer one buffer in the background to work on
|
|
|
|
image_count = capabilities.minImageCount + 1;
|
|
|
|
if (capabilities.maxImageCount > 0) {
|
|
|
|
image_count = std::min(image_count, capabilities.maxImageCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prefer identity transform if possible
|
|
|
|
transform = vk::SurfaceTransformFlagBitsKHR::eIdentity;
|
|
|
|
if (!(capabilities.supportedTransforms & transform)) {
|
|
|
|
transform = capabilities.currentTransform;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Opaque is not supported everywhere.
|
|
|
|
composite_alpha = vk::CompositeAlphaFlagBitsKHR::eOpaque;
|
|
|
|
if (!(capabilities.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::eOpaque)) {
|
|
|
|
composite_alpha = vk::CompositeAlphaFlagBitsKHR::eInherit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Swapchain::Destroy() {
|
|
|
|
vk::Device device = instance.GetDevice();
|
|
|
|
if (swapchain) {
|
|
|
|
device.destroySwapchainKHR(swapchain);
|
|
|
|
}
|
|
|
|
for (u32 i = 0; i < image_count; i++) {
|
|
|
|
device.destroySemaphore(image_acquired[i]);
|
|
|
|
device.destroySemaphore(present_ready[i]);
|
|
|
|
}
|
|
|
|
image_acquired.clear();
|
|
|
|
present_ready.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Swapchain::RefreshSemaphores() {
|
|
|
|
const vk::Device device = instance.GetDevice();
|
|
|
|
image_acquired.resize(image_count);
|
|
|
|
present_ready.resize(image_count);
|
|
|
|
|
|
|
|
for (vk::Semaphore& semaphore : image_acquired) {
|
|
|
|
semaphore = device.createSemaphore({});
|
|
|
|
}
|
|
|
|
for (vk::Semaphore& semaphore : present_ready) {
|
|
|
|
semaphore = device.createSemaphore({});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Swapchain::SetupImages() {
|
|
|
|
vk::Device device = instance.GetDevice();
|
|
|
|
images = device.getSwapchainImagesKHR(swapchain);
|
|
|
|
image_count = static_cast<u32>(images.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Vulkan
|