From d2bef2731cea600c49f1dfc6f0f34b81bc49c511 Mon Sep 17 00:00:00 2001 From: PavelBARABANOV Date: Tue, 24 Feb 2026 18:49:44 +0100 Subject: [PATCH] [android, tu] Adjusted PoD of Vertex/ Buffers for older turnip drivers (#3621) This PR aims to return an older way to bind and host vertex/ buffers (via toggle), which had a bunch of indirection and added unnecessary overhead during the drawing phase; current new approach adds just PoD for this operations, which seems to not be acceptable for older turnip drivers. Meanwhile the performance improvements are gonna be enabled only if the toggle is turned on, it will be required to use newer turnip drivers to make it work (26.0+), default behavior will allow older drivers work as intended. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3621 Reviewed-by: CamilleLaVey Co-authored-by: PavelBARABANOV Co-committed-by: PavelBARABANOV --- .../features/settings/model/BooleanSetting.kt | 1 + .../settings/model/view/SettingsItem.kt | 7 + .../settings/ui/SettingsFragmentPresenter.kt | 1 + .../app/src/main/res/values/strings.xml | 3 +- src/common/settings.h | 10 ++ src/video_core/buffer_cache/buffer_cache.h | 121 ++++++++++++------ 6 files changed, 105 insertions(+), 38 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 744c8acae9..2418003904 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -25,6 +25,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"), RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"), ENABLE_BUFFER_HISTORY("enable_buffer_history"), + USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"), SYNC_MEMORY_OPERATIONS("sync_memory_operations"), BUFFER_REORDER_DISABLE("disable_buffer_reorder"), RENDERER_DEBUG("debug"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 7f212e2cca..a8bd44983b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -795,6 +795,13 @@ abstract class SettingsItem( descriptionId = R.string.enable_buffer_history_description ) ) + put( + SwitchSetting( + BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS, + titleId = R.string.use_optimized_vertex_buffers, + descriptionId = R.string.use_optimized_vertex_buffers_description + ) + ) put( SwitchSetting( BooleanSetting.SYNC_MEMORY_OPERATIONS, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 8f3c28c7a8..332617804e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -278,6 +278,7 @@ class SettingsFragmentPresenter( add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key) add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key) add(BooleanSetting.ENABLE_BUFFER_HISTORY.key) + add(BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS.key) add(HeaderSetting(R.string.hacks)) diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index a34d75d7cb..fc1334863d 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -500,7 +500,8 @@ Improves rendering accuracy in some games at the cost of performance. Enable buffer history Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Optimized Vertex Buffers + Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers. Will crash on older drivers. Hacks diff --git a/src/common/settings.h b/src/common/settings.h index 984ae6cc64..1aff6cc74d 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -519,6 +519,16 @@ struct Values { true, true}; +#ifdef ANDROID + SwitchableSetting use_optimized_vertex_buffers{linkage, + false, + "use_optimized_vertex_buffers", + Category::RendererAdvanced, + Specialization::Default, + true, + true}; +#endif + // Renderer Hacks // SwitchableSetting fast_gpu_time{linkage, GpuOverclock::Medium, diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 7b92edecaa..014b4a318e 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -805,47 +805,94 @@ void BufferCache

::UpdateVertexBufferSlot(u32 index, const Binding& binding) { template void BufferCache

::BindHostVertexBuffers() { - auto& flags = maxwell3d->dirty.flags; - 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; + +#ifdef ANDROID + const bool use_optimized_vertex_buffers = Settings::values.use_optimized_vertex_buffers.GetValue(); +#else + constexpr bool use_optimized_vertex_buffers = true; +#endif + + if (use_optimized_vertex_buffers) { + auto& flags = maxwell3d->dirty.flags; + 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; + 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(); + } + 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; } - flags[Dirty::VertexBuffer0 + index] = false; - 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(); + flush_bindings(); + } else { + 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]; + Buffer& buffer = slot_buffers[binding.buffer_id]; + TouchBuffer(buffer, binding.buffer_id); + SynchronizeBuffer(buffer, binding.device_addr, binding.size); + if (!flags[Dirty::VertexBuffer0 + index]) { + 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 (bindings.buffers.empty()) { - bindings.min_index = index; + + 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); + } + runtime.BindVertexBuffers(host_bindings); } - 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