diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-29 01:26:52 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-29 01:26:52 -0400 |
| commit | c01459347405591446d50858d7b075d617a58be9 (patch) | |
| tree | e2b31109ab0ee66ca4b12612619bdeab03506be9 /src | |
| parent | e8bc734d1040044abe117a369a9f80a792e51df4 (diff) | |
video_core: cheaper small GPU reads, buffer usage tracking and compute uniforms
- MemoryManager::ReadBlockUnsafe reads within one 4 KiB page through a
single translation (as Read<T> does) instead of the page walk:
texture and sampler descriptors, several per draw (2.6% -> 0.1% of
title 1's GPU thread).
- UsageTracker skips ranges it tracked just before (until Reset): draws
bind the same multi-megabyte storage buffers again and again, and
each Track wrote a word per 4 KiB (2.0% -> 0.7%).
- Small compute uniform buffers the GPU has not written are streamed,
as graphics ones are; synchronizing them re-protected pages an inline
upload had just unprotected, two VirtualProtect calls per dispatch
(1.9% -> 0.7%, page protection 3.2% -> 2.3%).
Diffstat (limited to 'src')
| -rw-r--r-- | src/video_core/buffer_cache/buffer_cache.h | 9 | ||||
| -rw-r--r-- | src/video_core/buffer_cache/usage_tracker.h | 17 | ||||
| -rw-r--r-- | src/video_core/memory_manager.cpp | 10 |
3 files changed, 35 insertions, 1 deletions
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 4df855c0fe..ad2e200438 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -1211,8 +1211,15 @@ void BufferCache<P>::BindHostComputeUniformBuffers() { return alignment > 1 && (offset % alignment) != 0; } }(); + // Small buffers the GPU has not written are streamed, as graphics ones are: synchronizing + // them made every dispatch re-protect pages an inline upload had just unprotected (two + // VirtualProtect calls per dispatch, ~4,000 dispatches a second in title 1). + const bool use_fast_buffer = + needs_alignment_stream || + (has_host_buffer && size <= channel_state->uniform_buffer_skip_cache_size && + !memory_tracker.IsRegionGpuModified(binding.device_addr, size)); if constexpr (!IS_OPENGL) { - if (needs_alignment_stream) { + if (use_fast_buffer) { const std::span<u8> span = runtime.BindMappedUniformBuffer(0, binding_index, size); device_memory.ReadBlockUnsafe(binding.device_addr, span.data(), size); diff --git a/src/video_core/buffer_cache/usage_tracker.h b/src/video_core/buffer_cache/usage_tracker.h index 6f84e463e3..3e2da8bc0b 100644 --- a/src/video_core/buffer_cache/usage_tracker.h +++ b/src/video_core/buffer_cache/usage_tracker.h @@ -6,6 +6,10 @@ #pragma once +#include <array> +#include <utility> +#include <vector> + #include "common/alignment.h" #include "common/common_types.h" @@ -24,9 +28,19 @@ public: void Reset() noexcept { std::ranges::fill(pages, 0ULL); + recent.fill({~u64{0}, 0}); } void Track(u64 offset, u64 size) noexcept { + // Tracking is idempotent until Reset: skip ranges tracked just before. Draws bind the + // same (often multi-megabyte) storage buffers again and again, and each Track wrote a + // word per 4 KiB of the range. + for (const auto& [recent_offset, recent_size] : recent) { + if (recent_offset == offset && recent_size == size) { + return; + } + } + recent[recent_cursor++ % recent.size()] = {offset, size}; const size_t page = offset >> BUFFER_PAGE_SHIFT; const size_t page_end = (offset + size) >> BUFFER_PAGE_SHIFT; if (page_end < page || page_end >= pages.size()) { @@ -83,6 +97,9 @@ private: private: std::vector<u64> pages; + /// Ranges tracked last, (offset, size). + std::array<std::pair<u64, u64>, 4> recent{{{~u64{0}, 0}, {~u64{0}, 0}, {~u64{0}, 0}, {~u64{0}, 0}}}; + size_t recent_cursor{}; }; } // namespace VideoCommon diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index 9a9fdab7bc..0eae8fac24 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -410,6 +410,16 @@ void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::siz } void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, const std::size_t size) const { + // Small reads within one 4 KiB page (texture and sampler descriptors, several per draw): one + // translation and a copy, as Read<T> does, instead of the page walk (~3% of the GPU thread + // in title 1). Anything else, unmapped or sparse pages included, takes the full path. + constexpr std::size_t SmallPage = 0x1000; + if (size <= SmallPage && (gpu_src_addr & (SmallPage - 1)) + size <= SmallPage) { + if (const u8* const pointer = GetPointer(gpu_src_addr)) { + std::memcpy(dest_buffer, pointer, size); + return; + } + } ReadBlockImpl(gpu_src_addr, dest_buffer, size, VideoCommon::CacheType::None, true); } |
