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  if (callback) {
163  [buffer_
164  addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
165  [[maybe_unused]] auto result =
167  FML_DCHECK(result)
168  << "Must not have errors during command buffer submission.";
169  callback(ToCommitResult(buffer.status));
170  }];
171  }
172 
173  [buffer_ commit];
174 
175  buffer_ = nil;
176  return true;
177 }
178 
179 bool CommandBufferMTL::SubmitCommandsAsync(
180  std::shared_ptr<RenderPass> render_pass) {
181  TRACE_EVENT0("impeller", "CommandBufferMTL::SubmitCommandsAsync");
182  if (!IsValid() || !render_pass->IsValid()) {
183  return false;
184  }
185  auto context = context_.lock();
186  if (!context) {
187  return false;
188  }
189  [buffer_ enqueue];
190  auto buffer = buffer_;
191  buffer_ = nil;
192 
193  auto worker_task_runner = ContextMTL::Cast(*context).GetWorkerTaskRunner();
194  auto mtl_render_pass = static_cast<RenderPassMTL*>(render_pass.get());
195 
196  // Render command encoder creation has been observed to exceed the stack size
197  // limit for worker threads, and therefore is intentionally constructed on the
198  // raster thread.
199  auto render_command_encoder =
200  [buffer renderCommandEncoderWithDescriptor:mtl_render_pass->desc_];
201  if (!render_command_encoder) {
202  return false;
203  }
204 
205  auto task = fml::MakeCopyable(
206  [render_pass, buffer, render_command_encoder, weak_context = context_]() {
207  auto context = weak_context.lock();
208  if (!context) {
209  [render_command_encoder endEncoding];
210  return;
211  }
212 
213  auto mtl_render_pass = static_cast<RenderPassMTL*>(render_pass.get());
214  if (!mtl_render_pass->label_.empty()) {
215  [render_command_encoder setLabel:@(mtl_render_pass->label_.c_str())];
216  }
217 
218  auto result = mtl_render_pass->EncodeCommands(
219  context->GetResourceAllocator(), render_command_encoder);
220  [render_command_encoder endEncoding];
221  if (result) {
222  [buffer commit];
223  } else {
224  VALIDATION_LOG << "Failed to encode command buffer";
225  }
226  });
227  worker_task_runner->PostTask(task);
228  return true;
229 }
230 
231 void CommandBufferMTL::OnWaitUntilScheduled() {}
232 
233 std::shared_ptr<RenderPass> CommandBufferMTL::OnCreateRenderPass(
234  RenderTarget target) {
235  if (!buffer_) {
236  return nullptr;
237  }
238 
239  auto pass = std::shared_ptr<RenderPassMTL>(
240  new RenderPassMTL(context_, target, buffer_));
241  if (!pass->IsValid()) {
242  return nullptr;
243  }
244 
245  return pass;
246 }
247 
248 std::shared_ptr<BlitPass> CommandBufferMTL::OnCreateBlitPass() {
249  if (!buffer_) {
250  return nullptr;
251  }
252 
253  auto pass = std::shared_ptr<BlitPassMTL>(new BlitPassMTL(buffer_));
254  if (!pass->IsValid()) {
255  return nullptr;
256  }
257 
258  return pass;
259 }
260 
261 std::shared_ptr<ComputePass> CommandBufferMTL::OnCreateComputePass() {
262  if (!buffer_) {
263  return nullptr;
264  }
265 
266  auto pass =
267  std::shared_ptr<ComputePassMTL>(new ComputePassMTL(context_, buffer_));
268  if (!pass->IsValid()) {
269  return nullptr;
270  }
271 
272  return pass;
273 }
274 
275 } // 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:60
compute_pass_mtl.h
impeller::CommandBuffer::Status
Status
Definition: command_buffer.h:48
impeller
Definition: aiks_context.cc:10