Introduce Buffer and BufferView

Implements infrastructure for using guest buffers on the host for rendering, a `BufferManager` is still missing which'd handle mapping from guest buffers to host buffers and will be subsequently committed. It should be noted that `BufferView` is also disconnected from `Buffer` and shared for every instance with the same properties like `TextureView` is now.
This commit is contained in:
PixelyIon 2021-12-06 12:00:39 +05:30
parent 6eda1777c5
commit bde61d72cc
3 changed files with 284 additions and 0 deletions

View File

@ -159,6 +159,7 @@ add_library(skyline SHARED
${source_DIR}/skyline/gpu/texture_manager.cpp
${source_DIR}/skyline/gpu/command_scheduler.cpp
${source_DIR}/skyline/gpu/texture/texture.cpp
${source_DIR}/skyline/gpu/buffer.cpp
${source_DIR}/skyline/gpu/presentation_engine.cpp
${source_DIR}/skyline/gpu/shader_manager.cpp
${source_DIR}/skyline/gpu/interconnect/command_executor.cpp

View File

@ -0,0 +1,138 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <gpu.h>
#include <common/trace.h>
#include "buffer.h"
namespace skyline::gpu {
vk::DeviceSize GuestBuffer::BufferSize() const {
vk::DeviceSize size{};
for (const auto &buffer : mappings)
size += buffer.size_bytes();
return size;
}
Buffer::Buffer(GPU &gpu, GuestBuffer guest) : size(guest.BufferSize()), backing(gpu.memory.AllocateBuffer(size)), guest(std::move(guest)) {
SynchronizeHost();
}
void Buffer::WaitOnFence() {
TRACE_EVENT("gpu", "Buffer::WaitOnFence");
auto lCycle{cycle.lock()};
if (lCycle) {
lCycle->Wait();
cycle.reset();
}
}
void Buffer::SynchronizeHost() {
WaitOnFence();
TRACE_EVENT("gpu", "Buffer::SynchronizeHost");
auto host{backing.data()};
for (auto &mapping : guest.mappings) {
auto mappingSize{mapping.size_bytes()};
std::memcpy(host, mapping.data(), mappingSize);
host += mappingSize;
}
}
void Buffer::SynchronizeHostWithCycle(const std::shared_ptr<FenceCycle> &pCycle) {
if (pCycle != cycle.lock())
WaitOnFence();
TRACE_EVENT("gpu", "Buffer::SynchronizeHostWithCycle");
auto host{backing.data()};
for (auto &mapping : guest.mappings) {
auto mappingSize{mapping.size_bytes()};
std::memcpy(host, mapping.data(), mappingSize);
host += mappingSize;
}
}
void Buffer::SynchronizeGuest() {
WaitOnFence();
TRACE_EVENT("gpu", "Buffer::SynchronizeGuest");
auto host{backing.data()};
for (auto &mapping : guest.mappings) {
auto mappingSize{mapping.size_bytes()};
std::memcpy(mapping.data(), host, mappingSize);
host += mappingSize;
}
}
/**
* @brief A FenceCycleDependency that synchronizes the contents of a host buffer with the guest buffer
*/
struct BufferGuestSync : public FenceCycleDependency {
std::shared_ptr<Buffer> buffer;
explicit BufferGuestSync(std::shared_ptr<Buffer> buffer) : buffer(std::move(buffer)) {}
~BufferGuestSync() {
TRACE_EVENT("gpu", "Buffer::BufferGuestSync");
buffer->SynchronizeGuest();
}
};
void Buffer::SynchronizeGuestWithCycle(const std::shared_ptr<FenceCycle> &pCycle) {
if (pCycle != cycle.lock())
WaitOnFence();
pCycle->AttachObject(std::make_shared<BufferGuestSync>(shared_from_this()));
cycle = pCycle;
}
std::shared_ptr<BufferView> Buffer::GetView(vk::DeviceSize offset, vk::DeviceSize range, vk::Format format) {
for (const auto &viewWeak : views) {
auto view{viewWeak.lock()};
if (view && view->offset == offset && view->range == range && view->format == format)
return view;
}
auto view{std::make_shared<BufferView>(shared_from_this(), offset, range, format)};
views.push_back(view);
return view;
}
BufferView::BufferView(std::shared_ptr<Buffer> backing, vk::DeviceSize offset, vk::DeviceSize range, vk::Format format) : buffer(std::move(backing)), offset(offset), range(range), format(format) {}
void BufferView::lock() {
auto currentBacking{std::atomic_load(&buffer)};
while (true) {
currentBacking->lock();
auto newBacking{std::atomic_load(&buffer)};
if (currentBacking == newBacking)
return;
currentBacking->unlock();
currentBacking = newBacking;
}
}
void BufferView::unlock() {
buffer->unlock();
}
bool BufferView::try_lock() {
auto currentBacking{std::atomic_load(&buffer)};
while (true) {
bool success{currentBacking->try_lock()};
auto newBacking{std::atomic_load(&buffer)};
if (currentBacking == newBacking)
return success;
if (success)
currentBacking->unlock();
currentBacking = newBacking;
}
}
}

View File

@ -0,0 +1,145 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include "memory_manager.h"
namespace skyline::gpu {
/**
* @brief A descriptor for a GPU buffer on the guest
*/
struct GuestBuffer {
using Mappings = boost::container::small_vector<span<u8>, 3>;
Mappings mappings; //!< Spans to CPU memory for the underlying data backing this buffer
vk::Format format;
/**
* @return The total size of the buffer by adding up the size of all mappings
*/
vk::DeviceSize BufferSize() const;
};
struct BufferView;
/**
* @brief A buffer which is backed by host constructs while being synchronized with the underlying guest buffer
* @note This class conforms to the Lockable and BasicLockable C++ named requirements
*/
class Buffer : public std::enable_shared_from_this<Buffer>, public FenceCycleDependency {
private:
std::mutex mutex; //!< Synchronizes any mutations to the buffer or its backing
vk::DeviceSize size;
memory::Buffer backing;
GuestBuffer guest;
std::vector<std::weak_ptr<BufferView>> views; //!< BufferView(s) that are backed by this Buffer, used for repointing to a new Buffer on deletion
friend BufferView;
public:
std::weak_ptr<FenceCycle> cycle; //!< A fence cycle for when any host operation mutating the buffer has completed, it must be waited on prior to any mutations to the backing
constexpr vk::Buffer GetBacking() {
return backing.vkBuffer;
}
Buffer(GPU &gpu, GuestBuffer guest);
/**
* @brief Acquires an exclusive lock on the texture for the calling thread
* @note Naming is in accordance to the BasicLockable named requirement
*/
void lock() {
mutex.lock();
}
/**
* @brief Relinquishes an existing lock on the texture by the calling thread
* @note Naming is in accordance to the BasicLockable named requirement
*/
void unlock() {
mutex.unlock();
}
/**
* @brief Attempts to acquire an exclusive lock but returns immediately if it's captured by another thread
* @note Naming is in accordance to the Lockable named requirement
*/
bool try_lock() {
return mutex.try_lock();
}
/**
* @brief Waits on a fence cycle if it exists till it's signalled and resets it after
* @note The buffer **must** be locked prior to calling this
*/
void WaitOnFence();
/**
* @brief Synchronizes the host buffer with the guest
* @note The buffer **must** be locked prior to calling this
*/
void SynchronizeHost();
/**
* @brief Synchronizes the host buffer with the guest
* @param cycle A FenceCycle that is checked against the held one to skip waiting on it when equal
* @note The buffer **must** be locked prior to calling this
*/
void SynchronizeHostWithCycle(const std::shared_ptr<FenceCycle> &cycle);
/**
* @brief Synchronizes the guest buffer with the host buffer
* @note The buffer **must** be locked prior to calling this
*/
void SynchronizeGuest();
/**
* @brief Synchronizes the guest buffer with the host buffer when the FenceCycle is signalled
* @note The buffer **must** be locked prior to calling this
* @note The guest buffer should not be null prior to calling this
*/
void SynchronizeGuestWithCycle(const std::shared_ptr<FenceCycle> &cycle);
/**
* @return A cached or newly created view into this buffer with the supplied attributes
*/
std::shared_ptr<BufferView> GetView(vk::DeviceSize offset, vk::DeviceSize range, vk::Format format);
};
/**
* @brief A contiguous view into a Vulkan Buffer that represents a single guest buffer (as opposed to Buffer objects which contain multiple)
* @note The object **must** be locked prior to accessing any members as values will be mutated
* @note This class conforms to the Lockable and BasicLockable C++ named requirements
*/
struct BufferView {
std::shared_ptr<Buffer> buffer;
vk::DeviceSize offset;
vk::DeviceSize range;
vk::Format format;
/**
* @note A view must **NOT** be constructed directly, it should always be retrieved using Texture::GetView
*/
BufferView(std::shared_ptr<Buffer> backing, vk::DeviceSize offset, vk::DeviceSize range, vk::Format format);
/**
* @brief Acquires an exclusive lock on the buffer for the calling thread
* @note Naming is in accordance to the BasicLockable named requirement
*/
void lock();
/**
* @brief Relinquishes an existing lock on the buffer by the calling thread
* @note Naming is in accordance to the BasicLockable named requirement
*/
void unlock();
/**
* @brief Attempts to acquire an exclusive lock but returns immediately if it's captured by another thread
* @note Naming is in accordance to the Lockable named requirement
*/
bool try_lock();
};
}