From 3813af2f3c4536e14950b95b8211788b9a6b243f Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Wed, 25 Dec 2019 18:12:17 -0300 Subject: vk_staging_buffer_pool: Add a staging pool for temporary operations The job of this abstraction is to provide staging buffers for temporary operations. Think of image uploads or buffer uploads to device memory. It automatically deletes unused buffers. --- .../renderer_vulkan/vk_staging_buffer_pool.cpp | 127 +++++++++++++++++++++ .../renderer_vulkan/vk_staging_buffer_pool.h | 83 ++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp create mode 100644 src/video_core/renderer_vulkan/vk_staging_buffer_pool.h (limited to 'src/video_core/renderer_vulkan') diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp new file mode 100644 index 000000000..171d78afc --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -0,0 +1,127 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include + +#include "common/bit_util.h" +#include "common/common_types.h" +#include "video_core/renderer_vulkan/vk_device.h" +#include "video_core/renderer_vulkan/vk_resource_manager.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" + +namespace Vulkan { + +VKStagingBufferPool::StagingBuffer::StagingBuffer(std::unique_ptr buffer, VKFence& fence, + u64 last_epoch) + : buffer{std::move(buffer)}, watch{fence}, last_epoch{last_epoch} {} + +VKStagingBufferPool::StagingBuffer::StagingBuffer(StagingBuffer&& rhs) noexcept { + buffer = std::move(rhs.buffer); + watch = std::move(rhs.watch); + last_epoch = rhs.last_epoch; +} + +VKStagingBufferPool::StagingBuffer::~StagingBuffer() = default; + +VKStagingBufferPool::StagingBuffer& VKStagingBufferPool::StagingBuffer::operator=( + StagingBuffer&& rhs) noexcept { + buffer = std::move(rhs.buffer); + watch = std::move(rhs.watch); + last_epoch = rhs.last_epoch; + return *this; +} + +VKStagingBufferPool::VKStagingBufferPool(const VKDevice& device, VKMemoryManager& memory_manager, + VKScheduler& scheduler) + : device{device}, memory_manager{memory_manager}, scheduler{scheduler}, + is_device_integrated{device.IsIntegrated()} {} + +VKStagingBufferPool::~VKStagingBufferPool() = default; + +VKBuffer& VKStagingBufferPool::GetUnusedBuffer(std::size_t size, bool host_visible) { + if (const auto buffer = TryGetReservedBuffer(size, host_visible)) { + return *buffer; + } + return CreateStagingBuffer(size, host_visible); +} + +void VKStagingBufferPool::TickFrame() { + ++epoch; + current_delete_level = (current_delete_level + 1) % NumLevels; + + ReleaseCache(true); + if (!is_device_integrated) { + ReleaseCache(false); + } +} + +VKBuffer* VKStagingBufferPool::TryGetReservedBuffer(std::size_t size, bool host_visible) { + for (auto& entry : GetCache(host_visible)[Common::Log2Ceil64(size)].entries) { + if (entry.watch.TryWatch(scheduler.GetFence())) { + entry.last_epoch = epoch; + return &*entry.buffer; + } + } + return nullptr; +} + +VKBuffer& VKStagingBufferPool::CreateStagingBuffer(std::size_t size, bool host_visible) { + const auto usage = + vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst | + vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eIndexBuffer; + const u32 log2 = Common::Log2Ceil64(size); + const vk::BufferCreateInfo buffer_ci({}, 1ULL << log2, usage, vk::SharingMode::eExclusive, 0, + nullptr); + const auto dev = device.GetLogical(); + auto buffer = std::make_unique(); + buffer->handle = dev.createBufferUnique(buffer_ci, nullptr, device.GetDispatchLoader()); + buffer->commit = memory_manager.Commit(*buffer->handle, host_visible); + + auto& entries = GetCache(host_visible)[log2].entries; + return *entries.emplace_back(std::move(buffer), scheduler.GetFence(), epoch).buffer; +} + +VKStagingBufferPool::StagingBuffersCache& VKStagingBufferPool::GetCache(bool host_visible) { + return is_device_integrated || host_visible ? host_staging_buffers : device_staging_buffers; +} + +void VKStagingBufferPool::ReleaseCache(bool host_visible) { + auto& cache = GetCache(host_visible); + const u64 size = ReleaseLevel(cache, current_delete_level); + if (size == 0) { + return; + } +} + +u64 VKStagingBufferPool::ReleaseLevel(StagingBuffersCache& cache, std::size_t log2) { + static constexpr u64 epochs_to_destroy = 180; + static constexpr std::size_t deletions_per_tick = 16; + + auto& staging = cache[log2]; + auto& entries = staging.entries; + const std::size_t old_size = entries.size(); + + const auto is_deleteable = [this](const auto& entry) { + return entry.last_epoch + epochs_to_destroy < epoch && !entry.watch.IsUsed(); + }; + const std::size_t begin_offset = staging.delete_index; + const std::size_t end_offset = std::min(begin_offset + deletions_per_tick, old_size); + const auto begin = std::begin(entries) + begin_offset; + const auto end = std::begin(entries) + end_offset; + entries.erase(std::remove_if(begin, end, is_deleteable), end); + + const std::size_t new_size = entries.size(); + staging.delete_index += deletions_per_tick; + if (staging.delete_index >= new_size) { + staging.delete_index = 0; + } + + return (1ULL << log2) * (old_size - new_size); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h new file mode 100644 index 000000000..4530749f0 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h @@ -0,0 +1,83 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include + +#include "common/common_types.h" + +#include "video_core/renderer_vulkan/declarations.h" +#include "video_core/renderer_vulkan/vk_memory_manager.h" + +namespace Vulkan { + +class VKDevice; +class VKFenceWatch; +class VKScheduler; + +struct VKBuffer final { + UniqueBuffer handle; + VKMemoryCommit commit; +}; + +class VKStagingBufferPool final { +public: + explicit VKStagingBufferPool(const VKDevice& device, VKMemoryManager& memory_manager, + VKScheduler& scheduler); + ~VKStagingBufferPool(); + + VKBuffer& GetUnusedBuffer(std::size_t size, bool host_visible); + + void TickFrame(); + +private: + struct StagingBuffer final { + explicit StagingBuffer(std::unique_ptr buffer, VKFence& fence, u64 last_epoch); + StagingBuffer(StagingBuffer&& rhs) noexcept; + StagingBuffer(const StagingBuffer&) = delete; + ~StagingBuffer(); + + StagingBuffer& operator=(StagingBuffer&& rhs) noexcept; + + std::unique_ptr buffer; + VKFenceWatch watch; + u64 last_epoch; + }; + + struct StagingBuffers final { + std::vector entries; + std::size_t delete_index = 0; + }; + + static constexpr std::size_t NumLevels = sizeof(std::size_t) * CHAR_BIT; + using StagingBuffersCache = std::array; + + VKBuffer* TryGetReservedBuffer(std::size_t size, bool host_visible); + + VKBuffer& CreateStagingBuffer(std::size_t size, bool host_visible); + + StagingBuffersCache& GetCache(bool host_visible); + + void ReleaseCache(bool host_visible); + + u64 ReleaseLevel(StagingBuffersCache& cache, std::size_t log2); + + const VKDevice& device; + VKMemoryManager& memory_manager; + VKScheduler& scheduler; + const bool is_device_integrated; + + StagingBuffersCache host_staging_buffers; + StagingBuffersCache device_staging_buffers; + + u64 epoch = 0; + + std::size_t current_delete_level = 0; +}; + +} // namespace Vulkan -- cgit v1.2.3