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 
6 
7 #include "flutter/fml/make_copyable.h"
8 #include "flutter/fml/synchronization/semaphore.h"
9 #include "flutter/fml/trace_event.h"
10 
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: "
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 =
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