diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 7c558cd1..83d06668 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -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 diff --git a/app/src/main/cpp/skyline/gpu/buffer.cpp b/app/src/main/cpp/skyline/gpu/buffer.cpp new file mode 100644 index 00000000..fc85a345 --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/buffer.cpp @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#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 &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; + + explicit BufferGuestSync(std::shared_ptr buffer) : buffer(std::move(buffer)) {} + + ~BufferGuestSync() { + TRACE_EVENT("gpu", "Buffer::BufferGuestSync"); + buffer->SynchronizeGuest(); + } + }; + + void Buffer::SynchronizeGuestWithCycle(const std::shared_ptr &pCycle) { + if (pCycle != cycle.lock()) + WaitOnFence(); + + pCycle->AttachObject(std::make_shared(shared_from_this())); + cycle = pCycle; + } + + std::shared_ptr 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(shared_from_this(), offset, range, format)}; + views.push_back(view); + return view; + } + + BufferView::BufferView(std::shared_ptr 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; + } + } +} diff --git a/app/src/main/cpp/skyline/gpu/buffer.h b/app/src/main/cpp/skyline/gpu/buffer.h new file mode 100644 index 00000000..983adb6b --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/buffer.h @@ -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, 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, 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> views; //!< BufferView(s) that are backed by this Buffer, used for repointing to a new Buffer on deletion + + friend BufferView; + + public: + std::weak_ptr 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 &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 &cycle); + + /** + * @return A cached or newly created view into this buffer with the supplied attributes + */ + std::shared_ptr 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; + 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 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(); + }; +}