| Flutter Impeller |

context_mtl.mm
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/metal/context_mtl.h"
6 #include <Metal/Metal.h>
7
8 #include <memory>
9
10 #include "flutter/fml/concurrent_message_loop.h"
11 #include "flutter/fml/file.h"
12 #include "flutter/fml/logging.h"
13 #include "flutter/fml/paths.h"
14 #include "flutter/fml/synchronization/sync_switch.h"
15 #include "impeller/core/formats.h"
16 #include "impeller/core/runtime_types.h"
17 #include "impeller/core/sampler_descriptor.h"
18 #include "impeller/renderer/backend/metal/gpu_tracer_mtl.h"
19 #include "impeller/renderer/backend/metal/sampler_library_mtl.h"
20 #include "impeller/renderer/capabilities.h"
21
22 namespace impeller {
23
24 static bool DeviceSupportsFramebufferFetch(id<MTLDevice> device) {
25 // The iOS simulator lies about supporting framebuffer fetch.
26 #if FML_OS_IOS_SIMULATOR
27 return false;
28 #else // FML_OS_IOS_SIMULATOR
29
30 if (@available(macOS 10.15, iOS 13, tvOS 13, *)) {
31 return [device supportsFamily:MTLGPUFamilyApple2];
32 }
33 // According to
34 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf , Apple2
35 // corresponds to iOS GPU family 2, which supports A8 devices.
36 #if FML_OS_IOS
37 return [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v1];
38 #else
39 return false;
40 #endif // FML_OS_IOS
41 #endif // FML_OS_IOS_SIMULATOR
42 }
43
44 static bool DeviceSupportsComputeSubgroups(id<MTLDevice> device) {
45 bool supports_subgroups = false;
46 // Refer to the "SIMD-scoped reduction operations" feature in the table
47 // below: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
48 if (@available(ios 13.0, tvos 13.0, macos 10.15, *)) {
49 supports_subgroups = [device supportsFamily:MTLGPUFamilyApple7] ||
50 [device supportsFamily:MTLGPUFamilyMac2];
51 }
52 return supports_subgroups;
53 }
54
55 static std::unique_ptr<Capabilities> InferMetalCapabilities(
56 id<MTLDevice> device,
57 PixelFormat color_format) {
58 return CapabilitiesBuilder()
59 .SetSupportsOffscreenMSAA(true)
60 .SetSupportsSSBO(true)
61 .SetSupportsTextureToTextureBlits(true)
62 .SetSupportsDecalSamplerAddressMode(true)
63 .SetSupportsFramebufferFetch(DeviceSupportsFramebufferFetch(device))
64 .SetDefaultColorFormat(color_format)
65 .SetDefaultStencilFormat(PixelFormat::kS8UInt)
66 .SetDefaultDepthStencilFormat(PixelFormat::kD32FloatS8UInt)
67 .SetSupportsCompute(true)
68 .SetSupportsComputeSubgroups(DeviceSupportsComputeSubgroups(device))
69 .SetSupportsReadFromResolve(true)
70 .SetSupportsDeviceTransientTextures(true)
71 .SetDefaultGlyphAtlasFormat(PixelFormat::kA8UNormInt)
72 .SetSupportsTriangleFan(false)
73 .SetMaximumRenderPassAttachmentSize(DeviceMaxTextureSizeSupported(device))
74 .Build();
75 }
76
77 ContextMTL::ContextMTL(
78 id<MTLDevice> device,
79 id<MTLCommandQueue> command_queue,
80 NSArray<id<MTLLibrary>>* shader_libraries,
81 std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
82 std::optional<PixelFormat> pixel_format_override)
83 : device_(device),
84 command_queue_(command_queue),
85 is_gpu_disabled_sync_switch_(std::move(is_gpu_disabled_sync_switch)) {
86 // Validate device.
87 if (!device_) {
88 VALIDATION_LOG << "Could not set up valid Metal device.";
89 return;
90 }
91
92 sync_switch_observer_.reset(new SyncSwitchObserver(*this));
93 is_gpu_disabled_sync_switch_->AddObserver(sync_switch_observer_.get());
94
95 // Setup the shader library.
96 {
97 if (shader_libraries == nil) {
98 VALIDATION_LOG << "Shader libraries were null.";
99 return;
100 }
101
102 // std::make_shared disallowed because of private friend ctor.
103 auto library = std::shared_ptr<ShaderLibraryMTL>(
104 new ShaderLibraryMTL(shader_libraries));
105 if (!library->IsValid()) {
106 VALIDATION_LOG << "Could not create valid Metal shader library.";
107 return;
108 }
109 shader_library_ = std::move(library);
110 }
111
112 // Setup the pipeline library.
113 {
114 pipeline_library_ =
115 std::shared_ptr<PipelineLibraryMTL>(new PipelineLibraryMTL(device_));
116 }
117
118 // Setup the sampler library.
119 {
120 sampler_library_ =
121 std::shared_ptr<SamplerLibraryMTL>(new SamplerLibraryMTL(device_));
122 }
123
124 // Setup the resource allocator.
125 {
126 resource_allocator_ = std::shared_ptr<AllocatorMTL>(
127 new AllocatorMTL(device_, "Impeller Permanents Allocator"));
128 if (!resource_allocator_) {
129 VALIDATION_LOG << "Could not set up the resource allocator.";
130 return;
131 }
132 }
133
134 device_capabilities_ =
135 InferMetalCapabilities(device_, pixel_format_override.has_value()
136 ? pixel_format_override.value()
137 : PixelFormat::kB8G8R8A8UNormInt);
138 command_queue_ip_ = std::make_shared<CommandQueue>();
139 #ifdef IMPELLER_DEBUG
140 gpu_tracer_ = std::make_shared<GPUTracerMTL>();
141 capture_manager_ = std::make_shared<ImpellerMetalCaptureManager>(device_);
142 #endif // IMPELLER_DEBUG
143 is_valid_ = true;
144 }
145
146 static NSArray<id<MTLLibrary>>* MTLShaderLibraryFromFilePaths(
147 id<MTLDevice> device,
148 const std::vector<std::string>& libraries_paths) {
149 NSMutableArray<id<MTLLibrary>>* found_libraries = [NSMutableArray array];
150 for (const auto& library_path : libraries_paths) {
151 if (!fml::IsFile(library_path)) {
152 VALIDATION_LOG << "Shader library does not exist at path '"
153 << library_path << "'";
154 return nil;
155 }
156 NSError* shader_library_error = nil;
157 auto library = [device newLibraryWithFile:@(library_path.c_str())
158 error:&shader_library_error];
159 if (!library) {
160 FML_LOG(ERROR) << "Could not create shader library: "
161 << shader_library_error.localizedDescription.UTF8String;
162 return nil;
163 }
164 [found_libraries addObject:library];
165 }
166 return found_libraries;
167 }
168
169 static NSArray<id<MTLLibrary>>* MTLShaderLibraryFromFileData(
170 id<MTLDevice> device,
171 const std::vector<std::shared_ptr<fml::Mapping>>& libraries_data,
172 const std::string& label) {
173 NSMutableArray<id<MTLLibrary>>* found_libraries = [NSMutableArray array];
174 for (const auto& library_data : libraries_data) {
175 if (library_data == nullptr) {
176 FML_LOG(ERROR) << "Shader library data was null.";
177 return nil;
178 }
179
180 __block auto data = library_data;
181
182 auto dispatch_data =
183 ::dispatch_data_create(library_data->GetMapping(), // buffer
184 library_data->GetSize(), // size
185 dispatch_get_main_queue(), // queue
186 ^() {
187 // We just need a reference.
188 data.reset();
189 } // destructor
190 );
191 if (!dispatch_data) {
192 FML_LOG(ERROR) << "Could not wrap shader data in dispatch data.";
193 return nil;
194 }
195
196 NSError* shader_library_error = nil;
197 auto library = [device newLibraryWithData:dispatch_data
198 error:&shader_library_error];
199 if (!library) {
200 FML_LOG(ERROR) << "Could not create shader library: "
201 << shader_library_error.localizedDescription.UTF8String;
202 return nil;
203 }
204 if (!label.empty()) {
205 library.label = @(label.c_str());
206 }
207 [found_libraries addObject:library];
208 }
209 return found_libraries;
210 }
211
212 static id<MTLDevice> CreateMetalDevice() {
213 return ::MTLCreateSystemDefaultDevice();
214 }
215
216 static id<MTLCommandQueue> CreateMetalCommandQueue(id<MTLDevice> device) {
217 auto command_queue = device.newCommandQueue;
218 if (!command_queue) {
219 VALIDATION_LOG << "Could not set up the command queue.";
220 return nullptr;
221 }
222 command_queue.label = @"Impeller Command Queue";
223 return command_queue;
224 }
225
226 std::shared_ptr<ContextMTL> ContextMTL::Create(
227 const std::vector<std::string>& shader_library_paths,
228 std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch) {
229 auto device = CreateMetalDevice();
230 auto command_queue = CreateMetalCommandQueue(device);
231 if (!command_queue) {
232 return nullptr;
233 }
234 auto context = std::shared_ptr<ContextMTL>(new ContextMTL(
235 device, command_queue,
236 MTLShaderLibraryFromFilePaths(device, shader_library_paths),
237 std::move(is_gpu_disabled_sync_switch)));
238 if (!context->IsValid()) {
239 FML_LOG(ERROR) << "Could not create Metal context.";
240 return nullptr;
241 }
242 return context;
243 }
244
245 std::shared_ptr<ContextMTL> ContextMTL::Create(
246 const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
247 std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
248 const std::string& library_label,
249 std::optional<PixelFormat> pixel_format_override) {
250 auto device = CreateMetalDevice();
251 auto command_queue = CreateMetalCommandQueue(device);
252 if (!command_queue) {
253 return nullptr;
254 }
255 auto context = std::shared_ptr<ContextMTL>(new ContextMTL(
256 device, command_queue,
257 MTLShaderLibraryFromFileData(device, shader_libraries_data,
258 library_label),
259 std::move(is_gpu_disabled_sync_switch), pixel_format_override));
260 if (!context->IsValid()) {
261 FML_LOG(ERROR) << "Could not create Metal context.";
262 return nullptr;
263 }
264 return context;
265 }
266
267 std::shared_ptr<ContextMTL> ContextMTL::Create(
268 id<MTLDevice> device,
269 id<MTLCommandQueue> command_queue,
270 const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
271 std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
272 const std::string& library_label) {
273 auto context = std::shared_ptr<ContextMTL>(
274 new ContextMTL(device, command_queue,
275 MTLShaderLibraryFromFileData(device, shader_libraries_data,
276 library_label),
277 std::move(is_gpu_disabled_sync_switch)));
278 if (!context->IsValid()) {
279 FML_LOG(ERROR) << "Could not create Metal context.";
280 return nullptr;
281 }
282 return context;
283 }
284
285 ContextMTL::~ContextMTL() {
286 is_gpu_disabled_sync_switch_->RemoveObserver(sync_switch_observer_.get());
287 }
288
289 Context::BackendType ContextMTL::GetBackendType() const {
290 return Context::BackendType::kMetal;
291 }
292
293 // |Context|
294 std::string ContextMTL::DescribeGpuModel() const {
295 return std::string([[device_ name] UTF8String]);
296 }
297
298 // |Context|
299 bool ContextMTL::IsValid() const {
300 return is_valid_;
301 }
302
303 // |Context|
304 std::shared_ptr<ShaderLibrary> ContextMTL::GetShaderLibrary() const {
305 return shader_library_;
306 }
307
308 // |Context|
309 std::shared_ptr<PipelineLibrary> ContextMTL::GetPipelineLibrary() const {
310 return pipeline_library_;
311 }
312
313 // |Context|
314 std::shared_ptr<SamplerLibrary> ContextMTL::GetSamplerLibrary() const {
315 return sampler_library_;
316 }
317
318 // |Context|
319 std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBuffer() const {
320 return CreateCommandBufferInQueue(command_queue_);
321 }
322
323 // |Context|
324 void ContextMTL::Shutdown() {}
325
326 #ifdef IMPELLER_DEBUG
327 std::shared_ptr<GPUTracerMTL> ContextMTL::GetGPUTracer() const {
328 return gpu_tracer_;
329 }
330 #endif // IMPELLER_DEBUG
331
332 std::shared_ptr<const fml::SyncSwitch> ContextMTL::GetIsGpuDisabledSyncSwitch()
333 const {
334 return is_gpu_disabled_sync_switch_;
335 }
336
337 std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBufferInQueue(
338 id<MTLCommandQueue> queue) const {
339 if (!IsValid()) {
340 return nullptr;
341 }
342
343 auto buffer = std::shared_ptr<CommandBufferMTL>(
344 new CommandBufferMTL(weak_from_this(), device_, queue));
345 if (!buffer->IsValid()) {
346 return nullptr;
347 }
348 return buffer;
349 }
350
351 std::shared_ptr<Allocator> ContextMTL::GetResourceAllocator() const {
352 return resource_allocator_;
353 }
354
355 id<MTLDevice> ContextMTL::GetMTLDevice() const {
356 return device_;
357 }
358
359 const std::shared_ptr<const Capabilities>& ContextMTL::GetCapabilities() const {
360 return device_capabilities_;
361 }
362
363 void ContextMTL::SetCapabilities(
364 const std::shared_ptr<const Capabilities>& capabilities) {
365 device_capabilities_ = capabilities;
366 }
367
368 // |Context|
369 bool ContextMTL::UpdateOffscreenLayerPixelFormat(PixelFormat format) {
370 device_capabilities_ = InferMetalCapabilities(device_, format);
371 return true;
372 }
373
374 id<MTLCommandBuffer> ContextMTL::CreateMTLCommandBuffer(
375 const std::string& label) const {
376 auto buffer = [command_queue_ commandBuffer];
377 if (!label.empty()) {
378 [buffer setLabel:@(label.data())];
379 }
380 return buffer;
381 }
382
383 void ContextMTL::StoreTaskForGPU(const fml::closure& task,
384 const fml::closure& failure) {
385 std::vector<PendingTasks> failed_tasks;
386 {
387 Lock lock(tasks_awaiting_gpu_mutex_);
388 tasks_awaiting_gpu_.push_back(PendingTasks{task, failure});
389 int32_t failed_task_count =
390 tasks_awaiting_gpu_.size() - kMaxTasksAwaitingGPU;
391 if (failed_task_count > 0) {
392 failed_tasks.reserve(failed_task_count);
393 failed_tasks.insert(failed_tasks.end(),
394 std::make_move_iterator(tasks_awaiting_gpu_.begin()),
395 std::make_move_iterator(tasks_awaiting_gpu_.begin() +
396 failed_task_count));
397 tasks_awaiting_gpu_.erase(
398 tasks_awaiting_gpu_.begin(),
399 tasks_awaiting_gpu_.begin() + failed_task_count);
400 }
401 }
402 for (const PendingTasks& task : failed_tasks) {
403 if (task.failure) {
404 task.failure();
405 }
406 }
407 }
408
409 void ContextMTL::FlushTasksAwaitingGPU() {
410 std::deque<PendingTasks> tasks_awaiting_gpu;
411 {
412 Lock lock(tasks_awaiting_gpu_mutex_);
413 std::swap(tasks_awaiting_gpu, tasks_awaiting_gpu_);
414 }
415 for (const auto& task : tasks_awaiting_gpu) {
416 task.task();
417 }
418 }
419
420 ContextMTL::SyncSwitchObserver::SyncSwitchObserver(ContextMTL& parent)
421 : parent_(parent) {}
422
423 void ContextMTL::SyncSwitchObserver::OnSyncSwitchUpdate(bool new_is_disabled) {
424 if (!new_is_disabled) {
425 parent_.FlushTasksAwaitingGPU();
426 }
427 }
428
429 // |Context|
430 std::shared_ptr<CommandQueue> ContextMTL::GetCommandQueue() const {
431 return command_queue_ip_;
432 }
433
434 // |Context|
435 RuntimeStageBackend ContextMTL::GetRuntimeStageBackend() const {
436 return RuntimeStageBackend::kMetal;
437 }
438
439 #ifdef IMPELLER_DEBUG
440 const std::shared_ptr<ImpellerMetalCaptureManager>
441 ContextMTL::GetCaptureManager() const {
442 return capture_manager_;
443 }
444 #endif // IMPELLER_DEBUG
445
446 ImpellerMetalCaptureManager::ImpellerMetalCaptureManager(id<MTLDevice> device) {
447 current_capture_scope_ = [[MTLCaptureManager sharedCaptureManager]
448 newCaptureScopeWithDevice:device];
449 [current_capture_scope_ setLabel:@"Impeller Frame"];
450 }
451
452 bool ImpellerMetalCaptureManager::CaptureScopeActive() const {
453 return scope_active_;
454 }
455
456 void ImpellerMetalCaptureManager::StartCapture() {
457 if (scope_active_) {
458 return;
459 }
460 scope_active_ = true;
461 [current_capture_scope_ beginScope];
462 }
463
464 void ImpellerMetalCaptureManager::FinishCapture() {
465 FML_DCHECK(scope_active_);
466 [current_capture_scope_ endScope];
467 scope_active_ = false;
468 }
469
470 } // namespace impeller
impeller::PixelFormat::kS8UInt
@ kS8UInt
impeller::ContextMTL::GetRuntimeStageBackend
RuntimeStageBackend GetRuntimeStageBackend() const override
Retrieve the runtime stage for this context type.
Definition: context_mtl.mm:435
impeller::CapabilitiesBuilder::Build
std::unique_ptr< Capabilities > Build()
Definition: capabilities.cc:241
impeller::Context::BackendType
BackendType
Definition: context.h:48
impeller::DeviceSupportsComputeSubgroups
static bool DeviceSupportsComputeSubgroups(id< MTLDevice > device)
Definition: context_mtl.mm:44
impeller::PixelFormat::kA8UNormInt
@ kA8UNormInt
context_mtl.h
impeller::CreateCommandBuffer
static id< MTLCommandBuffer > CreateCommandBuffer(id< MTLCommandQueue > queue)
Definition: command_buffer_mtl.mm:117
data
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:64
impeller::Lock
Definition: thread.h:72
impeller::CapabilitiesBuilder
Definition: capabilities.h:127
formats.h
impeller::ImpellerMetalCaptureManager::CaptureScopeActive
bool CaptureScopeActive() const
Definition: context_mtl.mm:452
impeller::CapabilitiesBuilder::SetSupportsTriangleFan
CapabilitiesBuilder & SetSupportsTriangleFan(bool value)
Definition: capabilities.cc:230
impeller::MTLShaderLibraryFromFileData
static NSArray< id< MTLLibrary > > * MTLShaderLibraryFromFileData(id< MTLDevice > device, const std::vector< std::shared_ptr< fml::Mapping >> &libraries_data, const std::string &label)
Definition: context_mtl.mm:169
runtime_types.h
impeller::ImpellerMetalCaptureManager::StartCapture
void StartCapture()
Begin a new capture scope, no-op if the scope has already started.
Definition: context_mtl.mm:456
sampler_library_mtl.h
impeller::PixelFormat::kD32FloatS8UInt
@ kD32FloatS8UInt
impeller::PixelFormat
PixelFormat
The Pixel formats supported by Impeller. The naming convention denotes the usage of the component,...
Definition: formats.h:99
impeller::CreateMetalCommandQueue
static id< MTLCommandQueue > CreateMetalCommandQueue(id< MTLDevice > device)
Definition: context_mtl.mm:216
impeller::ImpellerMetalCaptureManager::FinishCapture
void FinishCapture()
End the current capture scope.
Definition: context_mtl.mm:464
impeller::CapabilitiesBuilder::SetSupportsSSBO
CapabilitiesBuilder & SetSupportsSSBO(bool value)
Definition: capabilities.cc:160
impeller::CapabilitiesBuilder::SetSupportsCompute
CapabilitiesBuilder & SetSupportsCompute(bool value)
Definition: capabilities.cc:177
impeller::CapabilitiesBuilder::SetDefaultDepthStencilFormat
CapabilitiesBuilder & SetDefaultDepthStencilFormat(PixelFormat value)
Definition: capabilities.cc:200
impeller::CapabilitiesBuilder::SetMaximumRenderPassAttachmentSize
CapabilitiesBuilder & SetMaximumRenderPassAttachmentSize(ISize size)
Definition: capabilities.cc:235
impeller::CapabilitiesBuilder::SetDefaultGlyphAtlasFormat
CapabilitiesBuilder & SetDefaultGlyphAtlasFormat(PixelFormat value)
Definition: capabilities.cc:224
impeller::RuntimeStageBackend
RuntimeStageBackend
Definition: runtime_types.h:16
impeller::DeviceSupportsFramebufferFetch
static bool DeviceSupportsFramebufferFetch(id< MTLDevice > device)
Definition: context_mtl.mm:24
impeller::CapabilitiesBuilder::SetSupportsFramebufferFetch
CapabilitiesBuilder & SetSupportsFramebufferFetch(bool value)
Definition: capabilities.cc:171
impeller::CapabilitiesBuilder::SetSupportsComputeSubgroups
CapabilitiesBuilder & SetSupportsComputeSubgroups(bool value)
Definition: capabilities.cc:182
capabilities.h
impeller::CapabilitiesBuilder::SetSupportsTextureToTextureBlits
CapabilitiesBuilder & SetSupportsTextureToTextureBlits(bool value)
Definition: capabilities.cc:165
impeller::CreateMetalDevice
static id< MTLDevice > CreateMetalDevice()
Definition: context_mtl.mm:212
impeller::InferMetalCapabilities
static std::unique_ptr< Capabilities > InferMetalCapabilities(id< MTLDevice > device, PixelFormat color_format)
Definition: context_mtl.mm:55
impeller::ImpellerMetalCaptureManager::ImpellerMetalCaptureManager
ImpellerMetalCaptureManager(id< MTLDevice > device)
Construct a new capture manager from the provided Metal device.
Definition: context_mtl.mm:446
impeller::CapabilitiesBuilder::SetDefaultColorFormat
CapabilitiesBuilder & SetDefaultColorFormat(PixelFormat value)
Definition: capabilities.cc:188
sampler_descriptor.h
impeller::CapabilitiesBuilder::SetDefaultStencilFormat
CapabilitiesBuilder & SetDefaultStencilFormat(PixelFormat value)
Definition: capabilities.cc:194
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:91
impeller::DeviceMaxTextureSizeSupported
ISize DeviceMaxTextureSizeSupported(id< MTLDevice > device)
Definition: allocator_mtl.mm:55
impeller::CapabilitiesBuilder::SetSupportsDecalSamplerAddressMode
CapabilitiesBuilder & SetSupportsDecalSamplerAddressMode(bool value)
Definition: capabilities.cc:212
std
Definition: comparable.h:95
impeller::MTLShaderLibraryFromFilePaths
static NSArray< id< MTLLibrary > > * MTLShaderLibraryFromFilePaths(id< MTLDevice > device, const std::vector< std::string > &libraries_paths)
Definition: context_mtl.mm:146
impeller::ContextMTL::GetCommandQueue
std::shared_ptr< CommandQueue > GetCommandQueue() const override
Return the graphics queue for submitting command buffers.
Definition: context_mtl.mm:430
gpu_tracer_mtl.h
impeller::CapabilitiesBuilder::SetSupportsDeviceTransientTextures
CapabilitiesBuilder & SetSupportsDeviceTransientTextures(bool value)
Definition: capabilities.cc:218
impeller::ContextMTL
Definition: context_mtl.h:65
impeller::PixelFormat::kB8G8R8A8UNormInt
@ kB8G8R8A8UNormInt
impeller::interop::Create
ScopedObject< Object > Create(CtorArgs &&... args)
Definition: object.h:160
impeller::RuntimeStageBackend::kMetal
@ kMetal
impeller
Definition: allocation.cc:12
impeller::CapabilitiesBuilder::SetSupportsReadFromResolve
CapabilitiesBuilder & SetSupportsReadFromResolve(bool value)
Definition: capabilities.cc:206
impeller::CapabilitiesBuilder::SetSupportsOffscreenMSAA
CapabilitiesBuilder & SetSupportsOffscreenMSAA(bool value)
Definition: capabilities.cc:155