| 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
5 #include "impeller/renderer/backend/metal/context_mtl.h"
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"
16 #include "impeller/core/sampler_descriptor.h"
17 #include "impeller/renderer/backend/metal/gpu_tracer_mtl.h"
18 #include "impeller/renderer/backend/metal/sampler_library_mtl.h"
19 #include "impeller/renderer/capabilities.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()
58 .SetSupportsOffscreenMSAA(true)
59 .SetSupportsSSBO(true)
60 .SetSupportsBufferToTextureBlits(true)
61 .SetSupportsTextureToTextureBlits(true)
62 .SetSupportsDecalSamplerAddressMode(true)
63 .SetSupportsFramebufferFetch(DeviceSupportsFramebufferFetch(device))
64 .SetDefaultColorFormat(color_format)
65 .SetDefaultStencilFormat(PixelFormat::kS8UInt)
66 .SetDefaultDepthStencilFormat(PixelFormat::kD32FloatS8UInt)
67 .SetSupportsCompute(true)
68 .SetSupportsComputeSubgroups(DeviceSupportsComputeSubgroups(device))
69 .SetSupportsReadFromResolve(true)
70 .SetSupportsDeviceTransientTextures(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_ =
149 InferMetalCapabilities(device_, PixelFormat::kB8G8R8A8UNormInt);
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