vk_swapchain.cpp
1 // Copyright 2023 Citra Emulator Project 2 // Licensed under GPLv2 or any later version 3 // Refer to the license.txt file included. 4 5 #include <algorithm> 6 #include <limits> 7 #include "common/logging/log.h" 8 #include "common/microprofile.h" 9 #include "common/settings.h" 10 #include "video_core/renderer_vulkan/vk_instance.h" 11 #include "video_core/renderer_vulkan/vk_swapchain.h" 12 13 MICROPROFILE_DEFINE(Vulkan_Acquire, "Vulkan", "Swapchain Acquire", MP_RGB(185, 66, 245)); 14 MICROPROFILE_DEFINE(Vulkan_Present, "Vulkan", "Swapchain Present", MP_RGB(66, 185, 245)); 15 16 namespace Vulkan { 17 18 Swapchain::Swapchain(const Instance& instance_, u32 width, u32 height, vk::SurfaceKHR surface_) 19 : instance{instance_}, surface{surface_} { 20 FindPresentFormat(); 21 SetPresentMode(); 22 Create(width, height, surface); 23 } 24 25 Swapchain::~Swapchain() { 26 Destroy(); 27 instance.GetInstance().destroySurfaceKHR(surface); 28 } 29 30 void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) { 31 width = width_; 32 height = height_; 33 surface = surface_; 34 needs_recreation = false; 35 36 Destroy(); 37 38 SetPresentMode(); 39 SetSurfaceProperties(); 40 41 const std::array queue_family_indices = { 42 instance.GetGraphicsQueueFamilyIndex(), 43 instance.GetPresentQueueFamilyIndex(), 44 }; 45 46 const bool exclusive = queue_family_indices[0] == queue_family_indices[1]; 47 const u32 queue_family_indices_count = exclusive ? 1u : 2u; 48 const vk::SharingMode sharing_mode = 49 exclusive ? vk::SharingMode::eExclusive : vk::SharingMode::eConcurrent; 50 const vk::SwapchainCreateInfoKHR swapchain_info = { 51 .surface = surface, 52 .minImageCount = image_count, 53 .imageFormat = surface_format.format, 54 .imageColorSpace = surface_format.colorSpace, 55 .imageExtent = extent, 56 .imageArrayLayers = 1, 57 .imageUsage = vk::ImageUsageFlagBits::eColorAttachment | 58 vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst, 59 .imageSharingMode = sharing_mode, 60 .queueFamilyIndexCount = queue_family_indices_count, 61 .pQueueFamilyIndices = queue_family_indices.data(), 62 .preTransform = transform, 63 .compositeAlpha = composite_alpha, 64 .presentMode = present_mode, 65 .clipped = true, 66 .oldSwapchain = nullptr, 67 }; 68 69 try { 70 swapchain = instance.GetDevice().createSwapchainKHR(swapchain_info); 71 } catch (vk::SystemError& err) { 72 LOG_CRITICAL(Render_Vulkan, "{}", err.what()); 73 UNREACHABLE(); 74 } 75 76 SetupImages(); 77 RefreshSemaphores(); 78 } 79 80 bool Swapchain::AcquireNextImage() { 81 MICROPROFILE_SCOPE(Vulkan_Acquire); 82 vk::Device device = instance.GetDevice(); 83 vk::Result result = 84 device.acquireNextImageKHR(swapchain, std::numeric_limits<u64>::max(), 85 image_acquired[frame_index], VK_NULL_HANDLE, &image_index); 86 87 switch (result) { 88 case vk::Result::eSuccess: 89 break; 90 case vk::Result::eSuboptimalKHR: 91 case vk::Result::eErrorSurfaceLostKHR: 92 case vk::Result::eErrorOutOfDateKHR: 93 needs_recreation = true; 94 break; 95 default: 96 LOG_CRITICAL(Render_Vulkan, "Swapchain acquire returned unknown result {}", result); 97 UNREACHABLE(); 98 break; 99 } 100 101 return !needs_recreation; 102 } 103 104 void Swapchain::Present() { 105 if (needs_recreation) { 106 return; 107 } 108 109 const vk::PresentInfoKHR present_info = { 110 .waitSemaphoreCount = 1, 111 .pWaitSemaphores = &present_ready[image_index], 112 .swapchainCount = 1, 113 .pSwapchains = &swapchain, 114 .pImageIndices = &image_index, 115 }; 116 117 MICROPROFILE_SCOPE(Vulkan_Present); 118 try { 119 [[maybe_unused]] vk::Result result = instance.GetPresentQueue().presentKHR(present_info); 120 } catch (vk::OutOfDateKHRError&) { 121 needs_recreation = true; 122 } catch (const vk::SystemError& err) { 123 LOG_CRITICAL(Render_Vulkan, "Swapchain presentation failed {}", err.what()); 124 UNREACHABLE(); 125 } 126 127 frame_index = (frame_index + 1) % image_count; 128 } 129 130 void Swapchain::FindPresentFormat() { 131 const auto formats = instance.GetPhysicalDevice().getSurfaceFormatsKHR(surface); 132 133 // If there is a single undefined surface format, the device doesn't care, so we'll just use 134 // RGBA. 135 if (formats[0].format == vk::Format::eUndefined) { 136 surface_format.format = vk::Format::eR8G8B8A8Unorm; 137 surface_format.colorSpace = vk::ColorSpaceKHR::eSrgbNonlinear; 138 return; 139 } 140 141 // Try to find a suitable format. 142 for (const vk::SurfaceFormatKHR& sformat : formats) { 143 vk::Format format = sformat.format; 144 if (format != vk::Format::eR8G8B8A8Unorm && format != vk::Format::eB8G8R8A8Unorm) { 145 continue; 146 } 147 148 surface_format.format = format; 149 surface_format.colorSpace = sformat.colorSpace; 150 return; 151 } 152 153 UNREACHABLE_MSG("Unable to find required swapchain format!"); 154 } 155 156 void Swapchain::SetPresentMode() { 157 const auto modes = instance.GetPhysicalDevice().getSurfacePresentModesKHR(surface); 158 const bool use_vsync = Settings::values.use_vsync_new.GetValue(); 159 const auto find_mode = [&modes](vk::PresentModeKHR requested) { 160 const auto it = 161 std::find_if(modes.begin(), modes.end(), 162 [&requested](vk::PresentModeKHR mode) { return mode == requested; }); 163 164 return it != modes.end(); 165 }; 166 167 present_mode = vk::PresentModeKHR::eFifo; 168 const bool has_immediate = find_mode(vk::PresentModeKHR::eImmediate); 169 const bool has_mailbox = find_mode(vk::PresentModeKHR::eMailbox); 170 if (!has_immediate && !has_mailbox) { 171 LOG_WARNING(Render_Vulkan, "Forcing Fifo present mode as no alternatives are available"); 172 return; 173 } 174 175 // If the user has disabled vsync use immediate mode for the least latency. 176 // This may have screen tearing. 177 if (!use_vsync) { 178 present_mode = 179 has_immediate ? vk::PresentModeKHR::eImmediate : vk::PresentModeKHR::eMailbox; 180 return; 181 } 182 // If vsync is enabled attempt to use mailbox mode in case the user wants to speedup/slowdown 183 // the game. If mailbox is not available use immediate and warn about it. 184 if (use_vsync && Settings::values.frame_limit.GetValue() > 100) { 185 present_mode = has_mailbox ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eImmediate; 186 if (!has_mailbox) { 187 LOG_WARNING( 188 Render_Vulkan, 189 "Vsync enabled while frame limiting and no mailbox support, expect tearing"); 190 } 191 return; 192 } 193 } 194 195 void Swapchain::SetSurfaceProperties() { 196 const vk::SurfaceCapabilitiesKHR capabilities = 197 instance.GetPhysicalDevice().getSurfaceCapabilitiesKHR(surface); 198 199 extent = capabilities.currentExtent; 200 if (capabilities.currentExtent.width == std::numeric_limits<u32>::max()) { 201 extent.width = std::max(capabilities.minImageExtent.width, 202 std::min(capabilities.maxImageExtent.width, width)); 203 extent.height = std::max(capabilities.minImageExtent.height, 204 std::min(capabilities.maxImageExtent.height, height)); 205 } 206 207 // Select number of images in swap chain, we prefer one buffer in the background to work on 208 image_count = capabilities.minImageCount + 1; 209 if (capabilities.maxImageCount > 0) { 210 image_count = std::min(image_count, capabilities.maxImageCount); 211 } 212 213 // Prefer identity transform if possible 214 transform = vk::SurfaceTransformFlagBitsKHR::eIdentity; 215 if (!(capabilities.supportedTransforms & transform)) { 216 transform = capabilities.currentTransform; 217 } 218 219 // Opaque is not supported everywhere. 220 composite_alpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; 221 if (!(capabilities.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::eOpaque)) { 222 composite_alpha = vk::CompositeAlphaFlagBitsKHR::eInherit; 223 } 224 } 225 226 void Swapchain::Destroy() { 227 vk::Device device = instance.GetDevice(); 228 if (swapchain) { 229 device.destroySwapchainKHR(swapchain); 230 } 231 for (u32 i = 0; i < image_count; i++) { 232 device.destroySemaphore(image_acquired[i]); 233 device.destroySemaphore(present_ready[i]); 234 } 235 image_acquired.clear(); 236 present_ready.clear(); 237 } 238 239 void Swapchain::RefreshSemaphores() { 240 const vk::Device device = instance.GetDevice(); 241 image_acquired.resize(image_count); 242 present_ready.resize(image_count); 243 244 for (vk::Semaphore& semaphore : image_acquired) { 245 semaphore = device.createSemaphore({}); 246 } 247 for (vk::Semaphore& semaphore : present_ready) { 248 semaphore = device.createSemaphore({}); 249 } 250 251 if (instance.HasDebuggingToolAttached()) { 252 for (u32 i = 0; i < image_count; ++i) { 253 Vulkan::SetObjectName(device, image_acquired[i], 254 "Swapchain Semaphore: image_acquired {}", i); 255 Vulkan::SetObjectName(device, present_ready[i], "Swapchain Semaphore: present_ready {}", 256 i); 257 } 258 } 259 } 260 261 void Swapchain::SetupImages() { 262 vk::Device device = instance.GetDevice(); 263 images = device.getSwapchainImagesKHR(swapchain); 264 image_count = static_cast<u32>(images.size()); 265 266 if (instance.HasDebuggingToolAttached()) { 267 for (u32 i = 0; i < image_count; ++i) { 268 Vulkan::SetObjectName(device, images[i], "Swapchain Image {}", i); 269 } 270 } 271 } 272 273 } // namespace Vulkan