blob: 206bbaafc1a2e7bcab61da2f4e5675b7ce838aad [file]
// Copyright 2019 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <limits>
#include <memory>
#include <span>
#include <utility>
#include "dawn/wire/WireCmd_autogen.h"
#include "src/dawn/common/StringViewUtils.h"
#include "src/dawn/wire/BufferConsumer.h"
#include "src/dawn/wire/WireResult.h"
#include "src/dawn/wire/server/Server.h"
#include "src/utils/assert.h"
namespace dawn::wire::server {
WireResult Server::PreHandleDeviceCreateErrorBuffer(const DeviceCreateErrorBufferCmd& cmd) {
// mappedAtCreation isn't implemented in CreateErrorBuffer.
// The client blocks this, so we can treat it as a fatal wire error (for fuzzers).
if (cmd.descriptor->mappedAtCreation) {
return WireResult::FatalError;
}
return WireResult::Success;
}
WireResult Server::PreHandleBufferUnmap(const BufferUnmapCmd& cmd) {
Known<WGPUBuffer> buffer;
WIRE_TRY(Get(cmd.selfId, &buffer));
buffer->mapState.Use([&](auto mapState) {
if (buffer->mappedAtCreation &&
!(buffer->usage & (WGPUBufferUsage_MapWrite | WGPUBufferUsage_MapRead))) {
// This indicates the memoryHandle is for mappedAtCreation only. Destroy on unmap
// memoryHandle could have possibly been deleted if buffer is already destroyed so we
// don't assert it's non-null
mapState->memoryHandle = nullptr;
}
});
return WireResult::Success;
}
WireResult Server::PreHandleBufferDestroy(const BufferDestroyCmd& cmd) {
// Destroying a buffer does an implicit unmapping.
Known<WGPUBuffer> buffer;
WIRE_TRY(Get(cmd.selfId, &buffer));
// The buffer was destroyed. Clear the MemoryHandle.
buffer->mapState.Use([](auto mapState) { mapState->memoryHandle = nullptr; });
return WireResult::Success;
}
WireResult Server::DoBufferMapAsync(Known<WGPUBuffer> buffer,
Known<WGPUInstance> instance,
Future future,
wgpu::MapMode mode,
size_t offset,
size_t size) {
// These requests are just forwarded to the buffer, with userdata containing what the
// client will require in the return command.
std::unique_ptr<MapUserdata> userdata = MakeUserdata<MapUserdata>();
userdata->buffer = buffer.AsHandle();
userdata->instanceId = instance.id;
userdata->future = future;
userdata->mode = mode;
// Make sure that the size is not WGPU_WHOLE_MAP_SIZE because we want the client to give us an
// explicit size (so we don't have to handle the defaulting here). The client needs to track
// the size of the buffer and can do the default size computation.
if (size >= WGPU_WHOLE_MAP_SIZE) {
return WireResult::FatalError;
}
userdata->offset = offset;
userdata->size = size;
mProcs->bufferMapAsync(
buffer->handle, ToAPI(mode), offset, size,
MakeCallbackInfo<WGPUBufferMapCallbackInfo, &Server::OnBufferMapAsyncCallback>(
userdata.release()));
return WireResult::Success;
}
WireResult Server::DoDeviceCreateBuffer(Known<WGPUDevice> device,
const BufferDescriptor* descriptor,
ObjectHandle bufferHandle,
Span<const std::byte> memoryHandleCreateInfo) {
// Create and register the buffer object.
Reserved<WGPUBuffer> buffer;
WIRE_TRY(Allocate(&buffer, bufferHandle));
buffer->usage = ToAPI(descriptor->usage);
buffer->mappedAtCreation = descriptor->mappedAtCreation;
bool isMappable =
descriptor->mappedAtCreation != 0u ||
((descriptor->usage & (wgpu::BufferUsage::MapRead | wgpu::BufferUsage::MapWrite)) !=
wgpu::BufferUsage::None);
// If the buffer is not mappable, create it normally without shared memory.
if (!isMappable) {
buffer->handle = mProcs->deviceCreateBuffer(device->handle, ToAPI(descriptor));
return WireResult::Success;
}
// Return `FatalError` if we fail to deserialize the memory handle.
std::unique_ptr<MemoryTransferService::MemoryHandle> memoryHandle =
mMemoryTransferService->DeserializeMemoryHandle(memoryHandleCreateInfo);
if (memoryHandle == nullptr) {
buffer->handle = mProcs->deviceCreateBuffer(device->handle, ToAPI(descriptor));
return WireResult::FatalError;
}
// Try to wrap the shared memory into a buffer. Note that:
// - `TryWrapInBuffer()` may return a valid buffer, an error buffer or nullptr to align with the
// behavior of `DeviceBase::APICreateBuffer()`.
// - When a valid buffer is returned, `beginAccess()` must have been called on it.
// - Index/indirect buffers are not supported to prevent the TOCTOU issue with races between
// GPU-side validation and the client modifying the buffer.
if (!(descriptor->usage & (wgpu::BufferUsage::Indirect | wgpu::BufferUsage::Index))) {
buffer->handle =
memoryHandle->TryWrapInBuffer(mProcs.get(), device->handle, ToAPI(descriptor));
}
if (buffer->handle != nullptr) {
buffer->backedWithSharedMemory = true;
return buffer->mapState.Use([&](auto mapState) {
mapState->memoryHandle = std::move(memoryHandle);
return WireResult::Success;
});
} else {
// Returning `nullptr` from `TryWrapInBuffer()` indicates that we failed to wrap the shared
// memory into a buffer. Try to create it normally without shared memory.
buffer->handle = mProcs->deviceCreateBuffer(device->handle, ToAPI(descriptor));
}
// A null buffer indicates that mapping-at-creation failed inside createBuffer. Unmark the
// buffer as allocated so we will skip freeing it.
if (buffer->handle == nullptr) {
DAWN_ASSERT(descriptor->mappedAtCreation);
buffer->state = AllocationState::Reserved;
return WireResult::Success;
}
return buffer->mapState.Use([&](auto mapState) {
mapState->memoryHandle = std::move(memoryHandle);
return WireResult::Success;
});
}
WireResult Server::DoBufferUpdateMappedData(Known<WGPUBuffer> buffer,
Span<const std::byte> writeDataUpdateInfo,
size_t offset,
size_t size) {
if (size == WGPU_WHOLE_MAP_SIZE) {
return WireResult::FatalError;
}
// Buffers backed with shared memory are directly shared with client; no deserialization needed.
if (buffer->backedWithSharedMemory) {
return WireResult::Success;
}
return buffer->mapState.Use([&](auto mapState) {
std::byte* mappedData =
static_cast<std::byte*>(mProcs->bufferGetMappedRange(buffer->handle, offset, size));
// There are a few valid reasons why getting the mapped range would fail here:
// - The buffer was implicitly unmapped because of a device.Destroy() call.
// - The buffer was an error buffer created just to replace an OOM mappedAtCreation buffer.
// Unfortunately validating exactly that the failure is due to a valid reason and not
// another is difficult, so we return WireResult::Success even for misuses of the wire
// protocol (like a size being larger than the buffer's size, etc).
if (mappedData == nullptr) {
return WireResult::Success;
}
// SAFETY: If GetMappedRange returns non-null, it points to at least `size` valid bytes.
Span<std::byte> DAWN_UNSAFE_BUFFERS(mappedRange{mappedData, size});
// However it is easy to check for misuses of the wire protocol to UpdateMappedData without
// a MemoryHandle.
if (!mapState->memoryHandle) {
return WireResult::FatalError;
}
// Deserialize the flush info and flush updated data from the handle into mappedRange.
if (!mapState->memoryHandle->DeserializeDataUpdate(writeDataUpdateInfo, offset, size,
mappedRange)) {
return WireResult::FatalError;
}
return WireResult::Success;
});
}
void Server::OnBufferMapAsyncCallback(MapUserdata* data,
wgpu::MapAsyncStatus status,
StringView message) {
// Skip sending the callback if the buffer has already been destroyed.
Known<WGPUBuffer> buffer;
if (Get(data->buffer.id, &buffer) != WireResult::Success ||
buffer->generation != data->buffer.generation) {
return;
}
bool isSuccess = status == wgpu::MapAsyncStatus::Success;
ReturnBufferMapAsyncCallbackCmd cmd = {};
cmd.instanceId = data->instanceId;
cmd.future = data->future;
cmd.status = status;
cmd.message = message;
if (!isSuccess) {
SerializeCommand(std::move(cmd));
return;
}
switch (data->mode) {
case wgpu::MapMode::Read: {
DAWN_ASSERT(data->size != WGPU_WHOLE_MAP_SIZE); // Validated in DoBufferMapAsync.
// Buffers backed with shared memory are directly shared with client; no deserialization
// needed.
if (buffer->backedWithSharedMemory) {
SerializeCommand(std::move(cmd));
break;
}
buffer->mapState.Use([&](auto mapState) {
const std::byte* mappedData = static_cast<const std::byte*>(
mProcs->bufferGetConstMappedRange(buffer->handle, data->offset, data->size));
// SAFETY: If GetConstMappedRange with size != WGPU_WHOLE_MAP_SIZE returns non-null,
// it points to at least `size` valid bytes.
Span<const std::byte> DAWN_UNSAFE_BUFFERS(mappedRange{mappedData, data->size});
auto* memoryHandle = mapState->memoryHandle.get();
if (!memoryHandle) {
cmd.status = wgpu::MapAsyncStatus::Aborted;
SerializeCommand(std::move(cmd));
return;
}
size_t dataUpdateInfoLength =
memoryHandle->GetSerializeDataUpdateSize(data->offset, data->size);
SerializeCommand(
std::move(cmd),
// Extensions to replace fields skipped by skip_serialize.
CommandExtension<&ReturnBufferMapAsyncCallbackCmd::readDataUpdateInfo>{
dataUpdateInfoLength, [memoryHandle, data, mappedRange](
Span<volatile std::byte> serializeBuffer) {
// The in-flight map request returned successfully.
memoryHandle->SerializeDataUpdate(serializeBuffer, data->offset,
data->size, mappedRange);
}});
});
break;
}
case wgpu::MapMode::Write: {
SerializeCommand(std::move(cmd));
break;
}
default:
// If we are not one of the two possible modes, we should never succeed.
DAWN_UNREACHABLE();
break;
}
}
} // namespace dawn::wire::server