Lock TextureManager/BufferManager during submission

Multiple threads concurrently accessing the `TextureManager`/`BufferManager` (Referred to as "resource managers") has a potential deadlock with a resource being locked while acquiring the resource manager lock while the thread owning it tries to acquire a lock on the resource resulting in a deadlock.

This has been fixed with locking of resource manager now being externally handled which ensures it can be locked prior to locking any resources, `CommandExecutor` provides accessors for retrieving the resource manager which automatically handles locking aside doing so on attachment of resources.
This commit is contained in:
PixelyIon 2022-06-26 14:58:58 +05:30
parent 1239907ce8
commit 0ac5f4ce27
No known key found for this signature in database
GPG Key ID: 11BC6C3201BC2C05
9 changed files with 117 additions and 16 deletions

View File

@ -12,6 +12,18 @@ namespace skyline::gpu {
return it->guest->begin().base() < pointer; return it->guest->begin().base() < pointer;
} }
void BufferManager::lock() {
mutex.lock();
}
void BufferManager::unlock() {
mutex.unlock();
}
bool BufferManager::try_lock() {
return mutex.try_lock();
}
BufferView BufferManager::FindOrCreate(GuestBuffer guestMapping, ContextTag tag) { BufferView BufferManager::FindOrCreate(GuestBuffer guestMapping, ContextTag tag) {
/* /*
* We align the buffer to the page boundary to ensure that: * We align the buffer to the page boundary to ensure that:
@ -22,8 +34,6 @@ namespace skyline::gpu {
vk::DeviceSize offset{static_cast<size_t>(guestMapping.begin().base() - alignedStart)}, size{guestMapping.size()}; vk::DeviceSize offset{static_cast<size_t>(guestMapping.begin().base() - alignedStart)}, size{guestMapping.size()};
guestMapping = span<u8>{alignedStart, alignedEnd}; guestMapping = span<u8>{alignedStart, alignedEnd};
std::scoped_lock lock(mutex);
// Lookup for any buffers overlapping with the supplied guest mapping // Lookup for any buffers overlapping with the supplied guest mapping
boost::container::small_vector<std::shared_ptr<Buffer>, 4> overlaps; boost::container::small_vector<std::shared_ptr<Buffer>, 4> overlaps;
for (auto entryIt{std::lower_bound(buffers.begin(), buffers.end(), guestMapping.end().base(), BufferLessThan)}; entryIt != buffers.begin() && (*--entryIt)->guest->begin() <= guestMapping.end();) for (auto entryIt{std::lower_bound(buffers.begin(), buffers.end(), guestMapping.end().base(), BufferLessThan)}; entryIt != buffers.begin() && (*--entryIt)->guest->begin() <= guestMapping.end();)
@ -89,7 +99,9 @@ namespace skyline::gpu {
return newBuffer->GetView(static_cast<vk::DeviceSize>(guestMapping.begin() - newBuffer->guest->begin()) + offset, size); return newBuffer->GetView(static_cast<vk::DeviceSize>(guestMapping.begin() - newBuffer->guest->begin()) + offset, size);
} }
BufferManager::MegaBufferSlot::MegaBufferSlot(GPU &gpu) : backing(gpu.memory.AllocateBuffer(Size)) {} constexpr static vk::DeviceSize MegaBufferSize{100 * 1024 * 1024}; //!< Size in bytes of the megabuffer (100MiB)
BufferManager::MegaBufferSlot::MegaBufferSlot(GPU &gpu) : backing(gpu.memory.AllocateBuffer(MegaBufferSize)) {}
MegaBuffer::MegaBuffer(BufferManager::MegaBufferSlot &slot) : slot{&slot}, freeRegion{slot.backing.subspan(PAGE_SIZE)} {} MegaBuffer::MegaBuffer(BufferManager::MegaBufferSlot &slot) : slot{&slot}, freeRegion{slot.backing.subspan(PAGE_SIZE)} {}
@ -135,7 +147,7 @@ namespace skyline::gpu {
} }
MegaBuffer BufferManager::AcquireMegaBuffer(const std::shared_ptr<FenceCycle> &cycle) { MegaBuffer BufferManager::AcquireMegaBuffer(const std::shared_ptr<FenceCycle> &cycle) {
std::scoped_lock lock{mutex}; std::scoped_lock lock{megaBufferMutex};
for (auto &slot : megaBuffers) { for (auto &slot : megaBuffers) {
if (!slot.active.test_and_set(std::memory_order_acq_rel)) { if (!slot.active.test_and_set(std::memory_order_acq_rel)) {

View File

@ -17,6 +17,8 @@ namespace skyline::gpu {
std::mutex mutex; //!< Synchronizes access to the buffer mappings std::mutex mutex; //!< Synchronizes access to the buffer mappings
std::vector<std::shared_ptr<Buffer>> buffers; //!< A sorted vector of all buffer mappings std::vector<std::shared_ptr<Buffer>> buffers; //!< A sorted vector of all buffer mappings
std::mutex megaBufferMutex; //!< Synchronizes access to the allocated megabuffers
friend class MegaBuffer; friend class MegaBuffer;
/** /**
@ -26,7 +28,6 @@ namespace skyline::gpu {
std::atomic_flag active{true}; //!< If the megabuffer is currently being utilized, we want to construct a buffer as active std::atomic_flag active{true}; //!< If the megabuffer is currently being utilized, we want to construct a buffer as active
std::shared_ptr<FenceCycle> cycle; //!< The latest cycle on the fence, all waits must be performed through this std::shared_ptr<FenceCycle> cycle; //!< The latest cycle on the fence, all waits must be performed through this
constexpr static vk::DeviceSize Size{100 * 1024 * 1024}; //!< Size in bytes of the megabuffer (100MiB)
memory::Buffer backing; //!< The GPU buffer as the backing storage for the megabuffer memory::Buffer backing; //!< The GPU buffer as the backing storage for the megabuffer
MegaBufferSlot(GPU &gpu); MegaBufferSlot(GPU &gpu);
@ -42,14 +43,34 @@ namespace skyline::gpu {
BufferManager(GPU &gpu); BufferManager(GPU &gpu);
/**
* @brief Acquires an exclusive lock on the texture for the calling thread
* @note Naming is in accordance to the BasicLockable named requirement
*/
void 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();
/**
* @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 A pre-existing or newly created Buffer object which covers the supplied mappings * @return A pre-existing or newly created Buffer object which covers the supplied mappings
* @note The buffer manager **must** be locked prior to calling this
*/ */
BufferView FindOrCreate(GuestBuffer guestMapping, ContextTag tag = {}); BufferView FindOrCreate(GuestBuffer guestMapping, ContextTag tag = {});
/** /**
* @return A dynamically allocated megabuffer which can be used to store buffer modifications allowing them to be replayed in-sequence on the GPU * @return A dynamically allocated megabuffer which can be used to store buffer modifications allowing them to be replayed in-sequence on the GPU
* @note This object **must** be destroyed to be reclaimed by the manager and prevent a memory leak * @note This object **must** be destroyed to be reclaimed by the manager and prevent a memory leak
* @note The buffer manager **doesn't** need to be locked prior to calling this
*/ */
MegaBuffer AcquireMegaBuffer(const std::shared_ptr<FenceCycle> &cycle); MegaBuffer AcquireMegaBuffer(const std::shared_ptr<FenceCycle> &cycle);
}; };

View File

@ -118,11 +118,12 @@ namespace skyline::gpu::interconnect {
auto srcGuestTexture{GetGuestTexture(srcSurface)}; auto srcGuestTexture{GetGuestTexture(srcSurface)};
auto dstGuestTexture{GetGuestTexture(dstSurface)}; auto dstGuestTexture{GetGuestTexture(dstSurface)};
auto srcTextureView{gpu.texture.FindOrCreate(srcGuestTexture, executor.tag)}; auto &textureManager{executor.AcquireTextureManager()};
executor.AttachTexture(&*srcTextureView); auto srcTextureView{textureManager.FindOrCreate(srcGuestTexture, executor.tag)};
executor.AttachTexture(srcTextureView.get());
auto dstTextureView{gpu.texture.FindOrCreate(dstGuestTexture, executor.tag)}; auto dstTextureView{textureManager.FindOrCreate(dstGuestTexture, executor.tag)};
executor.AttachTexture(&*dstTextureView); executor.AttachTexture(dstTextureView.get());
auto getSubresourceLayers{[](const vk::ImageSubresourceRange &range, vk::ImageAspectFlags aspect) { auto getSubresourceLayers{[](const vk::ImageSubresourceRange &range, vk::ImageAspectFlags aspect) {
return vk::ImageSubresourceLayers{ return vk::ImageSubresourceLayers{

View File

@ -11,6 +11,18 @@ namespace skyline::gpu::interconnect {
cycle->Cancel(); cycle->Cancel();
} }
TextureManager &CommandExecutor::AcquireTextureManager() {
if (!textureManagerLock)
textureManagerLock.emplace(gpu.texture);
return gpu.texture;
}
BufferManager &CommandExecutor::AcquireBufferManager() {
if (!bufferManagerLock)
bufferManagerLock.emplace(gpu.buffer);
return gpu.buffer;
}
bool CommandExecutor::CreateRenderPassWithSubpass(vk::Rect2D renderArea, span<TextureView *> inputAttachments, span<TextureView *> colorAttachments, TextureView *depthStencilAttachment) { bool CommandExecutor::CreateRenderPassWithSubpass(vk::Rect2D renderArea, span<TextureView *> inputAttachments, span<TextureView *> colorAttachments, TextureView *depthStencilAttachment) {
auto addSubpass{[&] { auto addSubpass{[&] {
renderPass->AddSubpass(inputAttachments, colorAttachments, depthStencilAttachment, gpu); renderPass->AddSubpass(inputAttachments, colorAttachments, depthStencilAttachment, gpu);
@ -85,6 +97,10 @@ namespace skyline::gpu::interconnect {
} }
bool CommandExecutor::AttachTexture(TextureView *view) { bool CommandExecutor::AttachTexture(TextureView *view) {
if (!textureManagerLock)
// Avoids a potential deadlock with this resource being locked while acquiring the TextureManager lock while the thread owning it tries to acquire a lock on this texture
textureManagerLock.emplace(gpu.texture);
bool didLock{view->LockWithTag(tag)}; bool didLock{view->LockWithTag(tag)};
if (didLock) if (didLock)
attachedTextures.emplace_back(view->texture); attachedTextures.emplace_back(view->texture);
@ -105,6 +121,10 @@ namespace skyline::gpu::interconnect {
} }
bool CommandExecutor::AttachBuffer(BufferView &view) { bool CommandExecutor::AttachBuffer(BufferView &view) {
if (!bufferManagerLock)
// See AttachTexture(...)
bufferManagerLock.emplace(gpu.buffer);
bool didLock{view->LockWithTag(tag)}; bool didLock{view->LockWithTag(tag)};
if (didLock) if (didLock)
attachedBuffers.emplace_back(view->buffer); attachedBuffers.emplace_back(view->buffer);

View File

@ -20,6 +20,9 @@ namespace skyline::gpu::interconnect {
node::RenderPassNode *renderPass{}; node::RenderPassNode *renderPass{};
size_t subpassCount{}; //!< The number of subpasses in the current render pass size_t subpassCount{}; //!< The number of subpasses in the current render pass
std::optional<std::scoped_lock<TextureManager>> textureManagerLock; //!< The lock on the texture manager, this is locked for the duration of the command execution from the first usage inside an execution to the submission
std::optional<std::scoped_lock<BufferManager>> bufferManagerLock; //!< The lock on the buffer manager, this is locked for the duration of the command execution from the first usage inside an execution to the submission
/** /**
* @brief A wrapper of a Texture object that has been locked beforehand and must be unlocked afterwards * @brief A wrapper of a Texture object that has been locked beforehand and must be unlocked afterwards
*/ */
@ -95,6 +98,12 @@ namespace skyline::gpu::interconnect {
~CommandExecutor(); ~CommandExecutor();
/**
* @return A reference to an instance of the Texture Manager which will be locked till execution
* @note Any access to the texture manager while recording commands **must** be done via this
*/
TextureManager &AcquireTextureManager();
/** /**
* @brief Attach the lifetime of the texture to the command buffer * @brief Attach the lifetime of the texture to the command buffer
* @return If this is the first usage of the backing of this resource within this execution * @return If this is the first usage of the backing of this resource within this execution
@ -103,6 +112,12 @@ namespace skyline::gpu::interconnect {
*/ */
bool AttachTexture(TextureView *view); bool AttachTexture(TextureView *view);
/**
* @return A reference to an instance of the Buffer Manager which will be locked till execution
* @note Any access to the buffer manager while recording commands **must** be done via this
*/
BufferManager &AcquireBufferManager();
/** /**
* @brief Attach the lifetime of a buffer to the command buffer * @brief Attach the lifetime of a buffer to the command buffer
* @return If this is the first usage of the backing of this resource within this execution * @return If this is the first usage of the backing of this resource within this execution

View File

@ -400,7 +400,7 @@ namespace skyline::gpu::interconnect {
return vk::ImageViewType::e2D; return vk::ImageViewType::e2D;
}(); }();
renderTarget.view = gpu.texture.FindOrCreate(renderTarget.guest, executor.tag); renderTarget.view = executor.AcquireTextureManager().FindOrCreate(renderTarget.guest, executor.tag);
return renderTarget.view.get(); return renderTarget.view.get();
} }
@ -725,7 +725,7 @@ namespace skyline::gpu::interconnect {
auto view{constantBufferCache.Lookup(constantBufferSelector.size, constantBufferSelector.iova)}; auto view{constantBufferCache.Lookup(constantBufferSelector.size, constantBufferSelector.iova)};
if (!view) { if (!view) {
auto mappings{channelCtx.asCtx->gmmu.TranslateRange(constantBufferSelector.iova, constantBufferSelector.size)}; auto mappings{channelCtx.asCtx->gmmu.TranslateRange(constantBufferSelector.iova, constantBufferSelector.size)};
view = gpu.buffer.FindOrCreate(mappings.front(), executor.tag); view = executor.AcquireBufferManager().FindOrCreate(mappings.front(), executor.tag);
constantBufferCache.Insert(constantBufferSelector.size, constantBufferSelector.iova, *view); constantBufferCache.Insert(constantBufferSelector.size, constantBufferSelector.iova, *view);
} }
@ -916,7 +916,7 @@ namespace skyline::gpu::interconnect {
if (mappings.size() != 1) if (mappings.size() != 1)
Logger::Warn("Multiple buffer mappings ({}) are not supported", mappings.size()); Logger::Warn("Multiple buffer mappings ({}) are not supported", mappings.size());
return gpu.buffer.FindOrCreate(mappings.front(), executor.tag); return executor.AcquireBufferManager().FindOrCreate(mappings.front(), executor.tag);
} }
/** /**
@ -1845,7 +1845,7 @@ namespace skyline::gpu::interconnect {
if (mappings.size() != 1) if (mappings.size() != 1)
Logger::Warn("Multiple buffer mappings ({}) are not supported", mappings.size()); Logger::Warn("Multiple buffer mappings ({}) are not supported", mappings.size());
vertexBuffer.view = gpu.buffer.FindOrCreate(mappings.front(), executor.tag); vertexBuffer.view = executor.AcquireBufferManager().FindOrCreate(mappings.front(), executor.tag);
return &vertexBuffer; return &vertexBuffer;
} }
@ -2329,7 +2329,7 @@ namespace skyline::gpu::interconnect {
return textureView; return textureView;
} }
auto textureView{gpu.texture.FindOrCreate(poolTexture.guest, executor.tag)}; auto textureView{executor.AcquireTextureManager().FindOrCreate(poolTexture.guest, executor.tag)};
poolTexture.view = textureView; poolTexture.view = textureView;
return textureView; return textureView;
} }
@ -2600,7 +2600,7 @@ namespace skyline::gpu::interconnect {
Logger::Warn("Multiple buffer mappings ({}) are not supported", mappings.size()); Logger::Warn("Multiple buffer mappings ({}) are not supported", mappings.size());
auto mapping{mappings.front()}; auto mapping{mappings.front()};
indexBuffer.view = gpu.buffer.FindOrCreate(span<u8>(mapping.data(), size), executor.tag); indexBuffer.view = executor.AcquireBufferManager().FindOrCreate(span<u8>(mapping.data(), size), executor.tag);
return indexBuffer.view; return indexBuffer.view;
} }

View File

@ -6,6 +6,18 @@
namespace skyline::gpu { namespace skyline::gpu {
TextureManager::TextureManager(GPU &gpu) : gpu(gpu) {} TextureManager::TextureManager(GPU &gpu) : gpu(gpu) {}
void TextureManager::lock() {
mutex.lock();
}
void TextureManager::unlock() {
mutex.unlock();
}
bool TextureManager::try_lock() {
return mutex.try_lock();
}
std::shared_ptr<TextureView> TextureManager::FindOrCreate(const GuestTexture &guestTexture, ContextTag tag) { std::shared_ptr<TextureView> TextureManager::FindOrCreate(const GuestTexture &guestTexture, ContextTag tag) {
auto guestMapping{guestTexture.mappings.front()}; auto guestMapping{guestTexture.mappings.front()};
@ -23,7 +35,6 @@ namespace skyline::gpu {
* 5) Create a new texture and insert it in the map then return it * 5) Create a new texture and insert it in the map then return it
*/ */
std::scoped_lock lock(mutex);
std::shared_ptr<Texture> match{}; std::shared_ptr<Texture> match{};
auto mappingEnd{std::upper_bound(textures.begin(), textures.end(), guestMapping)}, hostMapping{mappingEnd}; auto mappingEnd{std::upper_bound(textures.begin(), textures.end(), guestMapping)}, hostMapping{mappingEnd};
while (hostMapping != textures.begin() && (--hostMapping)->end() > guestMapping.begin()) { while (hostMapping != textures.begin() && (--hostMapping)->end() > guestMapping.begin()) {

View File

@ -32,8 +32,27 @@ namespace skyline::gpu {
public: public:
TextureManager(GPU &gpu); TextureManager(GPU &gpu);
/**
* @brief Acquires an exclusive lock on the texture for the calling thread
* @note Naming is in accordance to the BasicLockable named requirement
*/
void 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();
/**
* @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 A pre-existing or newly created Texture object which matches the specified criteria * @return A pre-existing or newly created Texture object which matches the specified criteria
* @note The texture manager **must** be locked prior to calling this
*/ */
std::shared_ptr<TextureView> FindOrCreate(const GuestTexture &guestTexture, ContextTag tag = {}); std::shared_ptr<TextureView> FindOrCreate(const GuestTexture &guestTexture, ContextTag tag = {});
}; };

View File

@ -347,6 +347,8 @@ namespace skyline::service::hosbinder {
gpu::texture::Dimensions dimensions(surface.width, surface.height); gpu::texture::Dimensions dimensions(surface.width, surface.height);
gpu::GuestTexture guestTexture(span<u8>{}, dimensions, format, tileConfig, vk::ImageViewType::e2D); gpu::GuestTexture guestTexture(span<u8>{}, dimensions, format, tileConfig, vk::ImageViewType::e2D);
guestTexture.mappings[0] = span<u8>(nvMapHandleObj->GetPointer() + surface.offset, guestTexture.GetLayerStride()); guestTexture.mappings[0] = span<u8>(nvMapHandleObj->GetPointer() + surface.offset, guestTexture.GetLayerStride());
std::scoped_lock textureLock{state.gpu->texture};
buffer.texture = state.gpu->texture.FindOrCreate(guestTexture)->texture; buffer.texture = state.gpu->texture.FindOrCreate(guestTexture)->texture;
} }