Flutter Impeller
khr_swapchain_impl_vk.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #include "fml/synchronization/semaphore.h"
18 
19 namespace impeller {
20 
21 static constexpr size_t kMaxFramesInFlight = 2u;
22 
24  vk::UniqueFence acquire;
25  vk::UniqueSemaphore render_ready;
26  std::shared_ptr<CommandBuffer> final_cmd_buffer;
27  bool is_valid = false;
28  // Whether the renderer attached an onscreen command buffer to render to.
29  bool has_onscreen = false;
30 
31  explicit KHRFrameSynchronizerVK(const vk::Device& device) {
32  auto acquire_res = device.createFenceUnique(
33  vk::FenceCreateInfo{vk::FenceCreateFlagBits::eSignaled});
34  auto render_res = device.createSemaphoreUnique({});
35  if (acquire_res.result != vk::Result::eSuccess ||
36  render_res.result != vk::Result::eSuccess) {
37  VALIDATION_LOG << "Could not create synchronizer.";
38  return;
39  }
40  acquire = std::move(acquire_res.value);
41  render_ready = std::move(render_res.value);
42  is_valid = true;
43  }
44 
46 
47  bool WaitForFence(const vk::Device& device) {
48  if (auto result = device.waitForFences(
49  *acquire, // fence
50  true, // wait all
51  std::numeric_limits<uint64_t>::max() // timeout (ns)
52  );
53  result != vk::Result::eSuccess) {
54  VALIDATION_LOG << "Fence wait failed: " << vk::to_string(result);
55  return false;
56  }
57  if (auto result = device.resetFences(*acquire);
58  result != vk::Result::eSuccess) {
59  VALIDATION_LOG << "Could not reset fence: " << vk::to_string(result);
60  return false;
61  }
62  return true;
63  }
64 };
65 
66 static bool ContainsFormat(const std::vector<vk::SurfaceFormatKHR>& formats,
67  vk::SurfaceFormatKHR format) {
68  return std::find(formats.begin(), formats.end(), format) != formats.end();
69 }
70 
71 static std::optional<vk::SurfaceFormatKHR> ChooseSurfaceFormat(
72  const std::vector<vk::SurfaceFormatKHR>& formats,
73  PixelFormat preference) {
74  const auto colorspace = vk::ColorSpaceKHR::eSrgbNonlinear;
75  const auto vk_preference =
76  vk::SurfaceFormatKHR{ToVKImageFormat(preference), colorspace};
77  if (ContainsFormat(formats, vk_preference)) {
78  return vk_preference;
79  }
80 
81  std::vector<vk::SurfaceFormatKHR> options = {
82  {vk::Format::eB8G8R8A8Unorm, colorspace},
83  {vk::Format::eR8G8B8A8Unorm, colorspace}};
84  for (const auto& format : options) {
85  if (ContainsFormat(formats, format)) {
86  return format;
87  }
88  }
89 
90  return std::nullopt;
91 }
92 
93 static std::optional<vk::CompositeAlphaFlagBitsKHR> ChooseAlphaCompositionMode(
94  vk::CompositeAlphaFlagsKHR flags) {
95  if (flags & vk::CompositeAlphaFlagBitsKHR::eInherit) {
96  return vk::CompositeAlphaFlagBitsKHR::eInherit;
97  }
98  if (flags & vk::CompositeAlphaFlagBitsKHR::ePreMultiplied) {
99  return vk::CompositeAlphaFlagBitsKHR::ePreMultiplied;
100  }
101  if (flags & vk::CompositeAlphaFlagBitsKHR::ePostMultiplied) {
102  return vk::CompositeAlphaFlagBitsKHR::ePostMultiplied;
103  }
104  if (flags & vk::CompositeAlphaFlagBitsKHR::eOpaque) {
105  return vk::CompositeAlphaFlagBitsKHR::eOpaque;
106  }
107 
108  return std::nullopt;
109 }
110 
111 std::shared_ptr<KHRSwapchainImplVK> KHRSwapchainImplVK::Create(
112  const std::shared_ptr<Context>& context,
113  vk::UniqueSurfaceKHR surface,
114  const ISize& size,
115  bool enable_msaa,
116  vk::SwapchainKHR old_swapchain) {
117  return std::shared_ptr<KHRSwapchainImplVK>(new KHRSwapchainImplVK(
118  context, std::move(surface), size, enable_msaa, old_swapchain));
119 }
120 
121 KHRSwapchainImplVK::KHRSwapchainImplVK(const std::shared_ptr<Context>& context,
122  vk::UniqueSurfaceKHR surface,
123  const ISize& size,
124  bool enable_msaa,
125  vk::SwapchainKHR old_swapchain) {
126  if (!context) {
127  VALIDATION_LOG << "Cannot create a swapchain without a context.";
128  return;
129  }
130 
131  auto& vk_context = ContextVK::Cast(*context);
132 
133  const auto [caps_result, surface_caps] =
134  vk_context.GetPhysicalDevice().getSurfaceCapabilitiesKHR(*surface);
135  if (caps_result != vk::Result::eSuccess) {
136  VALIDATION_LOG << "Could not get surface capabilities: "
137  << vk::to_string(caps_result);
138  return;
139  }
140 
141  auto [formats_result, formats] =
142  vk_context.GetPhysicalDevice().getSurfaceFormatsKHR(*surface);
143  if (formats_result != vk::Result::eSuccess) {
144  VALIDATION_LOG << "Could not get surface formats: "
145  << vk::to_string(formats_result);
146  return;
147  }
148 
149  const auto format = ChooseSurfaceFormat(
150  formats, vk_context.GetCapabilities()->GetDefaultColorFormat());
151  if (!format.has_value()) {
152  VALIDATION_LOG << "Swapchain has no supported formats.";
153  return;
154  }
155  vk_context.SetOffscreenFormat(ToPixelFormat(format.value().format));
156 
157  const auto composite =
158  ChooseAlphaCompositionMode(surface_caps.supportedCompositeAlpha);
159  if (!composite.has_value()) {
160  VALIDATION_LOG << "No composition mode supported.";
161  return;
162  }
163 
164  vk::SwapchainCreateInfoKHR swapchain_info;
165  swapchain_info.surface = *surface;
166  swapchain_info.imageFormat = format.value().format;
167  swapchain_info.imageColorSpace = format.value().colorSpace;
168  swapchain_info.presentMode = vk::PresentModeKHR::eFifo;
169  swapchain_info.imageExtent = vk::Extent2D{
170  std::clamp(static_cast<uint32_t>(size.width),
171  surface_caps.minImageExtent.width,
172  surface_caps.maxImageExtent.width),
173  std::clamp(static_cast<uint32_t>(size.height),
174  surface_caps.minImageExtent.height,
175  surface_caps.maxImageExtent.height),
176  };
177  swapchain_info.minImageCount =
178  std::clamp(surface_caps.minImageCount + 1u, // preferred image count
179  surface_caps.minImageCount, // min count cannot be zero
180  surface_caps.maxImageCount == 0u
181  ? surface_caps.minImageCount + 1u
182  : surface_caps.maxImageCount // max zero means no limit
183  );
184  swapchain_info.imageArrayLayers = 1u;
185  // Swapchain images are primarily used as color attachments (via resolve) or
186  // input attachments.
187  swapchain_info.imageUsage = vk::ImageUsageFlagBits::eColorAttachment |
188  vk::ImageUsageFlagBits::eInputAttachment;
189  swapchain_info.preTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity;
190  swapchain_info.compositeAlpha = composite.value();
191  // If we set the clipped value to true, Vulkan expects we will never read back
192  // from the buffer. This is analogous to [CAMetalLayer framebufferOnly] in
193  // Metal.
194  swapchain_info.clipped = true;
195  // Setting queue family indices is irrelevant since the present mode is
196  // exclusive.
197  swapchain_info.imageSharingMode = vk::SharingMode::eExclusive;
198  swapchain_info.oldSwapchain = old_swapchain;
199 
200  auto [swapchain_result, swapchain] =
201  vk_context.GetDevice().createSwapchainKHRUnique(swapchain_info);
202  if (swapchain_result != vk::Result::eSuccess) {
203  VALIDATION_LOG << "Could not create swapchain: "
204  << vk::to_string(swapchain_result);
205  return;
206  }
207 
208  auto [images_result, images] =
209  vk_context.GetDevice().getSwapchainImagesKHR(*swapchain);
210  if (images_result != vk::Result::eSuccess) {
211  VALIDATION_LOG << "Could not get swapchain images.";
212  return;
213  }
214 
215  TextureDescriptor texture_desc;
216  texture_desc.usage = TextureUsage::kRenderTarget;
217  texture_desc.storage_mode = StorageMode::kDevicePrivate;
218  texture_desc.format = ToPixelFormat(swapchain_info.imageFormat);
219  texture_desc.size = ISize::MakeWH(swapchain_info.imageExtent.width,
220  swapchain_info.imageExtent.height);
221 
222  std::vector<std::shared_ptr<KHRSwapchainImageVK>> swapchain_images;
223  std::vector<vk::UniqueSemaphore> present_semaphores;
224  for (const auto& image : images) {
225  auto swapchain_image = std::make_shared<KHRSwapchainImageVK>(
226  texture_desc, // texture descriptor
227  vk_context.GetDevice(), // device
228  image // image
229  );
230  if (!swapchain_image->IsValid()) {
231  VALIDATION_LOG << "Could not create swapchain image.";
232  return;
233  }
235  vk_context.GetDevice(), swapchain_image->GetImage(),
236  "SwapchainImage" + std::to_string(swapchain_images.size()));
238  vk_context.GetDevice(), swapchain_image->GetImageView(),
239  "SwapchainImageView" + std::to_string(swapchain_images.size()));
240 
241  swapchain_images.emplace_back(swapchain_image);
242 
243  auto present_res = vk_context.GetDevice().createSemaphoreUnique({});
244  if (present_res.result != vk::Result::eSuccess) {
245  VALIDATION_LOG << "Could not create presentation semaphore.";
246  return;
247  }
248  present_semaphores.push_back(std::move(present_res.value));
249  }
250 
251  std::vector<std::unique_ptr<KHRFrameSynchronizerVK>> synchronizers;
252  for (size_t i = 0u; i < kMaxFramesInFlight; i++) {
253  auto sync =
254  std::make_unique<KHRFrameSynchronizerVK>(vk_context.GetDevice());
255  if (!sync->is_valid) {
256  VALIDATION_LOG << "Could not create frame synchronizers.";
257  return;
258  }
259  synchronizers.emplace_back(std::move(sync));
260  }
261  FML_DCHECK(!synchronizers.empty());
262 
263  context_ = context;
264  surface_ = std::move(surface);
265  surface_format_ = swapchain_info.imageFormat;
266  swapchain_ = std::move(swapchain);
267  transients_ = std::make_shared<SwapchainTransientsVK>(context, texture_desc,
268  enable_msaa);
269  images_ = std::move(swapchain_images);
270  synchronizers_ = std::move(synchronizers);
271  present_semaphores_ = std::move(present_semaphores);
272  current_frame_ = synchronizers_.size() - 1u;
273  size_ = size;
274  enable_msaa_ = enable_msaa;
275  is_valid_ = true;
276 }
277 
280 }
281 
283  return size_;
284 }
285 
287  const {
288  if (!IsValid()) {
289  return std::nullopt;
290  }
291 
292  auto context = context_.lock();
293  if (!context) {
294  return std::nullopt;
295  }
296 
297  auto& vk_context = ContextVK::Cast(*context);
298  const auto [result, surface_caps] =
299  vk_context.GetPhysicalDevice().getSurfaceCapabilitiesKHR(surface_.get());
300  if (result != vk::Result::eSuccess) {
301  return std::nullopt;
302  }
303 
304  // From the spec: `currentExtent` is the current width and height of the
305  // surface, or the special value (0xFFFFFFFF, 0xFFFFFFFF) indicating that the
306  // surface size will be determined by the extent of a swapchain targeting the
307  // surface.
308  constexpr uint32_t kCurrentExtentsPlaceholder = 0xFFFFFFFF;
309  if (surface_caps.currentExtent.width == kCurrentExtentsPlaceholder ||
310  surface_caps.currentExtent.height == kCurrentExtentsPlaceholder) {
311  return std::nullopt;
312  }
313 
314  return ISize::MakeWH(surface_caps.currentExtent.width,
315  surface_caps.currentExtent.height);
316 }
317 
319  return is_valid_;
320 }
321 
322 void KHRSwapchainImplVK::WaitIdle() const {
323  if (auto context = context_.lock()) {
324  [[maybe_unused]] auto result =
325  ContextVK::Cast(*context).GetDevice().waitIdle();
326  }
327 }
328 
329 std::pair<vk::UniqueSurfaceKHR, vk::UniqueSwapchainKHR>
331  WaitIdle();
332  is_valid_ = false;
333  synchronizers_.clear();
334  images_.clear();
335  context_.reset();
336  return {std::move(surface_), std::move(swapchain_)};
337 }
338 
340  return surface_format_;
341 }
342 
343 std::shared_ptr<Context> KHRSwapchainImplVK::GetContext() const {
344  return context_.lock();
345 }
346 
348  auto context_strong = context_.lock();
349  if (!context_strong) {
351  }
352 
353  const auto& context = ContextVK::Cast(*context_strong);
354 
355  current_frame_ = (current_frame_ + 1u) % synchronizers_.size();
356 
357  const auto& sync = synchronizers_[current_frame_];
358 
359  //----------------------------------------------------------------------------
360  /// Wait on the host for the synchronizer fence.
361  ///
362  if (!sync->WaitForFence(context.GetDevice())) {
363  VALIDATION_LOG << "Could not wait for fence.";
365  }
366 
367  //----------------------------------------------------------------------------
368  /// Get the next image index.
369  ///
370  /// @bug Non-infinite timeouts are not supported on some older Android
371  /// devices and the only indication we get is log spam which serves to
372  /// add confusion. Just use an infinite timeout instead of being
373  /// defensive.
374  auto [acq_result, index] = context.GetDevice().acquireNextImageKHR(
375  *swapchain_, // swapchain
376  std::numeric_limits<uint64_t>::max(), // timeout (ns)
377  *sync->render_ready, // signal semaphore
378  nullptr // fence
379  );
380 
381  switch (acq_result) {
382  case vk::Result::eSuccess:
383  // Keep going.
384  break;
385  case vk::Result::eSuboptimalKHR:
386  case vk::Result::eErrorOutOfDateKHR:
387  // A recoverable error. Just say we are out of date.
388  return AcquireResult{true /* out of date */};
389  break;
390  default:
391  // An unrecoverable error.
392  VALIDATION_LOG << "Could not acquire next swapchain image: "
393  << vk::to_string(acq_result);
394  return AcquireResult{false /* out of date */};
395  }
396 
397  if (index >= images_.size()) {
398  VALIDATION_LOG << "Swapchain returned an invalid image index.";
400  }
401 
402  /// Record all subsequent cmd buffers as part of the current frame.
403  context.GetGPUTracer()->MarkFrameStart();
404 
405  auto image = images_[index % images_.size()];
406  uint32_t image_index = index;
408  transients_, // transients
409  image, // swapchain image
410  [weak_swapchain = weak_from_this(), image, image_index]() -> bool {
411  auto swapchain = weak_swapchain.lock();
412  if (!swapchain) {
413  return false;
414  }
415  return swapchain->Present(image, image_index);
416  } // swap callback
417  )};
418 }
419 
421  std::shared_ptr<CommandBuffer> cmd_buffer) {
422  const auto& sync = synchronizers_[current_frame_];
423  sync->final_cmd_buffer = std::move(cmd_buffer);
424  sync->has_onscreen = true;
425 }
426 
427 bool KHRSwapchainImplVK::Present(
428  const std::shared_ptr<KHRSwapchainImageVK>& image,
429  uint32_t index) {
430  auto context_strong = context_.lock();
431  if (!context_strong) {
432  return false;
433  }
434 
435  const auto& context = ContextVK::Cast(*context_strong);
436  const auto& sync = synchronizers_[current_frame_];
437  context.GetGPUTracer()->MarkFrameEnd();
438 
439  //----------------------------------------------------------------------------
440  /// Transition the image to color-attachment-optimal.
441  ///
442  if (!sync->has_onscreen) {
443  sync->final_cmd_buffer = context.CreateCommandBuffer();
444  }
445  sync->has_onscreen = false;
446  if (!sync->final_cmd_buffer) {
447  return false;
448  }
449 
450  auto vk_final_cmd_buffer =
451  CommandBufferVK::Cast(*sync->final_cmd_buffer).GetCommandBuffer();
452  {
453  BarrierVK barrier;
454  barrier.new_layout = vk::ImageLayout::ePresentSrcKHR;
455  barrier.cmd_buffer = vk_final_cmd_buffer;
456  barrier.src_access = vk::AccessFlagBits::eColorAttachmentWrite;
457  barrier.src_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
458  barrier.dst_access = {};
459  barrier.dst_stage = vk::PipelineStageFlagBits::eBottomOfPipe;
460 
461  if (!image->SetLayout(barrier).ok()) {
462  return false;
463  }
464 
465  if (vk_final_cmd_buffer.end() != vk::Result::eSuccess) {
466  return false;
467  }
468  }
469 
470  //----------------------------------------------------------------------------
471  /// Signal that the presentation semaphore is ready.
472  ///
473  {
474  vk::SubmitInfo submit_info;
475  vk::PipelineStageFlags wait_stage =
476  vk::PipelineStageFlagBits::eColorAttachmentOutput;
477  submit_info.setWaitDstStageMask(wait_stage);
478  submit_info.setWaitSemaphores(*sync->render_ready);
479  submit_info.setSignalSemaphores(*present_semaphores_[index]);
480  submit_info.setCommandBuffers(vk_final_cmd_buffer);
481  auto result =
482  context.GetGraphicsQueue()->Submit(submit_info, *sync->acquire);
483  if (result != vk::Result::eSuccess) {
484  VALIDATION_LOG << "Could not wait on render semaphore: "
485  << vk::to_string(result);
486  return false;
487  }
488  }
489 
490  //----------------------------------------------------------------------------
491  /// Present the image.
492  ///
493  uint32_t indices[] = {static_cast<uint32_t>(index)};
494 
495  vk::PresentInfoKHR present_info;
496  present_info.setSwapchains(*swapchain_);
497  present_info.setImageIndices(indices);
498  present_info.setWaitSemaphores(*present_semaphores_[index]);
499 
500  auto result = context.GetGraphicsQueue()->Present(present_info);
501 
502  switch (result) {
503  case vk::Result::eErrorOutOfDateKHR:
504  // Caller will recreate the impl on acquisition, not submission.
505  [[fallthrough]];
506  case vk::Result::eErrorSurfaceLostKHR:
507  // Vulkan guarantees that the set of queue operations will still
508  // complete successfully.
509  [[fallthrough]];
510  case vk::Result::eSuboptimalKHR:
511  // Even though we're handling rotation changes via polling, we
512  // still need to handle the case where the swapchain signals that
513  // it's suboptimal (i.e. every frame when we are rotated given we
514  // aren't doing Vulkan pre-rotation).
515  [[fallthrough]];
516  case vk::Result::eSuccess:
517  break;
518  default:
519  VALIDATION_LOG << "Could not present queue: " << vk::to_string(result);
520  break;
521  }
522 
523  return true;
524 }
525 
526 } // namespace impeller
static ContextVK & Cast(Context &base)
Definition: backend_cast.h:13
vk::CommandBuffer GetCommandBuffer() const
Retrieve the native command buffer from this object.
bool SetDebugName(T handle, std::string_view label) const
Definition: context_vk.h:151
const vk::Device & GetDevice() const
Definition: context_vk.cc:589
An instance of a swapchain that does NOT adapt to going out of date with the underlying surface....
std::shared_ptr< Context > GetContext() const
void AddFinalCommandBuffer(std::shared_ptr< CommandBuffer > cmd_buffer)
static std::shared_ptr< KHRSwapchainImplVK > Create(const std::shared_ptr< Context > &context, vk::UniqueSurfaceKHR surface, const ISize &size, bool enable_msaa=true, vk::SwapchainKHR old_swapchain=VK_NULL_HANDLE)
std::optional< ISize > GetCurrentUnderlyingSurfaceSize() const
std::pair< vk::UniqueSurfaceKHR, vk::UniqueSwapchainKHR > DestroySwapchain()
static std::unique_ptr< SurfaceVK > WrapSwapchainImage(const std::shared_ptr< SwapchainTransientsVK > &transients, const std::shared_ptr< TextureSourceVK > &swapchain_image, SwapCallback swap_callback)
Wrap the swapchain image in a Surface, which provides the additional configuration required for usage...
Definition: surface_vk.cc:13
constexpr PixelFormat ToPixelFormat(vk::Format format)
Definition: formats_vk.h:183
static std::optional< vk::SurfaceFormatKHR > ChooseSurfaceFormat(const std::vector< vk::SurfaceFormatKHR > &formats, PixelFormat preference)
static constexpr size_t kMaxFramesInFlight
static bool ContainsFormat(const std::vector< vk::SurfaceFormatKHR > &formats, vk::SurfaceFormatKHR format)
PixelFormat
The Pixel formats supported by Impeller. The naming convention denotes the usage of the component,...
Definition: formats.h:99
constexpr vk::Format ToVKImageFormat(PixelFormat format)
Definition: formats_vk.h:146
static std::optional< vk::CompositeAlphaFlagBitsKHR > ChooseAlphaCompositionMode(vk::CompositeAlphaFlagsKHR flags)
bool WaitForFence(const vk::Device &device)
KHRFrameSynchronizerVK(const vk::Device &device)
std::shared_ptr< CommandBuffer > final_cmd_buffer
Type height
Definition: size.h:29
Type width
Definition: size.h:28
static constexpr TSize MakeWH(Type width, Type height)
Definition: size.h:43
#define VALIDATION_LOG
Definition: validation.h:91