| Flutter Impeller |

command_buffer_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/command_buffer_mtl.h"
6
7 #include "flutter/fml/make_copyable.h"
8 #include "flutter/fml/synchronization/semaphore.h"
9 #include "flutter/fml/trace_event.h"
10
11 #include "impeller/renderer/backend/metal/blit_pass_mtl.h"
12 #include "impeller/renderer/backend/metal/compute_pass_mtl.h"
13 #include "impeller/renderer/backend/metal/context_mtl.h"
14 #include "impeller/renderer/backend/metal/render_pass_mtl.h"
15
16 namespace impeller {
17
18 API_AVAILABLE(ios(14.0), macos(11.0))
19 static NSString* MTLCommandEncoderErrorStateToString(
20 MTLCommandEncoderErrorState state) {
21 switch (state) {
22 case MTLCommandEncoderErrorStateUnknown:
23 return @"unknown";
24 case MTLCommandEncoderErrorStateCompleted:
25 return @"completed";
26 case MTLCommandEncoderErrorStateAffected:
27 return @"affected";
28 case MTLCommandEncoderErrorStatePending:
29 return @"pending";
30 case MTLCommandEncoderErrorStateFaulted:
31 return @"faulted";
32 }
33 return @"unknown";
34 }
35
36 static NSString* MTLCommandBufferErrorToString(MTLCommandBufferError code) {
37 switch (code) {
38 case MTLCommandBufferErrorNone:
39 return @"none";
40 case MTLCommandBufferErrorInternal:
41 return @"internal";
42 case MTLCommandBufferErrorTimeout:
43 return @"timeout";
44 case MTLCommandBufferErrorPageFault:
45 return @"page fault";
46 case MTLCommandBufferErrorNotPermitted:
47 return @"not permitted";
48 case MTLCommandBufferErrorOutOfMemory:
49 return @"out of memory";
50 case MTLCommandBufferErrorInvalidResource:
51 return @"invalid resource";
52 case MTLCommandBufferErrorMemoryless:
53 return @"memory-less";
54 default:
55 break;
56 }
57
58 return [NSString stringWithFormat:@"<unknown> %zu", code];
59 }
60
61 static bool LogMTLCommandBufferErrorIfPresent(id<MTLCommandBuffer> buffer) {
62 if (!buffer) {
63 return true;
64 }
65
66 if (buffer.status == MTLCommandBufferStatusCompleted) {
67 return true;
68 }
69
70 std::stringstream stream;
71 stream << ">>>>>>>" << std::endl;
72 stream << "Impeller command buffer could not be committed!" << std::endl;
73
74 if (auto desc = buffer.error.localizedDescription) {
75 stream << desc.UTF8String << std::endl;
76 }
77
78 if (buffer.error) {
79 stream << "Domain: "
80 << (buffer.error.domain.length > 0u ? buffer.error.domain.UTF8String
81 : "<unknown>")
82 << " Code: "
83 << MTLCommandBufferErrorToString(
84 static_cast<MTLCommandBufferError>(buffer.error.code))
85 .UTF8String
86 << std::endl;
87 }
88
89 if (@available(iOS 14.0, macOS 11.0, *)) {
90 NSArray<id<MTLCommandBufferEncoderInfo>>* infos =
91 buffer.error.userInfo[MTLCommandBufferEncoderInfoErrorKey];
92 for (id<MTLCommandBufferEncoderInfo> info in infos) {
93 stream << (info.label.length > 0u ? info.label.UTF8String
94 : "<Unlabelled Render Pass>")
95 << ": "
96 << MTLCommandEncoderErrorStateToString(info.errorState).UTF8String
97 << std::endl;
98
99 auto signposts = [info.debugSignposts componentsJoinedByString:@", "];
100 if (signposts.length > 0u) {
101 stream << signposts.UTF8String << std::endl;
102 }
103 }
104
105 for (id<MTLFunctionLog> log in buffer.logs) {
106 auto desc = log.description;
107 if (desc.length > 0u) {
108 stream << desc.UTF8String << std::endl;
109 }
110 }
111 }
112
113 stream << "<<<<<<<";
114 VALIDATION_LOG << stream.str();
115 return false;
116 }
117
118 static id<MTLCommandBuffer> CreateCommandBuffer(id<MTLCommandQueue> queue) {
119 #ifndef FLUTTER_RELEASE
120 if (@available(iOS 14.0, macOS 11.0, *)) {
121 auto desc = [[MTLCommandBufferDescriptor alloc] init];
122 // Degrades CPU performance slightly but is well worth the cost for typical
123 // Impeller workloads.
124 desc.errorOptions = MTLCommandBufferErrorOptionEncoderExecutionStatus;
125 return [queue commandBufferWithDescriptor:desc];
126 }
127 #endif // FLUTTER_RELEASE
128 return [queue commandBuffer];
129 }
130
131 CommandBufferMTL::CommandBufferMTL(const std::weak_ptr<const Context>& context,
132 id<MTLCommandQueue> queue)
133 : CommandBuffer(context), buffer_(CreateCommandBuffer(queue)) {}
134
135 CommandBufferMTL::~CommandBufferMTL() = default;
136
137 bool CommandBufferMTL::IsValid() const {
138 return buffer_ != nil;
139 }
140
141 void CommandBufferMTL::SetLabel(const std::string& label) const {
142 if (label.empty()) {
143 return;
144 }
145
146 [buffer_ setLabel:@(label.data())];
147 }
148
149 static CommandBuffer::Status ToCommitResult(MTLCommandBufferStatus status) {
150 switch (status) {
151 case MTLCommandBufferStatusCompleted:
152 return CommandBufferMTL::Status::kCompleted;
153 case MTLCommandBufferStatusEnqueued:
154 return CommandBufferMTL::Status::kPending;
155 default:
156 break;
157 }
158 return CommandBufferMTL::Status::kError;
159 }
160
161 bool CommandBufferMTL::OnSubmitCommands(CompletionCallback callback) {
162 auto context = context_.lock();
163 if (!context) {
164 return false;
165 }
166 #ifdef IMPELLER_DEBUG
167 ContextMTL::Cast(*context).GetGPUTracer()->RecordCmdBuffer(buffer_);
168 #endif // IMPELLER_DEBUG
169 if (callback) {
170 [buffer_
171 addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
172 [[maybe_unused]] auto result =
173 LogMTLCommandBufferErrorIfPresent(buffer);
174 FML_DCHECK(result)
175 << "Must not have errors during command buffer submission.";
176 callback(ToCommitResult(buffer.status));
177 }];
178 }
179
180 [buffer_ commit];
181
182 buffer_ = nil;
183 return true;
184 }
185
186 bool CommandBufferMTL::EncodeAndSubmit(
187 const std::shared_ptr<RenderPass>& render_pass) {
188 TRACE_EVENT0("impeller", "CommandBufferMTL::EncodeAndSubmit");
189 if (!IsValid() || !render_pass->IsValid()) {
190 return false;
191 }
192 auto context = context_.lock();
193 if (!context) {
194 return false;
195 }
196 [buffer_ enqueue];
197 auto buffer = buffer_;
198 buffer_ = nil;
199
200 #ifdef IMPELLER_DEBUG
201 ContextMTL::Cast(*context).GetGPUTracer()->RecordCmdBuffer(buffer);
202 #endif // IMPELLER_DEBUG
203
204 auto worker_task_runner = ContextMTL::Cast(*context).GetWorkerTaskRunner();
205 auto mtl_render_pass = static_cast<RenderPassMTL*>(render_pass.get());
206
207 // Render command encoder creation has been observed to exceed the stack size
208 // limit for worker threads, and therefore is intentionally constructed on the
209 // raster thread.
210 auto render_command_encoder =
211 [buffer renderCommandEncoderWithDescriptor:mtl_render_pass->desc_];
212 if (!render_command_encoder) {
213 return false;
214 }
215
216 auto task = fml::MakeCopyable(
217 [render_pass, buffer, render_command_encoder, weak_context = context_]() {
218 auto context = weak_context.lock();
219 if (!context) {
220 [render_command_encoder endEncoding];
221 return;
222 }
223
224 auto mtl_render_pass = static_cast<RenderPassMTL*>(render_pass.get());
225 if (!mtl_render_pass->label_.empty()) {
226 [render_command_encoder setLabel:@(mtl_render_pass->label_.c_str())];
227 }
228
229 auto result = mtl_render_pass->EncodeCommands(
230 context->GetResourceAllocator(), render_command_encoder);
231 [render_command_encoder endEncoding];
232 if (result) {
233 [buffer commit];
234 } else {
235 VALIDATION_LOG << "Failed to encode command buffer";
236 }
237 });
238 worker_task_runner->PostTask(task);
239 return true;
240 }
241
242 bool CommandBufferMTL::EncodeAndSubmit(
243 const std::shared_ptr<BlitPass>& blit_pass,
244 const std::shared_ptr<Allocator>& allocator) {
245 if (!IsValid() || !blit_pass->IsValid()) {
246 return false;
247 }
248 auto context = context_.lock();
249 if (!context) {
250 return false;
251 }
252 [buffer_ enqueue];
253 auto buffer = buffer_;
254 buffer_ = nil;
255
256 auto worker_task_runner = ContextMTL::Cast(*context).GetWorkerTaskRunner();
257 auto task = fml::MakeCopyable(
258 [blit_pass, buffer, weak_context = context_, allocator]() {
259 auto context = weak_context.lock();
260 if (!blit_pass->EncodeCommands(allocator)) {
261 VALIDATION_LOG << "Failed to encode blit pass.";
262 return;
263 }
264 [buffer commit];
265 });
266 worker_task_runner->PostTask(task);
267 return true;
268 }
269
270 void CommandBufferMTL::OnWaitUntilScheduled() {}
271
272 std::shared_ptr<RenderPass> CommandBufferMTL::OnCreateRenderPass(
273 RenderTarget target) {
274 if (!buffer_) {
275 return nullptr;
276 }
277
278 auto pass = std::shared_ptr<RenderPassMTL>(
279 new RenderPassMTL(context_, target, buffer_));
280 if (!pass->IsValid()) {
281 return nullptr;
282 }
283
284 return pass;
285 }
286
287 std::shared_ptr<BlitPass> CommandBufferMTL::OnCreateBlitPass() {
288 if (!buffer_) {
289 return nullptr;
290 }
291
292 auto pass = std::shared_ptr<BlitPassMTL>(new BlitPassMTL(buffer_));
293 if (!pass->IsValid()) {
294 return nullptr;
295 }
296
297 return pass;
298 }
299
300 std::shared_ptr<ComputePass> CommandBufferMTL::OnCreateComputePass() {
301 if (!buffer_) {
302 return nullptr;
303 }
304
305 auto pass =
306 std::shared_ptr<ComputePassMTL>(new ComputePassMTL(context_, buffer_));
307 if (!pass->IsValid()) {
308 return nullptr;
309 }
310
311 return pass;
312 }
313
314 } // namespace impeller
command_buffer_mtl.h
context_mtl.h
render_pass_mtl.h
impeller::CreateCommandBuffer
static id< MTLCommandBuffer > CreateCommandBuffer(id< MTLCommandQueue > queue)
Definition: command_buffer_mtl.mm:118
impeller::LogMTLCommandBufferErrorIfPresent
static bool LogMTLCommandBufferErrorIfPresent(id< MTLCommandBuffer > buffer)
Definition: command_buffer_mtl.mm:61
impeller::MTLCommandBufferErrorToString
static NSString * MTLCommandBufferErrorToString(MTLCommandBufferError code)
Definition: command_buffer_mtl.mm:36
blit_pass_mtl.h
impeller::ToCommitResult
static CommandBuffer::Status ToCommitResult(MTLCommandBufferStatus status)
Definition: command_buffer_mtl.mm:149
impeller::API_AVAILABLE
API_AVAILABLE(ios(14.0), macos(11.0)) static NSString *MTLCommandEncoderErrorStateToString(MTLCommandEncoderErrorState state)
Definition: command_buffer_mtl.mm:18
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:67
compute_pass_mtl.h
impeller::CommandBuffer::Status
Status
Definition: command_buffer.h:49
impeller
Definition: aiks_context.cc:10