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