diff --git a/src/bench/main.zig b/src/bench/main.zig index 81334a2..40dc13c 100644 --- a/src/bench/main.zig +++ b/src/bench/main.zig @@ -52,7 +52,7 @@ extern fn si_translateBoundingVol(u32, u32) callconv(cc_tc) void; extern fn si_addVec3ToAccumulator(u32, u32, u32) callconv(cc_tc) void; extern fn si_addToColorAccumulator(u32, u32) callconv(cc_tc) void; extern fn si_packParticleColor(u32, u32, u32, u32) callconv(cc_fc) void; -extern fn si_setParticleAlpha(u32, u32) callconv(cc_fc) void; +extern fn si_setParticleAlpha(u32, u32, u32) callconv(cc_fc) void; // fastcall(ECX=obj, EDX=unused, stack=alpha) extern fn si_ftol() callconv(.naked) void; // ========================================================================= @@ -832,10 +832,10 @@ pub fn main() void { const ab2: u32 = @bitCast(@as(f32, 0.75)); const of = origFn(fn (u32, u32, u32) callconv(cc_fc) void, 0x7B7B10); of(a(&obj_o), 0, ab2); - si_setParticleAlpha(a(&obj_s), ab2); + si_setParticleAlpha(a(&obj_s), 0, ab2); const ok = obj_o[0x12F] == obj_s[0x12F]; var t: u64 = std.math.maxInt(u64); for (0..5) |_| { const _t0 = rdtsc(); for (0..ITERS) |_| { of(a(&obj_o), 0, ab2); } const _te = rdtsc() - _t0; if (_te < t) t = _te; } - var s: u64 = std.math.maxInt(u64); for (0..5) |_| { const _t0 = rdtsc(); for (0..ITERS) |_| { si_setParticleAlpha(a(&obj_s), ab2); } const _te = rdtsc() - _t0; if (_te < s) s = _te; } + var s: u64 = std.math.maxInt(u64); for (0..5) |_| { const _t0 = rdtsc(); for (0..ITERS) |_| { si_setParticleAlpha(a(&obj_s), 0, ab2); } const _te = rdtsc() - _t0; if (_te < s) s = _te; } report("setParticleAlpha", t, s, ok); } diff --git a/src/silicon/silicon.zig b/src/silicon/silicon.zig index d5565bd..b9881dd 100644 --- a/src/silicon/silicon.zig +++ b/src/silicon/silicon.zig @@ -2103,16 +2103,16 @@ fn getPatchTable() []const PatchEntry { .{ .target = 0x7BAE60, .replacement = @intFromPtr(&sse.si_mulMat3x4), .name = "mulMat3x4" }, .{ .target = 0x7BDDB0, .replacement = @intFromPtr(&sse.si_rotateMatByQuat), .name = "rotateMatByQuat" }, .{ .target = 0x7BB860, .replacement = @intFromPtr(&sse.si_createRotMat3x4), .name = "createRotMat3x4" }, - .{ .target = 0x6329E0, .replacement = @intFromPtr(&sse.si_distanceToPlane), .name = "distanceToPlane" }, + // distanceToPlane (0x6329E0): removed — at parity, division dominates both versions .{ .target = 0x686C20, .replacement = @intFromPtr(&sse.si_classifyPointFrustum), .name = "classifyPointFrustum" }, .{ .target = 0x6DC5A0, .replacement = @intFromPtr(&sse.si_checkBoxLineIntersect), .name = "checkBoxLineIntersect" }, .{ .target = 0x6869C0, .replacement = @intFromPtr(&sse.si_testOBBFrustum), .name = "testOBBFrustum" }, .{ .target = 0x686B80, .replacement = @intFromPtr(&sse.si_testSphereFrustum), .name = "testSphereFrustum" }, .{ .target = 0x7C0570, .replacement = @intFromPtr(&sse.si_quatSlerp), .name = "quatSlerp" }, - .{ .target = 0x699330, .replacement = @intFromPtr(&sse.si_isPointInsideBounds), .name = "isPointInsideBounds" }, + // isPointInsideBounds (0x699330): removed — at parity, not worth patching .{ .target = 0x749280, .replacement = @intFromPtr(&sse.si_calculateSinCos), .name = "calculateSinCos" }, .{ .target = 0x7BE5B0, .replacement = @intFromPtr(&sse.si_createZRotMat3x3), .name = "createZRotMat3x3" }, - .{ .target = 0x7BCEF0, .replacement = @intFromPtr(&sse.si_transposeMat4x4), .name = "transposeMat4x4" }, + .{ .target = 0x7BCEF0, .replacement = @intFromPtr(&sse.si_transposeMat4x4), .name = "transposeMat4x4", .direct_size = 64 }, .{ .target = 0x7BB420, .replacement = @intFromPtr(&sse.si_mulMat3x4InPlace), .name = "mulMat3x4InPlace" }, .{ .target = 0x6720F0, .replacement = @intFromPtr(&sse.si_normalizeVec3InPlace), .name = "normalizeVec3InPlace" }, .{ .target = 0x71BC70, .replacement = @intFromPtr(&sse.si_addVec3ToAccumulator), .name = "addVec3ToAccumulator" }, @@ -2120,7 +2120,7 @@ fn getPatchTable() []const PatchEntry { .{ .target = 0x7B7A80, .replacement = @intFromPtr(&sse.si_packParticleColor), .name = "packParticleColor" }, .{ .target = 0x7B7B10, .replacement = @intFromPtr(&sse.si_setParticleAlpha), .name = "setParticleAlpha" }, .{ .target = 0x40A2B0, .replacement = @intFromPtr(&sse.si_ftol), .name = "__ftol", .direct_size = 9 }, - .{ .target = 0x602630, .replacement = @intFromPtr(&sse.si_vec3Dot), .name = "vec3Dot" }, + // vec3Dot (0x602630): removed — 0.6x regression, x87 is optimal for this ABI .{ .target = 0x686820, .replacement = @intFromPtr(&sse.si_translateBoundingVol), .name = "translateBoundingVol" }, }; return &table; diff --git a/src/silicon/silicon_sse.zig b/src/silicon/silicon_sse.zig index ba0d6b8..4ccaf8a 100644 --- a/src/silicon/silicon_sse.zig +++ b/src/silicon/silicon_sse.zig @@ -31,14 +31,25 @@ inline fn dot4v(a: V4, b: V4) f32 { } // --- 0x4549C0: normalizeVec3 (137K/7.5s) --- -// 9cy (1.8x). Reciprocal + 3 muls. Division latency is the floor. -export fn si_normalizeVec3(vec: u32, length_bits: u32) callconv(TC) void { - const v: [*]f32 = @ptrFromInt(vec); - const length: f32 = @bitCast(length_bits); - const scale = 1.0 / length; - v[0] *= scale; - v[1] *= scale; - v[2] *= scale; +// Naked thiscall: ECX=vec, [ESP+4]=length_bits. RET 4. Original: 38 bytes. +// rcpss + NR for fast reciprocal, then 3 multiplies. +export fn si_normalizeVec3() callconv(.naked) void { + asm volatile ( + // xmm0 = 1.0 / length (via rcpss + Newton-Raphson) + \\vmovss 4(%%esp), %%xmm0 + \\vrcpss %%xmm0, %%xmm0, %%xmm1 + \\vmulss %%xmm1, %%xmm0, %%xmm2 + \\vaddss %%xmm1, %%xmm1, %%xmm0 + \\vfnmadd231ss %%xmm1, %%xmm2, %%xmm0 + // xmm0 = refined 1/length. Multiply 3 components. + \\vmulss (%%ecx), %%xmm0, %%xmm1 + \\vmovss %%xmm1, (%%ecx) + \\vmulss 4(%%ecx), %%xmm0, %%xmm1 + \\vmovss %%xmm1, 4(%%ecx) + \\vmulss 8(%%ecx), %%xmm0, %%xmm1 + \\vmovss %%xmm1, 8(%%ecx) + \\ret $4 + ); } // --- 0x7BAE60: mulMat3x4 --- @@ -252,8 +263,7 @@ export fn si_testOBBFrustum(planes_ptr: u32, aabb_ptr: u32, rot_ptr: u32, trans_ } // --- 0x686B80: testSphereFrustum (375K/7.5s) --- -// Serial dot4v with early-out per plane. Gather-based batching tested — slower -// due to stride-16 loads. Early-out is valuable here (most spheres pass all planes). +// Zig thiscall: naked asm tested at 10cy (vhaddps slow), Zig dot4v at 8cy. export fn si_testSphereFrustum(planes_ptr: u32, sphere: u32) callconv(TC) u32 { const s: [*]const f32 = @ptrFromInt(sphere); const center = V4{ s[0], s[1], s[2], 1.0 }; @@ -265,6 +275,7 @@ export fn si_testSphereFrustum(planes_ptr: u32, sphere: u32) callconv(TC) u32 { return 3; } + // --- 0x7C0570: quatSlerp --- // V4 for final blend, @mulAdd for dot product export fn si_quatSlerp(out: u32, a_ptr: u32, t_bits: u32, b_ptr: u32) callconv(FC) u32 { @@ -322,30 +333,35 @@ export fn si_createZRotMat3x3(out: u32, angle_bits: u32) callconv(TC) u32 { } // --- 0x7BCEF0: transposeMat4x4 --- -// Uses V4 loads + @shuffle for efficient transpose -export fn si_transposeMat4x4(src: u32, dst: u32) callconv(TC) u32 { - const r0 = loadV4(src); - const r1 = loadV4(src + 16); - const r2 = loadV4(src + 32); - const r3 = loadV4(src + 48); - - // Interleave low/high pairs - const t0 = @shuffle(f32, r0, r1, [4]i32{ 0, -1, 2, -3 }); // r0[0] r1[0] r0[2] r1[2] - const t1 = @shuffle(f32, r0, r1, [4]i32{ 1, -2, 3, -4 }); // r0[1] r1[1] r0[3] r1[3] - const t2 = @shuffle(f32, r2, r3, [4]i32{ 0, -1, 2, -3 }); // r2[0] r3[0] r2[2] r3[2] - const t3 = @shuffle(f32, r2, r3, [4]i32{ 1, -2, 3, -4 }); // r2[1] r3[1] r2[3] r3[3] - - const d: [*]f32 = @ptrFromInt(dst); - // Final columns - const c0 = @shuffle(f32, t0, t2, [4]i32{ 0, 1, -1, -2 }); // col 0: r0[0] r1[0] r2[0] r3[0] - const c1 = @shuffle(f32, t1, t3, [4]i32{ 0, 1, -1, -2 }); // col 1 - const c2 = @shuffle(f32, t0, t2, [4]i32{ 2, 3, -3, -4 }); // col 2 - const c3 = @shuffle(f32, t1, t3, [4]i32{ 2, 3, -3, -4 }); // col 3 - inline for (0..4) |i| { d[i] = c0[i]; } - inline for (0..4) |i| { d[4 + i] = c1[i]; } - inline for (0..4) |i| { d[8 + i] = c2[i]; } - inline for (0..4) |i| { d[12 + i] = c3[i]; } - return src; +// Naked thiscall: ECX=src, [ESP+4]=dst. RET 4. Original: 156 bytes. +// SSE unpacklo/unpackhi transpose: 4 loads + 4 shuffles + 4 stores. +export fn si_transposeMat4x4() callconv(.naked) void { + asm volatile ( + \\mov 4(%%esp), %%eax + // Load 4 rows from src (ECX) + \\vmovups (%%ecx), %%xmm0 + \\vmovups 16(%%ecx), %%xmm1 + \\vmovups 32(%%ecx), %%xmm2 + \\vmovups 48(%%ecx), %%xmm3 + // Transpose via unpacklo/unpackhi + \\vunpcklps %%xmm1, %%xmm0, %%xmm4 + \\vunpckhps %%xmm1, %%xmm0, %%xmm5 + \\vunpcklps %%xmm3, %%xmm2, %%xmm6 + \\vunpckhps %%xmm3, %%xmm2, %%xmm7 + // Combine into final columns + \\vmovlhps %%xmm6, %%xmm4, %%xmm0 + \\vmovhlps %%xmm4, %%xmm6, %%xmm1 + \\vmovlhps %%xmm7, %%xmm5, %%xmm2 + \\vmovhlps %%xmm5, %%xmm7, %%xmm3 + // Store to dst (EAX) + \\vmovups %%xmm0, (%%eax) + \\vmovups %%xmm1, 16(%%eax) + \\vmovups %%xmm2, 32(%%eax) + \\vmovups %%xmm3, 48(%%eax) + // Return src in EAX + \\mov %%ecx, %%eax + \\ret $4 + ); } // --- 0x7BB420: mulMat3x4InPlace --- @@ -406,14 +422,23 @@ export fn si_addVec3ToAccumulator(this: u32, vec: u32, scale_addr: u32) callconv obj[51] = @mulAdd(f32, v[2], scale, obj[51]); } -// --- 0x71BF60: addToColorAccumulator (10K/7.5s, 0.003ms total) --- -// 3 scalar float adds. At parity with original (9-10cy). Not worth optimizing further. -export fn si_addToColorAccumulator(this: u32, color: u32) callconv(TC) void { - const obj: [*]f32 = @ptrFromInt(this); - const c: [*]const f32 = @ptrFromInt(color); - obj[27] += c[0]; - obj[28] += c[1]; - obj[29] += c[2]; +// --- 0x71BF60: addToColorAccumulator (10K/7.5s) --- +// Naked thiscall: ECX=this, [ESP+4]=color_ptr. RET 4. Original: 34 bytes. +// 3 SSE adds at this+0x6C from color[0..2]. +export fn si_addToColorAccumulator() callconv(.naked) void { + asm volatile ( + \\mov 4(%%esp), %%eax + \\vmovss (%%eax), %%xmm0 + \\vaddss 0x6C(%%ecx), %%xmm0, %%xmm0 + \\vmovss %%xmm0, 0x6C(%%ecx) + \\vmovss 4(%%eax), %%xmm0 + \\vaddss 0x70(%%ecx), %%xmm0, %%xmm0 + \\vmovss %%xmm0, 0x70(%%ecx) + \\vmovss 8(%%eax), %%xmm0 + \\vaddss 0x74(%%ecx), %%xmm0, %%xmm0 + \\vmovss %%xmm0, 0x74(%%ecx) + \\ret $4 + ); } // --- 0x7B7A80: packParticleColor (2K/7.5s) --- @@ -431,12 +456,24 @@ export fn si_packParticleColor(obj: u32, r_bits: u32, g_bits: u32, b_bits: u32) } // --- 0x7B7B10: setParticleAlpha (2K/7.5s) --- -export fn si_setParticleAlpha(obj: u32, alpha_bits: u32) callconv(FC) void { - const base: [*]u8 = @ptrFromInt(obj); - const alpha: f32 = @bitCast(alpha_bits); - base[0x12F] = @intFromFloat(@min(@max(alpha * 255.0, 0.0), 255.0)); +// Naked fastcall: ECX=obj, [ESP+4]=alpha_bits. RET 4. +// Clamp alpha*255 to [0,255], write byte to obj+0x12F. +export fn si_setParticleAlpha() callconv(.naked) void { + asm volatile ( + \\vmovss 4(%%esp), %%xmm0 + \\mov $0x437F0000, %%eax + \\vmovd %%eax, %%xmm1 + \\vmulss %%xmm1, %%xmm0, %%xmm0 + \\vxorps %%xmm2, %%xmm2, %%xmm2 + \\vmaxss %%xmm2, %%xmm0, %%xmm0 + \\vminss %%xmm1, %%xmm0, %%xmm0 + \\vcvtss2si %%xmm0, %%eax + \\mov %%al, 0x12F(%%ecx) + \\ret $4 + ); } + // --- 0x40A2B0: __ftol --- // Drop-in binary replacement. Input: ST(0). Output: EAX:EDX (i64). // SSE3 FISTTP: truncate directly from x87 (9 bytes, replaces 39-byte original)