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 
7 #include <Foundation/Foundation.h>
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)
64  .SetDefaultColorFormat(color_format)
67  .SetSupportsCompute(true)
71  .Build();
72 }
73 
74 ContextMTL::ContextMTL(
75  id<MTLDevice> device,
76  id<MTLCommandQueue> command_queue,
77  NSArray<id<MTLLibrary>>* shader_libraries,
78  std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch)
79  : device_(device),
80  command_queue_(command_queue),
81  is_gpu_disabled_sync_switch_(std::move(is_gpu_disabled_sync_switch)) {
82  // Validate device.
83  if (!device_) {
84  VALIDATION_LOG << "Could not set up valid Metal device.";
85  return;
86  }
87 
88  sync_switch_observer_.reset(new SyncSwitchObserver(*this));
89  is_gpu_disabled_sync_switch_->AddObserver(sync_switch_observer_.get());
90 
91  // Worker task runner.
92  {
93  raster_message_loop_ = fml::ConcurrentMessageLoop::Create(
94  std::min(4u, std::thread::hardware_concurrency()));
95  raster_message_loop_->PostTaskToAllWorkers([]() {
96  // See https://github.com/flutter/flutter/issues/65752
97  // Intentionally opt out of QoS for raster task workloads.
98  [[NSThread currentThread] setThreadPriority:1.0];
99  sched_param param;
100  int policy;
101  pthread_t thread = pthread_self();
102  if (!pthread_getschedparam(thread, &policy, &param)) {
103  param.sched_priority = 50;
104  pthread_setschedparam(thread, policy, &param);
105  }
106  });
107  }
108 
109  // Setup the shader library.
110  {
111  if (shader_libraries == nil) {
112  VALIDATION_LOG << "Shader libraries were null.";
113  return;
114  }
115 
116  // std::make_shared disallowed because of private friend ctor.
117  auto library = std::shared_ptr<ShaderLibraryMTL>(
118  new ShaderLibraryMTL(shader_libraries));
119  if (!library->IsValid()) {
120  VALIDATION_LOG << "Could not create valid Metal shader library.";
121  return;
122  }
123  shader_library_ = std::move(library);
124  }
125 
126  // Setup the pipeline library.
127  {
128  pipeline_library_ =
129  std::shared_ptr<PipelineLibraryMTL>(new PipelineLibraryMTL(device_));
130  }
131 
132  // Setup the sampler library.
133  {
134  sampler_library_ =
135  std::shared_ptr<SamplerLibraryMTL>(new SamplerLibraryMTL(device_));
136  }
137 
138  // Setup the resource allocator.
139  {
140  resource_allocator_ = std::shared_ptr<AllocatorMTL>(
141  new AllocatorMTL(device_, "Impeller Permanents Allocator"));
142  if (!resource_allocator_) {
143  VALIDATION_LOG << "Could not set up the resource allocator.";
144  return;
145  }
146  }
147 
148  device_capabilities_ =
150 #ifdef IMPELLER_DEBUG
151  gpu_tracer_ = std::make_shared<GPUTracerMTL>();
152 #endif // IMPELLER_DEBUG
153  is_valid_ = true;
154 }
155 
156 static NSArray<id<MTLLibrary>>* MTLShaderLibraryFromFilePaths(
157  id<MTLDevice> device,
158  const std::vector<std::string>& libraries_paths) {
159  NSMutableArray<id<MTLLibrary>>* found_libraries = [NSMutableArray array];
160  for (const auto& library_path : libraries_paths) {
161  if (!fml::IsFile(library_path)) {
162  VALIDATION_LOG << "Shader library does not exist at path '"
163  << library_path << "'";
164  return nil;
165  }
166  NSError* shader_library_error = nil;
167  auto library = [device newLibraryWithFile:@(library_path.c_str())
168  error:&shader_library_error];
169  if (!library) {
170  FML_LOG(ERROR) << "Could not create shader library: "
171  << shader_library_error.localizedDescription.UTF8String;
172  return nil;
173  }
174  [found_libraries addObject:library];
175  }
176  return found_libraries;
177 }
178 
179 static NSArray<id<MTLLibrary>>* MTLShaderLibraryFromFileData(
180  id<MTLDevice> device,
181  const std::vector<std::shared_ptr<fml::Mapping>>& libraries_data,
182  const std::string& label) {
183  NSMutableArray<id<MTLLibrary>>* found_libraries = [NSMutableArray array];
184  for (const auto& library_data : libraries_data) {
185  if (library_data == nullptr) {
186  FML_LOG(ERROR) << "Shader library data was null.";
187  return nil;
188  }
189 
190  __block auto data = library_data;
191 
192  auto dispatch_data =
193  ::dispatch_data_create(library_data->GetMapping(), // buffer
194  library_data->GetSize(), // size
195  dispatch_get_main_queue(), // queue
196  ^() {
197  // We just need a reference.
198  data.reset();
199  } // destructor
200  );
201  if (!dispatch_data) {
202  FML_LOG(ERROR) << "Could not wrap shader data in dispatch data.";
203  return nil;
204  }
205 
206  NSError* shader_library_error = nil;
207  auto library = [device newLibraryWithData:dispatch_data
208  error:&shader_library_error];
209  if (!library) {
210  FML_LOG(ERROR) << "Could not create shader library: "
211  << shader_library_error.localizedDescription.UTF8String;
212  return nil;
213  }
214  if (!label.empty()) {
215  library.label = @(label.c_str());
216  }
217  [found_libraries addObject:library];
218  }
219  return found_libraries;
220 }
221 
222 static id<MTLDevice> CreateMetalDevice() {
223  return ::MTLCreateSystemDefaultDevice();
224 }
225 
226 static id<MTLCommandQueue> CreateMetalCommandQueue(id<MTLDevice> device) {
227  auto command_queue = device.newCommandQueue;
228  if (!command_queue) {
229  VALIDATION_LOG << "Could not set up the command queue.";
230  return nullptr;
231  }
232  command_queue.label = @"Impeller Command Queue";
233  return command_queue;
234 }
235 
236 std::shared_ptr<ContextMTL> ContextMTL::Create(
237  const std::vector<std::string>& shader_library_paths,
238  std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch) {
239  auto device = CreateMetalDevice();
240  auto command_queue = CreateMetalCommandQueue(device);
241  if (!command_queue) {
242  return nullptr;
243  }
244  auto context = std::shared_ptr<ContextMTL>(new ContextMTL(
245  device, command_queue,
246  MTLShaderLibraryFromFilePaths(device, shader_library_paths),
247  std::move(is_gpu_disabled_sync_switch)));
248  if (!context->IsValid()) {
249  FML_LOG(ERROR) << "Could not create Metal context.";
250  return nullptr;
251  }
252  return context;
253 }
254 
255 std::shared_ptr<ContextMTL> ContextMTL::Create(
256  const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
257  std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
258  const std::string& library_label) {
259  auto device = CreateMetalDevice();
260  auto command_queue = CreateMetalCommandQueue(device);
261  if (!command_queue) {
262  return nullptr;
263  }
264  auto context = std::shared_ptr<ContextMTL>(
265  new ContextMTL(device, command_queue,
266  MTLShaderLibraryFromFileData(device, shader_libraries_data,
267  library_label),
268  std::move(is_gpu_disabled_sync_switch)));
269  if (!context->IsValid()) {
270  FML_LOG(ERROR) << "Could not create Metal context.";
271  return nullptr;
272  }
273  return context;
274 }
275 
276 std::shared_ptr<ContextMTL> ContextMTL::Create(
277  id<MTLDevice> device,
278  id<MTLCommandQueue> command_queue,
279  const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
280  std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
281  const std::string& library_label) {
282  auto context = std::shared_ptr<ContextMTL>(
283  new ContextMTL(device, command_queue,
284  MTLShaderLibraryFromFileData(device, shader_libraries_data,
285  library_label),
286  std::move(is_gpu_disabled_sync_switch)));
287  if (!context->IsValid()) {
288  FML_LOG(ERROR) << "Could not create Metal context.";
289  return nullptr;
290  }
291  return context;
292 }
293 
294 ContextMTL::~ContextMTL() {
295  is_gpu_disabled_sync_switch_->RemoveObserver(sync_switch_observer_.get());
296 }
297 
298 Context::BackendType ContextMTL::GetBackendType() const {
299  return Context::BackendType::kMetal;
300 }
301 
302 // |Context|
303 std::string ContextMTL::DescribeGpuModel() const {
304  return std::string([[device_ name] UTF8String]);
305 }
306 
307 // |Context|
308 bool ContextMTL::IsValid() const {
309  return is_valid_;
310 }
311 
312 // |Context|
313 std::shared_ptr<ShaderLibrary> ContextMTL::GetShaderLibrary() const {
314  return shader_library_;
315 }
316 
317 // |Context|
318 std::shared_ptr<PipelineLibrary> ContextMTL::GetPipelineLibrary() const {
319  return pipeline_library_;
320 }
321 
322 // |Context|
323 std::shared_ptr<SamplerLibrary> ContextMTL::GetSamplerLibrary() const {
324  return sampler_library_;
325 }
326 
327 // |Context|
328 std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBuffer() const {
329  return CreateCommandBufferInQueue(command_queue_);
330 }
331 
332 // |Context|
333 void ContextMTL::Shutdown() {
334  raster_message_loop_.reset();
335 }
336 
337 #ifdef IMPELLER_DEBUG
338 std::shared_ptr<GPUTracerMTL> ContextMTL::GetGPUTracer() const {
339  return gpu_tracer_;
340 }
341 #endif // IMPELLER_DEBUG
342 
343 const std::shared_ptr<fml::ConcurrentTaskRunner>
344 ContextMTL::GetWorkerTaskRunner() const {
345  return raster_message_loop_->GetTaskRunner();
346 }
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(), 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 std::function<void()>& task) {
400  tasks_awaiting_gpu_.emplace_back(task);
401  while (tasks_awaiting_gpu_.size() > kMaxTasksAwaitingGPU) {
402  tasks_awaiting_gpu_.front()();
403  tasks_awaiting_gpu_.pop_front();
404  }
405 }
406 
407 void ContextMTL::FlushTasksAwaitingGPU() {
408  for (const auto& task : tasks_awaiting_gpu_) {
409  task();
410  }
411  tasks_awaiting_gpu_.clear();
412 }
413 
414 ContextMTL::SyncSwitchObserver::SyncSwitchObserver(ContextMTL& parent)
415  : parent_(parent) {}
416 
417 void ContextMTL::SyncSwitchObserver::OnSyncSwitchUpdate(bool new_is_disabled) {
418  if (!new_is_disabled) {
419  parent_.FlushTasksAwaitingGPU();
420  }
421 }
422 
423 } // namespace impeller
impeller::PixelFormat::kS8UInt
@ kS8UInt
impeller::CapabilitiesBuilder::Build
std::unique_ptr< Capabilities > Build()
Definition: capabilities.cc:210
impeller::Context::BackendType
BackendType
Definition: context.h:49
impeller::DeviceSupportsComputeSubgroups
static bool DeviceSupportsComputeSubgroups(id< MTLDevice > device)
Definition: context_mtl.mm:43
context_mtl.h
impeller::CreateCommandBuffer
static id< MTLCommandBuffer > CreateCommandBuffer(id< MTLCommandQueue > queue)
Definition: command_buffer_mtl.mm:118
formats.h
impeller::CapabilitiesBuilder
Definition: capabilities.h:116
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:179
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:94
impeller::CreateMetalCommandQueue
static id< MTLCommandQueue > CreateMetalCommandQueue(id< MTLDevice > device)
Definition: context_mtl.mm:226
impeller::CapabilitiesBuilder::SetSupportsSSBO
CapabilitiesBuilder & SetSupportsSSBO(bool value)
Definition: capabilities.cc:140
impeller::CapabilitiesBuilder::SetSupportsCompute
CapabilitiesBuilder & SetSupportsCompute(bool value)
Definition: capabilities.cc:163
impeller::CapabilitiesBuilder::SetDefaultDepthStencilFormat
CapabilitiesBuilder & SetDefaultDepthStencilFormat(PixelFormat value)
Definition: capabilities.cc:186
impeller::DeviceSupportsFramebufferFetch
static bool DeviceSupportsFramebufferFetch(id< MTLDevice > device)
Definition: context_mtl.mm:23
impeller::CapabilitiesBuilder::SetSupportsFramebufferFetch
CapabilitiesBuilder & SetSupportsFramebufferFetch(bool value)
Definition: capabilities.cc:157
impeller::CapabilitiesBuilder::SetSupportsComputeSubgroups
CapabilitiesBuilder & SetSupportsComputeSubgroups(bool value)
Definition: capabilities.cc:168
capabilities.h
impeller::CapabilitiesBuilder::SetSupportsTextureToTextureBlits
CapabilitiesBuilder & SetSupportsTextureToTextureBlits(bool value)
Definition: capabilities.cc:151
impeller::CreateMetalDevice
static id< MTLDevice > CreateMetalDevice()
Definition: context_mtl.mm:222
impeller::InferMetalCapabilities
static std::unique_ptr< Capabilities > InferMetalCapabilities(id< MTLDevice > device, PixelFormat color_format)
Definition: context_mtl.mm:54
impeller::CapabilitiesBuilder::SetDefaultColorFormat
CapabilitiesBuilder & SetDefaultColorFormat(PixelFormat value)
Definition: capabilities.cc:174
sampler_descriptor.h
impeller::CapabilitiesBuilder::SetDefaultStencilFormat
CapabilitiesBuilder & SetDefaultStencilFormat(PixelFormat value)
Definition: capabilities.cc:180
impeller::CapabilitiesBuilder::SetSupportsBufferToTextureBlits
CapabilitiesBuilder & SetSupportsBufferToTextureBlits(bool value)
Definition: capabilities.cc:145
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:67
impeller::CapabilitiesBuilder::SetSupportsDecalSamplerAddressMode
CapabilitiesBuilder & SetSupportsDecalSamplerAddressMode(bool value)
Definition: capabilities.cc:198
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:156
gpu_tracer_mtl.h
impeller::CapabilitiesBuilder::SetSupportsDeviceTransientTextures
CapabilitiesBuilder & SetSupportsDeviceTransientTextures(bool value)
Definition: capabilities.cc:204
impeller::ContextMTL
Definition: context_mtl.h:34
impeller::PixelFormat::kB8G8R8A8UNormInt
@ kB8G8R8A8UNormInt
impeller
Definition: aiks_context.cc:10
impeller::CapabilitiesBuilder::SetSupportsReadFromResolve
CapabilitiesBuilder & SetSupportsReadFromResolve(bool value)
Definition: capabilities.cc:192
impeller::CapabilitiesBuilder::SetSupportsOffscreenMSAA
CapabilitiesBuilder & SetSupportsOffscreenMSAA(bool value)
Definition: capabilities.cc:135