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