mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-10-07 22:29:48 +00:00
[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>
This commit is contained in:
@@ -56,7 +56,8 @@ size_t BytesPerIndex(VkIndexType index_type) {
|
||||
}
|
||||
}
|
||||
|
||||
vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allocator, u64 size) {
|
||||
vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allocator, u64 size,
|
||||
VkDeviceSize sparse_alignment) {
|
||||
VkBufferUsageFlags flags =
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT |
|
||||
@@ -82,6 +83,9 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
};
|
||||
if (sparse_alignment > 1) {
|
||||
return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal, sparse_alignment);
|
||||
}
|
||||
return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
@@ -99,10 +103,14 @@ Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_p
|
||||
}
|
||||
}
|
||||
|
||||
Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_)
|
||||
Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_,
|
||||
bool sparse_compatible_)
|
||||
: VideoCommon::BufferBase(cpu_addr_, size_bytes_), device{&runtime.device},
|
||||
scheduler{&runtime.scheduler},
|
||||
buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())}, tracker{SizeBytes()} {
|
||||
buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes(),
|
||||
runtime.SparseAlignmentFor(sparse_compatible_))},
|
||||
tracker{SizeBytes()} {
|
||||
sparse_compatible = sparse_compatible_;
|
||||
if (runtime.device.HasDebuggingToolAttached()) {
|
||||
buffer.SetObjectNameEXT(fmt::format("Buffer {:#x}", CpuAddr()).c_str());
|
||||
}
|
||||
@@ -348,7 +356,8 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
|
||||
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
|
||||
staging_pool{staging_pool_}, guest_descriptor_queue{guest_descriptor_queue_},
|
||||
quad_index_pass(device, scheduler, descriptor_pool, staging_pool,
|
||||
compute_pass_descriptor_queue) {
|
||||
compute_pass_descriptor_queue),
|
||||
multi_range_buffers(device_) {
|
||||
const VkDriverIdKHR driver_id = device.GetDriverID();
|
||||
limit_dynamic_storage_buffers = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
|
||||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
|
||||
@@ -536,6 +545,37 @@ void BufferCacheRuntime::ClearBuffer(VkBuffer dest_buffer, u32 offset, size_t si
|
||||
});
|
||||
}
|
||||
|
||||
bool BufferCacheRuntime::BindMultiRangeStorageBuffer(u64 key, bool is_written) {
|
||||
if (multi_range_sources.empty() || multi_range_total == 0) {
|
||||
return false;
|
||||
}
|
||||
const MultiRangeRef ref = multi_range_buffers.Get(device, scheduler, memory_allocator, key,
|
||||
multi_range_sources, multi_range_total);
|
||||
if (ref.handle == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
if (is_written && !ref.sparse) {
|
||||
return false;
|
||||
}
|
||||
if (ref.needs_gather) {
|
||||
PreCopyBarrier();
|
||||
VkDeviceSize dst_offset = 0;
|
||||
for (const MultiRangeSource& source : multi_range_sources) {
|
||||
const std::array<VideoCommon::BufferCopy, 1> copy{VideoCommon::BufferCopy{
|
||||
.src_offset = u64(source.offset),
|
||||
.dst_offset = u64(dst_offset),
|
||||
.size = size_t(source.size),
|
||||
}};
|
||||
CopyBuffer(ref.handle, source.handle, copy, false);
|
||||
dst_offset += source.size;
|
||||
}
|
||||
PostCopyBarrier();
|
||||
multi_range_buffers.MarkGathered(key);
|
||||
}
|
||||
guest_descriptor_queue.AddBuffer(ref.handle, ref.address, 0, ref.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format,
|
||||
u32 base_vertex, u32 num_indices, VkBuffer buffer,
|
||||
u32 offset, [[maybe_unused]] u32 size) {
|
||||
|
||||
@@ -8,11 +8,14 @@
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "video_core/buffer_cache/buffer_cache_base.h"
|
||||
#include "video_core/buffer_cache/memory_tracker_base.h"
|
||||
#include "video_core/buffer_cache/usage_tracker.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/renderer_vulkan/vk_compute_pass.h"
|
||||
#include "video_core/renderer_vulkan/vk_multi_range_buffer.h"
|
||||
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
|
||||
#include "video_core/surface.h"
|
||||
@@ -31,7 +34,8 @@ class BufferCacheRuntime;
|
||||
class Buffer : public VideoCommon::BufferBase {
|
||||
public:
|
||||
explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params);
|
||||
explicit Buffer(BufferCacheRuntime& runtime, VAddr cpu_addr_, u64 size_bytes_);
|
||||
explicit Buffer(BufferCacheRuntime& runtime, VAddr cpu_addr_, u64 size_bytes_,
|
||||
bool sparse_compatible_);
|
||||
|
||||
[[nodiscard]] VkBufferView View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format);
|
||||
|
||||
@@ -43,6 +47,14 @@ public:
|
||||
return device_address;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsSparseCompatible() const noexcept {
|
||||
return sparse_compatible;
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::MemoryLocation Location() const noexcept {
|
||||
return buffer.Location();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsRegionUsed(u64 offset, u64 size) const noexcept {
|
||||
return tracker.IsUsed(offset, size);
|
||||
}
|
||||
@@ -77,6 +89,7 @@ private:
|
||||
VkDeviceAddress device_address{};
|
||||
u64 last_usage_tick{};
|
||||
bool is_null{};
|
||||
bool sparse_compatible{};
|
||||
};
|
||||
|
||||
class QuadArrayIndexBuffer;
|
||||
@@ -125,7 +138,7 @@ public:
|
||||
|
||||
void PreCopyBarrier();
|
||||
|
||||
void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer,
|
||||
void CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer,
|
||||
std::span<const VideoCommon::BufferCopy> copies, bool barrier,
|
||||
bool can_reorder_upload = false);
|
||||
|
||||
@@ -155,6 +168,46 @@ public:
|
||||
return ref.mapped_span;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkDeviceSize SparseAlignmentFor(bool sparse_compatible) const noexcept {
|
||||
if (!sparse_compatible || !multi_range_buffers.use_sparse) {
|
||||
return 0;
|
||||
}
|
||||
return multi_range_buffers.block_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool PrefersSparseSources() const noexcept {
|
||||
return multi_range_buffers.use_sparse;
|
||||
}
|
||||
|
||||
void ResetMultiRange() noexcept {
|
||||
multi_range_sources.clear();
|
||||
multi_range_total = 0;
|
||||
}
|
||||
|
||||
void PushMultiRangeSource(const Buffer& buffer, u32 offset, u32 size) {
|
||||
const vk::MemoryLocation location = buffer.Location();
|
||||
multi_range_sources.push_back(MultiRangeSource{
|
||||
.handle = buffer.Handle(),
|
||||
.memory = location.memory,
|
||||
.memory_offset = location.offset,
|
||||
.offset = offset,
|
||||
.size = size,
|
||||
.write_tick = buffer.getWriteTick(),
|
||||
.memory_type = location.memory_type,
|
||||
});
|
||||
multi_range_total += size;
|
||||
}
|
||||
|
||||
bool BindMultiRangeStorageBuffer(u64 key, bool is_written);
|
||||
|
||||
void InvalidateMultiRange(u64 key) {
|
||||
multi_range_buffers.Invalidate(key);
|
||||
}
|
||||
|
||||
void OnBufferDeleted(const Buffer& buffer) {
|
||||
multi_range_buffers.DropOwner(scheduler, buffer.Handle());
|
||||
}
|
||||
|
||||
void BindUniformBuffer(const Buffer& buffer, u32 offset, u32 size) {
|
||||
BindBuffer(buffer, offset, size);
|
||||
}
|
||||
@@ -208,6 +261,10 @@ private:
|
||||
std::unique_ptr<Uint8Pass> uint8_pass;
|
||||
QuadIndexedPass quad_index_pass;
|
||||
|
||||
MultiRangeBufferCache multi_range_buffers;
|
||||
boost::container::small_vector<MultiRangeSource, 16> multi_range_sources;
|
||||
VkDeviceSize multi_range_total{};
|
||||
|
||||
bool limit_dynamic_storage_buffers = false;
|
||||
u32 max_dynamic_storage_buffers = (std::numeric_limits<u32>::max)();
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -819,6 +819,7 @@ void RasterizerVulkan::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {
|
||||
std::scoped_lock lock{texture_cache.mutex};
|
||||
texture_cache.UnmapGPUMemory(as_id, addr, size);
|
||||
}
|
||||
buffer_cache.UnmapGPUMemory(as_id, addr, size);
|
||||
}
|
||||
|
||||
void RasterizerVulkan::SignalFence(std::function<void()>&& func) {
|
||||
|
||||
Reference in New Issue
Block a user