From 5373686bd4b5395435471fbda78a8a7c82d460c0 Mon Sep 17 00:00:00 2001 From: MarcelineVQ Date: Mon, 23 Mar 2026 22:47:50 -0700 Subject: [PATCH] =?UTF-8?q?particle:=20V4=20vertex=20store,=20inline=20for?= =?UTF-8?q?=20unroll=20=E2=80=94=20~42%=20total=20reduction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - V4 store (vmovups) writes xyz+color as one 16-byte op instead of 4 scalar stores. Unaligned but still 1 μop on modern CPUs. - inline for unrolls 4-vertex loops, letting LLVM schedule stores across vertices and fill pipeline bubbles. - Hoisted world_pos to locals to prevent array re-reads. - A/B: BASELINE ~433ms → CUSTOM ~299ms (~31% per-period reduction). Total from original: 520ms → 299ms = 42% reduction. --- src/transform44/particle_sse.zig | 54 +++++++++++++++++--------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/transform44/particle_sse.zig b/src/transform44/particle_sse.zig index 34c51ae..3179877 100644 --- a/src/transform44/particle_sse.zig +++ b/src/transform44/particle_sse.zig @@ -189,14 +189,12 @@ const VBState = struct { // All strides are 24 except normal (0, shared global). // Write contiguously when stride == 24 and layout matches. if (s.pos_stride == 24 and s.color_ptr == s.pos + 12 and s.texcoord == s.pos + 16) { - // Fast path: single contiguous 24-byte vertex write - const p: [*]u32 = @ptrFromInt(s.pos); - p[0] = @bitCast(px); // x - p[1] = @bitCast(py); // y - p[2] = @bitCast(pz); // z - p[3] = color; // color at +12 - p[4] = @bitCast(tu); // u at +16 - p[5] = @bitCast(tv); // v at +20 + // Fast path: contiguous 24-byte vertex. V4 store for xyz+color (16 bytes), + // then 2 scalar stores for uv (8 bytes). Unaligned V4 store via vmovups. + const xyzc = V4{ px, py, pz, @bitCast(color) }; + @as(*align(1) V4, @ptrFromInt(s.pos)).* = xyzc; + wf32(s.pos + 16, tu); + wf32(s.pos + 20, tv); s.pos += 24; s.color_ptr += 24; s.texcoord += 24; @@ -478,18 +476,21 @@ export fn renderParticleSprites_SSE(emitter: u32, particle_data: u32, vertex_buf // Assembly: eax starts at 0, adds 8 between X and Y reads. // X: [eax+0x87D714], eax+=8, Y: [eax+0x87D710]=[eax_new+0x87D710] // texU: [eax+0x87D72C], texV: [eax+0x87D730] (eax already incremented) - // 4 vertices with cached VB state to avoid pointer re-reads. + // Unrolled — inline for lets LLVM schedule stores across vertices. { var vs = VBState.load(vb); - var loop_off: u32 = 0; - while (loop_off < 0x20) : (loop_off += 8) { + const wpx = world_pos[0]; + const wpy = world_pos[1]; + const wpz = world_pos[2]; + inline for (0..4) |i| { + const off: u32 = @intCast(i * 8); vs.emit( - @mulAdd(f32, sprite_scale, rf32(G.billboard_offsets_x + loop_off), world_pos[0]), - @mulAdd(f32, sprite_scale, rf32(G.billboard_offsets_y + loop_off), world_pos[1]), - world_pos[2], + @mulAdd(f32, sprite_scale, rf32(G.billboard_offsets_x + off), wpx), + @mulAdd(f32, sprite_scale, rf32(G.billboard_offsets_y + off), wpy), + wpz, color_value, - @mulAdd(f32, rf32(G.sprite_tex_u + loop_off + 8), tex_scale_u, tex_u_base), - @mulAdd(f32, rf32(G.sprite_tex_v + loop_off + 8), tex_scale_v, tex_v_base), + @mulAdd(f32, rf32(G.sprite_tex_u + off + 8), tex_scale_u, tex_u_base), + @mulAdd(f32, rf32(G.sprite_tex_v + off + 8), tex_scale_v, tex_v_base), ); } vs.writeback(); @@ -538,17 +539,20 @@ export fn renderParticleSprites_SSE(emitter: u32, particle_data: u32, vertex_buf { var vs = VBState.load(vb); - var loop_off: u32 = 0; - while (loop_off < 0x20) : (loop_off += 8) { - const ox = rf32(G.billboard_offsets_x + loop_off); - const oy = rf32(G.billboard_offsets_y + loop_off); + const wpx = world_pos[0]; + const wpy = world_pos[1]; + const wpz = world_pos[2]; + inline for (0..4) |i| { + const off: u32 = @intCast(i * 8); + const ox = rf32(G.billboard_offsets_x + off); + const oy = rf32(G.billboard_offsets_y + off); vs.emit( - @mulAdd(f32, ox, scaled_cos, world_pos[0]) - oy * scaled_sin, - @mulAdd(f32, oy, scaled_cos, @mulAdd(f32, ox, scaled_sin, world_pos[1])), - world_pos[2], + @mulAdd(f32, ox, scaled_cos, wpx) - oy * scaled_sin, + @mulAdd(f32, oy, scaled_cos, @mulAdd(f32, ox, scaled_sin, wpy)), + wpz, color_value, - @mulAdd(f32, rf32(G.sprite_tex_u + loop_off + 8), tex_scale_u, tex_u_base), - @mulAdd(f32, rf32(G.sprite_tex_v + loop_off + 8), tex_scale_v, tex_v_base), + @mulAdd(f32, rf32(G.sprite_tex_u + off + 8), tex_scale_u, tex_u_base), + @mulAdd(f32, rf32(G.sprite_tex_v + off + 8), tex_scale_v, tex_v_base), ); } vs.writeback();