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