Add GPU instance setup
This commit is contained in:
parent
faeb366bfb
commit
3a54b0dde3
1 changed files with 95 additions and 27 deletions
|
|
@ -16,6 +16,7 @@ const CYAN = rl.Color{ .r = 0, .g = 255, .b = 255, .a = 255 };
|
||||||
// entity rendering
|
// entity rendering
|
||||||
const ENTITY_RADIUS: f32 = 4.0;
|
const ENTITY_RADIUS: f32 = 4.0;
|
||||||
const TEXTURE_SIZE: i32 = 16; // must be >= 2 * radius
|
const TEXTURE_SIZE: i32 = 16; // must be >= 2 * radius
|
||||||
|
const MESH_SIZE: f32 = @floatFromInt(TEXTURE_SIZE); // match texture size
|
||||||
|
|
||||||
// logging thresholds
|
// logging thresholds
|
||||||
const TARGET_FRAME_MS: f32 = 16.7; // 60fps
|
const TARGET_FRAME_MS: f32 = 16.7; // 60fps
|
||||||
|
|
@ -129,15 +130,38 @@ fn createCircleTexture() ?rl.Texture2D {
|
||||||
return target.texture;
|
return target.texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn createOrthoCamera() rl.Camera3D {
|
||||||
|
// orthographic camera looking down -Y axis at XZ plane
|
||||||
|
// positioned to match 2D screen coordinates
|
||||||
|
const hw = @as(f32, @floatFromInt(SCREEN_WIDTH)) / 2.0;
|
||||||
|
const hh = @as(f32, @floatFromInt(SCREEN_HEIGHT)) / 2.0;
|
||||||
|
return .{
|
||||||
|
.position = .{ .x = hw, .y = 1000, .z = hh },
|
||||||
|
.target = .{ .x = hw, .y = 0, .z = hh },
|
||||||
|
.up = .{ .x = 0, .y = 0, .z = -1 }, // -Z is up to match screen Y
|
||||||
|
.fovy = @floatFromInt(SCREEN_HEIGHT), // ortho uses fovy as height
|
||||||
|
.projection = .orthographic,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createInstanceMaterial(texture: rl.Texture2D) ?rl.Material {
|
||||||
|
var material = rl.loadMaterialDefault() catch return null;
|
||||||
|
rl.setMaterialTexture(&material, rl.MATERIAL_MAP_DIFFUSE, texture);
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
// parse args
|
// parse args
|
||||||
var bench_mode = false;
|
var bench_mode = false;
|
||||||
|
var use_instancing = 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;
|
||||||
|
} else if (std.mem.eql(u8, arg, "--gpu")) {
|
||||||
|
use_instancing = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,6 +186,36 @@ pub fn main() !void {
|
||||||
};
|
};
|
||||||
defer rl.unloadTexture(circle_texture);
|
defer rl.unloadTexture(circle_texture);
|
||||||
|
|
||||||
|
// GPU instancing setup (only if --gpu flag)
|
||||||
|
var quad_mesh: ?rl.Mesh = null;
|
||||||
|
var instance_material: ?rl.Material = null;
|
||||||
|
var ortho_camera: rl.Camera3D = undefined;
|
||||||
|
|
||||||
|
// static buffer for transforms - allocated once, reused each frame
|
||||||
|
var transforms: [sandbox.MAX_ENTITIES]rl.Matrix = undefined;
|
||||||
|
|
||||||
|
if (use_instancing) {
|
||||||
|
// create quad mesh (XZ plane, will view from above)
|
||||||
|
quad_mesh = rl.genMeshPlane(MESH_SIZE, MESH_SIZE, 1, 1);
|
||||||
|
rl.uploadMesh(&quad_mesh.?, false); // upload to GPU
|
||||||
|
|
||||||
|
// material with circle texture
|
||||||
|
instance_material = createInstanceMaterial(circle_texture) orelse {
|
||||||
|
std.debug.print("failed to create instance material\n", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// orthographic camera for 2D-like rendering
|
||||||
|
ortho_camera = createOrthoCamera();
|
||||||
|
|
||||||
|
std.debug.print("GPU instancing mode enabled\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
defer {
|
||||||
|
if (quad_mesh) |*m| rl.unloadMesh(m.*);
|
||||||
|
if (instance_material) |mat| mat.unload();
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
|
|
@ -242,39 +296,53 @@ pub fn main() !void {
|
||||||
rl.beginDrawing();
|
rl.beginDrawing();
|
||||||
rl.clearBackground(BG_COLOR);
|
rl.clearBackground(BG_COLOR);
|
||||||
|
|
||||||
// draw entities using rlgl quad batching
|
if (use_instancing) {
|
||||||
const size = @as(f32, @floatFromInt(TEXTURE_SIZE));
|
// GPU instancing path
|
||||||
const half = size / 2.0;
|
// fill transforms array with entity positions
|
||||||
|
for (entities.items[0..entities.count], 0..) |entity, i| {
|
||||||
|
// entity (x, y) maps to 3D (x, 0, y) on XZ plane
|
||||||
|
transforms[i] = rl.Matrix.translate(entity.x, 0, entity.y);
|
||||||
|
}
|
||||||
|
|
||||||
rl.gl.rlSetTexture(circle_texture.id);
|
// draw all entities with single instanced call
|
||||||
rl.gl.rlBegin(rl.gl.rl_quads);
|
ortho_camera.begin();
|
||||||
|
rl.drawMeshInstanced(quad_mesh.?, instance_material.?, transforms[0..entities.count]);
|
||||||
|
ortho_camera.end();
|
||||||
|
} else {
|
||||||
|
// rlgl quad batching path (original)
|
||||||
|
const size = @as(f32, @floatFromInt(TEXTURE_SIZE));
|
||||||
|
const half = size / 2.0;
|
||||||
|
|
||||||
for (entities.items[0..entities.count]) |entity| {
|
rl.gl.rlSetTexture(circle_texture.id);
|
||||||
// extract RGB from entity color (0xRRGGBB)
|
rl.gl.rlBegin(rl.gl.rl_quads);
|
||||||
const r: u8 = @truncate(entity.color >> 16);
|
|
||||||
const g: u8 = @truncate(entity.color >> 8);
|
|
||||||
const b: u8 = @truncate(entity.color);
|
|
||||||
rl.gl.rlColor4ub(r, g, b, 255);
|
|
||||||
|
|
||||||
const x1 = entity.x - half;
|
for (entities.items[0..entities.count]) |entity| {
|
||||||
const y1 = entity.y - half;
|
// extract RGB from entity color (0xRRGGBB)
|
||||||
const x2 = entity.x + half;
|
const r: u8 = @truncate(entity.color >> 16);
|
||||||
const y2 = entity.y + half;
|
const g: u8 = @truncate(entity.color >> 8);
|
||||||
|
const b: u8 = @truncate(entity.color);
|
||||||
|
rl.gl.rlColor4ub(r, g, b, 255);
|
||||||
|
|
||||||
// quad vertices: bottom-left, bottom-right, top-right, top-left
|
const x1 = entity.x - half;
|
||||||
rl.gl.rlTexCoord2f(0, 0);
|
const y1 = entity.y - half;
|
||||||
rl.gl.rlVertex2f(x1, y2);
|
const x2 = entity.x + half;
|
||||||
rl.gl.rlTexCoord2f(1, 0);
|
const y2 = entity.y + half;
|
||||||
rl.gl.rlVertex2f(x2, y2);
|
|
||||||
rl.gl.rlTexCoord2f(1, 1);
|
// quad vertices: bottom-left, bottom-right, top-right, top-left
|
||||||
rl.gl.rlVertex2f(x2, y1);
|
rl.gl.rlTexCoord2f(0, 0);
|
||||||
rl.gl.rlTexCoord2f(0, 1);
|
rl.gl.rlVertex2f(x1, y2);
|
||||||
rl.gl.rlVertex2f(x1, y1);
|
rl.gl.rlTexCoord2f(1, 0);
|
||||||
|
rl.gl.rlVertex2f(x2, y2);
|
||||||
|
rl.gl.rlTexCoord2f(1, 1);
|
||||||
|
rl.gl.rlVertex2f(x2, y1);
|
||||||
|
rl.gl.rlTexCoord2f(0, 1);
|
||||||
|
rl.gl.rlVertex2f(x1, y1);
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.gl.rlEnd();
|
||||||
|
rl.gl.rlSetTexture(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.gl.rlEnd();
|
|
||||||
rl.gl.rlSetTexture(0);
|
|
||||||
|
|
||||||
// metrics overlay (skip in bench mode for cleaner headless run)
|
// metrics overlay (skip in bench mode for cleaner headless run)
|
||||||
if (!bench_mode) {
|
if (!bench_mode) {
|
||||||
ui.drawMetrics(&entities, update_time_us, render_time_us, paused, ui_font);
|
ui.drawMetrics(&entities, update_time_us, render_time_us, paused, ui_font);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue