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