diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index dda11c1a..c806bc95 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -147,6 +147,13 @@ struct alignas(16) R5900Context // Initialize VU0 registers vu0_q = 1.0f; // Q register usually initialized to 1.0 + // vf0 is hardwired on real VU0 hardware to (x,y,z,w) = (0,0,0,1) and reads + // back that constant regardless of writes; it is the vector-unit analogue of + // GPR r0. memset above leaves it (0,0,0,0), so w reads 0 instead of 1. Pin it + // here so every context this runtime constructs starts hardware-correct. + // (_mm_set_ps arguments are in (w, z, y, x) order, so this is x=0,y=0,z=0,w=1.) + vu0_vf[0] = _mm_set_ps(1.0f, 0.0f, 0.0f, 0.0f); + // Reset COP0 registers cop0_random = 47; // Start at maximum value // cop0_status = 0x400000; // BEV set, ERL clear, kernel mode diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index b79e386a..f5c29ae8 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -524,6 +524,10 @@ PS2Runtime::PS2Runtime() // R0 is always zero in MIPS m_cpuContext.r[0] = _mm_set1_epi32(0); + // vf0 is hardwired to (0,0,0,1) on VU0 hardware; the memset above cleared + // the constructor's pin, so repin it here, mirroring r0. + m_cpuContext.vu0_vf[0] = _mm_set_ps(1.0f, 0.0f, 0.0f, 0.0f); + // Stack pointer (SP) and global pointer (GP) will be set by the loaded ELF m_loadedModules.clear(); diff --git a/ps2xTest/src/ps2_runtime_expansion_tests.cpp b/ps2xTest/src/ps2_runtime_expansion_tests.cpp index c31b4d42..0e12cc5b 100644 --- a/ps2xTest/src/ps2_runtime_expansion_tests.cpp +++ b/ps2xTest/src/ps2_runtime_expansion_tests.cpp @@ -2244,5 +2244,28 @@ void register_ps2_runtime_expansion_tests() std::memcpy(&directCmd, rdram.data() + kBaseAddr + 12u, sizeof(directCmd)); t.Equals(directCmd, 0x50000001u, "sceVif1PkCloseDirectCode should store a 1-QW DIRECT length"); }); + + tc.Run("R5900Context constructor pins vf0 to hardware (0,0,0,1)", [](TestCase &t) + { + R5900Context ctx; // default constructor is under test + alignas(16) float vf0[4]{}; + _mm_storeu_ps(vf0, ctx.vu0_vf[0]); // vf0[0..3] = x,y,z,w + t.Equals(vf0[0], 0.0f, "vf0.x must be 0"); + t.Equals(vf0[1], 0.0f, "vf0.y must be 0"); + t.Equals(vf0[2], 0.0f, "vf0.z must be 0"); + t.Equals(vf0[3], 1.0f, "vf0.w must be 1 (VU0 hardware homogeneous constant)"); + }); + + tc.Run("PS2Runtime's own CPU context has vf0 pinned to hardware (0,0,0,1)", [](TestCase &t) + { + PS2Runtime runtime; // runtime constructor's memset/repin is under test + const R5900Context &ctx = runtime.cpu(); + alignas(16) float vf0[4]{}; + _mm_storeu_ps(vf0, ctx.vu0_vf[0]); // vf0[0..3] = x,y,z,w + t.Equals(vf0[0], 0.0f, "runtime vf0.x must be 0"); + t.Equals(vf0[1], 0.0f, "runtime vf0.y must be 0"); + t.Equals(vf0[2], 0.0f, "runtime vf0.z must be 0"); + t.Equals(vf0[3], 1.0f, "runtime vf0.w must be 1 (VU0 hardware homogeneous constant)"); + }); }); }