From 52343fa2f39ca628b40a618a0082a9cff883a286 Mon Sep 17 00:00:00 2001 From: Malcolm Date: Tue, 15 Sep 2026 04:41:36 -0400 Subject: [PATCH] BufferUtils: Add native NEON upload paths --- rpcs3/Emu/RSX/Common/BufferUtils.cpp | 14 ++- rpcs3/Emu/RSX/Common/BufferUtils_neon.h | 143 ++++++++++++++++++++++++ rpcs3/emucore.vcxproj | 1 + rpcs3/emucore.vcxproj.filters | 3 + 4 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 rpcs3/Emu/RSX/Common/BufferUtils_neon.h diff --git a/rpcs3/Emu/RSX/Common/BufferUtils.cpp b/rpcs3/Emu/RSX/Common/BufferUtils.cpp index 29bc4e0fa8..fcfd290952 100644 --- a/rpcs3/Emu/RSX/Common/BufferUtils.cpp +++ b/rpcs3/Emu/RSX/Common/BufferUtils.cpp @@ -8,6 +8,8 @@ #if defined(ARCH_X64) #include "BufferUtils_avx512.h" +#elif defined(ARCH_ARM64) +#include "BufferUtils_neon.h" #endif #if !defined(_MSC_VER) @@ -183,6 +185,9 @@ namespace #if defined(ARCH_X64) DECLARE(copy_data_swap_u32) = build_function_asm("copy_data_swap_u32", &build_copy_data_swap_u32); DECLARE(copy_data_swap_u32_cmp) = build_function_asm("copy_data_swap_u32_cmp", &build_copy_data_swap_u32); +#elif defined(ARCH_ARM64) +DECLARE(copy_data_swap_u32) = copy_data_swap_u32_neon; +DECLARE(copy_data_swap_u32_cmp) = copy_data_swap_u32_neon; #else DECLARE(copy_data_swap_u32) = copy_data_swap_u32_naive; DECLARE(copy_data_swap_u32_cmp) = copy_data_swap_u32_naive; @@ -299,6 +304,8 @@ namespace r = upload_xi16(src.data(), dst.data(), count); else r = upload_xi32(src.data(), dst.data(), count); +#elif defined(ARCH_ARM64) + r = upload_untouched_neon(src.data(), dst.data(), count); #else r = upload_untouched_naive(src.data(), dst.data(), count); #endif @@ -405,6 +412,8 @@ namespace r = upload_xi16(src.data(), dst.data(), count, restart_index); else r = upload_xi32(src.data(), dst.data(), count, restart_index); +#elif defined(ARCH_ARM64) + r = upload_untouched_neon(src.data(), dst.data(), count, restart_index); #else r = upload_untouched_naive(src.data(), dst.data(), count, restart_index); #endif @@ -699,10 +708,13 @@ void iota16(u16* dst, u32 count) iota16_avx2(dst, count); return; } +#elif defined(ARCH_ARM64) + iota16_neon(dst, count); + return; #endif u32 i = 0; -#if defined(ARCH_X64) || defined(ARCH_ARM64) +#if defined(ARCH_X64) // Force 16-byte alignment const uptr mem_addr = reinterpret_cast(dst); const u32 head = std::min(count, ((0 - mem_addr) & 15) / sizeof(u16)); diff --git a/rpcs3/Emu/RSX/Common/BufferUtils_neon.h b/rpcs3/Emu/RSX/Common/BufferUtils_neon.h new file mode 100644 index 0000000000..9b29b7239d --- /dev/null +++ b/rpcs3/Emu/RSX/Common/BufferUtils_neon.h @@ -0,0 +1,143 @@ +#pragma once + +#include + +namespace +{ + template + auto copy_data_swap_u32_neon(u32* dst, const u32* src, u32 count) + { + uint32x4_t changed = vdupq_n_u32(0); + u32 tail_changed = 0; + u32 i = 0; + + for (; count - i >= 4; i += 4) + { + const auto value = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(reinterpret_cast(src + i)))); + + if constexpr (Compare) + { + changed = vorrq_u32(changed, veorq_u32(value, vld1q_u32(dst + i))); + } + + vst1q_u32(dst + i, value); + } + + for (; i < count; ++i) + { + const u32 value = stx::se_storage::swap(src[i]); + + if constexpr (Compare) + { + tail_changed |= value ^ dst[i]; + } + + dst[i] = value; + } + + if constexpr (Compare) + { + return (vmaxvq_u32(changed) | tail_changed) != 0; + } + } + + template + u64 upload_untouched_neon(const be_t* src, T* dst, u32 count, T restart_index = 0) + { + T min_index = static_cast(-1); + T max_index = 0; + u32 i = 0; + + if constexpr (sizeof(T) == 2) + { + auto min = vdupq_n_u16(-1); + auto max = vdupq_n_u16(0); + const auto restart = vdupq_n_u16(restart_index); + + for (; count - i >= 8; i += 8) + { + auto value = vreinterpretq_u16_u8(vrev16q_u8(vld1q_u8(reinterpret_cast(src + i)))); + + if constexpr (Restart) + { + const auto mask = vceqq_u16(value, restart); + max = vmaxq_u16(max, vbicq_u16(value, mask)); + value = vorrq_u16(value, mask); + } + else + { + max = vmaxq_u16(max, value); + } + + min = vminq_u16(min, value); + vst1q_u16(dst + i, value); + } + + min_index = vminvq_u16(min); + max_index = vmaxvq_u16(max); + } + else + { + auto min = vdupq_n_u32(-1); + auto max = vdupq_n_u32(0); + const auto restart = vdupq_n_u32(restart_index); + + for (; count - i >= 4; i += 4) + { + auto value = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(reinterpret_cast(src + i)))); + + if constexpr (Restart) + { + const auto mask = vceqq_u32(value, restart); + max = vmaxq_u32(max, vbicq_u32(value, mask)); + value = vorrq_u32(value, mask); + } + else + { + max = vmaxq_u32(max, value); + } + + min = vminq_u32(min, value); + vst1q_u32(dst + i, value); + } + + min_index = vminvq_u32(min); + max_index = vmaxvq_u32(max); + } + + for (; i < count; ++i) + { + const T value = src[i]; + if (Restart && value == restart_index) + { + dst[i] = static_cast(-1); + } + else + { + min_index = std::min(min_index, value); + max_index = std::max(max_index, value); + dst[i] = value; + } + } + + return (u64{max_index} << 32) | min_index; + } + + void iota16_neon(u16* dst, u32 count) + { + const u16 initial[] = {0, 1, 2, 3, 4, 5, 6, 7}; + auto value = vld1q_u16(initial); + u32 i = 0; + + for (; count - i >= 8; i += 8) + { + vst1q_u16(dst + i, value); + value = vaddq_u16(value, vdupq_n_u16(8)); + } + + for (; i < count; ++i) + { + dst[i] = static_cast(i); + } + } +} // namespace diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 682662d8bb..381b2a2874 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -1060,6 +1060,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 328b77b247..e269f77927 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1686,6 +1686,9 @@ Emu\GPU\RSX\Common + + Emu\GPU\RSX\Common + Utilities