blob: 4cff98d03075afc66990ec0a8de13495022fbf82 [file] [log] [blame]
// 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.
#ifndef SRC_DAWN_WIRE_SERVER_OBJECTSTORAGE_H_
#define SRC_DAWN_WIRE_SERVER_OBJECTSTORAGE_H_
#include <algorithm>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_set.h"
#include "dawn/wire/WireCmd_autogen.h"
#include "dawn/wire/WireServer.h"
#include "partition_alloc/pointers/raw_ptr.h"
namespace dawn::wire::server {
// Whether this object has been allocated, or reserved for async object creation.
// Used by the KnownObjects queries
enum class AllocationState : uint32_t {
Free,
Reserved,
Allocated,
};
template <typename T>
struct ObjectDataBase {
// The backend-provided handle and generation to this object.
T handle = nullptr;
ObjectGeneration generation = 0;
AllocationState state;
};
// Stores what the backend knows about the type.
template <typename T>
struct ObjectData : public ObjectDataBase<T> {};
enum class BufferMapWriteState { Unmapped, Mapped, MapError };
template <>
struct ObjectData<WGPUBuffer> : public ObjectDataBase<WGPUBuffer> {
// TODO(enga): Use a tagged pointer to save space.
std::unique_ptr<MemoryTransferService::ReadHandle> readHandle;
std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle;
BufferMapWriteState mapWriteState = BufferMapWriteState::Unmapped;
WGPUBufferUsageFlags usage = WGPUBufferUsage_None;
// Indicate if writeHandle needs to be destroyed on unmap
bool mappedAtCreation = false;
};
struct DeviceInfo {
raw_ptr<Server> server;
ObjectHandle self;
};
template <>
struct ObjectData<WGPUDevice> : public ObjectDataBase<WGPUDevice> {
// Store |info| as a separate allocation so that its address does not move.
// The pointer to |info| is used as the userdata to device callback.
std::unique_ptr<DeviceInfo> info = std::make_unique<DeviceInfo>();
};
// Information of both an ID and an object data for use as a shorthand in doers. Reserved objects
// are guaranteed to have been reserved, but not guaranteed to be backed by a valid backend handle.
template <typename T>
struct Reserved {
ObjectId id;
raw_ptr<ObjectData<T>> data;
const ObjectData<T>* operator->() const {
DAWN_ASSERT(data != nullptr);
return data;
}
ObjectData<T>* operator->() {
DAWN_ASSERT(data != nullptr);
return data;
}
ObjectHandle AsHandle() const {
DAWN_ASSERT(data != nullptr);
return {id, data->generation};
}
};
// Information of both an ID and an object data for use as a shorthand in doers. Known objects are
// guaranteed to be backed by a valid backend handle.
template <typename T>
struct Known {
ObjectId id;
raw_ptr<ObjectData<T>> data;
const ObjectData<T>* operator->() const {
DAWN_ASSERT(data != nullptr);
DAWN_ASSERT(data->state == AllocationState::Allocated);
return data;
}
ObjectData<T>* operator->() {
DAWN_ASSERT(data != nullptr);
DAWN_ASSERT(data->state == AllocationState::Allocated);
return data;
}
ObjectHandle AsHandle() const {
DAWN_ASSERT(data != nullptr);
DAWN_ASSERT(data->state == AllocationState::Allocated);
return {id, data->generation};
}
};
// Keeps track of the mapping between client IDs and backend objects.
template <typename T>
class KnownObjectsBase {
public:
using Data = ObjectData<T>;
KnownObjectsBase() {
// Reserve ID 0 so that it can be used to represent nullptr for optional object values
// in the wire format. However don't tag it as allocated so that it is an error to ask
// KnownObjects for ID 0.
Data reservation;
reservation.handle = nullptr;
reservation.state = AllocationState::Free;
mKnown.push_back(std::move(reservation));
}
// Get a backend objects for a given client ID.
// Returns an error if the object wasn't previously allocated.
WireResult GetNativeHandle(ObjectId id, T* handle) const {
if (id >= mKnown.size()) {
return WireResult::FatalError;
}
const Data* data = &mKnown[id];
if (data->state != AllocationState::Allocated) {
return WireResult::FatalError;
}
*handle = data->handle;
return WireResult::Success;
}
WireResult Get(ObjectId id, Reserved<T>* result) {
if (id >= mKnown.size()) {
return WireResult::FatalError;
}
Data* data = &mKnown[id];
if (data->state == AllocationState::Free) {
return WireResult::FatalError;
}
*result = Reserved<T>{id, data};
return WireResult::Success;
}
WireResult Get(ObjectId id, Known<T>* result) {
if (id >= mKnown.size()) {
return WireResult::FatalError;
}
Data* data = &mKnown[id];
if (data->state != AllocationState::Allocated) {
return WireResult::FatalError;
}
*result = Known<T>{id, data};
return WireResult::Success;
}
WireResult FillReservation(ObjectId id, T handle, Known<T>* known = nullptr) {
DAWN_ASSERT(id < mKnown.size());
DAWN_ASSERT(handle != nullptr);
Data* data = &mKnown[id];
if (data->state != AllocationState::Reserved) {
return WireResult::FatalError;
}
data->handle = handle;
data->state = AllocationState::Allocated;
if (known != nullptr) {
*known = {id, data};
}
return WireResult::Success;
}
// Allocates the data for a given ID and returns it in result.
// Returns false if the ID is already allocated, or too far ahead, or if ID is 0 (ID 0 is
// reserved for nullptr). Invalidates all the Data*
WireResult Allocate(Reserved<T>* result,
ObjectHandle handle,
AllocationState state = AllocationState::Allocated) {
if (handle.id == 0 || handle.id > mKnown.size()) {
return WireResult::FatalError;
}
Data data;
data.state = state;
data.handle = nullptr;
if (handle.id >= mKnown.size()) {
mKnown.push_back(std::move(data));
*result = {handle.id, &mKnown.back()};
return WireResult::Success;
}
if (mKnown[handle.id].state != AllocationState::Free) {
return WireResult::FatalError;
}
// The generation should be strictly increasing.
if (handle.generation <= mKnown[handle.id].generation) {
return WireResult::FatalError;
}
// update the generation in the slot
data.generation = handle.generation;
mKnown[handle.id] = std::move(data);
*result = {handle.id, &mKnown[handle.id]};
return WireResult::Success;
}
// Marks an ID as deallocated
void Free(ObjectId id) {
DAWN_ASSERT(id < mKnown.size());
mKnown[id].state = AllocationState::Free;
}
std::vector<T> AcquireAllHandles() {
std::vector<T> objects;
for (Data& data : mKnown) {
if (data.state == AllocationState::Allocated && data.handle != nullptr) {
objects.push_back(data.handle);
data.state = AllocationState::Free;
data.handle = nullptr;
}
}
return objects;
}
std::vector<T> GetAllHandles() const {
std::vector<T> objects;
for (const Data& data : mKnown) {
if (data.state == AllocationState::Allocated && data.handle != nullptr) {
objects.push_back(data.handle);
}
}
return objects;
}
protected:
std::vector<Data> mKnown;
};
template <typename T>
class KnownObjects : public KnownObjectsBase<T> {
public:
KnownObjects() = default;
};
template <>
class KnownObjects<WGPUDevice> : public KnownObjectsBase<WGPUDevice> {
public:
KnownObjects() = default;
WireResult Allocate(Reserved<WGPUDevice>* result,
ObjectHandle handle,
AllocationState state = AllocationState::Allocated) {
WIRE_TRY(KnownObjectsBase<WGPUDevice>::Allocate(result, handle, state));
return WireResult::Success;
}
WireResult FillReservation(ObjectId id, WGPUDevice handle, Known<WGPUDevice>* known = nullptr) {
auto result = KnownObjectsBase<WGPUDevice>::FillReservation(id, handle, known);
if (result == WireResult::Success) {
mKnownSet.insert((*known)->handle);
}
return result;
}
void Free(ObjectId id) {
mKnownSet.erase(mKnown[id].handle);
KnownObjectsBase<WGPUDevice>::Free(id);
}
bool IsKnown(WGPUDevice device) const { return mKnownSet.contains(device); }
private:
absl::flat_hash_set<WGPUDevice> mKnownSet;
};
} // namespace dawn::wire::server
#endif // SRC_DAWN_WIRE_SERVER_OBJECTSTORAGE_H_