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