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 
14 
15 namespace impeller {
16 
17 API_AVAILABLE(ios(14.0), macos(11.0))
18 static NSString* MTLCommandEncoderErrorStateToString(
19  MTLCommandEncoderErrorState state) {
20  switch (state) {
21  case MTLCommandEncoderErrorStateUnknown:
22  return @"unknown";
23  case MTLCommandEncoderErrorStateCompleted:
24  return @"completed";
25  case MTLCommandEncoderErrorStateAffected:
26  return @"affected";
27  case MTLCommandEncoderErrorStatePending:
28  return @"pending";
29  case MTLCommandEncoderErrorStateFaulted:
30  return @"faulted";
31  }
32  return @"unknown";
33 }
34 
35 static NSString* MTLCommandBufferErrorToString(MTLCommandBufferError code) {
36  switch (code) {
37  case MTLCommandBufferErrorNone:
38  return @"none";
39  case MTLCommandBufferErrorInternal:
40  return @"internal";
41  case MTLCommandBufferErrorTimeout:
42  return @"timeout";
43  case MTLCommandBufferErrorPageFault:
44  return @"page fault";
45  case MTLCommandBufferErrorNotPermitted:
46  return @"not permitted";
47  case MTLCommandBufferErrorOutOfMemory:
48  return @"out of memory";
49  case MTLCommandBufferErrorInvalidResource:
50  return @"invalid resource";
51  case MTLCommandBufferErrorMemoryless:
52  return @"memory-less";
53  default:
54  break;
55  }
56 
57  return [NSString stringWithFormat:@"<unknown> %zu", code];
58 }
59 
60 static bool LogMTLCommandBufferErrorIfPresent(id<MTLCommandBuffer> buffer) {
61  if (!buffer) {
62  return true;
63  }
64 
65  if (buffer.status == MTLCommandBufferStatusCompleted) {
66  return true;
67  }
68 
69  std::stringstream stream;
70  stream << ">>>>>>>" << std::endl;
71  stream << "Impeller command buffer could not be committed!" << std::endl;
72 
73  if (auto desc = buffer.error.localizedDescription) {
74  stream << desc.UTF8String << std::endl;
75  }
76 
77  if (buffer.error) {
78  stream << "Domain: "
79  << (buffer.error.domain.length > 0u ? buffer.error.domain.UTF8String
80  : "<unknown>")
81  << " Code: "
83  static_cast<MTLCommandBufferError>(buffer.error.code))
84  .UTF8String
85  << std::endl;
86  }
87 
88  if (@available(iOS 14.0, macOS 11.0, *)) {
89  NSArray<id<MTLCommandBufferEncoderInfo>>* infos =
90  buffer.error.userInfo[MTLCommandBufferEncoderInfoErrorKey];
91  for (id<MTLCommandBufferEncoderInfo> info in infos) {
92  stream << (info.label.length > 0u ? info.label.UTF8String
93  : "<Unlabelled Render Pass>")
94  << ": "
95  << MTLCommandEncoderErrorStateToString(info.errorState).UTF8String
96  << std::endl;
97 
98  auto signposts = [info.debugSignposts componentsJoinedByString:@", "];
99  if (signposts.length > 0u) {
100  stream << signposts.UTF8String << std::endl;
101  }
102  }
103 
104  for (id<MTLFunctionLog> log in buffer.logs) {
105  auto desc = log.description;
106  if (desc.length > 0u) {
107  stream << desc.UTF8String << std::endl;
108  }
109  }
110  }
111 
112  stream << "<<<<<<<";
113  VALIDATION_LOG << stream.str();
114  return false;
115 }
116 
117 static id<MTLCommandBuffer> CreateCommandBuffer(id<MTLCommandQueue> queue) {
118 #ifndef FLUTTER_RELEASE
119  if (@available(iOS 14.0, macOS 11.0, *)) {
120  auto desc = [[MTLCommandBufferDescriptor alloc] init];
121  // Degrades CPU performance slightly but is well worth the cost for typical
122  // Impeller workloads.
123  desc.errorOptions = MTLCommandBufferErrorOptionEncoderExecutionStatus;
124  return [queue commandBufferWithDescriptor:desc];
125  }
126 #endif // FLUTTER_RELEASE
127  return [queue commandBuffer];
128 }
129 
130 CommandBufferMTL::CommandBufferMTL(const std::weak_ptr<const Context>& context,
131  id<MTLDevice> device,
132  id<MTLCommandQueue> queue)
133  : CommandBuffer(context),
134  buffer_(CreateCommandBuffer(queue)),
135  device_(device) {}
136 
137 CommandBufferMTL::~CommandBufferMTL() = default;
138 
139 bool CommandBufferMTL::IsValid() const {
140  return buffer_ != nil;
141 }
142 
143 void CommandBufferMTL::SetLabel(std::string_view label) const {
144 #ifdef IMPELLER_DEBUG
145  if (label.empty()) {
146  return;
147  }
148 
149  [buffer_ setLabel:@(label.data())];
150 #endif // IMPELLER_DEBUG
151 }
152 
153 static CommandBuffer::Status ToCommitResult(MTLCommandBufferStatus status) {
154  switch (status) {
155  case MTLCommandBufferStatusCompleted:
156  return CommandBufferMTL::Status::kCompleted;
157  case MTLCommandBufferStatusEnqueued:
158  return CommandBufferMTL::Status::kPending;
159  default:
160  break;
161  }
162  return CommandBufferMTL::Status::kError;
163 }
164 
165 bool CommandBufferMTL::OnSubmitCommands(bool block_on_schedule,
166  CompletionCallback callback) {
167  auto context = context_.lock();
168  if (!context) {
169  return false;
170  }
171 #ifdef IMPELLER_DEBUG
172  ContextMTL::Cast(*context).GetGPUTracer()->RecordCmdBuffer(buffer_);
173 #endif // IMPELLER_DEBUG
174  if (callback) {
175  [buffer_
176  addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
177  [[maybe_unused]] auto result =
179  FML_DCHECK(result)
180  << "Must not have errors during command buffer submission.";
181  callback(ToCommitResult(buffer.status));
182  }];
183  }
184 
185  [buffer_ commit];
186  if (block_on_schedule) {
187  [buffer_ waitUntilScheduled];
188  }
189 
190  buffer_ = nil;
191  return true;
192 }
193 
194 void CommandBufferMTL::OnWaitUntilCompleted() {}
195 
196 void CommandBufferMTL::OnWaitUntilScheduled() {}
197 
198 std::shared_ptr<RenderPass> CommandBufferMTL::OnCreateRenderPass(
199  RenderTarget target) {
200  if (!buffer_) {
201  return nullptr;
202  }
203 
204  auto context = context_.lock();
205  if (!context) {
206  return nullptr;
207  }
208  auto pass = std::shared_ptr<RenderPassMTL>(
209  new RenderPassMTL(context, target, buffer_));
210  if (!pass->IsValid()) {
211  return nullptr;
212  }
213 
214  return pass;
215 }
216 
217 std::shared_ptr<BlitPass> CommandBufferMTL::OnCreateBlitPass() {
218  if (!buffer_) {
219  return nullptr;
220  }
221 
222  auto pass = std::shared_ptr<BlitPassMTL>(new BlitPassMTL(buffer_, device_));
223  if (!pass->IsValid()) {
224  return nullptr;
225  }
226 
227  return pass;
228 }
229 
230 std::shared_ptr<ComputePass> CommandBufferMTL::OnCreateComputePass() {
231  if (!buffer_) {
232  return nullptr;
233  }
234  auto context = context_.lock();
235  if (!context) {
236  return nullptr;
237  }
238 
239  auto pass =
240  std::shared_ptr<ComputePassMTL>(new ComputePassMTL(context, buffer_));
241  if (!pass->IsValid()) {
242  return nullptr;
243  }
244 
245  return pass;
246 }
247 
248 } // namespace impeller
static bool LogMTLCommandBufferErrorIfPresent(id< MTLCommandBuffer > buffer)
API_AVAILABLE(ios(14.0), macos(11.0)) static NSString *MTLCommandEncoderErrorStateToString(MTLCommandEncoderErrorState state)
static id< MTLCommandBuffer > CreateCommandBuffer(id< MTLCommandQueue > queue)
static NSString * MTLCommandBufferErrorToString(MTLCommandBufferError code)
static CommandBuffer::Status ToCommitResult(MTLCommandBufferStatus status)
#define VALIDATION_LOG
Definition: validation.h:91