/ src / video_core / renderer_vulkan / vk_swapchain.cpp
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