Corentin Wallez dbf805fe8d Update GetMappedRange to not produce validation errors
GetMappedRange never produces errors and instead returns nullptr when it
is disallowed. When in a correct state, should return a valid pointer as
much as possible, even if the buffer is an error or if the device is
lost.

Adds tests for error buffers and device loss, and modify existing tests
to not expect a device error.

Also removes some dead code in the Vulkan backend and adds a fix for
missing deallocation of VkMemory on device shutdown.

Bug: dawn:445

Change-Id: Ia844ee3493cdaf75083424743dd194fa94faf591
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/24160
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Stephen White <senorblanco@chromium.org>
2020-07-06 18:08:10 +00:00

454 lines
18 KiB
C++

// Copyright 2019 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "dawn_wire/client/Buffer.h"
#include "dawn_wire/client/Client.h"
#include "dawn_wire/client/Device.h"
namespace dawn_wire { namespace client {
namespace {
template <typename Handle>
void SerializeBufferMapAsync(const Buffer* buffer, uint32_t serial, Handle* handle) {
// TODO(enga): Remove the template when Read/Write handles are combined in a tagged
// pointer.
constexpr bool isWrite =
std::is_same<Handle, MemoryTransferService::WriteHandle>::value;
// Get the serialization size of the handle.
size_t handleCreateInfoLength = handle->SerializeCreateSize();
BufferMapAsyncCmd cmd;
cmd.bufferId = buffer->id;
cmd.requestSerial = serial;
cmd.isWrite = isWrite;
cmd.handleCreateInfoLength = handleCreateInfoLength;
cmd.handleCreateInfo = nullptr;
char* writeHandleSpace =
buffer->device->GetClient()->SerializeCommand(cmd, handleCreateInfoLength);
// Serialize the handle into the space after the command.
handle->SerializeCreate(writeHandleSpace);
}
} // namespace
// static
WGPUBuffer Buffer::Create(Device* device_, const WGPUBufferDescriptor* descriptor) {
Client* wireClient = device_->GetClient();
if ((descriptor->usage & (WGPUBufferUsage_MapRead | WGPUBufferUsage_MapWrite)) != 0 &&
descriptor->size > std::numeric_limits<size_t>::max()) {
device_->InjectError(WGPUErrorType_OutOfMemory, "Buffer is too large for map usage");
return device_->CreateErrorBuffer();
}
auto* bufferObjectAndSerial = wireClient->BufferAllocator().New(device_);
Buffer* buffer = bufferObjectAndSerial->object.get();
// Store the size of the buffer so that mapping operations can allocate a
// MemoryTransfer handle of the proper size.
buffer->mSize = descriptor->size;
DeviceCreateBufferCmd cmd;
cmd.self = ToAPI(device_);
cmd.descriptor = descriptor;
cmd.result = ObjectHandle{buffer->id, bufferObjectAndSerial->generation};
wireClient->SerializeCommand(cmd);
return ToAPI(buffer);
}
// static
WGPUCreateBufferMappedResult Buffer::CreateMapped(Device* device_,
const WGPUBufferDescriptor* descriptor) {
Client* wireClient = device_->GetClient();
WGPUCreateBufferMappedResult result;
result.data = nullptr;
result.dataLength = 0;
// This buffer is too large to be mapped and to make a WriteHandle for.
if (descriptor->size > std::numeric_limits<size_t>::max()) {
device_->InjectError(WGPUErrorType_OutOfMemory, "Buffer is too large for mapping");
result.buffer = device_->CreateErrorBuffer();
return result;
}
// Create a WriteHandle for the map request. This is the client's intent to write GPU
// memory.
std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle =
std::unique_ptr<MemoryTransferService::WriteHandle>(
wireClient->GetMemoryTransferService()->CreateWriteHandle(descriptor->size));
if (writeHandle == nullptr) {
device_->InjectError(WGPUErrorType_OutOfMemory, "Buffer mapping allocation failed");
result.buffer = device_->CreateErrorBuffer();
return result;
}
// CreateBufferMapped is synchronous and the staging buffer for upload should be immediately
// available.
// Open the WriteHandle. This returns a pointer and size of mapped memory.
// |result.data| may be null on error.
std::tie(result.data, result.dataLength) = writeHandle->Open();
if (result.data == nullptr) {
device_->InjectError(WGPUErrorType_OutOfMemory, "Buffer mapping allocation failed");
result.buffer = device_->CreateErrorBuffer();
return result;
}
auto* bufferObjectAndSerial = wireClient->BufferAllocator().New(device_);
Buffer* buffer = bufferObjectAndSerial->object.get();
buffer->mSize = descriptor->size;
// Successfully created staging memory. The buffer now owns the WriteHandle.
buffer->mWriteHandle = std::move(writeHandle);
buffer->mMappedData = result.data;
result.buffer = ToAPI(buffer);
// Get the serialization size of the WriteHandle.
size_t handleCreateInfoLength = buffer->mWriteHandle->SerializeCreateSize();
DeviceCreateBufferMappedCmd cmd;
cmd.device = ToAPI(device_);
cmd.descriptor = descriptor;
cmd.result = ObjectHandle{buffer->id, bufferObjectAndSerial->generation};
cmd.handleCreateInfoLength = handleCreateInfoLength;
cmd.handleCreateInfo = nullptr;
char* writeHandleSpace =
buffer->device->GetClient()->SerializeCommand(cmd, handleCreateInfoLength);
// Serialize the WriteHandle into the space after the command.
buffer->mWriteHandle->SerializeCreate(writeHandleSpace);
return result;
}
// static
WGPUBuffer Buffer::CreateError(Device* device_) {
auto* allocation = device_->GetClient()->BufferAllocator().New(device_);
DeviceCreateErrorBufferCmd cmd;
cmd.self = ToAPI(device_);
cmd.result = ObjectHandle{allocation->object->id, allocation->generation};
device_->GetClient()->SerializeCommand(cmd);
return ToAPI(allocation->object.get());
}
Buffer::~Buffer() {
// Callbacks need to be fired in all cases, as they can handle freeing resources
// so we call them with "Unknown" status.
ClearMapRequests(WGPUBufferMapAsyncStatus_Unknown);
}
void Buffer::ClearMapRequests(WGPUBufferMapAsyncStatus status) {
for (auto& it : mRequests) {
if (it.second.writeHandle) {
it.second.writeCallback(status, nullptr, 0, it.second.userdata);
} else {
it.second.readCallback(status, nullptr, 0, it.second.userdata);
}
}
mRequests.clear();
}
void Buffer::MapReadAsync(WGPUBufferMapReadCallback callback, void* userdata) {
uint32_t serial = mRequestSerial++;
ASSERT(mRequests.find(serial) == mRequests.end());
if (mSize > std::numeric_limits<size_t>::max()) {
// On buffer creation, we check that mappable buffers do not exceed this size.
// So this buffer must not have mappable usage. Inject a validation error.
device->InjectError(WGPUErrorType_Validation, "Buffer needs the correct map usage bit");
callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata);
return;
}
// Create a ReadHandle for the map request. This is the client's intent to read GPU
// memory.
MemoryTransferService::ReadHandle* readHandle =
device->GetClient()->GetMemoryTransferService()->CreateReadHandle(
static_cast<size_t>(mSize));
if (readHandle == nullptr) {
device->InjectError(WGPUErrorType_OutOfMemory, "Failed to create buffer mapping");
callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata);
return;
}
Buffer::MapRequestData request = {};
request.readCallback = callback;
request.userdata = userdata;
// The handle is owned by the MapRequest until the callback returns.
request.readHandle = std::unique_ptr<MemoryTransferService::ReadHandle>(readHandle);
// Store a mapping from serial -> MapRequest. The client can map/unmap before the map
// operations are returned by the server so multiple requests may be in flight.
mRequests[serial] = std::move(request);
SerializeBufferMapAsync(this, serial, readHandle);
}
void Buffer::MapWriteAsync(WGPUBufferMapWriteCallback callback, void* userdata) {
uint32_t serial = mRequestSerial++;
ASSERT(mRequests.find(serial) == mRequests.end());
if (mSize > std::numeric_limits<size_t>::max()) {
// On buffer creation, we check that mappable buffers do not exceed this size.
// So this buffer must not have mappable usage. Inject a validation error.
device->InjectError(WGPUErrorType_Validation, "Buffer needs the correct map usage bit");
callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata);
return;
}
// Create a WriteHandle for the map request. This is the client's intent to write GPU
// memory.
MemoryTransferService::WriteHandle* writeHandle =
device->GetClient()->GetMemoryTransferService()->CreateWriteHandle(
static_cast<size_t>(mSize));
if (writeHandle == nullptr) {
device->InjectError(WGPUErrorType_OutOfMemory, "Failed to create buffer mapping");
callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata);
return;
}
Buffer::MapRequestData request = {};
request.writeCallback = callback;
request.userdata = userdata;
// The handle is owned by the MapRequest until the callback returns.
request.writeHandle = std::unique_ptr<MemoryTransferService::WriteHandle>(writeHandle);
// Store a mapping from serial -> MapRequest. The client can map/unmap before the map
// operations are returned by the server so multiple requests may be in flight.
mRequests[serial] = std::move(request);
SerializeBufferMapAsync(this, serial, writeHandle);
}
bool Buffer::OnMapReadAsyncCallback(uint32_t requestSerial,
uint32_t status,
uint64_t initialDataInfoLength,
const uint8_t* initialDataInfo) {
// The requests can have been deleted via an Unmap so this isn't an error.
auto requestIt = mRequests.find(requestSerial);
if (requestIt == mRequests.end()) {
return true;
}
auto request = std::move(requestIt->second);
// Delete the request before calling the callback otherwise the callback could be fired a
// second time. If, for example, buffer.Unmap() is called inside the callback.
mRequests.erase(requestIt);
size_t mappedDataLength = 0;
const void* mappedData = nullptr;
auto GetMappedData = [&]() -> bool {
// It is an error for the server to call the read callback when we asked for a map write
if (request.writeHandle) {
return false;
}
if (status == WGPUBufferMapAsyncStatus_Success) {
if (mReadHandle || mWriteHandle) {
// Buffer is already mapped.
return false;
}
if (initialDataInfoLength > std::numeric_limits<size_t>::max()) {
// This is the size of data deserialized from the command stream, which must be
// CPU-addressable.
return false;
}
ASSERT(request.readHandle != nullptr);
// The server serializes metadata to initialize the contents of the ReadHandle.
// Deserialize the message and return a pointer and size of the mapped data for
// reading.
if (!request.readHandle->DeserializeInitialData(
initialDataInfo, static_cast<size_t>(initialDataInfoLength), &mappedData,
&mappedDataLength)) {
// Deserialization shouldn't fail. This is a fatal error.
return false;
}
ASSERT(mappedData != nullptr);
// The MapRead request was successful. The buffer now owns the ReadHandle until
// Unmap().
mReadHandle = std::move(request.readHandle);
}
return true;
};
if (!GetMappedData()) {
// Dawn promises that all callbacks are called in finite time. Even if a fatal error
// occurs, the callback is called.
request.readCallback(WGPUBufferMapAsyncStatus_DeviceLost, nullptr, 0, request.userdata);
return false;
} else {
mMappedData = const_cast<void*>(mappedData);
request.readCallback(static_cast<WGPUBufferMapAsyncStatus>(status), mMappedData,
static_cast<uint64_t>(mappedDataLength), request.userdata);
return true;
}
}
bool Buffer::OnMapWriteAsyncCallback(uint32_t requestSerial, uint32_t status) {
// The requests can have been deleted via an Unmap so this isn't an error.
auto requestIt = mRequests.find(requestSerial);
if (requestIt == mRequests.end()) {
return true;
}
auto request = std::move(requestIt->second);
// Delete the request before calling the callback otherwise the callback could be fired a
// second time. If, for example, buffer.Unmap() is called inside the callback.
mRequests.erase(requestIt);
size_t mappedDataLength = 0;
void* mappedData = nullptr;
auto GetMappedData = [&]() -> bool {
// It is an error for the server to call the write callback when we asked for a map read
if (request.readHandle) {
return false;
}
if (status == WGPUBufferMapAsyncStatus_Success) {
if (mReadHandle || mWriteHandle) {
// Buffer is already mapped.
return false;
}
ASSERT(request.writeHandle != nullptr);
// Open the WriteHandle. This returns a pointer and size of mapped memory.
// On failure, |mappedData| may be null.
std::tie(mappedData, mappedDataLength) = request.writeHandle->Open();
if (mappedData == nullptr) {
return false;
}
// The MapWrite request was successful. The buffer now owns the WriteHandle until
// Unmap().
mWriteHandle = std::move(request.writeHandle);
}
return true;
};
if (!GetMappedData()) {
// Dawn promises that all callbacks are called in finite time. Even if a fatal error
// occurs, the callback is called.
request.writeCallback(WGPUBufferMapAsyncStatus_DeviceLost, nullptr, 0,
request.userdata);
return false;
} else {
mMappedData = mappedData;
request.writeCallback(static_cast<WGPUBufferMapAsyncStatus>(status), mappedData,
static_cast<uint64_t>(mappedDataLength), request.userdata);
return true;
}
}
void* Buffer::GetMappedRange() {
if (!IsMappedForWriting()) {
return nullptr;
}
return mMappedData;
}
const void* Buffer::GetConstMappedRange() {
if (!IsMappedForWriting() && !IsMappedForReading()) {
return nullptr;
}
return mMappedData;
}
void Buffer::Unmap() {
// Invalidate the local pointer, and cancel all other in-flight requests that would
// turn into errors anyway (you can't double map). This prevents race when the following
// happens, where the application code would have unmapped a buffer but still receive a
// callback:
// - Client -> Server: MapRequest1, Unmap, MapRequest2
// - Server -> Client: Result of MapRequest1
// - Unmap locally on the client
// - Server -> Client: Result of MapRequest2
if (mWriteHandle) {
// Writes need to be flushed before Unmap is sent. Unmap calls all associated
// in-flight callbacks which may read the updated data.
ASSERT(mReadHandle == nullptr);
// Get the serialization size of metadata to flush writes.
size_t writeFlushInfoLength = mWriteHandle->SerializeFlushSize();
BufferUpdateMappedDataCmd cmd;
cmd.bufferId = id;
cmd.writeFlushInfoLength = writeFlushInfoLength;
cmd.writeFlushInfo = nullptr;
char* writeHandleSpace =
device->GetClient()->SerializeCommand(cmd, writeFlushInfoLength);
// Serialize flush metadata into the space after the command.
// This closes the handle for writing.
mWriteHandle->SerializeFlush(writeHandleSpace);
mWriteHandle = nullptr;
} else if (mReadHandle) {
mReadHandle = nullptr;
}
mMappedData = nullptr;
ClearMapRequests(WGPUBufferMapAsyncStatus_Unknown);
BufferUnmapCmd cmd;
cmd.self = ToAPI(this);
device->GetClient()->SerializeCommand(cmd);
}
void Buffer::Destroy() {
// Cancel or remove all mappings
mWriteHandle = nullptr;
mReadHandle = nullptr;
mMappedData = nullptr;
ClearMapRequests(WGPUBufferMapAsyncStatus_Unknown);
BufferDestroyCmd cmd;
cmd.self = ToAPI(this);
device->GetClient()->SerializeCommand(cmd);
}
void Buffer::SetSubData(uint64_t start, uint64_t count, const void* data) {
BufferSetSubDataInternalCmd cmd;
cmd.bufferId = id;
cmd.start = start;
cmd.count = count;
cmd.data = static_cast<const uint8_t*>(data);
device->GetClient()->SerializeCommand(cmd);
}
bool Buffer::IsMappedForReading() const {
return mReadHandle != nullptr;
}
bool Buffer::IsMappedForWriting() const {
return mWriteHandle != nullptr;
}
}} // namespace dawn_wire::client