Browse Source
Merge pull request #2302 from ReinUsesLisp/vk-swapchain
Merge pull request #2302 from ReinUsesLisp/vk-swapchain
vk_swapchain: Implement a swapchain managernce_cpp
committed by
GitHub
3 changed files with 305 additions and 1 deletions
-
4src/video_core/CMakeLists.txt
-
210src/video_core/renderer_vulkan/vk_swapchain.cpp
-
92src/video_core/renderer_vulkan/vk_swapchain.h
@ -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 |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue