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