Wire ssbo rendering into sandbox

This commit is contained in:
Jared Miller 2025-12-16 08:59:58 -05:00
parent 63d72dd94f
commit 3549f2de9c
3 changed files with 60 additions and 3 deletions

View file

@ -8,6 +8,7 @@ pub fn build(b: *std.Build) void {
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.linux_display_backend = .X11, .linux_display_backend = .X11,
.opengl_version = .gl_4_3,
}); });
// sandbox executable // sandbox executable

View file

@ -286,3 +286,35 @@ test "update respawns entity at edge when reaching center" {
try std.testing.expect(on_left or on_right or on_top or on_bottom); try std.testing.expect(on_left or on_right or on_top or on_bottom);
} }
// GPU entity for SSBO rendering (position + color only, no velocity)
pub const GpuEntity = extern struct {
x: f32,
y: f32,
color: u32,
};
test "GpuEntity struct has correct size for SSBO" {
// SSBO layout: x(4) + y(4) + color(4) = 12 bytes
try std.testing.expectEqual(@as(usize, 12), @sizeOf(GpuEntity));
}
test "GpuEntity can be created from Entity" {
const entity = Entity{
.x = 100.0,
.y = 200.0,
.vx = 1.5, // ignored for GPU
.vy = -0.5, // ignored for GPU
.color = 0x00FFFF,
};
const gpu_entity = GpuEntity{
.x = entity.x,
.y = entity.y,
.color = entity.color,
};
try std.testing.expectEqual(@as(f32, 100.0), gpu_entity.x);
try std.testing.expectEqual(@as(f32, 200.0), gpu_entity.y);
try std.testing.expectEqual(@as(u32, 0x00FFFF), gpu_entity.color);
}

View file

@ -5,6 +5,7 @@ const std = @import("std");
const rl = @import("raylib"); const rl = @import("raylib");
const sandbox = @import("sandbox.zig"); const sandbox = @import("sandbox.zig");
const ui = @import("ui.zig"); const ui = @import("ui.zig");
const SsboRenderer = @import("ssbo_renderer.zig").SsboRenderer;
const SCREEN_WIDTH = sandbox.SCREEN_WIDTH; const SCREEN_WIDTH = sandbox.SCREEN_WIDTH;
const SCREEN_HEIGHT = sandbox.SCREEN_HEIGHT; const SCREEN_HEIGHT = sandbox.SCREEN_HEIGHT;
@ -26,7 +27,7 @@ const HEARTBEAT_INTERVAL: f32 = 10.0; // seconds between periodic logs
// auto-benchmark settings // auto-benchmark settings
const BENCH_RAMP_INTERVAL: f32 = 2.0; // seconds between entity ramps const BENCH_RAMP_INTERVAL: f32 = 2.0; // seconds between entity ramps
const BENCH_RAMP_AMOUNT: usize = 10_000; // entities added per ramp const BENCH_RAMP_AMOUNT: usize = 50_000; // entities added per ramp
const BENCH_EXIT_THRESHOLD_MS: f32 = 25.0; // exit when frame time exceeds this const BENCH_EXIT_THRESHOLD_MS: f32 = 25.0; // exit when frame time exceeds this
const BENCH_EXIT_SUSTAIN: f32 = 1.0; // must stay above threshold for this long const BENCH_EXIT_SUSTAIN: f32 = 1.0; // must stay above threshold for this long
@ -154,14 +155,19 @@ pub fn main() !void {
// parse args // parse args
var bench_mode = false; var bench_mode = false;
var use_instancing = false; var use_instancing = false;
var use_ssbo = false;
var args = try std.process.argsWithAllocator(std.heap.page_allocator); var args = try std.process.argsWithAllocator(std.heap.page_allocator);
defer args.deinit(); defer args.deinit();
_ = args.skip(); // skip program name _ = args.skip(); // skip program name
while (args.next()) |arg| { while (args.next()) |arg| {
if (std.mem.eql(u8, arg, "--bench")) { if (std.mem.eql(u8, arg, "--bench")) {
bench_mode = true; bench_mode = true;
use_ssbo = true; // bench mode uses SSBO by default
} else if (std.mem.eql(u8, arg, "--gpu")) { } else if (std.mem.eql(u8, arg, "--gpu")) {
use_instancing = true; use_instancing = true;
use_ssbo = false; // explicit --gpu overrides SSBO
} else if (std.mem.eql(u8, arg, "--ssbo")) {
use_ssbo = true;
} }
} }
@ -221,6 +227,21 @@ pub fn main() !void {
if (transforms) |t| std.heap.page_allocator.free(t); if (transforms) |t| std.heap.page_allocator.free(t);
} }
// SSBO rendering setup (only if --ssbo flag)
var ssbo_renderer: ?SsboRenderer = null;
if (use_ssbo) {
ssbo_renderer = SsboRenderer.init(circle_texture) orelse {
std.debug.print("failed to initialize SSBO renderer\n", .{});
return;
};
std.debug.print("SSBO instancing mode enabled\n", .{});
}
defer {
if (ssbo_renderer) |*r| r.deinit();
}
// load UI font (embedded) // load UI font (embedded)
const font_data = @embedFile("verdanab.ttf"); const font_data = @embedFile("verdanab.ttf");
const ui_font = rl.loadFontFromMemory(".ttf", font_data, 32, null) catch { const ui_font = rl.loadFontFromMemory(".ttf", font_data, 32, null) catch {
@ -301,8 +322,11 @@ pub fn main() !void {
rl.beginDrawing(); rl.beginDrawing();
rl.clearBackground(BG_COLOR); rl.clearBackground(BG_COLOR);
if (use_instancing) { if (use_ssbo) {
// GPU instancing path // SSBO instanced rendering path (12 bytes per entity)
ssbo_renderer.?.render(&entities);
} else if (use_instancing) {
// GPU instancing path (64 bytes per entity)
const xforms = transforms.?; const xforms = transforms.?;
// fill transforms array with entity positions // fill transforms array with entity positions
for (entities.items[0..entities.count], 0..) |entity, i| { for (entities.items[0..entities.count], 0..) |entity, i| {