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 
9 #include "flutter/fml/concurrent_message_loop.h"
10 #include "flutter/fml/file.h"
11 #include "flutter/fml/logging.h"
12 #include "flutter/fml/paths.h"
13 #include "flutter/fml/synchronization/sync_switch.h"
17 
18 namespace impeller {
19 
20 static bool DeviceSupportsFramebufferFetch(id<MTLDevice> device) {
21  // The iOS simulator lies about supporting framebuffer fetch.
22 #if FML_OS_IOS_SIMULATOR
23  return false;
24 #else // FML_OS_IOS_SIMULATOR
25 
26  if (@available(macOS 10.15, iOS 13, tvOS 13, *)) {
27  return [device supportsFamily:MTLGPUFamilyApple2];
28  }
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 #if FML_OS_IOS
33  return [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v1];
34 #else
35  return false;
36 #endif // FML_OS_IOS
37 #endif // FML_OS_IOS_SIMULATOR
38 }
39 
40 static bool DeviceSupportsComputeSubgroups(id<MTLDevice> device) {
41  bool supports_subgroups = false;
42  // Refer to the "SIMD-scoped reduction operations" feature in the table
43  // below: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
44  if (@available(ios 13.0, tvos 13.0, macos 10.15, *)) {
45  supports_subgroups = [device supportsFamily:MTLGPUFamilyApple7] ||
46  [device supportsFamily:MTLGPUFamilyMac2];
47  }
48  return supports_subgroups;
49 }
50 
51 static std::unique_ptr<Capabilities> InferMetalCapabilities(
52  id<MTLDevice> device,
53  PixelFormat color_format) {
54  return CapabilitiesBuilder()
56  .SetSupportsSSBO(true)
61  .SetDefaultColorFormat(color_format)
64  .SetSupportsCompute(true)
69  .Build();
70 }
71 
72 ContextMTL::ContextMTL(
73  id<MTLDevice> device,
74  id<MTLCommandQueue> command_queue,
75  NSArray<id<MTLLibrary>>* shader_libraries,
76  std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch)
77  : device_(device),
78  command_queue_(command_queue),
79  is_gpu_disabled_sync_switch_(std::move(is_gpu_disabled_sync_switch)) {
80  // Validate device.
81  if (!device_) {
82  VALIDATION_LOG << "Could not set up valid Metal device.";
83  return;
84  }
85 
86  // Worker task runner.
87  {
88  raster_message_loop_ = fml::ConcurrentMessageLoop::Create(
89  std::min(4u, std::thread::hardware_concurrency()));
90  raster_message_loop_->PostTaskToAllWorkers([]() {
91  // See https://github.com/flutter/flutter/issues/65752
92  // Intentionally opt out of QoS for raster task workloads.
93  [[NSThread currentThread] setThreadPriority:1.0];
94  sched_param param;
95  int policy;
96  pthread_t thread = pthread_self();
97  if (!pthread_getschedparam(thread, &policy, &param)) {
98  param.sched_priority = 50;
99  pthread_setschedparam(thread, policy, &param);
100  }
101  });
102  }
103 
104  // Setup the shader library.
105  {
106  if (shader_libraries == nil) {
107  VALIDATION_LOG << "Shader libraries were null.";
108  return;
109  }
110 
111  // std::make_shared disallowed because of private friend ctor.
112  auto library = std::shared_ptr<ShaderLibraryMTL>(
113  new ShaderLibraryMTL(shader_libraries));
114  if (!library->IsValid()) {
115  VALIDATION_LOG << "Could not create valid Metal shader library.";
116  return;
117  }
118  shader_library_ = std::move(library);
119  }
120 
121  // Setup the pipeline library.
122  {
123  pipeline_library_ =
124  std::shared_ptr<PipelineLibraryMTL>(new PipelineLibraryMTL(device_));
125  }
126 
127  // Setup the sampler library.
128  {
129  sampler_library_ =
130  std::shared_ptr<SamplerLibraryMTL>(new SamplerLibraryMTL(device_));
131  }
132 
133  // Setup the resource allocator.
134  {
135  resource_allocator_ = std::shared_ptr<AllocatorMTL>(
136  new AllocatorMTL(device_, "Impeller Permanents Allocator"));
137  if (!resource_allocator_) {
138  VALIDATION_LOG << "Could not set up the resource allocator.";
139  return;
140  }
141  }
142 
143  device_capabilities_ =
145 
146  is_valid_ = true;
147 }
148 
149 static NSArray<id<MTLLibrary>>* MTLShaderLibraryFromFilePaths(
150  id<MTLDevice> device,
151  const std::vector<std::string>& libraries_paths) {
152  NSMutableArray<id<MTLLibrary>>* found_libraries = [NSMutableArray array];
153  for (const auto& library_path : libraries_paths) {
154  if (!fml::IsFile(library_path)) {
155  VALIDATION_LOG << "Shader library does not exist at path '"
156  << library_path << "'";
157  return nil;
158  }
159  NSError* shader_library_error = nil;
160  auto library = [device newLibraryWithFile:@(library_path.c_str())
161  error:&shader_library_error];
162  if (!library) {
163  FML_LOG(ERROR) << "Could not create shader library: "
164  << shader_library_error.localizedDescription.UTF8String;
165  return nil;
166  }
167  [found_libraries addObject:library];
168  }
169  return found_libraries;
170 }
171 
172 static NSArray<id<MTLLibrary>>* MTLShaderLibraryFromFileData(
173  id<MTLDevice> device,
174  const std::vector<std::shared_ptr<fml::Mapping>>& libraries_data,
175  const std::string& label) {
176  NSMutableArray<id<MTLLibrary>>* found_libraries = [NSMutableArray array];
177  for (const auto& library_data : libraries_data) {
178  if (library_data == nullptr) {
179  FML_LOG(ERROR) << "Shader library data was null.";
180  return nil;
181  }
182 
183  __block auto data = library_data;
184 
185  auto dispatch_data =
186  ::dispatch_data_create(library_data->GetMapping(), // buffer
187  library_data->GetSize(), // size
188  dispatch_get_main_queue(), // queue
189  ^() {
190  // We just need a reference.
191  data.reset();
192  } // destructor
193  );
194  if (!dispatch_data) {
195  FML_LOG(ERROR) << "Could not wrap shader data in dispatch data.";
196  return nil;
197  }
198 
199  NSError* shader_library_error = nil;
200  auto library = [device newLibraryWithData:dispatch_data
201  error:&shader_library_error];
202  if (!library) {
203  FML_LOG(ERROR) << "Could not create shader library: "
204  << shader_library_error.localizedDescription.UTF8String;
205  return nil;
206  }
207  if (!label.empty()) {
208  library.label = @(label.c_str());
209  }
210  [found_libraries addObject:library];
211  }
212  return found_libraries;
213 }
214 
215 static id<MTLDevice> CreateMetalDevice() {
216  return ::MTLCreateSystemDefaultDevice();
217 }
218 
219 static id<MTLCommandQueue> CreateMetalCommandQueue(id<MTLDevice> device) {
220  auto command_queue = device.newCommandQueue;
221  if (!command_queue) {
222  VALIDATION_LOG << "Could not set up the command queue.";
223  return nullptr;
224  }
225  command_queue.label = @"Impeller Command Queue";
226  return command_queue;
227 }
228 
229 std::shared_ptr<ContextMTL> ContextMTL::Create(
230  const std::vector<std::string>& shader_library_paths,
231  std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch) {
232  auto device = CreateMetalDevice();
233  auto command_queue = CreateMetalCommandQueue(device);
234  if (!command_queue) {
235  return nullptr;
236  }
237  auto context = std::shared_ptr<ContextMTL>(new ContextMTL(
238  device, command_queue,
239  MTLShaderLibraryFromFilePaths(device, shader_library_paths),
240  std::move(is_gpu_disabled_sync_switch)));
241  if (!context->IsValid()) {
242  FML_LOG(ERROR) << "Could not create Metal context.";
243  return nullptr;
244  }
245  return context;
246 }
247 
248 std::shared_ptr<ContextMTL> ContextMTL::Create(
249  const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
250  std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
251  const std::string& library_label) {
252  auto device = CreateMetalDevice();
253  auto command_queue = CreateMetalCommandQueue(device);
254  if (!command_queue) {
255  return nullptr;
256  }
257  auto context = std::shared_ptr<ContextMTL>(
258  new ContextMTL(device, command_queue,
259  MTLShaderLibraryFromFileData(device, shader_libraries_data,
260  library_label),
261  std::move(is_gpu_disabled_sync_switch)));
262  if (!context->IsValid()) {
263  FML_LOG(ERROR) << "Could not create Metal context.";
264  return nullptr;
265  }
266  return context;
267 }
268 
269 std::shared_ptr<ContextMTL> ContextMTL::Create(
270  id<MTLDevice> device,
271  id<MTLCommandQueue> command_queue,
272  const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
273  std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
274  const std::string& library_label) {
275  auto context = std::shared_ptr<ContextMTL>(
276  new ContextMTL(device, command_queue,
277  MTLShaderLibraryFromFileData(device, shader_libraries_data,
278  library_label),
279  std::move(is_gpu_disabled_sync_switch)));
280  if (!context->IsValid()) {
281  FML_LOG(ERROR) << "Could not create Metal context.";
282  return nullptr;
283  }
284  return context;
285 }
286 
287 ContextMTL::~ContextMTL() = default;
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  raster_message_loop_.reset();
326 }
327 
328 const std::shared_ptr<fml::ConcurrentTaskRunner>
329 ContextMTL::GetWorkerTaskRunner() const {
330  return raster_message_loop_->GetTaskRunner();
331 }
332 
333 std::shared_ptr<const fml::SyncSwitch> ContextMTL::GetIsGpuDisabledSyncSwitch()
334  const {
335  return is_gpu_disabled_sync_switch_;
336 }
337 
338 std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBufferInQueue(
339  id<MTLCommandQueue> queue) const {
340  if (!IsValid()) {
341  return nullptr;
342  }
343 
344  auto buffer = std::shared_ptr<CommandBufferMTL>(
345  new CommandBufferMTL(weak_from_this(), queue));
346  if (!buffer->IsValid()) {
347  return nullptr;
348  }
349  return buffer;
350 }
351 
352 std::shared_ptr<Allocator> ContextMTL::GetResourceAllocator() const {
353  return resource_allocator_;
354 }
355 
356 id<MTLDevice> ContextMTL::GetMTLDevice() const {
357  return device_;
358 }
359 
360 const std::shared_ptr<const Capabilities>& ContextMTL::GetCapabilities() const {
361  return device_capabilities_;
362 }
363 
364 // |Context|
365 bool ContextMTL::UpdateOffscreenLayerPixelFormat(PixelFormat format) {
366  device_capabilities_ = InferMetalCapabilities(device_, format);
367  return true;
368 }
369 
370 id<MTLCommandBuffer> ContextMTL::CreateMTLCommandBuffer(
371  const std::string& label) const {
372  auto buffer = [command_queue_ commandBuffer];
373  if (!label.empty()) {
374  [buffer setLabel:@(label.data())];
375  }
376  return buffer;
377 }
378 
379 } // namespace impeller
impeller::CapabilitiesBuilder::Build
std::unique_ptr< Capabilities > Build()
Definition: capabilities.cc:220
impeller::Context::BackendType
BackendType
Definition: context.h:49
impeller::DeviceSupportsComputeSubgroups
static bool DeviceSupportsComputeSubgroups(id< MTLDevice > device)
Definition: context_mtl.mm:40
context_mtl.h
impeller::CreateCommandBuffer
static id< MTLCommandBuffer > CreateCommandBuffer(id< MTLCommandQueue > queue)
Definition: command_buffer_mtl.mm:118
impeller::CapabilitiesBuilder
Definition: capabilities.h:112
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:172
sampler_library_mtl.h
impeller::CreateMetalCommandQueue
static id< MTLCommandQueue > CreateMetalCommandQueue(id< MTLDevice > device)
Definition: context_mtl.mm:219
impeller::CapabilitiesBuilder::SetSupportsSSBO
CapabilitiesBuilder & SetSupportsSSBO(bool value)
Definition: capabilities.cc:144
impeller::CapabilitiesBuilder::SetSupportsCompute
CapabilitiesBuilder & SetSupportsCompute(bool value)
Definition: capabilities.cc:167
impeller::CapabilitiesBuilder::SetDefaultDepthStencilFormat
CapabilitiesBuilder & SetDefaultDepthStencilFormat(PixelFormat value)
Definition: capabilities.cc:202
impeller::DeviceSupportsFramebufferFetch
static bool DeviceSupportsFramebufferFetch(id< MTLDevice > device)
Definition: context_mtl.mm:20
impeller::CapabilitiesBuilder::SetSupportsFramebufferFetch
CapabilitiesBuilder & SetSupportsFramebufferFetch(bool value)
Definition: capabilities.cc:161
impeller::PixelFormat::kD32FloatS8UInt
@ kD32FloatS8UInt
impeller::CapabilitiesBuilder::SetSupportsComputeSubgroups
CapabilitiesBuilder & SetSupportsComputeSubgroups(bool value)
Definition: capabilities.cc:172
capabilities.h
impeller::CapabilitiesBuilder::SetSupportsTextureToTextureBlits
CapabilitiesBuilder & SetSupportsTextureToTextureBlits(bool value)
Definition: capabilities.cc:155
impeller::CreateMetalDevice
static id< MTLDevice > CreateMetalDevice()
Definition: context_mtl.mm:215
impeller::PixelFormat::kB8G8R8A8UNormInt
@ kB8G8R8A8UNormInt
impeller::InferMetalCapabilities
static std::unique_ptr< Capabilities > InferMetalCapabilities(id< MTLDevice > device, PixelFormat color_format)
Definition: context_mtl.mm:51
impeller::CapabilitiesBuilder::SetDefaultColorFormat
CapabilitiesBuilder & SetDefaultColorFormat(PixelFormat value)
Definition: capabilities.cc:190
sampler_descriptor.h
impeller::CapabilitiesBuilder::SetDefaultStencilFormat
CapabilitiesBuilder & SetDefaultStencilFormat(PixelFormat value)
Definition: capabilities.cc:196
impeller::CapabilitiesBuilder::SetSupportsBufferToTextureBlits
CapabilitiesBuilder & SetSupportsBufferToTextureBlits(bool value)
Definition: capabilities.cc:149
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:60
impeller::CapabilitiesBuilder::SetSupportsDecalSamplerAddressMode
CapabilitiesBuilder & SetSupportsDecalSamplerAddressMode(bool value)
Definition: capabilities.cc:208
std
Definition: comparable.h:98
impeller::PixelFormat::kS8UInt
@ kS8UInt
impeller::MTLShaderLibraryFromFilePaths
static NSArray< id< MTLLibrary > > * MTLShaderLibraryFromFilePaths(id< MTLDevice > device, const std::vector< std::string > &libraries_paths)
Definition: context_mtl.mm:149
impeller::CapabilitiesBuilder::SetSupportsDeviceTransientTextures
CapabilitiesBuilder & SetSupportsDeviceTransientTextures(bool value)
Definition: capabilities.cc:214
impeller::PixelFormat
PixelFormat
The Pixel formats supported by Impeller. The naming convention denotes the usage of the component,...
Definition: formats.h:94
impeller::ContextMTL
Definition: context_mtl.h:32
impeller::CapabilitiesBuilder::SetSupportsReadFromOnscreenTexture
CapabilitiesBuilder & SetSupportsReadFromOnscreenTexture(bool value)
Definition: capabilities.cc:178
impeller
Definition: aiks_context.cc:10
impeller::CapabilitiesBuilder::SetSupportsReadFromResolve
CapabilitiesBuilder & SetSupportsReadFromResolve(bool value)
Definition: capabilities.cc:184
impeller::CapabilitiesBuilder::SetSupportsOffscreenMSAA
CapabilitiesBuilder & SetSupportsOffscreenMSAA(bool value)
Definition: capabilities.cc:139