particle: faithful RenderParticleSprites recreation, A/B verified

Full recreation of RenderParticleSprites (0x7B2A50, 2688 bytes) in
particle_sse.zig. All 5 code paths: 2D billboard, 3D billboard,
2D+rotation (sin/cos), 3D+rotation (axis-angle matrix), tail particles.

Key fixes during verification:
- colorCtx address: removed Ghidra's spurious -0x12 offset
- calcColor arg2: pass raw u32 from emitter+0x1A8, not truncated
- Texture coord lookups: +8 offset to match assembly's eax increment
  between position and texcoord reads in the vertex loop

Verified in-game: particles render identically in CUSTOM vs BASELINE.
Next: optimize with SSE (inline calcColor, V4 vertex math).
This commit is contained in:
MarcelineVQ
2026-03-23 22:12:17 -07:00
parent d205693dbb
commit 574f96f661
2 changed files with 501 additions and 130 deletions
+8
View File
@@ -23,6 +23,7 @@ extern fn rotateMatrixByAxisAngle(u32, u32, u32, u32) void;
extern fn multiplyMatrix4x4(u32, u32, u32) u32;
extern fn transformImpl_SSE(u32, u32, u32, u32, u32) callconv(.c) void;
extern fn calcColorValues_SSE(u32, u32, u32, u32, u32, u32, u32) callconv(.{ .x86_thiscall = .{} }) void;
extern fn renderParticleSprites_SSE(u32, u32, u32) callconv(.{ .x86_thiscall = .{} }) u32;
/// Thiscall wrapper for the SSE implementation. Lives here (baseline SSE2 unit)
/// so LLVM can't inline transformImpl_SSE's alignment into the thiscall frame.
@@ -925,7 +926,14 @@ fn glyphDetour(a: u32, b: u32, c: u32, d: u32) callconv(hook.cc.fastcall) ?*anyo
return ret;
}
fn particleDetour(a: u32, b: u32, c: u32, d: u32) callconv(hook.cc.fastcall) ?*anyopaque {
// a=ECX(emitter), b=EDX(unused), c=particleData, d=vertexBuffers
const s = rdtsc();
if (ab_use_custom) {
const result = renderParticleSprites_SSE(a, c, d);
prof.particle_cycles +|= rdtsc() - s;
prof.particle_calls +|= 1;
return @ptrFromInt(result);
}
const ret = particle_hook.callOriginal(.{ a, b, c, d });
prof.particle_cycles +|= rdtsc() - s;
prof.particle_calls +|= 1;