From bf7e32cdae4d444dfcc6f81ff77a56a2c77fe538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 15 Oct 2026 15:24:28 +0200 Subject: [PATCH] Vertex decoder: Build the skin matrix without a branch per weight on ARM64 The steps skipped bones with a zero weight. With weights that vary from vertex to vertex that branch mispredicts a lot, and it saves only a few multiplies. Now every weight is applied, with fused multiply-adds, as the vertex JIT does. Without the vertex JIT (as on iOS): Tekken 4.5% fewer cycles, God Eater 2.6%, God of War 1.7%. Co-Authored-By: Claude Opus 5.5 (1M context) --- GPU/Common/VertexDecoderCommon.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index dc8a6eb58a..8b0a368f03 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -247,12 +247,20 @@ void VertexDecoder::ComputeSkinMatrix(const float weights[8]) const { float32x4_t skin2 = vdupq_n_f32(0.0f); for (int j = 0; j < count; j++) { const float *bone = &gstate.boneMatrix[j * 12]; +#if PPSSPP_ARCH(ARM64_NEON) + // Every weight, fused, like the vertex JIT (and no branch on varying weights). + const float32x4_t w = vdupq_n_f32(weights[j]); + skin0 = vfmaq_f32(skin0, vld1q_f32(bone), w); + skin1 = vfmaq_f32(skin1, vld1q_f32(bone + 4), w); + skin2 = vfmaq_f32(skin2, vld1q_f32(bone + 8), w); +#else if (weights[j] != 0.0f) { float32x4_t w = vdupq_n_f32(weights[j]); skin0 = vaddq_f32(skin0, vmulq_f32(vld1q_f32(bone), w)); skin1 = vaddq_f32(skin1, vmulq_f32(vld1q_f32(bone + 4), w)); skin2 = vaddq_f32(skin2, vmulq_f32(vld1q_f32(bone + 8), w)); } +#endif } vst1q_f32(skinMatrix, skin0); vst1q_f32(skinMatrix + 4, skin1);