fix: put all opengl things on main thread to render chunks correctly

This commit is contained in:
catangent 2025-08-30 16:33:42 +01:00
parent 5e0bae610e
commit 6d4d9394f4
4 changed files with 127 additions and 67 deletions

View file

@ -10,6 +10,7 @@ const chunks = @import("world/chunk.zig");
const WorldState = @import("world/world_state.zig").WorldState;
const CameraChunkloader = @import("world/world_state.zig").CameraChunkloader;
const chunk_generators = @import("world/chunk_generators.zig");
const GraphicsTaskManager = @import("util/task_manager.zig").GraphicsTaskManager;
const TILE_TEXTURE_RESOLUTION = 16;
@ -52,6 +53,15 @@ var time: struct {
}
} = .{};
pub const ChunkGraphicsResources = struct {
tile_rows: u32,
tile_columns: u32,
texture: raylib.Texture,
ambient_occlusion_texture: raylib.Texture,
shader: raylib.Shader,
graphics_task_manager: *GraphicsTaskManager,
};
pub fn drawCameraPosition(camera: raylib.Camera3D, x: i32, y: i32) !void {
var buf: [256:0]u8 = undefined;
@ -128,24 +138,22 @@ pub fn main() !void {
try thread_pool.init(.{.allocator = allocator});
defer thread_pool.deinit();
var global_graphics_mutex: std.Thread.Mutex = undefined;
var graphics_task_manager: GraphicsTaskManager = GraphicsTaskManager.init(allocator);
defer graphics_task_manager.deinit();
const chunk_graphics_resources: chunks.ChunkGraphicsResources = .{
const chunk_graphics_resources: ChunkGraphicsResources = .{
.tile_rows = tile_rows,
.tile_columns = tile_columns,
.texture = texture,
.ambient_occlusion_texture = ambient_occlusion_texture,
.shader = shader,
.global_graphics_mutex = &global_graphics_mutex
.graphics_task_manager = &graphics_task_manager,
};
var world_state: WorldState = WorldState.init(&thread_pool, allocator, chunk_graphics_resources);
defer world_state.deinit();
// for (0..10) |x| for (0..10) |z| {
// try world_state.queueLoadChunk(.{@intCast(x), 0, @intCast(z)});
// };
var chunkloader = CameraChunkloader.init(allocator, 3);
var chunkloader = CameraChunkloader.init(allocator, 10);
try chunkloader.update(&world_state, .{0, 0, 0});
defer chunkloader.deinit();
@ -182,47 +190,40 @@ pub fn main() !void {
}
}
raylib.ClearBackground(raylib.BLACK);
{
global_graphics_mutex.lock();
defer global_graphics_mutex.unlock();
raylib.MakeContextCurrent();
defer raylib.DropContextCurrent();
raylib.ClearBackground(raylib.BLACK);
raylib.BeginDrawing();
defer raylib.EndDrawing();
var chunk_count: u32 = 0;
var shown_count: u32 = 0;
{
raylib.BeginDrawing();
defer raylib.EndDrawing();
raylib.BeginMode3D(camera);
defer raylib.EndMode3D();
var chunk_count: u32 = 0;
var shown_count: u32 = 0;
world_state.chunks_access_mutex.lock();
defer world_state.chunks_access_mutex.unlock();
{
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;
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);
}
raylib.SwapScreenBuffer();
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);
}
raylib.SwapScreenBuffer();
try graphics_task_manager.executeAll();
time.update();
}
}

75
src/util/task_manager.zig Normal file
View file

@ -0,0 +1,75 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Mutex = std.Thread.Mutex;
const Condition = std.Thread.Condition;
const ArrayList = std.ArrayList;
const ChunkGraphicsResources = @import("../main.zig").ChunkGraphicsResources;
const raylib_helper = @import("../lib_helpers/raylib_helper.zig");
const raylib = raylib_helper.raylib;
pub const GraphicsTaskManager = struct {
pub const Task = union(enum) {
upload_chunk_mesh: *raylib.ChunkMesh,
set_chunk_ambient_occlusion: struct {model: *raylib.ChunkModel, resources: ChunkGraphicsResources},
signal: *Condition,
print: []const u8,
};
allocator: Allocator,
tasks: ArrayList(Task),
mutex: Mutex = .{},
pub fn init(allocator: Allocator) GraphicsTaskManager {
var self: GraphicsTaskManager = undefined;
self.allocator = allocator;
self.tasks = ArrayList(Task).init(allocator);
return self;
}
pub fn deinit(self: *GraphicsTaskManager) void{
self.tasks.deinit();
self.* = undefined;
}
pub fn executeAll(self: *GraphicsTaskManager) !void {
self.mutex.lock();
const tasks = try self.tasks.toOwnedSlice();
defer self.allocator.free(tasks);
self.mutex.unlock();
for(tasks) |task| {
switch (task) {
Task.upload_chunk_mesh => |mesh_ptr| {
raylib.UploadChunkMesh(mesh_ptr, false);
},
Task.set_chunk_ambient_occlusion => |args| {
args.model.materials[0].shader.locs[raylib.SHADER_LOC_MAP_DIFFUSE+1] = raylib.GetShaderLocation(args.resources.shader, "occlusionMap");
raylib.SetShaderValueTexture(args.resources.shader, args.model.materials[0].shader.locs[raylib.SHADER_LOC_MAP_DIFFUSE+1], args.resources.ambient_occlusion_texture);
args.model.materials[0].maps[raylib.MATERIAL_MAP_DIFFUSE+1].texture = args.resources.ambient_occlusion_texture;
},
Task.signal => |condition| {
condition.signal();
},
Task.print => |str| {
std.debug.print("{s}", .{str});
}
}
}
}
pub fn submit(self: *GraphicsTaskManager, task: Task) !void {
self.mutex.lock();
defer self.mutex.unlock();
try self.tasks.append(task);
}
pub fn wait(self: *GraphicsTaskManager) !void {
var mutex: Mutex = .{};
var condition: Condition = .{};
mutex.lock();
defer mutex.unlock();
try self.submit(.{.signal = &condition});
condition.wait(&mutex);
}
};

View file

@ -54,16 +54,6 @@ comptime {
}
}
pub const ChunkGraphicsResources = struct {
tile_rows: u32,
tile_columns: u32,
texture: raylib.Texture,
ambient_occlusion_texture: raylib.Texture,
shader: raylib.Shader,
global_graphics_mutex: *std.Thread.Mutex
};
pub const Chunk = struct {
tiles: []u32,
allocator: Allocator,

View file

@ -5,7 +5,7 @@ const AutoHashMap = std.AutoHashMap;
const ArrayList = std.ArrayList;
const Chunk = @import("chunk.zig").Chunk;
const ChunkGraphicsResources = @import("chunk.zig").ChunkGraphicsResources;
const ChunkGraphicsResources = @import("../main.zig").ChunkGraphicsResources;
const chunk_generators = @import("chunk_generators.zig");
const raylib_helper = @import("../lib_helpers/raylib_helper.zig");
@ -42,7 +42,7 @@ pub const CameraChunkloader = struct {
if(self.loaded.contains(position)){
return;
}
if(@reduce(.Add, (position-self.current_position)*(position-self.current_position)) > self.render_distance*self.render_distance){
if(@reduce(.Add, (position-self.current_position)*(position-self.current_position)) >= self.render_distance*self.render_distance){
try self.to_load.put(position, {});
return;
}
@ -135,7 +135,7 @@ pub const WorldState = struct {
return chunk;
}
pub fn generateChunkModel(self: *WorldState, pos: @Vector(3, i64)) !raylib.ChunkModel {
pub fn generateChunkModel(self: *WorldState, pos: @Vector(3, i64)) !void {
const resources = self.chunk_graphics_resources;
self.chunks_access_mutex.lock();
const chunk_entry = blk: {
@ -152,25 +152,19 @@ pub const WorldState = struct {
const chunk = chunk_entry.chunk;
if(chunk == null) return WorldStateError.ChunkNotGeneratedError;
// TODO: we wait twice here for a frame. perhaps we can do something different and wait just once or not wait at all?
var mesh = try chunk.?.createMesh(resources.tile_rows, resources.tile_columns);
const model = blk: {
resources.global_graphics_mutex.lock();
defer resources.global_graphics_mutex.unlock();
raylib.MakeContextCurrent();
defer raylib.DropContextCurrent();
try resources.graphics_task_manager.submit(.{.upload_chunk_mesh = &mesh});
try resources.graphics_task_manager.wait();
raylib.UploadChunkMesh(@ptrCast(&mesh), false);
var model = raylib.LoadChunkModelFromMesh(mesh);
model.materials[0].maps[raylib.MATERIAL_MAP_DIFFUSE].texture = resources.texture;
model.materials[0].shader = resources.shader;
const model = raylib.LoadChunkModelFromMesh(mesh);
model.materials[0].maps[raylib.MATERIAL_MAP_DIFFUSE].texture = resources.texture;
model.materials[0].shader = resources.shader;
try resources.graphics_task_manager.submit(.{.set_chunk_ambient_occlusion = .{.model = &model, .resources = resources}});
try resources.graphics_task_manager.wait();
model.materials[0].shader.locs[raylib.SHADER_LOC_MAP_DIFFUSE+1] = raylib.GetShaderLocation(resources.shader, "occlusionMap");
raylib.SetShaderValueTexture(resources.shader, model.materials[0].shader.locs[raylib.SHADER_LOC_MAP_DIFFUSE+1], resources.ambient_occlusion_texture);
model.materials[0].maps[raylib.MATERIAL_MAP_DIFFUSE+1].texture = resources.ambient_occlusion_texture;
break :blk model;
};
chunk_entry.model = model;
return model;
}
};