From 21905799c5777c82fefa81cb2736ad55ea45fa87 Mon Sep 17 00:00:00 2001 From: MarcelineVQ Date: Sat, 4 Apr 2026 18:59:06 -0700 Subject: [PATCH] perf: SSE frustum culling -- AABB test, 8-corner occlusion, behind-camera fix Hook 0x686940 (testAABBFrustum): SSE center/extent P-vertex method replaces x87 sign-bit corner selection. ~160M cycles in profile, 50% rejection rate. Hook 0x686180 (FrustumCullBoundingBox 8-corner): SSE replacement transforms all 8 AABB corners through view-proj, horizon buffer scan with 4-wide test. Fix behind-camera bug in both FrustumCullBoundingBox variants (0x686000, 0x686180): original returned 0 for behind-camera objects, callers interpret 0 as "visible, render it". Now returns 2 (occluded) when all geometry is behind the camera (Z < 0 in clip space). --- src/weirdperformance/silicon_sse.zig | 152 +++++++++++++++++++++- src/weirdperformance/weirdperformance.zig | 3 + 2 files changed, 152 insertions(+), 3 deletions(-) diff --git a/src/weirdperformance/silicon_sse.zig b/src/weirdperformance/silicon_sse.zig index 478df51..b596acb 100644 --- a/src/weirdperformance/silicon_sse.zig +++ b/src/weirdperformance/silicon_sse.zig @@ -13,6 +13,7 @@ const TC: CC = .{ .x86_thiscall = .{} }; const FC: CC = .{ .x86_fastcall = .{} }; const SC: CC = .{ .x86_stdcall = .{} }; + inline fn loadV4(ptr: u32) V4 { return @as(*align(1) const V4, @ptrFromInt(ptr)).*; } @@ -537,13 +538,16 @@ pub fn si_translateBoundingVol(this: u32, offset: u32) callconv(TC) void { obj[51] += dx; obj[52] += dy; obj[53] += dz; } -// --- 0x686000: FrustumCullBoundingBox --- -// Transforms bbox through view-proj matrix, perspective divides, projects to 320-column -// occlusion buffer. Returns 0 (culled) / 2 (visible). +// --- 0x686000: FrustumCullBoundingBox (center+radius variant) --- +// Transforms bbox center through view-proj matrix, perspective divides, projects to 320-column +// occlusion buffer. Returns 0 (visible/passthrough) / 2 (occluded). +// Callers treat 0 as "render" and 2 as "skip". Original returned 0 for behind-camera +// objects, which is wrong -- they should be occluded (2). // Original: 380 bytes, 2 calls to mat*vec3 (0x7BCA80), x87 perspective divide, x87 column scan. // SSE: inline V4 mat*vec3, SSE perspective divide, 4-wide column scan. // __fastcall(bbox_ECX, flags_EDX, radius_stack), RET 0x4 pub fn si_frustumCullBBox(bbox: u32, flags: u32, radius_bits: u32) callconv(FC) u32 { + // Early out: global occlusion flag bit 5 if ((@as(*const u8, @ptrFromInt(0xC7B2A4)).* & 0x20) == 0) return 0; @@ -575,6 +579,7 @@ pub fn si_frustumCullBBox(bbox: u32, flags: u32, radius_bits: u32) callconv(FC) // Behind-camera check (unless flags & 8) if ((flags & 0x8) == 0) { + if (center[2] < 0.0) return 2; if (center[2] < @as(*align(1) const f32, @ptrFromInt(0x80FED4)).*) return 0; } @@ -630,6 +635,147 @@ pub fn si_frustumCullBBox(bbox: u32, flags: u32, radius_bits: u32) callconv(FC) return 2; // visible — survived all columns } +// --- 0x686180: FrustumCullBoundingBox (8-corner AABB variant) --- +// Transforms all 8 AABB corners through view-proj matrix, perspective divides, +// tracks min/max screen X and max depth, checks 320-column horizon buffer. +// Returns 0 (visible/passthrough) / 2 (occluded). +// Original bug: returned 0 for behind-camera corners, callers treat 0 as "render". +// Fix: return 2 for behind-camera to properly cull. +// __fastcall(bbox_ECX, flags_EDX), RET (no stack cleanup) +pub fn si_frustumCullBBox8(bbox: u32, flags: u32) callconv(FC) u32 { + + // Early out: global occlusion flag bit 5 + if ((@as(*const u8, @ptrFromInt(0xC7B2A4)).* & 0x20) == 0) return 0; + + // Early out: global value must be in valid range + const global_val: f32 = @as(*align(1) const f32, @ptrFromInt(0xC7CFF4)).*; + if (global_val < @as(*align(1) const f32, @ptrFromInt(0x8101AC)).*) return 0; + if (global_val > @as(*align(1) const f32, @ptrFromInt(0x804588)).*) return 0; + + const bp_min: [*]const f32 = @ptrFromInt(bbox); + const bp_max: [*]const f32 = @ptrFromInt(bbox + 0xC); + const check_behind = (flags & 0x8) == 0; + const near_z: f32 = @as(*align(1) const f32, @ptrFromInt(0x80FED4)).*; + const K: f32 = @as(*align(1) const f32, @ptrFromInt(0x7FF9D8)).*; + + var min_sx: f32 = std.math.floatMax(f32); + var max_sx: f32 = -std.math.floatMax(f32); + var max_depth: f32 = -std.math.floatMax(f32); + var corners_behind: u32 = 0; + var any_near: bool = false; + + // Corner lookup: bit 0=x, bit 1=y, bit 2=z; 0=min, 1=max + const m1: u32 = 0xC7B700; + const c0 = loadV4(m1); + const c1 = loadV4(m1 + 16); + const c2 = loadV4(m1 + 32); + const c3 = loadV4(m1 + 48); + + // Transform all 8 corners, classify each + var clip_results: [8]V4 = undefined; + inline for (0..8) |ci| { + const corner_x: f32 = if (ci & 1 != 0) bp_max[0] else bp_min[0]; + const corner_y: f32 = if (ci & 2 != 0) bp_max[1] else bp_min[1]; + const corner_z: f32 = if (ci & 4 != 0) bp_max[2] else bp_min[2]; + const vx: V4 = @splat(corner_x); + const vy: V4 = @splat(corner_y); + const vz: V4 = @splat(corner_z); + clip_results[ci] = @mulAdd(V4, vz, c2, @mulAdd(V4, vy, c1, @mulAdd(V4, vx, c0, c3))); + } + + for (&clip_results) |clip| { + if (check_behind) { + if (clip[2] < 0.0) { + corners_behind += 1; + continue; + } + if (clip[2] < near_z) { + any_near = true; + continue; + } + } + + const inv_w = K * fastRecip(clip[2]); + const sx = clip[0] * inv_w; + const sy = clip[1] * inv_w; + + if (sx < min_sx) min_sx = sx; + if (sx > max_sx) max_sx = sx; + if (sy > max_depth) max_depth = sy; + } + + // All 8 corners behind camera -- cull + if (check_behind and corners_behind == 8) return 2; + + // Any corner in near zone or no valid projections -- can't test horizon + if (any_near or min_sx > max_sx) return 0; + + // Column projection + const col_scale: f32 = @as(*align(1) const f32, @ptrFromInt(0x810170)).*; + const col_offset: f32 = @as(*align(1) const f32, @ptrFromInt(0x86861C)).*; + + var left_col: i32 = cvtss2si(@mulAdd(f32, min_sx, col_scale, -col_offset)); + left_col += 0xA0; + var right_col: i32 = cvtss2si(@mulAdd(f32, max_sx, col_scale, -col_offset)); + right_col += 0xA1; + + // Bounds check + if (left_col >= 0x140) return 0; + if (right_col < 0) return 0; + if (left_col < 0) left_col = 0; + if (right_col >= 0x140) right_col = 0x13F; + if (left_col > right_col) return 2; + + // Horizon buffer scan + const horizon_base: u32 = 0xC7B750; + var col: u32 = @intCast(left_col); + const end: u32 = @intCast(right_col); + const depth_v: V4 = @splat(max_depth); + + while (col + 3 <= end) { + const h = loadV4(horizon_base + col * 4); + const lt_bits: u4 = @bitCast(h < depth_v); + if (lt_bits != 0) return 0; + col += 4; + } + while (col <= end) { + if (@as(*align(1) const f32, @ptrFromInt(horizon_base + col * 4)).* < max_depth) return 0; + col += 1; + } + + return 2; // visible -- survived all columns +} + +// --- 0x686940: testAABBFrustum --- +// Tests AABB against 6 frustum planes using P-vertex method (sign-bit corner selection). +// For each plane, selects the AABB corner most aligned with the plane normal. +// If that corner is behind the plane, the entire AABB is outside. +// ~160M cycles/7.5s across all callers. SSE: center/extent formulation avoids branching. +// __thiscall(frustumPlanes_ECX, aabb_stack), RET 0x4 +// Returns 0 (outside) / 3 (inside/intersecting) +pub fn si_testAABBFrustum(planes_ptr: u32, aabb_ptr: u32) callconv(TC) u32 { + + const mn: [*]const f32 = @ptrFromInt(aabb_ptr); + const mx: [*]const f32 = @ptrFromInt(aabb_ptr + 0xC); + + // Center/extent formulation: for each plane, dist = dot(center, n) + d + // P-vertex dist = dist + dot(extent, |n|). If < tolerance, outside. + const center = V4{ (mn[0] + mx[0]) * 0.5, (mn[1] + mx[1]) * 0.5, (mn[2] + mx[2]) * 0.5, 0 }; + const extent = V4{ (mx[0] - mn[0]) * 0.5, (mx[1] - mn[1]) * 0.5, (mx[2] - mn[2]) * 0.5, 0 }; + const tolerance: f32 = @bitCast(@as(*const u32, @ptrFromInt(0x8101B4)).*); + + inline for (0..6) |i| { + const pl = loadV4(planes_ptr + i * 16); + const n = V4{ pl[0], pl[1], pl[2], 0 }; + const abs_n = @abs(n); + const center_dist = dot3v(center, n) + pl[3]; + const extent_dist = dot3v(extent, abs_n); + // P-vertex distance = center_dist + extent_dist + if (center_dist + extent_dist < tolerance) return 0; + } + return 3; +} + // --- 0x6ABC40: processLinkedListCollision --- // Walks intrusive linked list, per-node AABB overlap test, calls addGeometryToBuffer on hit. // Original: 329 bytes, 6 x87 FCOMP/FNSTSW comparisons per node. diff --git a/src/weirdperformance/weirdperformance.zig b/src/weirdperformance/weirdperformance.zig index 3c04fe5..69e3440 100644 --- a/src/weirdperformance/weirdperformance.zig +++ b/src/weirdperformance/weirdperformance.zig @@ -260,6 +260,8 @@ fn installPatches() u32 { .{ .target = 0x686820, .replacement = @intFromPtr(&sse.si_translateBoundingVol), .name = "translateBoundingVol" }, .{ .target = 0x6ABC40, .replacement = @intFromPtr(&sse.si_processLinkedListCollision), .name = "processLinkedListCollision" }, .{ .target = 0x686000, .replacement = @intFromPtr(&sse.si_frustumCullBBox), .name = "frustumCullBBox" }, + .{ .target = 0x686180, .replacement = @intFromPtr(&sse.si_frustumCullBBox8), .name = "frustumCullBBox8" }, + .{ .target = 0x686940, .replacement = @intFromPtr(&sse.si_testAABBFrustum), .name = "testAABBFrustum" }, .{ .target = 0x6B8C60, .replacement = @intFromPtr(&performSpatialCulling), .name = "PerformSpatialCulling" }, .{ .target = 0x6B88E0, .replacement = @intFromPtr(&performCollisionDetectionSSE), .name = "performCollisionDetection" }, .{ .target = 0x7C2C40, .replacement = @intFromPtr(&rayTriIntersectIndexedInt), .name = "rayTriIndexedInt" }, @@ -277,6 +279,7 @@ fn installPatches() u32 { return count; } + // ============================================================================= // Install / Remove // =============================================================================