xref: /aosp_15_r20/external/skia/src/gpu/graphite/dawn/DawnBuffer.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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