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 
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"
21 
22 namespace impeller {
23 
24 static 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 
36 static 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.
48 static 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).
55 static 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.
62 static bool DeviceSupportsTextureCompressionMobile(id<MTLDevice> device) {
63  return [device supportsFamily:MTLGPUFamilyApple2];
64 }
65 
66 // ASTC HDR requires Apple GPU family 6 (A13) or later.
67 static bool DeviceSupportsTextureCompressionAstcHdr(id<MTLDevice> device) {
68  return [device supportsFamily:MTLGPUFamilyApple6];
69 }
70 
71 static std::unique_ptr<Capabilities> InferMetalCapabilities(
72  id<MTLDevice> device,
73  PixelFormat color_format) {
74  return CapabilitiesBuilder()
76  .SetSupportsSSBO(true)
80  .SetDefaultColorFormat(color_format)
83  .SetSupportsCompute(true)
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 
114 ContextMTL::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 
185 static 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 
208 static 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 
251 static id<MTLDevice> CreateMetalDevice() {
252  return ::MTLCreateSystemDefaultDevice();
253 }
254 
255 static 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 
265 std::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 
285 std::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 
308 std::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 
327 ContextMTL::~ContextMTL() {
328  is_gpu_disabled_sync_switch_->RemoveObserver(sync_switch_observer_.get());
329 }
330 
331 Context::BackendType ContextMTL::GetBackendType() const {
332  return Context::BackendType::kMetal;
333 }
334 
335 // |Context|
336 std::string ContextMTL::DescribeGpuModel() const {
337  return std::string([[device_ name] UTF8String]);
338 }
339 
340 // |Context|
341 bool ContextMTL::IsValid() const {
342  return is_valid_;
343 }
344 
345 // |Context|
346 std::shared_ptr<ShaderLibrary> ContextMTL::GetShaderLibrary() const {
347  return shader_library_;
348 }
349 
350 // |Context|
351 std::shared_ptr<PipelineLibrary> ContextMTL::GetPipelineLibrary() const {
352  return pipeline_library_;
353 }
354 
355 // |Context|
356 std::shared_ptr<SamplerLibrary> ContextMTL::GetSamplerLibrary() const {
357  return sampler_library_;
358 }
359 
360 // |Context|
361 std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBuffer() const {
362  return CreateCommandBufferInQueue(command_queue_);
363 }
364 
365 // |Context|
366 void ContextMTL::Shutdown() {}
367 
368 #ifdef IMPELLER_DEBUG
369 std::shared_ptr<GPUTracerMTL> ContextMTL::GetGPUTracer() const {
370  return gpu_tracer_;
371 }
372 #endif // IMPELLER_DEBUG
373 
374 std::shared_ptr<const fml::SyncSwitch> ContextMTL::GetIsGpuDisabledSyncSwitch()
375  const {
376  return is_gpu_disabled_sync_switch_;
377 }
378 
379 std::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 
393 std::shared_ptr<Allocator> ContextMTL::GetResourceAllocator() const {
394  return resource_allocator_;
395 }
396 
397 id<MTLDevice> ContextMTL::GetMTLDevice() const {
398  return device_;
399 }
400 
401 const std::shared_ptr<const Capabilities>& ContextMTL::GetCapabilities() const {
402  return device_capabilities_;
403 }
404 
405 void ContextMTL::SetCapabilities(
406  const std::shared_ptr<const Capabilities>& capabilities) {
407  device_capabilities_ = capabilities;
408 }
409 
410 // |Context|
411 bool ContextMTL::UpdateOffscreenLayerPixelFormat(PixelFormat format) {
412  device_capabilities_ = InferMetalCapabilities(device_, format);
413  return true;
414 }
415 
416 id<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 
425 void 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 
451 void 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 
480 bool 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 
494 ContextMTL::SyncSwitchObserver::SyncSwitchObserver(ContextMTL& parent)
495  : parent_(parent) {}
496 
497 void ContextMTL::SyncSwitchObserver::OnSyncSwitchUpdate(bool new_is_disabled) {
498  if (!new_is_disabled) {
499  parent_.FlushTasksAwaitingGPU();
500  }
501 }
502 
503 // |Context|
504 std::shared_ptr<CommandQueue> ContextMTL::GetCommandQueue() const {
505  return command_queue_ip_;
506 }
507 
508 // |Context|
511 }
512 
513 #ifdef IMPELLER_DEBUG
514 const std::shared_ptr<ImpellerMetalCaptureManager>
515 ContextMTL::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.
Definition: context_mtl.mm:504
RuntimeStageBackend GetRuntimeStageBackend() const override
Retrieve the runtime stage for this context type.
Definition: context_mtl.mm:509
ImpellerMetalCaptureManager(id< MTLDevice > device)
Construct a new capture manager from the provided Metal device.
Definition: context_mtl.mm:520
void FinishCapture()
End the current capture scope.
Definition: context_mtl.mm:540
void StartCapture()
Begin a new capture scope, no-op if the scope has already started.
Definition: context_mtl.mm:532
ScopedObject< Object > Create(CtorArgs &&... args)
Definition: object.h:161
static bool DeviceSupportsExtendedRangeFormats(id< MTLDevice > device)
Definition: context_mtl.mm:48
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:208
static bool DeviceSupportsTextureCompressionMobile(id< MTLDevice > device)
Definition: context_mtl.mm:62
static id< MTLDevice > CreateMetalDevice()
Definition: context_mtl.mm:251
PixelFormat
The Pixel formats supported by Impeller. The naming convention denotes the usage of the component,...
Definition: formats.h:99
@ 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 id< MTLCommandBuffer > CreateCommandBuffer(id< MTLCommandQueue > queue)
static NSArray< id< MTLLibrary > > * MTLShaderLibraryFromFilePaths(id< MTLDevice > device, const std::vector< std::string > &libraries_paths)
Definition: context_mtl.mm:185
static id< MTLCommandQueue > CreateMetalCommandQueue(id< MTLDevice > device)
Definition: context_mtl.mm:255
static std::unique_ptr< Capabilities > InferMetalCapabilities(id< MTLDevice > device, PixelFormat color_format)
Definition: context_mtl.mm:71
static bool DeviceSupportsTextureCompressionAstcHdr(id< MTLDevice > device)
Definition: context_mtl.mm:67
static bool DeviceSupportsComputeSubgroups(id< MTLDevice > device)
Definition: context_mtl.mm:36
static bool DeviceSupportsFramebufferFetch(id< MTLDevice > device)
Definition: context_mtl.mm:24
static bool DeviceSupportsTextureCompressionBC(id< MTLDevice > device)
Definition: context_mtl.mm:55
Definition: comparable.h:93
std::shared_ptr< ContextGLES > context
std::shared_ptr< CommandBuffer > command_buffer
#define VALIDATION_LOG
Definition: validation.h:91