blob: 8c7db5cdbba49efe630c93a2d604375278732972 [file] [log] [blame]
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrDawnBuffer_DEFINED
#define GrDawnBuffer_DEFINED
#include "src/gpu/ganesh/GrGpuBuffer.h"
#include "webgpu/webgpu_cpp.h"
#include <functional>
class GrDawnGpu;
// GrDawnBuffer is the GrGpuBuffer implementation for the Dawn backend.
//
// Some notes on the implementation:
//
// LIFETIME AND CREATION
// ---------------------
// When a GrDawnBuffer is constructed, it allocates a GPU buffer. Depending on the requested access
// pattern, the buffer is typically immediately mapped at creation (which happens synchronously and
// relatively fast). If a client requests to create a buffer with data, then it will be immediately
// unmapped after the data is copied into the buffer (see `GrDawnGpu::onCreateBuffer` and
// `GrDawnBuffer::onUpdateData`).
//
// Clients usually create buffers through a GrResourceProvider or a GrStagingBufferManager. These
// buffers are constructed in `GrDawnGpu::onCreateBuffer` and GrDawnGpu is involved in their
// lifetime and mapping. Depending on the requested buffer type, a GrDawnBuffer that is
// initialized as `Mappable::kNot` can itself be backed by another GrDawnBuffer that is owned by a
// GrStagingBufferManager. In this case the CPU mapping happens via the `fStagingBuffer` member
// instead of `fBuffer`. The backing `fStagingBuffer` is initialized in `GrDawnBuffer::onMap` and
// its contents are instructed to be copied into `fBuffer` in `GrDawnBuffer::onUnmap` (which does
// not take effect until the command is submitted to the GPU).
//
// ASYNC MAP/UNMAP
// ---------------
// The Dawn API provides two ways to map the CPU-accessible memory of a wgpu::Buffer:
// * wgpu::Device::CreateBuffer which can synchronously map the buffer at creation;
// * wgpu::Buffer::MapAsync which asynchronously maps a buffer at any time.
//
// When a GrDawnBuffer gets created it starts out as mapped (except it gets unmapped immediately if
// initialized with data). A buffer gets unmapped when its owner calls `GrGpuBuffer::unmap()`. A
// buffer that is managed by a GrStagingBufferManager is always unmapped before its ownership is
// passed to the associated GrDawnGpu.
//
// Dawn only provides an asynchronous API for mapping an unmapped buffer and `GrGpuBuffer::map()`
// must work synchronously. However, blocking in a busy-wait that yields to the underlying event
// loop can stall the calling thread in the order of milliseconds. We optimize this specifically
// for staging buffers:
// 1. GrStagingBufferManager first unmaps the buffer and passes its ownership to GrDawnBuffer; at
// this stage no client is expected to access the buffer and it can remain unmapped.
// 2. GrDawnBuffer requests to map the buffer asynchronously and does not return it back to the
// backing resource provider until the map finishes. Thus, the buffer is never handed back to
// clients in an unmapped state.
// 3. If a client needs a staging buffer before the map finishes, they will need to allocate a
// new buffer which can get mapped at creation and avoid an async map.
//
// For all other buffers, a blocking map procedure is provided which allows them to remap a buffer
// if needed. For instance, a write-only non-staging buffer can be safely unmapped and mapped by a
// client.
class GrDawnBuffer : public GrGpuBuffer {
public:
static sk_sp<GrDawnBuffer> Make(GrDawnGpu* gpu,
size_t sizeInBytes,
GrGpuBufferType type,
GrAccessPattern pattern,
std::string_view label);
~GrDawnBuffer() override = default;
void onMap(MapType) override;
void onUnmap(MapType) override;
bool onClearToZero() override;
void onRelease() override;
bool onUpdateData(const void* src, size_t offset, size_t size, bool preserve) override;
GrDawnGpu* getDawnGpu() const;
wgpu::Buffer get() const { return fBuffer; }
// Map this buffer using the asynchronous map procedure. This function is intended to be used by
// the owning GrDawnGpu to manage the lifetime of this buffer and it has the following
// restrictions:
// - It must not be called while an async map is already in progress.
// - It must not be called on a buffer that is already mapped.
// - It must not be called on a buffer that is initialized as "unmappable".
//
// `callback` is called asynchronously with the result of this procedure once it's complete.
using MapAsyncCallback = std::function<void(bool success)>;
void mapAsync(MapAsyncCallback callback);
private:
enum class Mappable {
// Corresponds to Vertex and Index buffers. When a mapping is requested, these buffers are
// always backed by a staging buffer. NOTE: Staging buffers that are created by
// GrStagingBufferManager themselves are always `Mappable::kWriteOnly`.
kNot,
// Corresponds to `GrGpuBufferType::kXferGpuToCpu`. NOT mapped at creation. Will use a
// blocking-map if a mapping is requested.
kReadOnly,
// Corresponds to `GrGpuBufferType::kXferCpuToGpu`. Always mapped at creation. Will use a
// blocking-map if a mapping is requested. IF this is a staging buffer, then it will be
// asynchronously mapped by GrDawnGpu.
kWriteOnly,
};
GrDawnBuffer(GrDawnGpu* gpu,
size_t sizeInBytes,
GrGpuBufferType type,
GrAccessPattern pattern,
std::string_view label,
Mappable mappable,
wgpu::Buffer buffer,
void* mapPtr);
void* internalMap(MapType type, size_t offset, size_t size);
void internalUnmap(MapType type, size_t offset, size_t size);
// Called to handle the asynchronous mapAsync callback.
void mapAsyncDone(WGPUBufferMapAsyncStatus status);
// Map a buffer and busy-wait until the asynchronous mapping procedure completes. This function
// only needs to be called for a buffer that has been unmapped since buffers start out as mapped
// at creation.
//
// The blocking map incurs a cost in the form of yielding to the underlying event loop until the
// map finishes and can block the calling thread in the order of milliseconds. This might be
// undesirable for buffers that are mapped and unmapped frequently.
//
// This procedure is used to cover the case where a buffer that is not managed by a
// GrStagingBufferManager (and thus not asynchronously mapped by the owning GrDawnGpu) is
// unmapped and needs to get re-mapped for use (e.g. in onUpdateData()).
//
// Returns nullptr if the buffer fails to map.
void* blockingMap(size_t offset, size_t size);
wgpu::Buffer fBuffer;
Mappable fMappable = Mappable::kNot;
bool fUnmapped;
// A callback is only present when a request for MapAsync is pending. The callback is reset once
// the procedure is complete.
MapAsyncCallback fMapAsyncCallback;
// Buffers that are of the "not mappable" type are backed by another GrDawnBuffer that is
// managed by a GrStagingBufferManager.
wgpu::Buffer fStagingBuffer;
size_t fStagingOffset = 0;
using INHERITED = GrGpuBuffer;
};
#endif