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)
90 // Anisotropic filtering with a clamp in the range [1, 16] is supported
91 // on all Metal devices.
106#if FML_OS_IOS && !TARGET_OS_SIMULATOR
108#else
110#endif // FML_OS_IOS && !TARGET_OS_SIMULATOR
111 .Build();
112}
113
114ContextMTL::ContextMTL(
115 const Flags& flags,
116 id<MTLDevice> device,
117 id<MTLCommandQueue> command_queue,
118 NSArray<id<MTLLibrary>>* shader_libraries,
119 std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
120 std::optional<PixelFormat> pixel_format_override)
121 : Context(flags),
122 device_(device),
123 command_queue_(command_queue),
124 is_gpu_disabled_sync_switch_(std::move(is_gpu_disabled_sync_switch)) {
125 // Validate device.
126 if (!device_) {
127 VALIDATION_LOG << "Could not set up valid Metal device.";
128 return;
129 }
130
131 sync_switch_observer_.reset(new SyncSwitchObserver(*this));
132 is_gpu_disabled_sync_switch_->AddObserver(sync_switch_observer_.get());
133
134 // Setup the shader library.
135 {
136 if (shader_libraries == nil) {
137 VALIDATION_LOG << "Shader libraries were null.";
138 return;
139 }
140
141 // std::make_shared disallowed because of private friend ctor.
142 auto library = std::shared_ptr<ShaderLibraryMTL>(
143 new ShaderLibraryMTL(shader_libraries));
144 if (!library->IsValid()) {
145 VALIDATION_LOG << "Could not create valid Metal shader library.";
146 return;
147 }
148 shader_library_ = std::move(library);
149 }
150
151 // Setup the pipeline library.
152 {
153 pipeline_library_ =
154 std::shared_ptr<PipelineLibraryMTL>(new PipelineLibraryMTL(device_));
155 }
156
157 // Setup the sampler library.
158 {
159 sampler_library_ =
160 std::shared_ptr<SamplerLibraryMTL>(new SamplerLibraryMTL(device_));
161 }
162
163 // Setup the resource allocator.
164 {
165 resource_allocator_ = std::shared_ptr<AllocatorMTL>(
166 new AllocatorMTL(device_, "Impeller Permanents Allocator"));
167 if (!resource_allocator_) {
168 VALIDATION_LOG << "Could not set up the resource allocator.";
169 return;
170 }
171 }
172
173 device_capabilities_ =
174 InferMetalCapabilities(device_, pixel_format_override.has_value()
175 ? pixel_format_override.value()
177 command_queue_ip_ = std::make_shared<CommandQueue>();
178#ifdef IMPELLER_DEBUG
179 gpu_tracer_ = std::make_shared<GPUTracerMTL>();
180 capture_manager_ = std::make_shared<ImpellerMetalCaptureManager>(device_);
181#endif // IMPELLER_DEBUG
182 is_valid_ = true;
183}
184
185static NSArray<id<MTLLibrary>>* MTLShaderLibraryFromFilePaths(
186 id<MTLDevice> device,
187 const std::vector<std::string>& libraries_paths) {
188 NSMutableArray<id<MTLLibrary>>* found_libraries = [NSMutableArray array];
189 for (const auto& library_path : libraries_paths) {
190 if (!fml::IsFile(library_path)) {
191 VALIDATION_LOG << "Shader library does not exist at path '"
192 << library_path << "'";
193 return nil;
194 }
195 NSError* shader_library_error = nil;
196 auto library = [device newLibraryWithFile:@(library_path.c_str())
197 error:&shader_library_error];
198 if (!library) {
199 FML_LOG(ERROR) << "Could not create shader library: "
200 << shader_library_error.localizedDescription.UTF8String;
201 return nil;
202 }
203 [found_libraries addObject:library];
204 }
205 return found_libraries;
206}
207
208static NSArray<id<MTLLibrary>>* MTLShaderLibraryFromFileData(
209 id<MTLDevice> device,
210 const std::vector<std::shared_ptr<fml::Mapping>>& libraries_data,
211 const std::string& label) {
212 NSMutableArray<id<MTLLibrary>>* found_libraries = [NSMutableArray array];
213 for (const auto& library_data : libraries_data) {
214 if (library_data == nullptr) {
215 FML_LOG(ERROR) << "Shader library data was null.";
216 return nil;
217 }
218
219 __block auto data = library_data;
220
221 auto dispatch_data =
222 ::dispatch_data_create(library_data->GetMapping(), // buffer
223 library_data->GetSize(), // size
224 dispatch_get_main_queue(), // queue
225 ^() {
226 // We just need a reference.
227 data.reset();
228 } // destructor
229 );
230 if (!dispatch_data) {
231 FML_LOG(ERROR) << "Could not wrap shader data in dispatch data.";
232 return nil;
233 }
234
235 NSError* shader_library_error = nil;
236 auto library = [device newLibraryWithData:dispatch_data
237 error:&shader_library_error];
238 if (!library) {
239 FML_LOG(ERROR) << "Could not create shader library: "
240 << shader_library_error.localizedDescription.UTF8String;
241 return nil;
242 }
243 if (!label.empty()) {
244 library.label = @(label.c_str());
245 }
246 [found_libraries addObject:library];
247 }
248 return found_libraries;
249}
250
251static id<MTLDevice> CreateMetalDevice() {
252 return ::MTLCreateSystemDefaultDevice();
253}
254
255static id<MTLCommandQueue> CreateMetalCommandQueue(id<MTLDevice> device) {
256 auto command_queue = device.newCommandQueue;
257 if (!command_queue) {
258 VALIDATION_LOG << "Could not set up the command queue.";
259 return nullptr;
260 }
261 command_queue.label = @"Impeller Command Queue";
262 return command_queue;
263}
264
265std::shared_ptr<ContextMTL> ContextMTL::Create(
266 const Flags& flags,
267 const std::vector<std::string>& shader_library_paths,
268 std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch) {
269 auto device = CreateMetalDevice();
270 auto command_queue = CreateMetalCommandQueue(device);
271 if (!command_queue) {
272 return nullptr;
273 }
274 auto context = std::shared_ptr<ContextMTL>(new ContextMTL(
275 flags, device, command_queue,
276 MTLShaderLibraryFromFilePaths(device, shader_library_paths),
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
285std::shared_ptr<ContextMTL> ContextMTL::Create(
286 const Flags& flags,
287 const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
288 std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
289 const std::string& library_label,
290 std::optional<PixelFormat> pixel_format_override) {
291 auto device = CreateMetalDevice();
292 auto command_queue = CreateMetalCommandQueue(device);
293 if (!command_queue) {
294 return nullptr;
295 }
296 auto context = std::shared_ptr<ContextMTL>(new ContextMTL(
297 flags, device, command_queue,
298 MTLShaderLibraryFromFileData(device, shader_libraries_data,
299 library_label),
300 std::move(is_gpu_disabled_sync_switch), pixel_format_override));
301 if (!context->IsValid()) {
302 FML_LOG(ERROR) << "Could not create Metal context.";
303 return nullptr;
304 }
305 return context;
306}
307
308std::shared_ptr<ContextMTL> ContextMTL::Create(
309 const Flags& flags,
310 id<MTLDevice> device,
311 id<MTLCommandQueue> command_queue,
312 const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
313 std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
314 const std::string& library_label) {
315 auto context = std::shared_ptr<ContextMTL>(
316 new ContextMTL(flags, device, command_queue,
317 MTLShaderLibraryFromFileData(device, shader_libraries_data,
318 library_label),
319 std::move(is_gpu_disabled_sync_switch)));
320 if (!context->IsValid()) {
321 FML_LOG(ERROR) << "Could not create Metal context.";
322 return nullptr;
323 }
324 return context;
325}
326
327ContextMTL::~ContextMTL() {
328 is_gpu_disabled_sync_switch_->RemoveObserver(sync_switch_observer_.get());
329}
330
331Context::BackendType ContextMTL::GetBackendType() const {
332 return Context::BackendType::kMetal;
333}
334
335// |Context|
336std::string ContextMTL::DescribeGpuModel() const {
337 return std::string([[device_ name] UTF8String]);
338}
339
340// |Context|
341bool ContextMTL::IsValid() const {
342 return is_valid_;
343}
344
345// |Context|
346std::shared_ptr<ShaderLibrary> ContextMTL::GetShaderLibrary() const {
347 return shader_library_;
348}
349
350// |Context|
351std::shared_ptr<PipelineLibrary> ContextMTL::GetPipelineLibrary() const {
352 return pipeline_library_;
353}
354
355// |Context|
356std::shared_ptr<SamplerLibrary> ContextMTL::GetSamplerLibrary() const {
357 return sampler_library_;
358}
359
360// |Context|
361std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBuffer() const {
362 return CreateCommandBufferInQueue(command_queue_);
363}
364
365// |Context|
366void ContextMTL::Shutdown() {}
367
368#ifdef IMPELLER_DEBUG
369std::shared_ptr<GPUTracerMTL> ContextMTL::GetGPUTracer() const {
370 return gpu_tracer_;
371}
372#endif // IMPELLER_DEBUG
373
374std::shared_ptr<const fml::SyncSwitch> ContextMTL::GetIsGpuDisabledSyncSwitch()
375 const {
376 return is_gpu_disabled_sync_switch_;
377}
378
379std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBufferInQueue(
380 id<MTLCommandQueue> queue) const {
381 if (!IsValid()) {
382 return nullptr;
383 }
384
385 auto buffer = std::shared_ptr<CommandBufferMTL>(
386 new CommandBufferMTL(weak_from_this(), device_, queue));
387 if (!buffer->IsValid()) {
388 return nullptr;
389 }
390 return buffer;
391}
392
393std::shared_ptr<Allocator> ContextMTL::GetResourceAllocator() const {
394 return resource_allocator_;
395}
396
397id<MTLDevice> ContextMTL::GetMTLDevice() const {
398 return device_;
399}
400
401const std::shared_ptr<const Capabilities>& ContextMTL::GetCapabilities() const {
402 return device_capabilities_;
403}
404
405void ContextMTL::SetCapabilities(
406 const std::shared_ptr<const Capabilities>& capabilities) {
407 device_capabilities_ = capabilities;
408}
409
410// |Context|
411bool ContextMTL::UpdateOffscreenLayerPixelFormat(PixelFormat format) {
412 device_capabilities_ = InferMetalCapabilities(device_, format);
413 return true;
414}
415
416id<MTLCommandBuffer> ContextMTL::CreateMTLCommandBuffer(
417 const std::string& label) const {
418 auto buffer = [command_queue_ commandBuffer];
419 if (!label.empty()) {
420 [buffer setLabel:@(label.data())];
421 }
422 return buffer;
423}
424
425void ContextMTL::StoreTaskForGPU(const fml::closure& task,
426 const fml::closure& failure) {
427 std::vector<PendingTasks> failed_tasks;
428 {
429 Lock lock(tasks_awaiting_gpu_mutex_);
430 tasks_awaiting_gpu_.push_back(PendingTasks{task, failure});
431 int32_t failed_task_count =
432 tasks_awaiting_gpu_.size() - kMaxTasksAwaitingGPU;
433 if (failed_task_count > 0) {
434 failed_tasks.reserve(failed_task_count);
435 failed_tasks.insert(failed_tasks.end(),
436 std::make_move_iterator(tasks_awaiting_gpu_.begin()),
437 std::make_move_iterator(tasks_awaiting_gpu_.begin() +
438 failed_task_count));
439 tasks_awaiting_gpu_.erase(
440 tasks_awaiting_gpu_.begin(),
441 tasks_awaiting_gpu_.begin() + failed_task_count);
442 }
443 }
444 for (const PendingTasks& task : failed_tasks) {
445 if (task.failure) {
446 task.failure();
447 }
448 }
449}
450
451void ContextMTL::FlushTasksAwaitingGPU() {
452 std::deque<PendingTasks> tasks_awaiting_gpu;
453 {
454 Lock lock(tasks_awaiting_gpu_mutex_);
455 std::swap(tasks_awaiting_gpu, tasks_awaiting_gpu_);
456 }
457 std::vector<PendingTasks> tasks_to_queue;
458 for (const auto& task : tasks_awaiting_gpu) {
459 is_gpu_disabled_sync_switch_->Execute(fml::SyncSwitch::Handlers()
460 .SetIfFalse([&] { task.task(); })
461 .SetIfTrue([&] {
462 // Lost access to the GPU
463 // immediately after it was
464 // activated. This may happen if
465 // the app was quickly
466 // foregrounded/backgrounded
467 // from a push notification.
468 // Store the tasks on the
469 // context again.
470 tasks_to_queue.push_back(task);
471 }));
472 }
473 if (!tasks_to_queue.empty()) {
474 Lock lock(tasks_awaiting_gpu_mutex_);
475 tasks_awaiting_gpu_.insert(tasks_awaiting_gpu_.end(),
476 tasks_to_queue.begin(), tasks_to_queue.end());
477 }
478}
479
480bool ContextMTL::FinishQueue() {
481 id<MTLCommandBuffer> command_buffer =
482 ContextMTL::Cast(this)->CreateMTLCommandBuffer("Finish Queue Waiter");
483 [command_buffer commit];
484 // clang-format off
485 // This isn't documented in the method, but there are places where they
486 // imply that they will wait even for an empty buffer...
487 //
488 // See https://developer.apple.com/documentation/metalperformanceshaders/tuning-hints
489 // clang-format on
490 [command_buffer waitUntilCompleted];
491 return true;
492}
493
494ContextMTL::SyncSwitchObserver::SyncSwitchObserver(ContextMTL& parent)
495 : parent_(parent) {}
496
497void ContextMTL::SyncSwitchObserver::OnSyncSwitchUpdate(bool new_is_disabled) {
498 if (!new_is_disabled) {
499 parent_.FlushTasksAwaitingGPU();
500 }
501}
502
503// |Context|
504std::shared_ptr<CommandQueue> ContextMTL::GetCommandQueue() const {
505 return command_queue_ip_;
506}
507
508// |Context|
512
513#ifdef IMPELLER_DEBUG
514const std::shared_ptr<ImpellerMetalCaptureManager>
515ContextMTL::GetCaptureManager() const {
516 return capture_manager_;
517}
518#endif // IMPELLER_DEBUG
519
521 current_capture_scope_ = [[MTLCaptureManager sharedCaptureManager]
522 newCaptureScopeWithDevice:device];
523 [current_capture_scope_ setLabel:@"Impeller Frame"];
524 [[MTLCaptureManager sharedCaptureManager]
525 setDefaultCaptureScope:current_capture_scope_];
526}
527
529 return scope_active_;
530}
531
533 if (scope_active_) {
534 return;
535 }
536 scope_active_ = true;
537 [current_capture_scope_ beginScope];
538}
539
541 FML_DCHECK(scope_active_);
542 [current_capture_scope_ endScope];
543 scope_active_ = false;
544}
545
546} // 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 & SetMaxSamplerAnisotropy(uint32_t 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