feat: parallelise chunkgen, model generation and uploading

This commit is contained in:
catangent 2025-08-13 00:10:26 +01:00
parent dd41aabf0f
commit c029f40bc7
6 changed files with 150 additions and 46 deletions

View file

@ -8,6 +8,8 @@ pub fn build(b: *std.Build) void {
const rl = b.dependency("raylib", .{ const rl = b.dependency("raylib", .{
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
//.config = "-DSUPPORT_CUSTOM_FRAME_CONTROL",
.config = @as([]const u8, "-DSUPPORT_CUSTOM_FRAME_CONTROL"),
}); });
break :raylib rl; break :raylib rl;
}; };

2
raylib

@ -1 +1 @@
Subproject commit 77e626060d3c5419c539edfa4fe4f268f9015ade Subproject commit f740d0941f1bf73077bb369c161bc673e27b735a

View file

@ -14,6 +14,43 @@ const TILE_TEXTURE_RESOLUTION = 16;
const debug = true; const debug = true;
const FPS_HISTORY_LENGTH = 30;
var time: struct {
current: f64 = 0.0,
previous: f64 = 0.0,
target: f64 = 1.0/165.0,
frameCounter: u64 = 0,
fpsHistory: [FPS_HISTORY_LENGTH]f64 = .{0.0} ** FPS_HISTORY_LENGTH,
pub fn deltaTime(self: @This()) f64 {
return self.current - self.previous;
}
pub fn fps(self: *@This()) f64 {
self.fpsHistory[self.frameCounter % FPS_HISTORY_LENGTH] = 1/(self.current - self.previous);
if(self.frameCounter < FPS_HISTORY_LENGTH) return 0;
var result: f64 = 0.0;
for(self.fpsHistory) |entry| {
result += entry;
}
result /= FPS_HISTORY_LENGTH;
return result;
}
pub fn update(self: *@This()) void {
self.previous = self.current;
self.current = raylib.GetTime();
if (self.deltaTime() < self.target){
raylib.WaitTime(self.target - self.deltaTime());
self.current = raylib.GetTime();
}
self.frameCounter += 1;
}
} = .{};
pub fn drawCameraPosition(camera: raylib.Camera3D, x: i32, y: i32) !void { pub fn drawCameraPosition(camera: raylib.Camera3D, x: i32, y: i32) !void {
var buf: [256:0]u8 = undefined; var buf: [256:0]u8 = undefined;
@ -25,6 +62,17 @@ pub fn drawCameraPosition(camera: raylib.Camera3D, x: i32, y: i32) !void {
raylib.DrawText(slice, x, y, 20, raylib.YELLOW); raylib.DrawText(slice, x, y, 20, raylib.YELLOW);
} }
pub fn drawFmtText(comptime fmt: []const u8, args: anytype, x: i32, y: i32) !void {
var buf: [256:0]u8 = undefined;
const slice = try std.fmt.bufPrintZ(
&buf,
fmt,
args,
);
raylib.DrawText(slice, x, y, 20, raylib.BLUE);
}
pub fn moveCamera(camera: *raylib.Camera3D, vec: raylib.Vector3) void { pub fn moveCamera(camera: *raylib.Camera3D, vec: raylib.Vector3) void {
camera.position = v3.add(camera.position, vec); camera.position = v3.add(camera.position, vec);
@ -34,17 +82,13 @@ pub fn moveCamera(camera: *raylib.Camera3D, vec: raylib.Vector3) void {
pub fn main() !void { pub fn main() !void {
if (!debug) raylib.SetTraceLogLevel(raylib.LOG_ERROR); if (!debug) raylib.SetTraceLogLevel(raylib.LOG_ERROR);
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{.thread_safe = true}){};
const allocator = gpa.allocator(); const allocator = gpa.allocator();
defer { defer {
const status = gpa.deinit(); const status = gpa.deinit();
if (status == .leak) std.debug.print("MEMORY LEAK DETECTED!!!!!!!!!!!!!!!!!!!!!!\n", .{}) else std.debug.print("no leaks detected.\n", .{}); if (status == .leak) std.debug.print("MEMORY LEAK DETECTED!!!!!!!!!!!!!!!!!!!!!!\n", .{}) else std.debug.print("no leaks detected.\n", .{});
} }
var thread_pool: std.Thread.Pool = undefined;
try thread_pool.init(.{.allocator = allocator});
defer thread_pool.deinit();
raylib.SetConfigFlags(raylib.FLAG_WINDOW_RESIZABLE | raylib.FLAG_FULLSCREEN_MODE); raylib.SetConfigFlags(raylib.FLAG_WINDOW_RESIZABLE | raylib.FLAG_FULLSCREEN_MODE);
const display = raylib.GetCurrentMonitor(); const display = raylib.GetCurrentMonitor();
@ -79,21 +123,26 @@ pub fn main() !void {
defer raylib.UnloadShader(shader); defer raylib.UnloadShader(shader);
raylib.SetShaderValue(shader, raylib.GetShaderLocation(shader, "textureTiling"), &.{ @as(f32, @floatFromInt(tile_columns)), @as(f32, @floatFromInt(tile_rows)) }, raylib.SHADER_UNIFORM_VEC2); raylib.SetShaderValue(shader, raylib.GetShaderLocation(shader, "textureTiling"), &.{ @as(f32, @floatFromInt(tile_columns)), @as(f32, @floatFromInt(tile_rows)) }, raylib.SHADER_UNIFORM_VEC2);
var world_state: WorldState = WorldState.init(thread_pool, allocator); var thread_pool: std.Thread.Pool = undefined;
try thread_pool.init(.{.allocator = allocator});
defer thread_pool.deinit();
var global_graphics_mutex: std.Thread.Mutex = undefined;
var world_state: WorldState = WorldState.init(&thread_pool, allocator);
defer world_state.deinit(); defer world_state.deinit();
for (0..10) |x| for (0..10) |z| { for (0..50) |x| for (0..50) |z| {
_ = try world_state.generateChunk(.{@intCast(x), 0, @intCast(z)}); try world_state.queueLoadChunk(.{@intCast(x), 0, @intCast(z)}, tile_rows, tile_columns, texture, ambient_occlusion_texture, shader, &global_graphics_mutex);
_ = try world_state.generateChunkModel(.{@intCast(x), 0, @intCast(z)}, tile_rows, tile_columns, texture, ambient_occlusion_texture, shader);
}; };
while (!raylib.WindowShouldClose()) { while (!raylib.WindowShouldClose()) {
raylib.ClearBackground(raylib.BLACK); raylib.PollInputEvents();
const right = v3.neg(v3.nor(v3.cross(camera.up, v3.sub(camera.target, camera.position)))); const right = v3.neg(v3.nor(v3.cross(camera.up, v3.sub(camera.target, camera.position))));
const forward = v3.cross(right, v3.neg(camera.up)); const forward = v3.cross(right, v3.neg(camera.up));
const speed = @as(f32, if (raylib.IsKeyDown(raylib.KEY_LEFT_CONTROL)) 25 else 5) * raylib.GetFrameTime(); const speed = @as(f32, if (raylib.IsKeyDown(raylib.KEY_LEFT_CONTROL)) 25 else 5) * time.deltaTime();
var movement = v3.new(0, 0, 0); var movement = v3.new(0, 0, 0);
if (raylib.IsKeyDown(raylib.KEY_SPACE)) movement.y += 1; if (raylib.IsKeyDown(raylib.KEY_SPACE)) movement.y += 1;
@ -104,7 +153,7 @@ pub fn main() !void {
if (raylib.IsKeyDown(raylib.KEY_D)) movement = v3.add(movement, right); if (raylib.IsKeyDown(raylib.KEY_D)) movement = v3.add(movement, right);
if (raylib.IsKeyDown(raylib.KEY_A)) movement = v3.sub(movement, right); if (raylib.IsKeyDown(raylib.KEY_A)) movement = v3.sub(movement, right);
moveCamera(&camera, v3.scl(v3.nor(movement), speed)); moveCamera(&camera, v3.scl(v3.nor(movement), @floatCast(speed)));
const delta = raylib.GetMouseDelta(); const delta = raylib.GetMouseDelta();
// on the first mouse movement, for some reason mouse delta is way too large, so we just ignore too large deltas // on the first mouse movement, for some reason mouse delta is way too large, so we just ignore too large deltas
@ -120,26 +169,48 @@ pub fn main() !void {
} }
} }
raylib.BeginDrawing();
defer raylib.EndDrawing();
{ {
raylib.BeginMode3D(camera); global_graphics_mutex.lock();
defer raylib.EndMode3D(); defer global_graphics_mutex.unlock();
raylib.MakeContextCurrent();
defer raylib.DropContextCurrent();
raylib.ClearBackground(raylib.BLACK);
world_state.chunks_access_mutex.lock(); {
var chunks_iterator = world_state.chunks.valueIterator(); raylib.BeginDrawing();
while (chunks_iterator.next()) |entry| { defer raylib.EndDrawing();
if (entry.*.model) |model| {
const model_position = v3.new(@floatFromInt(entry.position[0]*16), @floatFromInt(entry.position[1]*16), @floatFromInt(entry.position[2]*16)); var chunk_count: u32 = 0;
raylib.DrawChunkModel(model, model_position, 0.5, raylib.WHITE); var shown_count: u32 = 0;
{
raylib.BeginMode3D(camera);
defer raylib.EndMode3D();
world_state.chunks_access_mutex.lock();
defer world_state.chunks_access_mutex.unlock();
var chunks_iterator = world_state.chunks.valueIterator();
while (chunks_iterator.next()) |entry_ptr| {
const entry = entry_ptr.*;
if (entry.*.model) |model| {
const model_position = v3.new(@floatFromInt(entry.position[0]*16), @floatFromInt(entry.position[1]*16), @floatFromInt(entry.position[2]*16));
raylib.DrawChunkModel(model, model_position, 0.5, raylib.WHITE);
shown_count += 1;
}
chunk_count += 1;
}
} }
try drawFmtText("fps: {d:.0}", .{time.fps()}, 10, 10);
try drawCameraPosition(camera, 10, 30);
try drawFmtText("chunks shown: {}, chunks total: {}", .{shown_count, chunk_count}, 10, 50);
} }
world_state.chunks_access_mutex.unlock();
raylib.SwapScreenBuffer();
} }
time.update();
raylib.DrawFPS(10, 10);
try drawCameraPosition(camera, 10, 30);
} }
} }

View file

@ -387,10 +387,7 @@ pub const Chunk = struct {
var raw_quads = try scanForRawQuads(chunk); var raw_quads = try scanForRawQuads(chunk);
defer raw_quads.deinit(); defer raw_quads.deinit();
var mesh = packMeshFromRawQuads(raw_quads, tile_columns, tile_rows); const mesh = packMeshFromRawQuads(raw_quads, tile_columns, tile_rows);
raylib.UploadChunkMesh(@ptrCast(&mesh), false);
return mesh; return mesh;
} }
}; };

View file

@ -2,6 +2,7 @@ const std = @import("std");
const Thread = std.Thread; const Thread = std.Thread;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const AutoHashMap = std.AutoHashMap; const AutoHashMap = std.AutoHashMap;
const ArrayList = std.ArrayList;
const Chunk = @import("chunk.zig").Chunk; const Chunk = @import("chunk.zig").Chunk;
const chunk_generators = @import("chunk_generators.zig"); const chunk_generators = @import("chunk_generators.zig");
@ -10,28 +11,29 @@ const raylib_helper = @import("../lib_helpers/raylib_helper.zig");
const raylib = raylib_helper.raylib; const raylib = raylib_helper.raylib;
pub const WorldStateError = error{ pub const WorldStateError = error{
ChunkNotGeneratedError, ChunkNotGeneratedError,
}; };
pub const ChunkEntry = struct { pub const ChunkEntry = struct {
mutex: Thread.Mutex = .{},
chunk: ?Chunk = null, chunk: ?Chunk = null,
model: ?raylib.ChunkModel = null, model: ?raylib.ChunkModel = null,
position: @Vector(3, i64), position: @Vector(3, i64),
}; };
pub const WorldState = struct { pub const WorldState = struct {
pool: Thread.Pool = undefined, pool: *Thread.Pool = undefined,
allocator: Allocator = undefined, allocator: Allocator = undefined,
// TODO: we can do better than a hashmap and a mutex // TODO: we can do better than a hashmap and a mutex
chunks: AutoHashMap(@Vector(3, i64), ChunkEntry) = undefined, chunks: AutoHashMap(@Vector(3, i64), *ChunkEntry) = undefined,
chunks_access_mutex: Thread.Mutex = .{}, chunks_access_mutex: Thread.Mutex = .{},
pub fn init(global_pool: Thread.Pool, allocator: Allocator) WorldState { pub fn init(global_pool: *Thread.Pool, allocator: Allocator) WorldState {
var world_state: WorldState = undefined; var world_state: WorldState = undefined;
world_state.pool = global_pool; world_state.pool = global_pool;
world_state.allocator = allocator; world_state.allocator = allocator;
world_state.chunks = AutoHashMap(@Vector(3, i64), ChunkEntry).init(allocator); world_state.chunks = AutoHashMap(@Vector(3, i64), *ChunkEntry).init(allocator);
return world_state; return world_state;
} }
@ -44,33 +46,65 @@ pub const WorldState = struct {
if(entry.*.model) |model| { if(entry.*.model) |model| {
raylib.UnloadChunkModel(model); raylib.UnloadChunkModel(model);
} }
self.allocator.destroy(entry.*);
} }
self.chunks.deinit(); self.chunks.deinit();
self.* = undefined;
}
pub fn queueLoadChunk(self: *WorldState, pos: @Vector(3, i64), tile_rows: u32, tile_columns: u32, texture: raylib.Texture, ambient_occlusion_texture: raylib.Texture, shader: raylib.Shader, global_graphics_mutex: *Thread.Mutex) !void {
try self.pool.spawn(completeChunk, .{self, pos, tile_rows, tile_columns, texture, ambient_occlusion_texture, shader, global_graphics_mutex});
}
pub fn completeChunk(self: *WorldState, pos: @Vector(3, i64), tile_rows: u32, tile_columns: u32, texture: raylib.Texture, ambient_occlusion_texture: raylib.Texture, shader: raylib.Shader, global_graphics_mutex: *Thread.Mutex) void {
_ = generateChunk(self, pos) catch |err| std.debug.print("error while generating chunk: {}", .{err});
_ = generateChunkModel(self, pos, tile_rows, tile_columns, texture, ambient_occlusion_texture, shader, global_graphics_mutex) catch |err| std.debug.print("error while generating chunk model: {}", .{err});
} }
pub fn generateChunk(self: *WorldState, pos: @Vector(3, i64)) !Chunk { pub fn generateChunk(self: *WorldState, pos: @Vector(3, i64)) !Chunk {
const chunk = try chunk_generators.createChunk(self.allocator, pos); const chunk = try chunk_generators.createChunk(self.allocator, pos);
const entry_ptr = try self.allocator.create(ChunkEntry);
entry_ptr.* = .{.chunk = chunk, .position = pos};
self.chunks_access_mutex.lock(); self.chunks_access_mutex.lock();
try self.chunks.put(pos, .{.chunk = chunk, .position = pos}); try self.chunks.put(pos, entry_ptr);
self.chunks_access_mutex.unlock(); self.chunks_access_mutex.unlock();
return chunk; return chunk;
} }
pub fn generateChunkModel(self: *WorldState, pos: @Vector(3, i64), tile_rows: u32, tile_columns: u32, texture: raylib.Texture, ambient_occlusion_texture: raylib.Texture, shader: raylib.Shader) !raylib.ChunkModel { pub fn generateChunkModel(self: *WorldState, pos: @Vector(3, i64), tile_rows: u32, tile_columns: u32, texture: raylib.Texture, ambient_occlusion_texture: raylib.Texture, shader: raylib.Shader, global_graphics_mutex: *Thread.Mutex) !raylib.ChunkModel {
self.chunks_access_mutex.lock(); self.chunks_access_mutex.lock();
const chunk_entry = (try self.chunks.getOrPutValue(pos, .{.position = pos})).value_ptr; const chunk_entry = blk: {
const result = try self.chunks.getOrPut(pos);
if(!result.found_existing) {
const entry_ptr = try self.allocator.create(ChunkEntry);
entry_ptr.* = .{.position = pos};
result.value_ptr.* = entry_ptr;
}
break :blk result.value_ptr.*;
};
self.chunks_access_mutex.unlock(); self.chunks_access_mutex.unlock();
const chunk = chunk_entry.chunk; const chunk = chunk_entry.chunk;
if(chunk == null) return WorldStateError.ChunkNotGeneratedError; if(chunk == null) return WorldStateError.ChunkNotGeneratedError;
const model = raylib.LoadChunkModelFromMesh(try chunk.?.createMesh(tile_rows, tile_columns)); var mesh = try chunk.?.createMesh(tile_rows, tile_columns);
model.materials[0].maps[raylib.MATERIAL_MAP_DIFFUSE].texture = texture; const model = blk: {
model.materials[0].shader = shader; global_graphics_mutex.lock();
defer global_graphics_mutex.unlock();
raylib.MakeContextCurrent();
defer raylib.DropContextCurrent();
model.materials[0].shader.locs[raylib.SHADER_LOC_MAP_DIFFUSE+1] = raylib.GetShaderLocation(shader, "occlusionMap"); raylib.UploadChunkMesh(@ptrCast(&mesh), false);
raylib.SetShaderValueTexture(shader, model.materials[0].shader.locs[raylib.SHADER_LOC_MAP_DIFFUSE+1], ambient_occlusion_texture); model.materials[0].maps[raylib.MATERIAL_MAP_DIFFUSE+1].texture = ambient_occlusion_texture;
const model = raylib.LoadChunkModelFromMesh(mesh);
model.materials[0].maps[raylib.MATERIAL_MAP_DIFFUSE].texture = texture;
model.materials[0].shader = shader;
model.materials[0].shader.locs[raylib.SHADER_LOC_MAP_DIFFUSE+1] = raylib.GetShaderLocation(shader, "occlusionMap");
raylib.SetShaderValueTexture(shader, model.materials[0].shader.locs[raylib.SHADER_LOC_MAP_DIFFUSE+1], ambient_occlusion_texture);
model.materials[0].maps[raylib.MATERIAL_MAP_DIFFUSE+1].texture = ambient_occlusion_texture;
break :blk model;
};
chunk_entry.model = model; chunk_entry.model = model;
return model; return model;
} }

2
znoise

@ -1 +1 @@
Subproject commit 96f9458c2da975a8bf1cdf95e819c7b070965198 Subproject commit 76724581c99be0b2f6aa43eb8b63d6f27bada27e