| 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 #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"
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 .SetSupportsTextureToTextureBlits(true)
61 .SetSupportsDecalSamplerAddressMode(true)
62 .SetSupportsFramebufferFetch(DeviceSupportsFramebufferFetch(device))
63 .SetDefaultColorFormat(color_format)
64 .SetDefaultStencilFormat(PixelFormat::kS8UInt)
65 .SetDefaultDepthStencilFormat(PixelFormat::kD32FloatS8UInt)
66 .SetSupportsCompute(true)
67 .SetSupportsComputeSubgroups(DeviceSupportsComputeSubgroups(device))
68 .SetSupportsReadFromResolve(true)
69 .SetSupportsDeviceTransientTextures(true)
70 .SetDefaultGlyphAtlasFormat(PixelFormat::kA8UNormInt)
71 .SetSupportsTriangleFan(false)
72 .SetMaximumRenderPassAttachmentSize(DeviceMaxTextureSizeSupported(device))
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()
136 : PixelFormat::kB8G8R8A8UNormInt);
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
440 ImpellerMetalCaptureManager::ImpellerMetalCaptureManager(id<MTLDevice> device) {
441 current_capture_scope_ = [[MTLCaptureManager sharedCaptureManager]
442 newCaptureScopeWithDevice:device];
443 [current_capture_scope_ setLabel:@"Impeller Frame"];
444 }
445
446 bool ImpellerMetalCaptureManager::CaptureScopeActive() const {
447 return scope_active_;
448 }
449
450 void ImpellerMetalCaptureManager::StartCapture() {
451 if (scope_active_) {
452 return;
453 }
454 scope_active_ = true;
455 [current_capture_scope_ beginScope];
456 }
457
458 void ImpellerMetalCaptureManager::FinishCapture() {
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