Browse Source
Merge pull request #3249 from ReinUsesLisp/vk-staging-buffer-pool
Merge pull request #3249 from ReinUsesLisp/vk-staging-buffer-pool
vk_staging_buffer_pool: Add a staging pool for temporary operationsnce_cpp
committed by
GitHub
3 changed files with 212 additions and 0 deletions
-
2src/video_core/CMakeLists.txt
-
127src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
-
83src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@ -0,0 +1,127 @@ |
|||||
|
// Copyright 2019 yuzu Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <algorithm>
|
||||
|
#include <unordered_map>
|
||||
|
#include <utility>
|
||||
|
#include <vector>
|
||||
|
|
||||
|
#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<VKBuffer> 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<VKBuffer>(); |
||||
|
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
|
||||
@ -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 <climits> |
||||
|
#include <unordered_map> |
||||
|
#include <utility> |
||||
|
#include <vector> |
||||
|
|
||||
|
#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<VKBuffer> buffer, VKFence& fence, u64 last_epoch); |
||||
|
StagingBuffer(StagingBuffer&& rhs) noexcept; |
||||
|
StagingBuffer(const StagingBuffer&) = delete; |
||||
|
~StagingBuffer(); |
||||
|
|
||||
|
StagingBuffer& operator=(StagingBuffer&& rhs) noexcept; |
||||
|
|
||||
|
std::unique_ptr<VKBuffer> buffer; |
||||
|
VKFenceWatch watch; |
||||
|
u64 last_epoch = 0; |
||||
|
}; |
||||
|
|
||||
|
struct StagingBuffers final { |
||||
|
std::vector<StagingBuffer> entries; |
||||
|
std::size_t delete_index = 0; |
||||
|
}; |
||||
|
|
||||
|
static constexpr std::size_t NumLevels = sizeof(std::size_t) * CHAR_BIT; |
||||
|
using StagingBuffersCache = std::array<StagingBuffers, NumLevels>; |
||||
|
|
||||
|
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 |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue