1 /*
2 * Copyright 2022 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "src/gpu/graphite/dawn/DawnBuffer.h"
9
10 #include "include/core/SkTraceMemoryDump.h"
11 #include "include/private/base/SkAlign.h"
12 #include "src/gpu/graphite/Log.h"
13 #include "src/gpu/graphite/dawn/DawnAsyncWait.h"
14 #include "src/gpu/graphite/dawn/DawnSharedContext.h"
15
16 namespace skgpu::graphite {
17 namespace {
18 #if defined(__EMSCRIPTEN__)
is_map_succeeded(WGPUBufferMapAsyncStatus status)19 bool is_map_succeeded(WGPUBufferMapAsyncStatus status) {
20 return status == WGPUBufferMapAsyncStatus_Success;
21 }
22
23 [[maybe_unused]]
log_map_error(WGPUBufferMapAsyncStatus status,const char *)24 void log_map_error(WGPUBufferMapAsyncStatus status, const char*) {
25 const char* statusStr;
26 LogPriority priority = LogPriority::kError;
27 switch (status) {
28 case WGPUBufferMapAsyncStatus_ValidationError:
29 statusStr = "ValidationError";
30 break;
31 case WGPUBufferMapAsyncStatus_Unknown:
32 statusStr = "Unknown";
33 break;
34 case WGPUBufferMapAsyncStatus_DeviceLost:
35 statusStr = "DeviceLost";
36 break;
37 case WGPUBufferMapAsyncStatus_DestroyedBeforeCallback:
38 statusStr = "DestroyedBeforeCallback";
39 priority = LogPriority::kDebug;
40 break;
41 case WGPUBufferMapAsyncStatus_UnmappedBeforeCallback:
42 statusStr = "UnmappedBeforeCallback";
43 priority = LogPriority::kDebug;
44 break;
45 case WGPUBufferMapAsyncStatus_MappingAlreadyPending:
46 statusStr = "MappingAlreadyPending";
47 break;
48 case WGPUBufferMapAsyncStatus_OffsetOutOfRange:
49 statusStr = "OffsetOutOfRange";
50 break;
51 case WGPUBufferMapAsyncStatus_SizeOutOfRange:
52 statusStr = "SizeOutOfRange";
53 break;
54 default:
55 statusStr = "<other>";
56 break;
57 }
58 SKGPU_LOG(priority, "Buffer async map failed with status %s.", statusStr);
59 }
60
61 #else
62
63 bool is_map_succeeded(wgpu::MapAsyncStatus status) {
64 return status == wgpu::MapAsyncStatus::Success;
65 }
66
67 void log_map_error(wgpu::MapAsyncStatus status, wgpu::StringView message) {
68 const char* statusStr;
69 switch (status) {
70 case wgpu::MapAsyncStatus::InstanceDropped:
71 statusStr = "InstanceDropped";
72 break;
73 case wgpu::MapAsyncStatus::Error:
74 statusStr = "Error";
75 break;
76 case wgpu::MapAsyncStatus::Aborted:
77 statusStr = "Aborted";
78 break;
79 case wgpu::MapAsyncStatus::Unknown:
80 statusStr = "Unknown";
81 break;
82 case wgpu::MapAsyncStatus::Success:
83 SK_ABORT("This status is not an error");
84 break;
85 }
86 SKGPU_LOG(LogPriority::kError,
87 "Buffer async map failed with status %s, message '%.*s'.",
88 statusStr,
89 static_cast<int>(message.length),
90 message.data);
91 }
92 #endif // defined(__EMSCRIPTEN__)
93 } // namespace
94
Make(const DawnSharedContext * sharedContext,size_t size,BufferType type,AccessPattern accessPattern)95 sk_sp<DawnBuffer> DawnBuffer::Make(const DawnSharedContext* sharedContext,
96 size_t size,
97 BufferType type,
98 AccessPattern accessPattern) {
99 if (size <= 0) {
100 return nullptr;
101 }
102
103 wgpu::BufferUsage usage = wgpu::BufferUsage::None;
104
105 switch (type) {
106 case BufferType::kVertex:
107 usage = wgpu::BufferUsage::Vertex | wgpu::BufferUsage::CopyDst;
108 break;
109 case BufferType::kIndex:
110 usage = wgpu::BufferUsage::Index | wgpu::BufferUsage::CopyDst;
111 break;
112 case BufferType::kXferCpuToGpu:
113 usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite;
114 break;
115 case BufferType::kXferGpuToCpu:
116 usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
117 break;
118 case BufferType::kUniform:
119 usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst;
120 break;
121 case BufferType::kStorage:
122 usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst |
123 wgpu::BufferUsage::CopySrc;
124 break;
125 case BufferType::kQuery:
126 usage = wgpu::BufferUsage::QueryResolve | wgpu::BufferUsage::CopySrc;
127 break;
128 case BufferType::kIndirect:
129 usage = wgpu::BufferUsage::Indirect | wgpu::BufferUsage::Storage |
130 wgpu::BufferUsage::CopyDst;
131 break;
132 case BufferType::kVertexStorage:
133 usage = wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Storage;
134 break;
135 case BufferType::kIndexStorage:
136 usage = wgpu::BufferUsage::Index | wgpu::BufferUsage::Storage;
137 break;
138 }
139
140 if (sharedContext->caps()->drawBufferCanBeMapped() &&
141 accessPattern == AccessPattern::kHostVisible && type != BufferType::kXferGpuToCpu) {
142 if (type == BufferType::kQuery) {
143 // We can map the query buffer to get the results directly rather than having to copy to
144 // a transfer buffer.
145 usage |= wgpu::BufferUsage::MapRead;
146 } else {
147 // If the buffer is intended to be mappable, add MapWrite usage and remove
148 // CopyDst.
149 // We don't want to allow both CPU and GPU to write to the same buffer.
150 usage |= wgpu::BufferUsage::MapWrite;
151 usage &= ~wgpu::BufferUsage::CopyDst;
152 }
153 }
154
155 wgpu::BufferDescriptor desc;
156 desc.usage = usage;
157 desc.size = size;
158 // Specifying mappedAtCreation avoids clearing the buffer on the GPU which can cause MapAsync to
159 // be very slow as it waits for GPU execution to complete.
160 desc.mappedAtCreation = SkToBool(usage & wgpu::BufferUsage::MapWrite);
161
162 auto buffer = sharedContext->device().CreateBuffer(&desc);
163 if (!buffer) {
164 return {};
165 }
166
167 void* mappedAtCreationPtr = nullptr;
168 if (desc.mappedAtCreation) {
169 mappedAtCreationPtr = buffer.GetMappedRange();
170 SkASSERT(mappedAtCreationPtr);
171 }
172
173 return sk_sp<DawnBuffer>(
174 new DawnBuffer(sharedContext, size, std::move(buffer), mappedAtCreationPtr));
175 }
176
DawnBuffer(const DawnSharedContext * sharedContext,size_t size,wgpu::Buffer buffer,void * mappedAtCreationPtr)177 DawnBuffer::DawnBuffer(const DawnSharedContext* sharedContext,
178 size_t size,
179 wgpu::Buffer buffer,
180 void* mappedAtCreationPtr)
181 : Buffer(sharedContext,
182 size,
183 Protected::kNo, // Dawn doesn't support protected memory
184 /*commandBufferRefsAsUsageRefs=*/buffer.GetUsage() & wgpu::BufferUsage::MapWrite)
185 , fBuffer(std::move(buffer)) {
186 fMapPtr = mappedAtCreationPtr;
187 }
188
189 #if defined(__EMSCRIPTEN__)
prepareForReturnToCache(const std::function<void ()> & takeRef)190 void DawnBuffer::prepareForReturnToCache(const std::function<void()>& takeRef) {
191 // This function is only useful for Emscripten where we have to pre-map the buffer
192 // once it is returned to the cache.
193 SkASSERT(this->sharedContext()->caps()->bufferMapsAreAsync());
194
195 // This implementation is almost Dawn-agnostic. However, Buffer base class doesn't have any
196 // way of distinguishing a buffer that is mappable for writing from one mappable for reading.
197 // We only need to re-map the former.
198 if (!(fBuffer.GetUsage() & wgpu::BufferUsage::MapWrite)) {
199 return;
200 }
201 // We cannot start an async map while the GPU is still using the buffer. We asked that
202 // our Resource convert command buffer refs to usage refs. So we should never have any
203 // command buffer refs.
204 SkASSERT(!this->debugHasCommandBufferRef());
205 // Note that the map state cannot change on another thread when we are here. We got here
206 // because there were no UsageRefs on the buffer but async mapping holds a UsageRef until it
207 // completes.
208 if (this->isMapped()) {
209 return;
210 }
211 takeRef();
212 this->asyncMap([](void* ctx, skgpu::CallbackResult result) {
213 sk_sp<DawnBuffer> buffer(static_cast<DawnBuffer*>(ctx));
214 if (result != skgpu::CallbackResult::kSuccess) {
215 buffer->setDeleteASAP();
216 }
217 },
218 this);
219 }
220
onAsyncMap(GpuFinishedProc proc,GpuFinishedContext ctx)221 void DawnBuffer::onAsyncMap(GpuFinishedProc proc, GpuFinishedContext ctx) {
222 // This function is only useful for Emscripten where we have to use asyncMap().
223 SkASSERT(this->sharedContext()->caps()->bufferMapsAreAsync());
224
225 if (proc) {
226 SkAutoMutexExclusive ex(fAsyncMutex);
227 if (this->isMapped()) {
228 proc(ctx, CallbackResult::kSuccess);
229 return;
230 }
231 fAsyncMapCallbacks.push_back(RefCntedCallback::Make(proc, ctx));
232 }
233 if (this->isUnmappable()) {
234 return;
235 }
236 SkASSERT(fBuffer);
237 SkASSERT((fBuffer.GetUsage() & wgpu::BufferUsage::MapRead) ||
238 (fBuffer.GetUsage() & wgpu::BufferUsage::MapWrite));
239 SkASSERT(fBuffer.GetMapState() == wgpu::BufferMapState::Unmapped);
240 bool isWrite = fBuffer.GetUsage() & wgpu::BufferUsage::MapWrite;
241 auto buffer = sk_ref_sp(this);
242
243 fBuffer.MapAsync(
244 isWrite ? wgpu::MapMode::Write : wgpu::MapMode::Read,
245 0,
246 fBuffer.GetSize(),
247 [](WGPUBufferMapAsyncStatus s, void* userData) {
248 sk_sp<DawnBuffer> buffer(static_cast<DawnBuffer*>(userData));
249 buffer->mapCallback(s, /*message=*/nullptr);
250 },
251 buffer.release());
252 }
253
onMap()254 void DawnBuffer::onMap() {
255 SKGPU_LOG_W("Synchronous buffer mapping not supported in Dawn. Failing map request.");
256 }
257
258 #else
259
onMap()260 void DawnBuffer::onMap() {
261 SkASSERT(!this->sharedContext()->caps()->bufferMapsAreAsync());
262 SkASSERT(fBuffer);
263 SkASSERT((fBuffer.GetUsage() & wgpu::BufferUsage::MapRead) ||
264 (fBuffer.GetUsage() & wgpu::BufferUsage::MapWrite));
265 bool isWrite = fBuffer.GetUsage() & wgpu::BufferUsage::MapWrite;
266
267 // Use wgpu::Future and WaitAny with timeout=0 to trigger callback immediately.
268 // This should work because our resource tracking mechanism should make sure that
269 // the buffer is free of any GPU use at this point.
270 wgpu::FutureWaitInfo mapWaitInfo{};
271
272 mapWaitInfo.future = fBuffer.MapAsync(
273 isWrite ? wgpu::MapMode::Write : wgpu::MapMode::Read,
274 0,
275 fBuffer.GetSize(),
276 wgpu::CallbackMode::WaitAnyOnly,
277 [this](wgpu::MapAsyncStatus s, wgpu::StringView m) { this->mapCallback(s, m); });
278
279 wgpu::Device device = static_cast<const DawnSharedContext*>(sharedContext())->device();
280 wgpu::Instance instance = device.GetAdapter().GetInstance();
281 [[maybe_unused]] auto status = instance.WaitAny(1, &mapWaitInfo, /*timeoutNS=*/0);
282
283 if (status != wgpu::WaitStatus::Success) {
284 // WaitAny(timeout=0) might fail in this scenario:
285 // - Allocates a buffer.
286 // - Encodes a command buffer to copy a texture to this buffer.
287 // - Submits the command buffer. If OOM happens, this command buffer will fail to
288 // be submitted.
289 // - The buffer is *supposed* to be free of any GPU use since the command buffer that would
290 // have used it wasn't submitted successfully.
291 // - If we try to map this buffer at this point, internally Dawn will try to use GPU to
292 // clear this buffer to zeros, since this is its 1st use. WaitAny(timeout=0) won't work
293 // since the buffer now has a pending GPU clear operation.
294 //
295 // To work around this, we need to try again with a blocking WaitAny(), to wait for the
296 // clear operation to finish.
297 // Notes:
298 // - This fallback should be rare since it is caused by an OOM error during buffer
299 // readbacks.
300 // - For buffer writing cases, since we use mappedAtCreation, the GPU clear won't happen.
301 status = instance.WaitAny(
302 1, &mapWaitInfo, /*timeoutNS=*/std::numeric_limits<uint64_t>::max());
303 }
304
305 SkASSERT(status == wgpu::WaitStatus::Success);
306 SkASSERT(mapWaitInfo.completed);
307 }
308 #endif // defined(__EMSCRIPTEN__)
309
onUnmap()310 void DawnBuffer::onUnmap() {
311 SkASSERT(fBuffer);
312 SkASSERT(this->isUnmappable());
313
314 fMapPtr = nullptr;
315 fBuffer.Unmap();
316 }
317
318 template <typename StatusT, typename MessageT>
mapCallback(StatusT status,MessageT message)319 void DawnBuffer::mapCallback(StatusT status, MessageT message) {
320 SkAutoMutexExclusive em(this->fAsyncMutex);
321 if (is_map_succeeded(status)) {
322 if (this->fBuffer.GetUsage() & wgpu::BufferUsage::MapWrite) {
323 this->fMapPtr = this->fBuffer.GetMappedRange();
324 } else {
325 // If buffer is only created with MapRead usage, Dawn only allows returning
326 // constant pointer. We need to use const_cast as a workaround here.
327 this->fMapPtr = const_cast<void*>(this->fBuffer.GetConstMappedRange());
328 }
329 } else {
330 log_map_error(status, message);
331 for (auto& cb : this->fAsyncMapCallbacks) {
332 cb->setFailureResult();
333 }
334 }
335 this->fAsyncMapCallbacks.clear();
336 }
337
isUnmappable() const338 bool DawnBuffer::isUnmappable() const {
339 return fBuffer.GetMapState() != wgpu::BufferMapState::Unmapped;
340 }
341
freeGpuData()342 void DawnBuffer::freeGpuData() {
343 if (fBuffer) {
344 // Explicitly destroy the buffer since it might be ref'd by cached bind groups which are
345 // not immediately cleaned up. Graphite should already guarantee that all command buffers
346 // using this buffer (indirectly via BindGroups) are already completed.
347 fBuffer.Destroy();
348 fBuffer = nullptr;
349 }
350 }
351
setBackendLabel(char const * label)352 void DawnBuffer::setBackendLabel(char const* label) {
353 SkASSERT(label);
354 if (sharedContext()->caps()->setBackendLabels()) {
355 fBuffer.SetLabel(label);
356 }
357 }
358
359 } // namespace skgpu::graphite
360