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
397std::shared_ptr<const GpuSubmissionTracker> ContextMTL::GetSubmissionTracker()
398 const {
399 return submission_tracker_;
400}
401
402const std::shared_ptr<GpuSubmissionTracker>&
403ContextMTL::GetMutableSubmissionTracker() const {
404 return submission_tracker_;
405}
406
407id<MTLDevice> ContextMTL::GetMTLDevice() const {
408 return device_;
409}
410
411const std::shared_ptr<const Capabilities>& ContextMTL::GetCapabilities() const {
412 return device_capabilities_;
413}
414
415void ContextMTL::SetCapabilities(
416 const std::shared_ptr<const Capabilities>& capabilities) {
417 device_capabilities_ = capabilities;
418}
419
420// |Context|
421bool ContextMTL::UpdateOffscreenLayerPixelFormat(PixelFormat format) {
422 device_capabilities_ = InferMetalCapabilities(device_, format);
423 return true;
424}
425
426id<MTLCommandBuffer> ContextMTL::CreateMTLCommandBuffer(
427 const std::string& label) const {
428 auto buffer = [command_queue_ commandBuffer];
429 if (!label.empty()) {
430 [buffer setLabel:@(label.data())];
431 }
432 return buffer;
433}
434
435void ContextMTL::StoreTaskForGPU(const fml::closure& task,
436 const fml::closure& failure) {
437 std::vector<PendingTasks> failed_tasks;
438 {
439 Lock lock(tasks_awaiting_gpu_mutex_);
440 tasks_awaiting_gpu_.push_back(PendingTasks{task, failure});
441 int32_t failed_task_count =
442 tasks_awaiting_gpu_.size() - kMaxTasksAwaitingGPU;
443 if (failed_task_count > 0) {
444 failed_tasks.reserve(failed_task_count);
445 failed_tasks.insert(failed_tasks.end(),
446 std::make_move_iterator(tasks_awaiting_gpu_.begin()),
447 std::make_move_iterator(tasks_awaiting_gpu_.begin() +
448 failed_task_count));
449 tasks_awaiting_gpu_.erase(
450 tasks_awaiting_gpu_.begin(),
451 tasks_awaiting_gpu_.begin() + failed_task_count);
452 }
453 }
454 for (const PendingTasks& task : failed_tasks) {
455 if (task.failure) {
456 task.failure();
457 }
458 }
459}
460
461void ContextMTL::FlushTasksAwaitingGPU() {
462 std::deque<PendingTasks> tasks_awaiting_gpu;
463 {
464 Lock lock(tasks_awaiting_gpu_mutex_);
465 std::swap(tasks_awaiting_gpu, tasks_awaiting_gpu_);
466 }
467 std::vector<PendingTasks> tasks_to_queue;
468 for (const auto& task : tasks_awaiting_gpu) {
469 is_gpu_disabled_sync_switch_->Execute(fml::SyncSwitch::Handlers()
470 .SetIfFalse([&] { task.task(); })
471 .SetIfTrue([&] {
472 // Lost access to the GPU
473 // immediately after it was
474 // activated. This may happen if
475 // the app was quickly
476 // foregrounded/backgrounded
477 // from a push notification.
478 // Store the tasks on the
479 // context again.
480 tasks_to_queue.push_back(task);
481 }));
482 }
483 if (!tasks_to_queue.empty()) {
484 Lock lock(tasks_awaiting_gpu_mutex_);
485 tasks_awaiting_gpu_.insert(tasks_awaiting_gpu_.end(),
486 tasks_to_queue.begin(), tasks_to_queue.end());
487 }
488}
489
490bool ContextMTL::FinishQueue() {
491 id<MTLCommandBuffer> command_buffer =
492 ContextMTL::Cast(this)->CreateMTLCommandBuffer("Finish Queue Waiter");
493 [command_buffer commit];
494 // clang-format off
495 // This isn't documented in the method, but there are places where they
496 // imply that they will wait even for an empty buffer...
497 //
498 // See https://developer.apple.com/documentation/metalperformanceshaders/tuning-hints
499 // clang-format on
500 [command_buffer waitUntilCompleted];
501 return true;
502}
503
504ContextMTL::SyncSwitchObserver::SyncSwitchObserver(ContextMTL& parent)
505 : parent_(parent) {}
506
507void ContextMTL::SyncSwitchObserver::OnSyncSwitchUpdate(bool new_is_disabled) {
508 if (!new_is_disabled) {
509 parent_.FlushTasksAwaitingGPU();
510 }
511}
512
513// |Context|
514std::shared_ptr<CommandQueue> ContextMTL::GetCommandQueue() const {
515 return command_queue_ip_;
516}
517
518// |Context|
522
523#ifdef IMPELLER_DEBUG
524const std::shared_ptr<ImpellerMetalCaptureManager>
525ContextMTL::GetCaptureManager() const {
526 return capture_manager_;
527}
528#endif // IMPELLER_DEBUG
529
531 current_capture_scope_ = [[MTLCaptureManager sharedCaptureManager]
532 newCaptureScopeWithDevice:device];
533 [current_capture_scope_ setLabel:@"Impeller Frame"];
534 [[MTLCaptureManager sharedCaptureManager]
535 setDefaultCaptureScope:current_capture_scope_];
536}
537
539 return scope_active_;
540}
541
543 if (scope_active_) {
544 return;
545 }
546 scope_active_ = true;
547 [current_capture_scope_ beginScope];
548}
549
551 FML_DCHECK(scope_active_);
552 [current_capture_scope_ endScope];
553 scope_active_ = false;
554}
555
556} // 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