From 901f556af5ca5bb92817e22489d47af3bd9bc5fe Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 10 Feb 2026 22:24:21 -0400 Subject: [PATCH] [buffer_cache, memory, pipeline] Added translation entry structure and update buffer cache handling --- src/core/device_memory_manager.h | 10 + src/core/device_memory_manager.inc | 98 ++++++- src/video_core/buffer_cache/buffer_base.h | 8 +- src/video_core/buffer_cache/buffer_cache.h | 259 +++++++++++++++--- .../buffer_cache/buffer_cache_base.h | 48 ++++ .../renderer_opengl/gl_compute_pipeline.cpp | 6 +- .../renderer_opengl/gl_graphics_pipeline.cpp | 6 +- .../renderer_vulkan/vk_compute_pipeline.cpp | 4 + .../renderer_vulkan/vk_graphics_pipeline.cpp | 4 + 9 files changed, 407 insertions(+), 36 deletions(-) diff --git a/src/core/device_memory_manager.h b/src/core/device_memory_manager.h index 41227591c7..f166f54fec 100644 --- a/src/core/device_memory_manager.h +++ b/src/core/device_memory_manager.h @@ -127,6 +127,11 @@ public: void UpdatePagesCachedBatch(std::span> ranges, s32 delta); private: + struct TranslationEntry { + DAddr guest_page{}; + u8* host_ptr{}; + }; + // Internal helper that performs the update assuming the caller already holds the necessary lock. void UpdatePagesCachedCountNoLock(DAddr addr, size_t size, s32 delta); @@ -195,6 +200,11 @@ private: } Common::VirtualBuffer cpu_backing_address; + inline static thread_local TranslationEntry t_slot0{}; + inline static thread_local TranslationEntry t_slot1{}; + inline static thread_local TranslationEntry t_slot2{}; + inline static thread_local TranslationEntry t_slot3{}; + inline static thread_local u32 cache_cursor = 0; using CounterType = u8; using CounterAtomicType = std::atomic_uint8_t; static constexpr size_t subentries = 8 / sizeof(CounterType); diff --git a/src/core/device_memory_manager.inc b/src/core/device_memory_manager.inc index 4be26d9631..14117c78a8 100644 --- a/src/core/device_memory_manager.inc +++ b/src/core/device_memory_manager.inc @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -247,6 +247,10 @@ void DeviceMemoryManager::Map(DAddr address, VAddr virtual_address, size } impl->multi_dev_address.Register(new_dev, start_id); } + t_slot0 = {}; + t_slot1 = {}; + t_slot2 = {}; + t_slot3 = {}; if (track) { TrackContinuityImpl(address, virtual_address, size, asid); } @@ -278,6 +282,10 @@ void DeviceMemoryManager::Unmap(DAddr address, size_t size) { compressed_device_addr[phys_addr - 1] = new_start | MULTI_FLAG; } } + t_slot0 = {}; + t_slot1 = {}; + t_slot2 = {}; + t_slot3 = {}; } template void DeviceMemoryManager::TrackContinuityImpl(DAddr address, VAddr virtual_address, @@ -417,6 +425,50 @@ void DeviceMemoryManager::WalkBlock(DAddr addr, std::size_t size, auto o template void DeviceMemoryManager::ReadBlock(DAddr address, void* dest_pointer, size_t size) { device_inter->FlushRegion(address, size); + const std::size_t page_offset = address & Memory::YUZU_PAGEMASK; + if (size <= Memory::YUZU_PAGESIZE - page_offset) { + const DAddr guest_page = address & ~static_cast(Memory::YUZU_PAGEMASK); + if (t_slot0.guest_page == guest_page && t_slot0.host_ptr != nullptr) { + std::memcpy(dest_pointer, t_slot0.host_ptr + page_offset, size); + return; + } + if (t_slot1.guest_page == guest_page && t_slot1.host_ptr != nullptr) { + std::memcpy(dest_pointer, t_slot1.host_ptr + page_offset, size); + return; + } + if (t_slot2.guest_page == guest_page && t_slot2.host_ptr != nullptr) { + std::memcpy(dest_pointer, t_slot2.host_ptr + page_offset, size); + return; + } + if (t_slot3.guest_page == guest_page && t_slot3.host_ptr != nullptr) { + std::memcpy(dest_pointer, t_slot3.host_ptr + page_offset, size); + return; + } + + const std::size_t page_index = address >> Memory::YUZU_PAGEBITS; + const auto phys_addr = compressed_physical_ptr[page_index]; + if (phys_addr != 0) { + auto* const mem_ptr = GetPointerFromRaw( + (static_cast(phys_addr - 1) << Memory::YUZU_PAGEBITS)); + switch (cache_cursor & 3U) { + case 0: + t_slot0 = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr}; + break; + case 1: + t_slot1 = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr}; + break; + case 2: + t_slot2 = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr}; + break; + default: + t_slot3 = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr}; + break; + } + cache_cursor = (cache_cursor + 1) & 3U; + std::memcpy(dest_pointer, mem_ptr + page_offset, size); + return; + } + } WalkBlock( address, size, [&](size_t copy_amount, DAddr current_vaddr) { @@ -455,6 +507,50 @@ void DeviceMemoryManager::WriteBlock(DAddr address, const void* src_poin template void DeviceMemoryManager::ReadBlockUnsafe(DAddr address, void* dest_pointer, size_t size) { + const std::size_t page_offset = address & Memory::YUZU_PAGEMASK; + if (size <= Memory::YUZU_PAGESIZE - page_offset) { + const DAddr guest_page = address & ~static_cast(Memory::YUZU_PAGEMASK); + if (t_slot0.guest_page == guest_page && t_slot0.host_ptr != nullptr) { + std::memcpy(dest_pointer, t_slot0.host_ptr + page_offset, size); + return; + } + if (t_slot1.guest_page == guest_page && t_slot1.host_ptr != nullptr) { + std::memcpy(dest_pointer, t_slot1.host_ptr + page_offset, size); + return; + } + if (t_slot2.guest_page == guest_page && t_slot2.host_ptr != nullptr) { + std::memcpy(dest_pointer, t_slot2.host_ptr + page_offset, size); + return; + } + if (t_slot3.guest_page == guest_page && t_slot3.host_ptr != nullptr) { + std::memcpy(dest_pointer, t_slot3.host_ptr + page_offset, size); + return; + } + + const std::size_t page_index = address >> Memory::YUZU_PAGEBITS; + const auto phys_addr = compressed_physical_ptr[page_index]; + if (phys_addr != 0) { + auto* const mem_ptr = GetPointerFromRaw( + (static_cast(phys_addr - 1) << Memory::YUZU_PAGEBITS)); + switch (cache_cursor & 3U) { + case 0: + t_slot0 = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr}; + break; + case 1: + t_slot1 = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr}; + break; + case 2: + t_slot2 = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr}; + break; + default: + t_slot3 = TranslationEntry{.guest_page = guest_page, .host_ptr = mem_ptr}; + break; + } + cache_cursor = (cache_cursor + 1) & 3U; + std::memcpy(dest_pointer, mem_ptr + page_offset, size); + return; + } + } WalkBlock( address, size, [&](size_t copy_amount, DAddr current_vaddr) { diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h index 40e98e3952..10af740615 100644 --- a/src/video_core/buffer_cache/buffer_base.h +++ b/src/video_core/buffer_cache/buffer_base.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -39,7 +42,8 @@ public: static constexpr u64 BASE_PAGE_SIZE = 1ULL << BASE_PAGE_BITS; explicit BufferBase(VAddr cpu_addr_, u64 size_bytes_) - : cpu_addr{cpu_addr_}, size_bytes{size_bytes_} {} + : cpu_addr{cpu_addr_}, cpu_addr_cached{static_cast(cpu_addr_)}, + size_bytes{size_bytes_} {} explicit BufferBase(NullBufferParams) {} @@ -97,6 +101,8 @@ public: return cpu_addr; } + DAddr cpu_addr_cached = 0; + /// Returns the offset relative to the given CPU address /// @pre IsInBounds returns true [[nodiscard]] u32 Offset(VAddr other_cpu_addr) const noexcept { diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 9675391684..cd96af9c2e 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -382,6 +382,10 @@ void BufferCache

::BindHostComputeBuffers() { BindHostComputeUniformBuffers(); BindHostComputeStorageBuffers(); BindHostComputeTextureBuffers(); + if (any_buffer_uploaded) { + runtime.PostCopyBarrier(); + any_buffer_uploaded = false; + } } template @@ -766,45 +770,231 @@ void BufferCache

::BindHostIndexBuffer() { } } +template +void BufferCache

::BindHostVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, + u32 stride) { + if constexpr (IS_OPENGL) { + runtime.BindVertexBuffer(index, buffer, offset, size, stride); + } else { + runtime.BindVertexBuffer(index, buffer.Handle(), offset, size, stride); + } +} + +template +Binding& BufferCache

::VertexBufferSlot(u32 index) { + ASSERT(index < NUM_VERTEX_BUFFERS); + switch (index) { + case 0: + return v_buffer0; + case 1: + return v_buffer1; + case 2: + return v_buffer2; + case 3: + return v_buffer3; + case 4: + return v_buffer4; + case 5: + return v_buffer5; + case 6: + return v_buffer6; + case 7: + return v_buffer7; + case 8: + return v_buffer8; + case 9: + return v_buffer9; + case 10: + return v_buffer10; + case 11: + return v_buffer11; + case 12: + return v_buffer12; + case 13: + return v_buffer13; + case 14: + return v_buffer14; + case 15: + return v_buffer15; +#ifndef __APPLE__ + case 16: + return v_buffer16; + case 17: + return v_buffer17; + case 18: + return v_buffer18; + case 19: + return v_buffer19; + case 20: + return v_buffer20; + case 21: + return v_buffer21; + case 22: + return v_buffer22; + case 23: + return v_buffer23; + case 24: + return v_buffer24; + case 25: + return v_buffer25; + case 26: + return v_buffer26; + case 27: + return v_buffer27; + case 28: + return v_buffer28; + case 29: + return v_buffer29; + case 30: + return v_buffer30; + case 31: + return v_buffer31; +#endif + default: +#ifdef __APPLE__ + return v_buffer15; +#else + return v_buffer31; +#endif + } +} + +template +const Binding& BufferCache

::VertexBufferSlot(u32 index) const { + ASSERT(index < NUM_VERTEX_BUFFERS); + switch (index) { + case 0: + return v_buffer0; + case 1: + return v_buffer1; + case 2: + return v_buffer2; + case 3: + return v_buffer3; + case 4: + return v_buffer4; + case 5: + return v_buffer5; + case 6: + return v_buffer6; + case 7: + return v_buffer7; + case 8: + return v_buffer8; + case 9: + return v_buffer9; + case 10: + return v_buffer10; + case 11: + return v_buffer11; + case 12: + return v_buffer12; + case 13: + return v_buffer13; + case 14: + return v_buffer14; + case 15: + return v_buffer15; +#ifndef __APPLE__ + case 16: + return v_buffer16; + case 17: + return v_buffer17; + case 18: + return v_buffer18; + case 19: + return v_buffer19; + case 20: + return v_buffer20; + case 21: + return v_buffer21; + case 22: + return v_buffer22; + case 23: + return v_buffer23; + case 24: + return v_buffer24; + case 25: + return v_buffer25; + case 26: + return v_buffer26; + case 27: + return v_buffer27; + case 28: + return v_buffer28; + case 29: + return v_buffer29; + case 30: + return v_buffer30; + case 31: + return v_buffer31; +#endif + default: +#ifdef __APPLE__ + return v_buffer15; +#else + return v_buffer31; +#endif + } +} + +template +void BufferCache

::UpdateVertexBufferSlot(u32 index, const Binding& binding) { + Binding& slot = VertexBufferSlot(index); + if (slot.device_addr != binding.device_addr || slot.size != binding.size) { + ++vertex_buffers_serial; + } + slot = binding; + if (binding.buffer_id != NULL_BUFFER_ID && binding.size != 0) { + enabled_vertex_buffers_mask |= (1u << index); + } else { + enabled_vertex_buffers_mask &= ~(1u << index); + } +} + template void BufferCache

::BindHostVertexBuffers() { - HostBindings host_bindings; - bool any_valid{false}; auto& flags = maxwell3d->dirty.flags; - for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { - const Binding& binding = channel_state->vertex_buffers[index]; + u32 enabled_mask = enabled_vertex_buffers_mask; + HostBindings bindings{}; + u32 last_index = std::numeric_limits::max(); + const auto flush_bindings = [&]() { + if (bindings.buffers.empty()) { + return; + } + bindings.max_index = bindings.min_index + static_cast(bindings.buffers.size()); + runtime.BindVertexBuffers(bindings); + bindings = HostBindings{}; + last_index = std::numeric_limits::max(); + }; + while (enabled_mask != 0) { + const u32 index = std::countr_zero(enabled_mask); + enabled_mask &= (enabled_mask - 1); + const Binding& binding = VertexBufferSlot(index); Buffer& buffer = slot_buffers[binding.buffer_id]; TouchBuffer(buffer, binding.buffer_id); SynchronizeBuffer(buffer, binding.device_addr, binding.size); if (!flags[Dirty::VertexBuffer0 + index]) { + flush_bindings(); continue; } flags[Dirty::VertexBuffer0 + index] = false; - - host_bindings.min_index = (std::min)(host_bindings.min_index, index); - host_bindings.max_index = (std::max)(host_bindings.max_index, index); - any_valid = true; - } - - if (any_valid) { - host_bindings.max_index++; - for (u32 index = host_bindings.min_index; index < host_bindings.max_index; index++) { - flags[Dirty::VertexBuffer0 + index] = false; - - const Binding& binding = channel_state->vertex_buffers[index]; - Buffer& buffer = slot_buffers[binding.buffer_id]; - - const u32 stride = maxwell3d->regs.vertex_streams[index].stride; - const u32 offset = buffer.Offset(binding.device_addr); - buffer.MarkUsage(offset, binding.size); - - host_bindings.buffers.push_back(&buffer); - host_bindings.offsets.push_back(offset); - host_bindings.sizes.push_back(binding.size); - host_bindings.strides.push_back(stride); + const u32 stride = maxwell3d->regs.vertex_streams[index].stride; + const u32 offset = buffer.Offset(binding.device_addr); + buffer.MarkUsage(offset, binding.size); + if (!bindings.buffers.empty() && index != last_index + 1) { + flush_bindings(); } - runtime.BindVertexBuffers(host_bindings); + if (bindings.buffers.empty()) { + bindings.min_index = index; + } + bindings.buffers.push_back(&buffer); + bindings.offsets.push_back(offset); + bindings.sizes.push_back(binding.size); + bindings.strides.push_back(stride); + last_index = index; } + flush_bindings(); } template @@ -1208,17 +1398,20 @@ void BufferCache

::UpdateVertexBuffer(u32 index) { u32 size = address_size; // TODO: Analyze stride and number of vertices if (array.enable == 0 || size == 0 || !device_addr) { channel_state->vertex_buffers[index] = NULL_BINDING; + UpdateVertexBufferSlot(index, NULL_BINDING); return; } if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || size >= 64_MiB) { size = static_cast(gpu_memory->MaxContinuousRange(gpu_addr_begin, size)); } const BufferId buffer_id = FindBuffer(*device_addr, size); - channel_state->vertex_buffers[index] = Binding{ + const Binding binding{ .device_addr = *device_addr, .size = size, .buffer_id = buffer_id, }; + channel_state->vertex_buffers[index] = binding; + UpdateVertexBufferSlot(index, binding); } template @@ -1531,12 +1724,12 @@ void BufferCache

::TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept { template bool BufferCache

::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 size) { - boost::container::small_vector copies; + upload_copies.clear(); u64 total_size_bytes = 0; u64 largest_copy = 0; - DAddr buffer_start = buffer.CpuAddr(); + const DAddr buffer_start = buffer.cpu_addr_cached; memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) { - copies.push_back(BufferCopy{ + upload_copies.push_back(BufferCopy{ .src_offset = total_size_bytes, .dst_offset = device_addr_out - buffer_start, .size = range_size, @@ -1547,8 +1740,9 @@ bool BufferCache

::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 si if (total_size_bytes == 0) { return true; } - const std::span copies_span(copies.data(), copies.size()); + const std::span copies_span(upload_copies.data(), upload_copies.size()); UploadMemory(buffer, total_size_bytes, largest_copy, copies_span); + any_buffer_uploaded = true; return false; } @@ -1738,6 +1932,7 @@ void BufferCache

::DeleteBuffer(BufferId buffer_id, bool do_not_mark) { auto& binding = channel_state->vertex_buffers[index]; if (binding.buffer_id == buffer_id) { binding.buffer_id = BufferId{}; + UpdateVertexBufferSlot(index, binding); dirty_vertex_buffers.push_back(index); } } diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index c26402c43c..76783a446b 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -321,6 +321,7 @@ public: std::recursive_mutex mutex; Runtime& runtime; + bool any_buffer_uploaded = false; private: template @@ -373,6 +374,8 @@ private: void BindHostTransformFeedbackBuffers(); + void BindHostVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride); + void BindHostComputeUniformBuffers(); void BindHostComputeStorageBuffers(); @@ -454,6 +457,12 @@ private: [[nodiscard]] bool HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept; + [[nodiscard]] Binding& VertexBufferSlot(u32 index); + + [[nodiscard]] const Binding& VertexBufferSlot(u32 index) const; + + void UpdateVertexBufferSlot(u32 index, const Binding& binding); + void ClearDownload(DAddr base_addr, u64 size); void InlineMemoryImplementation(DAddr dest_address, size_t copy_size, @@ -473,6 +482,45 @@ private: u32 last_index_count = 0; + u32 enabled_vertex_buffers_mask = 0; + u64 vertex_buffers_serial = 0; + Binding v_buffer0{}; + Binding v_buffer1{}; + Binding v_buffer2{}; + Binding v_buffer3{}; + Binding v_buffer4{}; + Binding v_buffer5{}; + Binding v_buffer6{}; + Binding v_buffer7{}; + Binding v_buffer8{}; + Binding v_buffer9{}; + Binding v_buffer10{}; + Binding v_buffer11{}; + Binding v_buffer12{}; + Binding v_buffer13{}; + Binding v_buffer14{}; + Binding v_buffer15{}; +#ifndef __APPLE__ + Binding v_buffer16{}; + Binding v_buffer17{}; + Binding v_buffer18{}; + Binding v_buffer19{}; + Binding v_buffer20{}; + Binding v_buffer21{}; + Binding v_buffer22{}; + Binding v_buffer23{}; + Binding v_buffer24{}; + Binding v_buffer25{}; + Binding v_buffer26{}; + Binding v_buffer27{}; + Binding v_buffer28{}; + Binding v_buffer29{}; + Binding v_buffer30{}; + Binding v_buffer31{}; +#endif + + boost::container::small_vector upload_copies; + MemoryTracker memory_tracker; Common::RangeSet uncommitted_gpu_modified_ranges; Common::PageBitsetRangeSet gpu_modified_ranges; diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp index 661fbef2b0..d1c61be743 100644 --- a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -189,6 +189,10 @@ void ComputePipeline::Configure() { buffer_cache.runtime.SetEnableStorageBuffers(use_storage_buffers); buffer_cache.runtime.SetImagePointers(textures.data(), images.data()); buffer_cache.BindHostComputeBuffers(); + if (buffer_cache.any_buffer_uploaded) { + buffer_cache.runtime.PostCopyBarrier(); + buffer_cache.any_buffer_uploaded = false; + } const VideoCommon::ImageViewInOut* views_it{views.data() + num_texture_buffers + num_image_buffers}; diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp index 2abbd0de78..ee3498428e 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -558,6 +558,10 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { if (image_binding != 0) { glBindImageTextures(0, image_binding, images.data()); } + if (buffer_cache.any_buffer_uploaded) { + buffer_cache.runtime.PostCopyBarrier(); + buffer_cache.any_buffer_uploaded = false; + } return true; } diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 96a9fe59e7..51b5141a06 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -203,6 +203,10 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, buffer_cache.UpdateComputeBuffers(); buffer_cache.BindHostComputeBuffers(); + if (buffer_cache.any_buffer_uploaded) { + buffer_cache.runtime.PostCopyBarrier(); + buffer_cache.any_buffer_uploaded = false; + } RescalingPushConstant rescaling; const VideoCommon::SamplerId* samplers_it{samplers.data()}; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index d36553da4a..d156baa77b 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -496,6 +496,10 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { if constexpr (Spec::enabled_stages[4]) { prepare_stage(4); } + if (buffer_cache.any_buffer_uploaded) { + buffer_cache.runtime.PostCopyBarrier(); + buffer_cache.any_buffer_uploaded = false; + } texture_cache.UpdateRenderTargets(false); texture_cache.CheckFeedbackLoop(views); ConfigureDraw(rescaling, render_area);