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