diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 83d06668..25f03f41 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -157,6 +157,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/gpu/quirk_manager.cpp ${source_DIR}/skyline/gpu/memory_manager.cpp ${source_DIR}/skyline/gpu/texture_manager.cpp + ${source_DIR}/skyline/gpu/buffer_manager.cpp ${source_DIR}/skyline/gpu/command_scheduler.cpp ${source_DIR}/skyline/gpu/texture/texture.cpp ${source_DIR}/skyline/gpu/buffer.cpp diff --git a/app/src/main/cpp/skyline/gpu.cpp b/app/src/main/cpp/skyline/gpu.cpp index 55ce3daf..ce81d89c 100644 --- a/app/src/main/cpp/skyline/gpu.cpp +++ b/app/src/main/cpp/skyline/gpu.cpp @@ -213,5 +213,6 @@ namespace skyline::gpu { scheduler(*this), presentation(state, *this), texture(*this), + buffer(*this), shader(state, *this) {} } diff --git a/app/src/main/cpp/skyline/gpu.h b/app/src/main/cpp/skyline/gpu.h index 9e3c2dee..e69fdcbd 100644 --- a/app/src/main/cpp/skyline/gpu.h +++ b/app/src/main/cpp/skyline/gpu.h @@ -8,6 +8,7 @@ #include "gpu/command_scheduler.h" #include "gpu/presentation_engine.h" #include "gpu/texture_manager.h" +#include "gpu/buffer_manager.h" #include "gpu/shader_manager.h" namespace skyline::gpu { @@ -45,6 +46,7 @@ namespace skyline::gpu { PresentationEngine presentation; TextureManager texture; + BufferManager buffer; ShaderManager shader; diff --git a/app/src/main/cpp/skyline/gpu/buffer.h b/app/src/main/cpp/skyline/gpu/buffer.h index 983adb6b..9008229c 100644 --- a/app/src/main/cpp/skyline/gpu/buffer.h +++ b/app/src/main/cpp/skyline/gpu/buffer.h @@ -21,6 +21,7 @@ namespace skyline::gpu { }; struct BufferView; + class BufferManager; /** * @brief A buffer which is backed by host constructs while being synchronized with the underlying guest buffer @@ -36,6 +37,7 @@ namespace skyline::gpu { std::vector> views; //!< BufferView(s) that are backed by this Buffer, used for repointing to a new Buffer on deletion friend BufferView; + friend BufferManager; 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 diff --git a/app/src/main/cpp/skyline/gpu/buffer_manager.cpp b/app/src/main/cpp/skyline/gpu/buffer_manager.cpp new file mode 100644 index 00000000..ca583f1d --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/buffer_manager.cpp @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include + +#include "buffer_manager.h" + +namespace skyline::gpu { + BufferManager::BufferManager(GPU &gpu) : gpu(gpu) {} + + std::shared_ptr BufferManager::FindOrCreate(const GuestBuffer &guest) { + auto guestMapping{guest.mappings.front()}; + + /* + * Iterate over all buffers that overlap with the first mapping of the guest buffer and compare the mappings: + * 1) All mappings match up perfectly, we check that the rest of the supplied mappings correspond to mappings in the buffer + * 1.1) If they match as well, we return a view encompassing the entire buffer + * 2) Only a contiguous range of mappings match, we check for the overlap bounds, it can go two ways: + * 2.1) If the supplied buffer is smaller than the matching buffer, we return a view encompassing the mappings into the buffer + * 2.2) If the matching buffer is smaller than the supplied buffer, we make the matching buffer larger and return it + * 3) If there's another overlap we go back to (1) with it else we go to (4) + * 4) Create a new buffer and insert it in the map then return it + */ + + std::scoped_lock lock(mutex); + std::shared_ptr match{}; + auto mappingEnd{std::upper_bound(buffers.begin(), buffers.end(), guestMapping)}, hostMapping{mappingEnd}; + if (hostMapping != buffers.begin() && (--hostMapping)->end() > guestMapping.begin()) { + auto &hostMappings{hostMapping->buffer->guest.mappings}; + if (hostMapping->contains(guestMapping)) { + // We need to check that all corresponding mappings in the candidate buffer and the guest buffer match up + // Only the start of the first matched mapping and the end of the last mapping can not match up as this is the case for views + auto firstHostMapping{hostMapping->iterator}; + auto lastGuestMapping{guest.mappings.back()}; + auto endHostMapping{std::find_if(firstHostMapping, hostMappings.end(), [&lastGuestMapping](const span &it) { + return lastGuestMapping.begin() > it.begin() && lastGuestMapping.end() > it.end(); + })}; //!< A past-the-end iterator for the last host mapping, the final valid mapping is prior to this iterator + bool mappingMatch{std::equal(firstHostMapping, endHostMapping, guest.mappings.begin(), guest.mappings.end(), [](const span &lhs, const span &rhs) { + return lhs.end() == rhs.end(); // We check end() here to implicitly ignore any offset from the first mapping + })}; + + auto &lastHostMapping{*std::prev(endHostMapping)}; + if (firstHostMapping == hostMappings.begin() && firstHostMapping->begin() == guestMapping.begin() && mappingMatch && endHostMapping == hostMappings.end() && lastGuestMapping.end() == lastHostMapping.end()) { + // We've gotten a perfect 1:1 match for *all* mappings from the start to end + std::scoped_lock bufferLock(*hostMapping->buffer); + return hostMapping->buffer->GetView(0, hostMapping->buffer->size, guest.format); + } else if (mappingMatch && firstHostMapping->begin() > guestMapping.begin() && lastHostMapping.end() > lastGuestMapping.end()) { + // We've gotten a guest buffer that is located entirely within a host buffer + std::scoped_lock bufferLock(*hostMapping->buffer); + return hostMapping->buffer->GetView(hostMapping->offset + static_cast(hostMapping->begin() - guestMapping.begin()), guest.BufferSize(), guest.format); + } + } + } + + /* TODO: Handle overlapping buffers + // Create a list of all overlapping buffers and update the guest mappings to fit them all + boost::container::small_vector, u32>, 4> overlappingBuffers; + GuestBuffer::Mappings newMappings; + + auto guestMappingIt{guest.mappings.begin()}; + while (true) { + do { + hostMapping->begin(); + overlappingBuffers.emplace_back(hostMapping->buffer, 4); + } while (hostMapping != buffers.begin() && (--hostMapping)->end() > guestMappingIt->begin()); + + // Iterate over all guest mappings to find overlapping buffers, not just the first + auto nextGuestMappingIt{std::next(guestMappingIt)}; + if (nextGuestMappingIt != guest.mappings.end()) + hostMapping = std::upper_bound(buffers.begin(), buffers.end(), *nextGuestMappingIt); + else + break; + guestMappingIt = nextGuestMappingIt; + } + + // Create a buffer that can contain all the overlapping buffers + auto buffer{std::make_shared(gpu, guest)}; + + // Delete mappings from all overlapping buffers and repoint all buffer views + for (auto &overlappingBuffer : overlappingBuffers) { + std::scoped_lock overlappingBufferLock(*overlappingBuffer.first); + auto &bufferMappings{hostMapping->buffer->guest.mappings}; + + // Delete all mappings of the overlapping buffers + while ((++it) != buffer->guest.mappings.end()) { + guestMapping = *it; + auto mapping{std::upper_bound(buffers.begin(), buffers.end(), guestMapping)}; + buffers.emplace(mapping, BufferMapping{buffer, it, offset, guestMapping}); + offset += mapping->size_bytes(); + } + } + */ + + auto buffer{std::make_shared(gpu, guest)}; + auto it{buffer->guest.mappings.begin()}; + buffers.emplace(mappingEnd, BufferMapping{buffer, it, 0, guestMapping}); + + vk::DeviceSize offset{}; + while ((++it) != buffer->guest.mappings.end()) { + guestMapping = *it; + auto mapping{std::upper_bound(buffers.begin(), buffers.end(), guestMapping)}; + buffers.emplace(mapping, BufferMapping{buffer, it, offset, guestMapping}); + offset += mapping->size_bytes(); + } + + return buffer->GetView(0, buffer->size, guest.format); + } +} diff --git a/app/src/main/cpp/skyline/gpu/buffer_manager.h b/app/src/main/cpp/skyline/gpu/buffer_manager.h new file mode 100644 index 00000000..0b1d40d8 --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/buffer_manager.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include "buffer.h" + +namespace skyline::gpu { + /** + * @brief The Buffer Manager is responsible for maintaining a global view of buffers being mapped from the guest to the host, any lookups and creation of host buffer from equivalent guest buffer alongside reconciliation of any overlaps with existing textures + */ + class BufferManager { + private: + /** + * @brief A single contiguous mapping of a buffer in the CPU address space + */ + struct BufferMapping : span { + std::shared_ptr buffer; + GuestBuffer::Mappings::iterator iterator; //!< An iterator to the mapping in the buffer's GuestBufferMappings corresponding to this mapping + vk::DeviceSize offset; //!< Offset of this mapping relative to the start of the buffer + + template + BufferMapping(std::shared_ptr buffer, GuestBuffer::Mappings::iterator iterator, vk::DeviceSize offset, Args &&... args) + : span(std::forward(args)...), + buffer(std::move(buffer)), + iterator(iterator), + offset(offset) {} + }; + + GPU &gpu; + std::mutex mutex; //!< Synchronizes access to the buffer mappings + std::vector buffers; //!< A sorted vector of all buffer mappings + + public: + BufferManager(GPU &gpu); + + /** + * @return A pre-existing or newly created Buffer object which covers the supplied mappings + */ + std::shared_ptr FindOrCreate(const GuestBuffer &guest); + }; +}