Introduce BufferManager

The Buffer Manager handles mapping of guest buffers to host buffer views with automatic handling of sub-buffers and eventually supporting recreation of overlapping buffers to create a single larger buffer.
This commit is contained in:
PixelyIon 2021-12-06 21:43:43 +05:30
parent bde61d72cc
commit 03314ec7d2
6 changed files with 156 additions and 0 deletions

View File

@ -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

View File

@ -213,5 +213,6 @@ namespace skyline::gpu {
scheduler(*this),
presentation(state, *this),
texture(*this),
buffer(*this),
shader(state, *this) {}
}

View File

@ -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;

View File

@ -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<std::weak_ptr<BufferView>> 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<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

View File

@ -0,0 +1,108 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <gpu.h>
#include "buffer_manager.h"
namespace skyline::gpu {
BufferManager::BufferManager(GPU &gpu) : gpu(gpu) {}
std::shared_ptr<BufferView> 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<Buffer> 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<u8> &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<u8> &lhs, const span<u8> &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<vk::DeviceSize>(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<std::pair<std::shared_ptr<Buffer>, 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<Buffer>(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<Buffer>(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);
}
}

View File

@ -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<u8> {
std::shared_ptr<Buffer> 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<typename... Args>
BufferMapping(std::shared_ptr<Buffer> buffer, GuestBuffer::Mappings::iterator iterator, vk::DeviceSize offset, Args &&... args)
: span<u8>(std::forward<Args>(args)...),
buffer(std::move(buffer)),
iterator(iterator),
offset(offset) {}
};
GPU &gpu;
std::mutex mutex; //!< Synchronizes access to the buffer mappings
std::vector<BufferMapping> 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<BufferView> FindOrCreate(const GuestBuffer &guest);
};
}