Browse Source
[shader_recompiler, vulkan] Virtual buffer pages with multi-range + sparse buffers binding (#4362)
[shader_recompiler, vulkan] Virtual buffer pages with multi-range + sparse buffers binding (#4362)
Storage buffers can span GPU pages that aren't contiguous in host memory, but the buffer cache assumed one contiguous buffer per binding, so anything crossing a mapping boundary read the wrong bytes. Multi-range binding resolves the real segments and presents them to the shader as one buffer, aliasing their memory into a sparse VkBuffer or gathering them into a copy, all of this was mostly fixed by making a path to use sparse on buffers (the same way as texture cache has it) and retrieve properly the mapping ranges to the virtual pages, including the actual structure on the use of binding sparse. Meanwhile this fixes Monster Hunter Sunbreak z-fighting, bad performance and mostly vertex explosions (coming from the contiguous buffer read from the shader's game) it's only the basis for a further investigation towards how are we gonna treat phi calculations, optimized them and mostly eradicate the currently excess of exposure on the textures where the lightning trace should not being reflected. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4362 Reviewed-by: lizzie <lizzie@eden-emu.dev> Reviewed-by: Maufeat <sahyno1996@gmail.com>xbzk/bundled-application-program-IDs-fix
committed by
crueter
No known key found for this signature in database
GPG Key ID: 425ACD2D4830EBC6
17 changed files with 975 additions and 43 deletions
-
3src/video_core/CMakeLists.txt
-
160src/video_core/buffer_cache/buffer_cache.h
-
26src/video_core/buffer_cache/buffer_cache_base.h
-
173src/video_core/buffer_cache/virtual_range_cache.h
-
2src/video_core/renderer_opengl/gl_buffer_cache.cpp
-
3src/video_core/renderer_opengl/gl_buffer_cache.h
-
48src/video_core/renderer_vulkan/vk_buffer_cache.cpp
-
61src/video_core/renderer_vulkan/vk_buffer_cache.h
-
338src/video_core/renderer_vulkan/vk_multi_range_buffer.cpp
-
105src/video_core/renderer_vulkan/vk_multi_range_buffer.h
-
1src/video_core/renderer_vulkan/vk_rasterizer.cpp
-
2src/video_core/vulkan_common/vulkan_device.cpp
-
5src/video_core/vulkan_common/vulkan_device.h
-
60src/video_core/vulkan_common/vulkan_memory_allocator.cpp
-
5src/video_core/vulkan_common/vulkan_memory_allocator.h
-
1src/video_core/vulkan_common/vulkan_wrapper.cpp
-
25src/video_core/vulkan_common/vulkan_wrapper.h
@ -0,0 +1,173 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <atomic> |
|||
#include <limits> |
|||
#include <mutex> |
|||
#include <optional> |
|||
#include <vector> |
|||
|
|||
#include <boost/container/small_vector.hpp> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "common/container/unordered_map.h" |
|||
#include "video_core/memory_manager.h" |
|||
|
|||
namespace VideoCommon { |
|||
|
|||
struct VirtualSegment { |
|||
GPUVAddr gpu_addr; |
|||
DAddr device_addr; |
|||
u32 size; |
|||
}; |
|||
|
|||
using VirtualSegments = boost::container::small_vector<VirtualSegment, 8>; |
|||
|
|||
class VirtualRangeCache { |
|||
public: |
|||
static constexpr size_t MAX_ENTRIES = 8192; |
|||
static constexpr size_t MAX_DEFERRED = 4096; |
|||
|
|||
const VirtualSegments* Query(Tegra::MemoryManager& memory, GPUVAddr gpu_addr, u32 size) { |
|||
if (has_deferred.load(std::memory_order_acquire)) { |
|||
ApplyDeferred(); |
|||
} |
|||
if (entries.size() > MAX_ENTRIES) { |
|||
entries.clear(); |
|||
} |
|||
const size_t as_id = memory.GetID(); |
|||
const u64 key = MakeKey(as_id, gpu_addr); |
|||
const auto it = entries.find(key); |
|||
if (it != entries.end() && it->second.as_id == as_id && |
|||
it->second.gpu_addr == gpu_addr && it->second.size == size) { |
|||
return &it->second.segments; |
|||
} |
|||
Entry entry{}; |
|||
entry.as_id = as_id; |
|||
entry.gpu_addr = gpu_addr; |
|||
entry.size = size; |
|||
const auto ranges = memory.GetSubmappedRange(gpu_addr, size); |
|||
GPUVAddr expected = gpu_addr; |
|||
bool contiguous = true; |
|||
for (const auto& [range_addr, range_size] : ranges) { |
|||
if (range_addr != expected || range_size == 0) { |
|||
contiguous = false; |
|||
break; |
|||
} |
|||
const std::optional<DAddr> device_addr = memory.GpuToCpuAddress(range_addr); |
|||
if (!device_addr || *device_addr == 0) { |
|||
contiguous = false; |
|||
break; |
|||
} |
|||
if (range_size > static_cast<size_t>((std::numeric_limits<u32>::max)())) { |
|||
contiguous = false; |
|||
break; |
|||
} |
|||
entry.segments.push_back(VirtualSegment{ |
|||
.gpu_addr = range_addr, |
|||
.device_addr = *device_addr, |
|||
.size = static_cast<u32>(range_size), |
|||
}); |
|||
expected += range_size; |
|||
} |
|||
if (!contiguous || expected != gpu_addr + size) { |
|||
entry.segments.clear(); |
|||
} |
|||
const auto result = entries.insert_or_assign(key, std::move(entry)); |
|||
return &result.first->second.segments; |
|||
} |
|||
|
|||
void Unmap(size_t as_id, GPUVAddr gpu_addr, u64 size) { |
|||
if (size == 0) { |
|||
return; |
|||
} |
|||
{ |
|||
std::scoped_lock lock{deferred_mutex}; |
|||
if (!deferred.empty()) { |
|||
DeferredUnmap& last = deferred.back(); |
|||
if (last.as_id == as_id && last.gpu_addr + last.size == gpu_addr) { |
|||
last.size += size; |
|||
has_deferred.store(true, std::memory_order_release); |
|||
return; |
|||
} |
|||
} |
|||
if (deferred.size() >= MAX_DEFERRED) { |
|||
deferred.clear(); |
|||
deferred_overflow = true; |
|||
} else { |
|||
deferred.push_back(DeferredUnmap{ |
|||
.as_id = as_id, |
|||
.gpu_addr = gpu_addr, |
|||
.size = size, |
|||
}); |
|||
} |
|||
} |
|||
has_deferred.store(true, std::memory_order_release); |
|||
} |
|||
|
|||
private: |
|||
struct Entry { |
|||
VirtualSegments segments; |
|||
size_t as_id{}; |
|||
GPUVAddr gpu_addr{}; |
|||
u32 size{}; |
|||
}; |
|||
|
|||
struct DeferredUnmap { |
|||
size_t as_id; |
|||
GPUVAddr gpu_addr; |
|||
u64 size; |
|||
}; |
|||
|
|||
static u64 MakeKey(size_t as_id, GPUVAddr gpu_addr) { |
|||
return (static_cast<u64>(as_id) << 48) ^ gpu_addr; |
|||
} |
|||
|
|||
void ApplyDeferred() { |
|||
std::vector<DeferredUnmap> pending; |
|||
bool overflow = false; |
|||
{ |
|||
std::scoped_lock lock{deferred_mutex}; |
|||
has_deferred.store(false, std::memory_order_release); |
|||
pending.swap(deferred); |
|||
overflow = deferred_overflow; |
|||
deferred_overflow = false; |
|||
} |
|||
if (overflow) { |
|||
entries.clear(); |
|||
return; |
|||
} |
|||
if (pending.empty() || entries.empty()) { |
|||
return; |
|||
} |
|||
for (auto it = entries.begin(); it != entries.end();) { |
|||
const Entry& entry = it->second; |
|||
const GPUVAddr entry_end = entry.gpu_addr + entry.size; |
|||
bool overlaps = false; |
|||
for (const DeferredUnmap& unmap : pending) { |
|||
if (unmap.as_id != entry.as_id) { |
|||
continue; |
|||
} |
|||
if (entry.gpu_addr < unmap.gpu_addr + unmap.size && unmap.gpu_addr < entry_end) { |
|||
overlaps = true; |
|||
break; |
|||
} |
|||
} |
|||
if (overlaps) { |
|||
it = entries.erase(it); |
|||
} else { |
|||
++it; |
|||
} |
|||
} |
|||
} |
|||
|
|||
::Common::unordered_map<u64, Entry> entries; |
|||
std::vector<DeferredUnmap> deferred; |
|||
std::mutex deferred_mutex; |
|||
std::atomic<bool> has_deferred{false}; |
|||
bool deferred_overflow{}; |
|||
}; |
|||
|
|||
} // namespace VideoCommon |
|||
@ -0,0 +1,338 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
|
|||
#include <algorithm>
|
|||
#include <mutex>
|
|||
#include <utility>
|
|||
|
|||
#include "video_core/renderer_vulkan/vk_multi_range_buffer.h"
|
|||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
|||
#include "video_core/vulkan_common/vulkan_device.h"
|
|||
|
|||
namespace Vulkan { |
|||
|
|||
MultiRangeBufferCache::MultiRangeBufferCache(const Device& device) { |
|||
sparse_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | |
|||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; |
|||
if (device.IsBufferDeviceAddressSupported()) { |
|||
sparse_usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; |
|||
} |
|||
if (!device.IsSparseBindingSupported()) { |
|||
return; |
|||
} |
|||
u32 memory_type_bits = 0; |
|||
const VkDeviceSize queried = QueryBlockSize(device, memory_type_bits); |
|||
if (queried == 0 || memory_type_bits == 0) { |
|||
return; |
|||
} |
|||
block_size = queried; |
|||
sparse_memory_type_bits = memory_type_bits; |
|||
use_sparse = true; |
|||
} |
|||
|
|||
VkDeviceSize MultiRangeBufferCache::QueryBlockSize(const Device& device, |
|||
u32& memory_type_bits) const { |
|||
const VkDevice logical = *device.GetLogical(); |
|||
const auto& dld = device.GetDispatchLoader(); |
|||
const VkBufferCreateInfo probe_ci{ |
|||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, |
|||
.pNext = nullptr, |
|||
.flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT | VK_BUFFER_CREATE_SPARSE_ALIASED_BIT, |
|||
.size = DEFAULT_BLOCK_SIZE, |
|||
.usage = sparse_usage, |
|||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE, |
|||
.queueFamilyIndexCount = 0, |
|||
.pQueueFamilyIndices = nullptr, |
|||
}; |
|||
VkBuffer probe{}; |
|||
if (dld.vkCreateBuffer(logical, &probe_ci, nullptr, &probe) != VK_SUCCESS) { |
|||
return 0; |
|||
} |
|||
const SparseBuffer owned{probe, logical, dld}; |
|||
const VkBufferMemoryRequirementsInfo2 reqs_info{ |
|||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2, |
|||
.pNext = nullptr, |
|||
.buffer = probe, |
|||
}; |
|||
VkMemoryRequirements2 reqs2{ |
|||
.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, |
|||
.pNext = nullptr, |
|||
.memoryRequirements = {}, |
|||
}; |
|||
dld.vkGetBufferMemoryRequirements2(logical, &reqs_info, &reqs2); |
|||
memory_type_bits = reqs2.memoryRequirements.memoryTypeBits; |
|||
return reqs2.memoryRequirements.alignment; |
|||
} |
|||
|
|||
u64 MultiRangeBufferCache::HashSources(std::span<const MultiRangeSource> sources) const { |
|||
u64 hash = 0xcbf29ce484222325ULL; |
|||
const auto mix = [&hash](u64 value) { |
|||
hash ^= value; |
|||
hash *= 0x100000001b3ULL; |
|||
}; |
|||
for (const MultiRangeSource& source : sources) { |
|||
mix(u64(source.handle)); |
|||
mix(u64(source.offset)); |
|||
mix(u64(source.size)); |
|||
} |
|||
return hash; |
|||
} |
|||
|
|||
u64 MultiRangeBufferCache::HashContent(std::span<const MultiRangeSource> sources) const { |
|||
u64 hash = 0xcbf29ce484222325ULL; |
|||
for (const MultiRangeSource& source : sources) { |
|||
hash ^= source.write_tick; |
|||
hash *= 0x100000001b3ULL; |
|||
} |
|||
return hash; |
|||
} |
|||
|
|||
bool MultiRangeBufferCache::CanBindSparse(std::span<const MultiRangeSource> sources) const { |
|||
return use_sparse && |
|||
std::none_of(sources.begin(), sources.end(), |
|||
[block = block_size, bits = sparse_memory_type_bits](auto const& e) { |
|||
const VkDeviceSize memory_offset = e.memory_offset + e.offset; |
|||
return e.memory == VK_NULL_HANDLE || e.memory_type >= 32 || |
|||
((bits >> e.memory_type) & 1) == 0 || |
|||
(memory_offset % block) != 0 || (e.size % block) != 0; |
|||
}); |
|||
} |
|||
|
|||
SparseBuffer MultiRangeBufferCache::CreateSparse(const Device& device, Scheduler& scheduler, |
|||
std::span<const MultiRangeSource> sources, |
|||
VkDeviceSize total) { |
|||
const VkDevice logical = *device.GetLogical(); |
|||
const auto& dld = device.GetDispatchLoader(); |
|||
const VkBufferCreateInfo buffer_ci{ |
|||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, |
|||
.pNext = nullptr, |
|||
.flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT | VK_BUFFER_CREATE_SPARSE_ALIASED_BIT, |
|||
.size = total, |
|||
.usage = sparse_usage, |
|||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE, |
|||
.queueFamilyIndexCount = 0, |
|||
.pQueueFamilyIndices = nullptr, |
|||
}; |
|||
VkBuffer raw{}; |
|||
if (dld.vkCreateBuffer(logical, &buffer_ci, nullptr, &raw) != VK_SUCCESS) { |
|||
return SparseBuffer{}; |
|||
} |
|||
SparseBuffer handle{raw, logical, dld}; |
|||
std::vector<VkSparseMemoryBind> binds; |
|||
binds.reserve(sources.size()); |
|||
VkDeviceSize resource_offset = 0; |
|||
for (const MultiRangeSource& source : sources) { |
|||
binds.push_back(VkSparseMemoryBind{ |
|||
.resourceOffset = resource_offset, |
|||
.size = source.size, |
|||
.memory = source.memory, |
|||
.memoryOffset = source.memory_offset + source.offset, |
|||
.flags = 0, |
|||
}); |
|||
resource_offset += source.size; |
|||
} |
|||
const VkSparseBufferMemoryBindInfo buffer_bind{ |
|||
.buffer = raw, |
|||
.bindCount = static_cast<u32>(binds.size()), |
|||
.pBinds = binds.data(), |
|||
}; |
|||
const VkBindSparseInfo bind_info{ |
|||
.sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO, |
|||
.pNext = nullptr, |
|||
.waitSemaphoreCount = 0, |
|||
.pWaitSemaphores = nullptr, |
|||
.bufferBindCount = 1, |
|||
.pBufferBinds = &buffer_bind, |
|||
.imageOpaqueBindCount = 0, |
|||
.pImageOpaqueBinds = nullptr, |
|||
.imageBindCount = 0, |
|||
.pImageBinds = nullptr, |
|||
.signalSemaphoreCount = 0, |
|||
.pSignalSemaphores = nullptr, |
|||
}; |
|||
const VkFenceCreateInfo fence_ci{ |
|||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, |
|||
.pNext = nullptr, |
|||
.flags = 0, |
|||
}; |
|||
vk::Fence fence = device.GetLogical().CreateFence(fence_ci); |
|||
VkResult bind_result = VK_ERROR_UNKNOWN; |
|||
{ |
|||
std::scoped_lock lock{scheduler.submit_mutex}; |
|||
bind_result = device.GetGraphicsQueue().BindSparse(bind_info, *fence); |
|||
} |
|||
if (bind_result != VK_SUCCESS) { |
|||
return SparseBuffer{}; |
|||
} |
|||
fence.Wait(); |
|||
return handle; |
|||
} |
|||
|
|||
void MultiRangeBufferCache::RetireEntry(Scheduler& scheduler, Entry& entry) { |
|||
if (!entry.sparse_handle && !entry.gathered) { |
|||
return; |
|||
} |
|||
if (retired.size() == retired.capacity()) { |
|||
DrainRetired(scheduler); |
|||
} |
|||
if (retired.size() == retired.capacity()) { |
|||
u64 oldest = retired.front().tick; |
|||
for (const Retired& item : retired) { |
|||
if (item.tick < oldest) { |
|||
oldest = item.tick; |
|||
} |
|||
} |
|||
scheduler.Wait(oldest); |
|||
DrainRetired(scheduler); |
|||
} |
|||
retired.push_back(Retired{ |
|||
.handle = std::move(entry.sparse_handle), |
|||
.gathered = std::move(entry.gathered), |
|||
.tick = scheduler.CurrentTick(), |
|||
}); |
|||
} |
|||
|
|||
void MultiRangeBufferCache::DrainRetired(Scheduler& scheduler) { |
|||
size_t index = 0; |
|||
while (index < retired.size()) { |
|||
if (scheduler.IsFree(retired[index].tick)) { |
|||
if (index + 1 != retired.size()) { |
|||
retired[index] = std::move(retired.back()); |
|||
} |
|||
retired.pop_back(); |
|||
} else { |
|||
++index; |
|||
} |
|||
} |
|||
} |
|||
|
|||
MultiRangeRef MultiRangeBufferCache::Get(const Device& device, Scheduler& scheduler, |
|||
MemoryAllocator& memory_allocator, u64 key, |
|||
std::span<const MultiRangeSource> sources, |
|||
VkDeviceSize total) { |
|||
if (sources.empty() || total == 0) { |
|||
return MultiRangeRef{}; |
|||
} |
|||
if (!retired.empty()) { |
|||
DrainRetired(scheduler); |
|||
} |
|||
const u64 geometry = HashSources(sources); |
|||
const u64 content = HashContent(sources); |
|||
const auto it = entries.find(key); |
|||
if (it != entries.end() && it->second.geometry == geometry && it->second.size == total) { |
|||
Entry& entry = it->second; |
|||
if (entry.content != content) { |
|||
entry.content = content; |
|||
entry.dirty = true; |
|||
} |
|||
MultiRangeRef ref{ |
|||
.handle = *entry.sparse_handle, |
|||
.address = entry.address, |
|||
.size = entry.size, |
|||
.sparse = true, |
|||
.needs_gather = false, |
|||
}; |
|||
if (!entry.sparse_handle) { |
|||
ref.handle = *entry.gathered; |
|||
ref.sparse = false; |
|||
ref.needs_gather = entry.dirty; |
|||
} |
|||
return ref; |
|||
} |
|||
if (it != entries.end()) { |
|||
RetireEntry(scheduler, it->second); |
|||
entries.erase(it); |
|||
} |
|||
|
|||
Entry entry{}; |
|||
entry.geometry = geometry; |
|||
entry.content = content; |
|||
entry.size = total; |
|||
if (CanBindSparse(sources)) { |
|||
entry.sparse_handle = CreateSparse(device, scheduler, sources, total); |
|||
if (entry.sparse_handle) { |
|||
entry.owners.reserve(sources.size()); |
|||
for (const MultiRangeSource& source : sources) { |
|||
entry.owners.push_back(source.handle); |
|||
} |
|||
} |
|||
} |
|||
if (!entry.sparse_handle) { |
|||
VkBufferUsageFlags flags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | |
|||
VK_BUFFER_USAGE_TRANSFER_DST_BIT | |
|||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; |
|||
if (device.IsBufferDeviceAddressSupported()) { |
|||
flags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; |
|||
} |
|||
const VkBufferCreateInfo gather_ci{ |
|||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, |
|||
.pNext = nullptr, |
|||
.flags = 0, |
|||
.size = total, |
|||
.usage = flags, |
|||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE, |
|||
.queueFamilyIndexCount = 0, |
|||
.pQueueFamilyIndices = nullptr, |
|||
}; |
|||
entry.gathered = memory_allocator.CreateBuffer(gather_ci, MemoryUsage::DeviceLocal); |
|||
entry.dirty = true; |
|||
} |
|||
if (device.IsBufferDeviceAddressSupported()) { |
|||
VkBuffer address_handle = *entry.sparse_handle; |
|||
if (!entry.sparse_handle) { |
|||
address_handle = *entry.gathered; |
|||
} |
|||
entry.address = device.GetLogical().GetBufferDeviceAddress(address_handle); |
|||
} |
|||
|
|||
MultiRangeRef ref{ |
|||
.handle = *entry.sparse_handle, |
|||
.address = entry.address, |
|||
.size = entry.size, |
|||
.sparse = true, |
|||
.needs_gather = false, |
|||
}; |
|||
if (!entry.sparse_handle) { |
|||
ref.handle = *entry.gathered; |
|||
ref.sparse = false; |
|||
ref.needs_gather = true; |
|||
} |
|||
entries.emplace(key, std::move(entry)); |
|||
return ref; |
|||
} |
|||
|
|||
void MultiRangeBufferCache::MarkGathered(u64 key) { |
|||
if (auto const it = entries.find(key); it != entries.end()) { |
|||
it->second.dirty = false; |
|||
} |
|||
} |
|||
|
|||
void MultiRangeBufferCache::DropOwner(Scheduler& scheduler, VkBuffer owner) { |
|||
if (owner == VK_NULL_HANDLE) { |
|||
return; |
|||
} |
|||
for (auto it = entries.begin(); it != entries.end();) { |
|||
Entry& entry = it->second; |
|||
bool owned = false; |
|||
for (const VkBuffer handle : entry.owners) { |
|||
if (handle == owner) { |
|||
owned = true; |
|||
break; |
|||
} |
|||
} |
|||
if (!owned) { |
|||
++it; |
|||
continue; |
|||
} |
|||
RetireEntry(scheduler, entry); |
|||
it = entries.erase(it); |
|||
} |
|||
} |
|||
|
|||
void MultiRangeBufferCache::Invalidate(u64 key) { |
|||
if (auto const it = entries.find(key); it != entries.end()) { |
|||
it->second.dirty = true; |
|||
} |
|||
} |
|||
|
|||
} // namespace Vulkan
|
|||
@ -0,0 +1,105 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <span> |
|||
#include <vector> |
|||
|
|||
#include <boost/container/static_vector.hpp> |
|||
|
|||
#include "common/common_funcs.h" |
|||
#include "common/common_types.h" |
|||
#include "common/container/unordered_map.h" |
|||
#include "video_core/vulkan_common/vulkan_memory_allocator.h" |
|||
#include "video_core/vulkan_common/vulkan_wrapper.h" |
|||
|
|||
namespace Vulkan { |
|||
|
|||
using SparseBuffer = vk::Handle<VkBuffer, VkDevice, vk::DeviceDispatch>; |
|||
|
|||
class Device; |
|||
class Scheduler; |
|||
|
|||
struct MultiRangeSource { |
|||
VkBuffer handle{}; |
|||
VkDeviceMemory memory{}; |
|||
VkDeviceSize memory_offset{}; |
|||
VkDeviceSize offset{}; |
|||
VkDeviceSize size{}; |
|||
u64 write_tick{}; |
|||
u32 memory_type{}; |
|||
}; |
|||
|
|||
struct MultiRangeRef { |
|||
VkBuffer handle{}; |
|||
VkDeviceAddress address{}; |
|||
VkDeviceSize size{}; |
|||
bool sparse{}; |
|||
bool needs_gather{}; |
|||
}; |
|||
|
|||
class MultiRangeBufferCache final { |
|||
public: |
|||
static constexpr VkDeviceSize DEFAULT_BLOCK_SIZE = 64 * 1024; |
|||
static constexpr size_t MAX_RETIRED = 256; |
|||
|
|||
explicit MultiRangeBufferCache(const Device& device); |
|||
|
|||
YUZU_NON_COPYABLE(MultiRangeBufferCache); |
|||
|
|||
[[nodiscard]] MultiRangeRef Get(const Device& device, Scheduler& scheduler, |
|||
MemoryAllocator& memory_allocator, u64 key, |
|||
std::span<const MultiRangeSource> sources, |
|||
VkDeviceSize total); |
|||
|
|||
void MarkGathered(u64 key); |
|||
|
|||
void Invalidate(u64 key); |
|||
|
|||
void DropOwner(Scheduler& scheduler, VkBuffer owner); |
|||
|
|||
VkDeviceSize block_size{DEFAULT_BLOCK_SIZE}; |
|||
bool use_sparse{}; |
|||
|
|||
private: |
|||
struct Retired { |
|||
SparseBuffer handle; |
|||
vk::Buffer gathered; |
|||
u64 tick{}; |
|||
}; |
|||
|
|||
struct Entry { |
|||
vk::Buffer gathered; |
|||
SparseBuffer sparse_handle; |
|||
std::vector<VkBuffer> owners; |
|||
VkDeviceAddress address{}; |
|||
VkDeviceSize size{}; |
|||
u64 geometry{}; |
|||
u64 content{}; |
|||
bool dirty{true}; |
|||
}; |
|||
|
|||
[[nodiscard]] u64 HashSources(std::span<const MultiRangeSource> sources) const; |
|||
|
|||
[[nodiscard]] u64 HashContent(std::span<const MultiRangeSource> sources) const; |
|||
|
|||
[[nodiscard]] bool CanBindSparse(std::span<const MultiRangeSource> sources) const; |
|||
|
|||
[[nodiscard]] SparseBuffer CreateSparse(const Device& device, Scheduler& scheduler, |
|||
std::span<const MultiRangeSource> sources, |
|||
VkDeviceSize total); |
|||
|
|||
[[nodiscard]] VkDeviceSize QueryBlockSize(const Device& device, u32& memory_type_bits) const; |
|||
|
|||
void RetireEntry(Scheduler& scheduler, Entry& entry); |
|||
|
|||
void DrainRetired(Scheduler& scheduler); |
|||
|
|||
::Common::unordered_map<u64, Entry> entries; |
|||
boost::container::static_vector<Retired, MAX_RETIRED> retired; |
|||
u32 sparse_memory_type_bits{}; |
|||
VkBufferUsageFlags sparse_usage{}; |
|||
}; |
|||
|
|||
} // namespace Vulkan |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue