Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
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
6#include <Metal/Metal.h>
7
8#include <memory>
9
11#include "flutter/fml/file.h"
12#include "flutter/fml/logging.h"
13#include "flutter/fml/paths.h"
21
22namespace impeller {
23
24static bool DeviceSupportsFramebufferFetch(id<MTLDevice> device) {
25#if FML_OS_IOS_SIMULATOR
26 // The iOS simulator lies about supporting framebuffer fetch.
27 return false;
28#else // FML_OS_IOS_SIMULATOR
29 // According to
30 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf, Apple2
31 // corresponds to iOS GPU family 2, which supports A8 devices.
32 return [device supportsFamily:MTLGPUFamilyApple2];
33#endif // FML_OS_IOS_SIMULATOR
34}
35
36static bool DeviceSupportsComputeSubgroups(id<MTLDevice> device) {
37 // Refer to the "SIMD-scoped reduction operations" feature in the table
38 // below: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
39 return [device supportsFamily:MTLGPUFamilyApple7] ||
40 [device supportsFamily:MTLGPUFamilyMac2];
41}
42
43// See "Extended Range and wide color pixel formats" in the Metal Feature Set
44// Tables: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
45// Wide gamut requires Apple3+ GPU family (supports 10-bit and F16 formats).
46// This includes all iOS devices with A9+ chip and Apple Silicon Macs (M1+).
47// Intel Macs (Mac2 family) do not support wide gamut.
48static bool DeviceSupportsExtendedRangeFormats(id<MTLDevice> device) {
49 return [device supportsFamily:MTLGPUFamilyApple3];
50}
51
52// See "Pixel Format Capabilities" in the Metal Feature Set Tables:
53// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
54// BC formats are available on the Mac family and on Apple7+ (A14/M1 and newer).
55static bool DeviceSupportsTextureCompressionBC(id<MTLDevice> device) {
56 return [device supportsFamily:MTLGPUFamilyMac2] ||
57 [device supportsFamily:MTLGPUFamilyApple7];
58}
59
60// ETC2 and ASTC LDR are available on all Apple GPU families but not on the Mac
61// (Intel/AMD) family.
63 return [device supportsFamily:MTLGPUFamilyApple2];
64}
65
66// ASTC HDR requires Apple GPU family 6 (A13) or later.
68 return [device supportsFamily:MTLGPUFamilyApple6];
69}
70
71static std::unique_ptr<Capabilities> InferMetalCapabilities(
72 id<MTLDevice> device,
73 PixelFormat color_format) {
74 return CapabilitiesBuilder()
76 .SetSupportsSSBO(true)
80 .SetDefaultColorFormat(color_format)
103#if FML_OS_IOS && !TARGET_OS_SIMULATOR
105#else
107#endif // FML_OS_IOS && !TARGET_OS_SIMULATOR
108 .Build();
109}
110
111ContextMTL::ContextMTL(
112 const Flags& flags,
113 id<MTLDevice> device,
114 id<MTLCommandQueue> command_queue,
115 NSArray<id<MTLLibrary>>* shader_libraries,
116 std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
117 std::optional<PixelFormat> pixel_format_override)
118 : Context(flags),
119 device_(device),
120 command_queue_(command_queue),
121 is_gpu_disabled_sync_switch_(std::move(is_gpu_disabled_sync_switch)) {
122 // Validate device.
123 if (!device_) {
124 VALIDATION_LOG << "Could not set up valid Metal device.";
125 return;
126 }
127
128 sync_switch_observer_.reset(new SyncSwitchObserver(*this));
129 is_gpu_disabled_sync_switch_->AddObserver(sync_switch_observer_.get());
130
131 // Setup the shader library.
132 {
133 if (shader_libraries == nil) {
134 VALIDATION_LOG << "Shader libraries were null.";
135 return;
136 }
137
138 // std::make_shared disallowed because of private friend ctor.
139 auto library = std::shared_ptr<ShaderLibraryMTL>(
140 new ShaderLibraryMTL(shader_libraries));
141 if (!library->IsValid()) {
142 VALIDATION_LOG << "Could not create valid Metal shader library.";
143 return;
144 }
145 shader_library_ = std::move(library);
146 }
147
148 // Setup the pipeline library.
149 {
150 pipeline_library_ =
151 std::shared_ptr<PipelineLibraryMTL>(new PipelineLibraryMTL(device_));
152 }
153
154 // Setup the sampler library.
155 {
156 sampler_library_ =
157 std::shared_ptr<SamplerLibraryMTL>(new SamplerLibraryMTL(device_));
158 }
159
160 // Setup the resource allocator.
161 {
162 resource_allocator_ = std::shared_ptr<AllocatorMTL>(
163 new AllocatorMTL(device_, "Impeller Permanents Allocator"));
164 if (!resource_allocator_) {
165 VALIDATION_LOG << "Could not set up the resource allocator.";
166 return;
167 }
168 }
169
170 device_capabilities_ =
171 InferMetalCapabilities(device_, pixel_format_override.has_value()
172 ? pixel_format_override.value()
174 command_queue_ip_ = std::make_shared<CommandQueue>();
175#ifdef IMPELLER_DEBUG
176 gpu_tracer_ = std::make_shared<GPUTracerMTL>();
177 capture_manager_ = std::make_shared<ImpellerMetalCaptureManager>(device_);
178#endif // IMPELLER_DEBUG
179 is_valid_ = true;
180}
181
182static NSArray<id<MTLLibrary>>* MTLShaderLibraryFromFilePaths(
183 id<MTLDevice> device,
184 const std::vector<std::string>& libraries_paths) {
185 NSMutableArray<id<MTLLibrary>>* found_libraries = [NSMutableArray array];
186 for (const auto& library_path : libraries_paths) {
187 if (!fml::IsFile(library_path)) {
188 VALIDATION_LOG << "Shader library does not exist at path '"
189 << library_path << "'";
190 return nil;
191 }
192 NSError* shader_library_error = nil;
193 auto library = [device newLibraryWithFile:@(library_path.c_str())
194 error:&shader_library_error];
195 if (!library) {
196 FML_LOG(ERROR) << "Could not create shader library: "
197 << shader_library_error.localizedDescription.UTF8String;
198 return nil;
199 }
200 [found_libraries addObject:library];
201 }
202 return found_libraries;
203}
204
205static NSArray<id<MTLLibrary>>* MTLShaderLibraryFromFileData(
206 id<MTLDevice> device,
207 const std::vector<std::shared_ptr<fml::Mapping>>& libraries_data,
208 const std::string& label) {
209 NSMutableArray<id<MTLLibrary>>* found_libraries = [NSMutableArray array];
210 for (const auto& library_data : libraries_data) {
211 if (library_data == nullptr) {
212 FML_LOG(ERROR) << "Shader library data was null.";
213 return nil;
214 }
215
216 __block auto data = library_data;
217
218 auto dispatch_data =
219 ::dispatch_data_create(library_data->GetMapping(), // buffer
220 library_data->GetSize(), // size
221 dispatch_get_main_queue(), // queue
222 ^() {
223 // We just need a reference.
224 data.reset();
225 } // destructor
226 );
227 if (!dispatch_data) {
228 FML_LOG(ERROR) << "Could not wrap shader data in dispatch data.";
229 return nil;
230 }
231
232 NSError* shader_library_error = nil;
233 auto library = [device newLibraryWithData:dispatch_data
234 error:&shader_library_error];
235 if (!library) {
236 FML_LOG(ERROR) << "Could not create shader library: "
237 << shader_library_error.localizedDescription.UTF8String;
238 return nil;
239 }
240 if (!label.empty()) {
241 library.label = @(label.c_str());
242 }
243 [found_libraries addObject:library];
244 }
245 return found_libraries;
246}
247
248static id<MTLDevice> CreateMetalDevice() {
249 return ::MTLCreateSystemDefaultDevice();
250}
251
252static id<MTLCommandQueue> CreateMetalCommandQueue(id<MTLDevice> device) {
253 auto command_queue = device.newCommandQueue;
254 if (!command_queue) {
255 VALIDATION_LOG << "Could not set up the command queue.";
256 return nullptr;
257 }
258 command_queue.label = @"Impeller Command Queue";
259 return command_queue;
260}
261
262std::shared_ptr<ContextMTL> ContextMTL::Create(
263 const Flags& flags,
264 const std::vector<std::string>& shader_library_paths,
265 std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch) {
266 auto device = CreateMetalDevice();
267 auto command_queue = CreateMetalCommandQueue(device);
268 if (!command_queue) {
269 return nullptr;
270 }
271 auto context = std::shared_ptr<ContextMTL>(new ContextMTL(
272 flags, device, command_queue,
273 MTLShaderLibraryFromFilePaths(device, shader_library_paths),
274 std::move(is_gpu_disabled_sync_switch)));
275 if (!context->IsValid()) {
276 FML_LOG(ERROR) << "Could not create Metal context.";
277 return nullptr;
278 }
279 return context;
280}
281
282std::shared_ptr<ContextMTL> ContextMTL::Create(
283 const Flags& flags,
284 const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
285 std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
286 const std::string& library_label,
287 std::optional<PixelFormat> pixel_format_override) {
288 auto device = CreateMetalDevice();
289 auto command_queue = CreateMetalCommandQueue(device);
290 if (!command_queue) {
291 return nullptr;
292 }
293 auto context = std::shared_ptr<ContextMTL>(new ContextMTL(
294 flags, device, command_queue,
295 MTLShaderLibraryFromFileData(device, shader_libraries_data,
296 library_label),
297 std::move(is_gpu_disabled_sync_switch), pixel_format_override));
298 if (!context->IsValid()) {
299 FML_LOG(ERROR) << "Could not create Metal context.";
300 return nullptr;
301 }
302 return context;
303}
304
305std::shared_ptr<ContextMTL> ContextMTL::Create(
306 const Flags& flags,
307 id<MTLDevice> device,
308 id<MTLCommandQueue> command_queue,
309 const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
310 std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
311 const std::string& library_label) {
312 auto context = std::shared_ptr<ContextMTL>(
313 new ContextMTL(flags, device, command_queue,
314 MTLShaderLibraryFromFileData(device, shader_libraries_data,
315 library_label),
316 std::move(is_gpu_disabled_sync_switch)));
317 if (!context->IsValid()) {
318 FML_LOG(ERROR) << "Could not create Metal context.";
319 return nullptr;
320 }
321 return context;
322}
323
324ContextMTL::~ContextMTL() {
325 is_gpu_disabled_sync_switch_->RemoveObserver(sync_switch_observer_.get());
326}
327
328Context::BackendType ContextMTL::GetBackendType() const {
329 return Context::BackendType::kMetal;
330}
331
332// |Context|
333std::string ContextMTL::DescribeGpuModel() const {
334 return std::string([[device_ name] UTF8String]);
335}
336
337// |Context|
338bool ContextMTL::IsValid() const {
339 return is_valid_;
340}
341
342// |Context|
343std::shared_ptr<ShaderLibrary> ContextMTL::GetShaderLibrary() const {
344 return shader_library_;
345}
346
347// |Context|
348std::shared_ptr<PipelineLibrary> ContextMTL::GetPipelineLibrary() const {
349 return pipeline_library_;
350}
351
352// |Context|
353std::shared_ptr<SamplerLibrary> ContextMTL::GetSamplerLibrary() const {
354 return sampler_library_;
355}
356
357// |Context|
358std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBuffer() const {
359 return CreateCommandBufferInQueue(command_queue_);
360}
361
362// |Context|
363void ContextMTL::Shutdown() {}
364
365#ifdef IMPELLER_DEBUG
366std::shared_ptr<GPUTracerMTL> ContextMTL::GetGPUTracer() const {
367 return gpu_tracer_;
368}
369#endif // IMPELLER_DEBUG
370
371std::shared_ptr<const fml::SyncSwitch> ContextMTL::GetIsGpuDisabledSyncSwitch()
372 const {
373 return is_gpu_disabled_sync_switch_;
374}
375
376std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBufferInQueue(
377 id<MTLCommandQueue> queue) const {
378 if (!IsValid()) {
379 return nullptr;
380 }
381
382 auto buffer = std::shared_ptr<CommandBufferMTL>(
383 new CommandBufferMTL(weak_from_this(), device_, queue));
384 if (!buffer->IsValid()) {
385 return nullptr;
386 }
387 return buffer;
388}
389
390std::shared_ptr<Allocator> ContextMTL::GetResourceAllocator() const {
391 return resource_allocator_;
392}
393
394id<MTLDevice> ContextMTL::GetMTLDevice() const {
395 return device_;
396}
397
398const std::shared_ptr<const Capabilities>& ContextMTL::GetCapabilities() const {
399 return device_capabilities_;
400}
401
402void ContextMTL::SetCapabilities(
403 const std::shared_ptr<const Capabilities>& capabilities) {
404 device_capabilities_ = capabilities;
405}
406
407// |Context|
408bool ContextMTL::UpdateOffscreenLayerPixelFormat(PixelFormat format) {
409 device_capabilities_ = InferMetalCapabilities(device_, format);
410 return true;
411}
412
413id<MTLCommandBuffer> ContextMTL::CreateMTLCommandBuffer(
414 const std::string& label) const {
415 auto buffer = [command_queue_ commandBuffer];
416 if (!label.empty()) {
417 [buffer setLabel:@(label.data())];
418 }
419 return buffer;
420}
421
422void ContextMTL::StoreTaskForGPU(const fml::closure& task,
423 const fml::closure& failure) {
424 std::vector<PendingTasks> failed_tasks;
425 {
426 Lock lock(tasks_awaiting_gpu_mutex_);
427 tasks_awaiting_gpu_.push_back(PendingTasks{task, failure});
428 int32_t failed_task_count =
429 tasks_awaiting_gpu_.size() - kMaxTasksAwaitingGPU;
430 if (failed_task_count > 0) {
431 failed_tasks.reserve(failed_task_count);
432 failed_tasks.insert(failed_tasks.end(),
433 std::make_move_iterator(tasks_awaiting_gpu_.begin()),
434 std::make_move_iterator(tasks_awaiting_gpu_.begin() +
435 failed_task_count));
436 tasks_awaiting_gpu_.erase(
437 tasks_awaiting_gpu_.begin(),
438 tasks_awaiting_gpu_.begin() + failed_task_count);
439 }
440 }
441 for (const PendingTasks& task : failed_tasks) {
442 if (task.failure) {
443 task.failure();
444 }
445 }
446}
447
448void ContextMTL::FlushTasksAwaitingGPU() {
449 std::deque<PendingTasks> tasks_awaiting_gpu;
450 {
451 Lock lock(tasks_awaiting_gpu_mutex_);
452 std::swap(tasks_awaiting_gpu, tasks_awaiting_gpu_);
453 }
454 std::vector<PendingTasks> tasks_to_queue;
455 for (const auto& task : tasks_awaiting_gpu) {
456 is_gpu_disabled_sync_switch_->Execute(fml::SyncSwitch::Handlers()
457 .SetIfFalse([&] { task.task(); })
458 .SetIfTrue([&] {
459 // Lost access to the GPU
460 // immediately after it was
461 // activated. This may happen if
462 // the app was quickly
463 // foregrounded/backgrounded
464 // from a push notification.
465 // Store the tasks on the
466 // context again.
467 tasks_to_queue.push_back(task);
468 }));
469 }
470 if (!tasks_to_queue.empty()) {
471 Lock lock(tasks_awaiting_gpu_mutex_);
472 tasks_awaiting_gpu_.insert(tasks_awaiting_gpu_.end(),
473 tasks_to_queue.begin(), tasks_to_queue.end());
474 }
475}
476
477bool ContextMTL::FinishQueue() {
478 id<MTLCommandBuffer> command_buffer =
479 ContextMTL::Cast(this)->CreateMTLCommandBuffer("Finish Queue Waiter");
480 [command_buffer commit];
481 // clang-format off
482 // This isn't documented in the method, but there are places where they
483 // imply that they will wait even for an empty buffer...
484 //
485 // See https://developer.apple.com/documentation/metalperformanceshaders/tuning-hints
486 // clang-format on
487 [command_buffer waitUntilCompleted];
488 return true;
489}
490
491ContextMTL::SyncSwitchObserver::SyncSwitchObserver(ContextMTL& parent)
492 : parent_(parent) {}
493
494void ContextMTL::SyncSwitchObserver::OnSyncSwitchUpdate(bool new_is_disabled) {
495 if (!new_is_disabled) {
496 parent_.FlushTasksAwaitingGPU();
497 }
498}
499
500// |Context|
501std::shared_ptr<CommandQueue> ContextMTL::GetCommandQueue() const {
502 return command_queue_ip_;
503}
504
505// |Context|
509
510#ifdef IMPELLER_DEBUG
511const std::shared_ptr<ImpellerMetalCaptureManager>
512ContextMTL::GetCaptureManager() const {
513 return capture_manager_;
514}
515#endif // IMPELLER_DEBUG
516
518 current_capture_scope_ = [[MTLCaptureManager sharedCaptureManager]
519 newCaptureScopeWithDevice:device];
520 [current_capture_scope_ setLabel:@"Impeller Frame"];
521 [[MTLCaptureManager sharedCaptureManager]
522 setDefaultCaptureScope:current_capture_scope_];
523}
524
526 return scope_active_;
527}
528
530 if (scope_active_) {
531 return;
532 }
533 scope_active_ = true;
534 [current_capture_scope_ beginScope];
535}
536
538 FML_DCHECK(scope_active_);
539 [current_capture_scope_ endScope];
540 scope_active_ = false;
541}
542
543} // namespace impeller
CapabilitiesBuilder & SetDefaultColorFormat(PixelFormat value)
CapabilitiesBuilder & SetSupportsComputeSubgroups(bool value)
CapabilitiesBuilder & SetMinimumUniformAlignment(size_t value)
CapabilitiesBuilder & SetSupportsTextureToTextureBlits(bool value)
CapabilitiesBuilder & SetDefaultStencilFormat(PixelFormat value)
CapabilitiesBuilder & SetSupportsDeviceTransientTextures(bool value)
CapabilitiesBuilder & SetSupportsTriangleFan(bool value)
CapabilitiesBuilder & SetSupportsFramebufferFetch(bool value)
CapabilitiesBuilder & SetSupportsDecalSamplerAddressMode(bool value)
CapabilitiesBuilder & SetSupportsTextureCompression(CompressedTextureFamily family, bool value)
CapabilitiesBuilder & SetSupportsOffscreenMSAA(bool value)
CapabilitiesBuilder & SetSupportsSSBO(bool value)
CapabilitiesBuilder & SetMaximumRenderPassAttachmentSize(ISize size)
CapabilitiesBuilder & SetSupportsExtendedRangeFormats(bool value)
CapabilitiesBuilder & SetDefaultGlyphAtlasFormat(PixelFormat value)
CapabilitiesBuilder & SetSupportsCompute(bool value)
std::unique_ptr< Capabilities > Build()
CapabilitiesBuilder & SetDefaultDepthStencilFormat(PixelFormat value)
CapabilitiesBuilder & SetSupportsReadFromResolve(bool value)
std::shared_ptr< CommandQueue > GetCommandQueue() const override
Return the graphics queue for submitting command buffers.
RuntimeStageBackend GetRuntimeStageBackend() const override
Retrieve the runtime stage for this context type.
ImpellerMetalCaptureManager(id< MTLDevice > device)
Construct a new capture manager from the provided Metal device.
void FinishCapture()
End the current capture scope.
void StartCapture()
Begin a new capture scope, no-op if the scope has already started.
VkDevice device
Definition main.cc:69
VkQueue queue
Definition main.cc:71
const uint8_t uint32_t uint32_t GError ** error
uint32_t uint32_t * format
#define FML_LOG(severity)
Definition logging.h:101
#define FML_DCHECK(condition)
Definition logging.h:122
const char * name
Definition fuchsia.cc:50
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set profile Make the profiler discard new samples once the profiler sample buffer is full When this flag is not the profiler sample buffer is used as a ring buffer
Definition switch_defs.h:98
bool IsFile(const std::string &path)
std::function< void()> closure
Definition closure.h:14
static bool DeviceSupportsExtendedRangeFormats(id< MTLDevice > device)
static bool DeviceSupportsTextureCompressionMobile(id< MTLDevice > device)
static NSArray< id< MTLLibrary > > * MTLShaderLibraryFromFilePaths(id< MTLDevice > device, const std::vector< std::string > &libraries_paths)
static id< MTLCommandQueue > CreateMetalCommandQueue(id< MTLDevice > device)
PixelFormat
The Pixel formats supported by Impeller. The naming convention denotes the usage of the component,...
Definition formats.h:99
static id< MTLDevice > CreateMetalDevice()
static NSArray< id< MTLLibrary > > * MTLShaderLibraryFromFileData(id< MTLDevice > device, const std::vector< std::shared_ptr< fml::Mapping > > &libraries_data, const std::string &label)
@ kASTCHDR
ASTC HDR. A separate device feature from ASTC LDR.
@ kBC
S3TC, RGTC, and BPTC (BC1 through BC7). Desktop GPUs.
@ kETC2
ETC2 and EAC. Mobile, OpenGL ES 3.0, and WebGL2.
@ kASTC
ASTC LDR. Modern mobile and some desktop.
ISize DeviceMaxTextureSizeSupported(id< MTLDevice > device)
static bool DeviceSupportsTextureCompressionAstcHdr(id< MTLDevice > device)
static bool DeviceSupportsComputeSubgroups(id< MTLDevice > device)
static bool DeviceSupportsFramebufferFetch(id< MTLDevice > device)
static std::unique_ptr< Capabilities > InferMetalCapabilities(id< MTLDevice > device, PixelFormat color_format)
static bool DeviceSupportsTextureCompressionBC(id< MTLDevice > device)
Definition ref_ptr.h:261
std::shared_ptr< ContextGLES > context
std::shared_ptr< CommandBuffer > command_buffer
Represents the 2 code paths available when calling |SyncSwitchExecute|.
Definition sync_switch.h:35
Handlers & SetIfFalse(const std::function< void()> &handler)
Sets the handler that will be executed if the |SyncSwitch| is false.
#define VALIDATION_LOG
Definition validation.h:91