Add computer shader infrastructure
This commit is contained in:
parent
45c37bfcd2
commit
62d010bdc0
3 changed files with 155 additions and 2 deletions
80
src/compute.zig
Normal file
80
src/compute.zig
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
// compute shader module for GPU entity updates
|
||||||
|
// wraps raw GL calls that raylib doesn't expose directly
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const rl = @import("raylib");
|
||||||
|
|
||||||
|
const comp_source = @embedFile("shaders/entity_update.comp");
|
||||||
|
|
||||||
|
// GL constants not exposed by raylib-zig
|
||||||
|
const GL_SHADER_STORAGE_BARRIER_BIT: u32 = 0x00002000;
|
||||||
|
|
||||||
|
// function pointer type for glMemoryBarrier
|
||||||
|
const GlMemoryBarrierFn = *const fn (barriers: u32) callconv(.c) void;
|
||||||
|
|
||||||
|
pub const ComputeShader = struct {
|
||||||
|
program_id: u32,
|
||||||
|
entity_count_loc: i32,
|
||||||
|
glMemoryBarrier: GlMemoryBarrierFn,
|
||||||
|
|
||||||
|
pub fn init() ?ComputeShader {
|
||||||
|
// load glMemoryBarrier dynamically
|
||||||
|
const barrier_ptr = rl.gl.rlGetProcAddress("glMemoryBarrier");
|
||||||
|
const glMemoryBarrier: GlMemoryBarrierFn = @ptrCast(barrier_ptr);
|
||||||
|
|
||||||
|
// compile compute shader
|
||||||
|
const shader_id = rl.gl.rlCompileShader(comp_source, rl.gl.rl_compute_shader);
|
||||||
|
if (shader_id == 0) {
|
||||||
|
std.debug.print("compute: failed to compile compute shader\n", .{});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// link compute program
|
||||||
|
const program_id = rl.gl.rlLoadComputeShaderProgram(shader_id);
|
||||||
|
if (program_id == 0) {
|
||||||
|
std.debug.print("compute: failed to link compute program\n", .{});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get uniform locations
|
||||||
|
const entity_count_loc = rl.gl.rlGetLocationUniform(program_id, "entityCount");
|
||||||
|
if (entity_count_loc < 0) {
|
||||||
|
std.debug.print("compute: warning - entityCount uniform not found\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.print("compute: shader loaded successfully (program_id={})\n", .{program_id});
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.program_id = program_id,
|
||||||
|
.entity_count_loc = entity_count_loc,
|
||||||
|
.glMemoryBarrier = glMemoryBarrier,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *ComputeShader) void {
|
||||||
|
rl.gl.rlUnloadShaderProgram(self.program_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dispatch(self: *ComputeShader, ssbo_id: u32, entity_count: u32) void {
|
||||||
|
if (entity_count == 0) return;
|
||||||
|
|
||||||
|
// bind compute shader
|
||||||
|
rl.gl.rlEnableShader(self.program_id);
|
||||||
|
|
||||||
|
// set entityCount uniform
|
||||||
|
rl.gl.rlSetUniform(self.entity_count_loc, &entity_count, @intFromEnum(rl.gl.rlShaderUniformDataType.rl_shader_uniform_uint), 1);
|
||||||
|
|
||||||
|
// bind SSBO to binding point 0
|
||||||
|
rl.gl.rlBindShaderBuffer(ssbo_id, 0);
|
||||||
|
|
||||||
|
// dispatch compute workgroups: ceil(entity_count / 256)
|
||||||
|
const groups = (entity_count + 255) / 256;
|
||||||
|
rl.gl.rlComputeShaderDispatch(groups, 1, 1);
|
||||||
|
|
||||||
|
// memory barrier - ensure compute writes are visible to vertex shader
|
||||||
|
self.glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||||
|
|
||||||
|
// unbind
|
||||||
|
rl.gl.rlBindShaderBuffer(0, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -7,6 +7,7 @@ const ztracy = @import("ztracy");
|
||||||
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 SsboRenderer = @import("ssbo_renderer.zig").SsboRenderer;
|
||||||
|
const ComputeShader = @import("compute.zig").ComputeShader;
|
||||||
|
|
||||||
const SCREEN_WIDTH = sandbox.SCREEN_WIDTH;
|
const SCREEN_WIDTH = sandbox.SCREEN_WIDTH;
|
||||||
const SCREEN_HEIGHT = sandbox.SCREEN_HEIGHT;
|
const SCREEN_HEIGHT = sandbox.SCREEN_HEIGHT;
|
||||||
|
|
@ -163,6 +164,7 @@ pub fn main() !void {
|
||||||
var use_instancing = false;
|
var use_instancing = false;
|
||||||
var use_ssbo = true;
|
var use_ssbo = true;
|
||||||
var use_vsync = false;
|
var use_vsync = false;
|
||||||
|
var use_compute = 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
|
||||||
|
|
@ -176,6 +178,8 @@ pub fn main() !void {
|
||||||
use_ssbo = false; // legacy rlgl batched path
|
use_ssbo = false; // legacy rlgl batched path
|
||||||
} else if (std.mem.eql(u8, arg, "--vsync")) {
|
} else if (std.mem.eql(u8, arg, "--vsync")) {
|
||||||
use_vsync = true;
|
use_vsync = true;
|
||||||
|
} else if (std.mem.eql(u8, arg, "--compute")) {
|
||||||
|
use_compute = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -257,6 +261,26 @@ pub fn main() !void {
|
||||||
if (ssbo_renderer) |*r| r.deinit();
|
if (ssbo_renderer) |*r| r.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compute shader setup (only if --compute flag)
|
||||||
|
var compute_shader: ?ComputeShader = null;
|
||||||
|
|
||||||
|
if (use_compute) {
|
||||||
|
if (!use_ssbo) {
|
||||||
|
std.debug.print("--compute requires SSBO mode (default), ignoring\n", .{});
|
||||||
|
} else {
|
||||||
|
compute_shader = ComputeShader.init();
|
||||||
|
if (compute_shader == null) {
|
||||||
|
std.debug.print("failed to initialize compute shader, falling back to CPU\n", .{});
|
||||||
|
} else {
|
||||||
|
std.debug.print("compute shader mode enabled\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defer {
|
||||||
|
if (compute_shader) |*c| c.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 {
|
||||||
|
|
@ -335,7 +359,16 @@ pub fn main() !void {
|
||||||
const tracy_update = ztracy.ZoneN(@src(), "update");
|
const tracy_update = ztracy.ZoneN(@src(), "update");
|
||||||
defer tracy_update.End();
|
defer tracy_update.End();
|
||||||
const update_start = std.time.microTimestamp();
|
const update_start = std.time.microTimestamp();
|
||||||
sandbox.update(&entities, &rng);
|
|
||||||
|
if (compute_shader != null) {
|
||||||
|
// GPU compute update - positions updated on GPU
|
||||||
|
// still need CPU update for respawn logic until Step 3
|
||||||
|
sandbox.update(&entities, &rng);
|
||||||
|
} else {
|
||||||
|
// CPU update path
|
||||||
|
sandbox.update(&entities, &rng);
|
||||||
|
}
|
||||||
|
|
||||||
update_time_us = std.time.microTimestamp() - update_start;
|
update_time_us = std.time.microTimestamp() - update_start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -348,7 +381,14 @@ pub fn main() !void {
|
||||||
rl.clearBackground(BG_COLOR);
|
rl.clearBackground(BG_COLOR);
|
||||||
|
|
||||||
if (use_ssbo) {
|
if (use_ssbo) {
|
||||||
// SSBO instanced rendering path (12 bytes per entity)
|
// dispatch compute shader before render (if enabled)
|
||||||
|
if (compute_shader) |*cs| {
|
||||||
|
const tracy_compute = ztracy.ZoneN(@src(), "compute_dispatch");
|
||||||
|
defer tracy_compute.End();
|
||||||
|
cs.dispatch(ssbo_renderer.?.ssbo_id, @intCast(entities.count));
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSBO instanced rendering path (16 bytes per entity)
|
||||||
ssbo_renderer.?.render(&entities, zoom, pan);
|
ssbo_renderer.?.render(&entities, zoom, pan);
|
||||||
} else if (use_instancing) {
|
} else if (use_instancing) {
|
||||||
// GPU instancing path (64 bytes per entity)
|
// GPU instancing path (64 bytes per entity)
|
||||||
|
|
|
||||||
33
src/shaders/entity_update.comp
Normal file
33
src/shaders/entity_update.comp
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#version 430
|
||||||
|
|
||||||
|
layout(local_size_x = 256) in;
|
||||||
|
|
||||||
|
struct Entity {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
int packedVel; // vx high 16 bits, vy low 16 bits (fixed-point 8.8)
|
||||||
|
uint color;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(std430, binding = 0) buffer Entities {
|
||||||
|
Entity entities[];
|
||||||
|
};
|
||||||
|
|
||||||
|
uniform uint entityCount;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
uint id = gl_GlobalInvocationID.x;
|
||||||
|
if (id >= entityCount) return;
|
||||||
|
|
||||||
|
Entity e = entities[id];
|
||||||
|
|
||||||
|
// unpack velocity (fixed-point 8.8)
|
||||||
|
float vx = float(e.packedVel >> 16) / 256.0;
|
||||||
|
float vy = float((e.packedVel << 16) >> 16) / 256.0; // sign-extend low 16 bits
|
||||||
|
|
||||||
|
// update position
|
||||||
|
e.x += vx;
|
||||||
|
e.y += vy;
|
||||||
|
|
||||||
|
entities[id] = e;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue