| 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
10 #include "impeller/renderer/backend/metal/blit_pass_mtl.h"
11 #include "impeller/renderer/backend/metal/compute_pass_mtl.h"
12 #include "impeller/renderer/backend/metal/context_mtl.h"
13 #include "impeller/renderer/backend/metal/render_pass_mtl.h"
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: "
82 << MTLCommandBufferErrorToString(
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 =
178 LogMTLCommandBufferErrorIfPresent(buffer);
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
blit_pass_mtl.h
impeller::CommandBuffer::Status
Status
Definition: command_buffer.h:49
command_buffer_mtl.h
compute_pass_mtl.h
impeller
Definition: allocation.cc:12
impeller::LogMTLCommandBufferErrorIfPresent
static bool LogMTLCommandBufferErrorIfPresent(id< MTLCommandBuffer > buffer)
Definition: command_buffer_mtl.mm:60
impeller::API_AVAILABLE
API_AVAILABLE(ios(14.0), macos(11.0)) static NSString *MTLCommandEncoderErrorStateToString(MTLCommandEncoderErrorState state)
Definition: command_buffer_mtl.mm:17
impeller::CreateCommandBuffer
static id< MTLCommandBuffer > CreateCommandBuffer(id< MTLCommandQueue > queue)
Definition: command_buffer_mtl.mm:117
impeller::MTLCommandBufferErrorToString
static NSString * MTLCommandBufferErrorToString(MTLCommandBufferError code)
Definition: command_buffer_mtl.mm:35
impeller::ToCommitResult
static CommandBuffer::Status ToCommitResult(MTLCommandBufferStatus status)
Definition: command_buffer_mtl.mm:153
render_pass_mtl.h
context_mtl.h
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:91