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