diff --git a/build.zig.zon b/build.zig.zon index f23e262..79ed2d0 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -8,14 +8,14 @@ .url = "https://git.sr.ht/~altagos/a/archive/8c8f2cc405743d062f5b617e2f8ab85ce5dc0409.tar.gz", .hash = "12203338074c5e0fed0696abfa1ec868da0d1bb22539e5fd4220b2585a8f3cf0c788", }, - .zigimg = .{ - .url = "https://github.com/rockorager/zigimg/archive/19a49a7e44fb4b1c22341dfbd6566019de742055.tar.gz", - .hash = "1220ebfa8587cfd644995fc08e218dbb3ebd7344fb8e129ff02bc5a6d52a2325370d", - }, .spall = .{ .url = "https://git.sr.ht/~altagos/zig-spall/archive/7cae52aa2d1a519006e0c90fbd179cf7fcac0c83.tar.gz", .hash = "1220753b2f9e7c4f3fb6cdc8fc275cc9073904ea7032a1f5596aa776a857de69f72c", }, + .zigimg = .{ + .url = "git+https://github.com/MidlightStudio/zigimg#29d34702995a7e84e097004e87ede905cdb674dd", + .hash = "12206b669a4712ff9c06d4111a650f298d7597dd39f9d4853858d7f298c819353a65", + }, // libs folder .zmath = .{ .path = "libs/zmath" }, diff --git a/src/rayray.zig b/src/rayray.zig index 30b7412..6cb1732 100644 --- a/src/rayray.zig +++ b/src/rayray.zig @@ -4,32 +4,36 @@ const spall = @import("spall"); const zigimg = @import("zigimg"); const color = zigimg.color; +const IntervalUsize = @import("a").interval.IntervalUsize; + pub const Camera = @import("camera.zig"); pub const hittable = @import("hittable.zig"); pub const material = @import("material.zig"); pub const tracer = @import("tracer.zig"); -const IntervalUsize = @import("a").interval.IntervalUsize; - const log = std.log.scoped(.rayray); -const ThreadTracker = struct { - thread: std.Thread, - done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), +pub const TaskTracker = struct { marked_as_done: bool = false, + done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), }; pub const Raytracer = struct { const Self = @This(); allocator: std.mem.Allocator, + thread_pool: *std.Thread.Pool, camera: Camera, world: hittable.HittableList, pub fn init(allocator: std.mem.Allocator, world: hittable.HittableList, camera_opts: Camera.Options) !Self { + var thread_pool = try allocator.create(std.Thread.Pool); + try thread_pool.init(.{ .allocator = allocator }); + return .{ .allocator = allocator, + .thread_pool = thread_pool, .camera = try Camera.init(allocator, camera_opts), .world = world, }; @@ -38,6 +42,9 @@ pub const Raytracer = struct { pub fn deinit(self: *Self) void { self.camera.deinit(); self.world.deinit(); + + self.thread_pool.deinit(); + self.allocator.destroy(self.thread_pool); } // TODO: Render in cubes not in rows @@ -45,33 +52,65 @@ pub const Raytracer = struct { const s = spall.trace(@src(), "Render", .{}); defer s.end(); - const rows: usize = try std.Thread.getCpuCount() - 1; - const row_height = @divTrunc(self.camera.image_height, rows); - const num_threads = blk: { - if (self.camera.image_height % rows == 0) { - break :blk rows; - } - break :blk rows + 1; - }; + // const rows: usize = try std.Thread.getCpuCount() - 1; + // const row_height = @divTrunc(self.camera.image_height, rows); + // const num_threads = blk: { + // if (self.camera.image_height % rows == 0) { + // break :blk rows; + // } + // break :blk rows + 1; + // }; + + const chunk_height: usize = 25; + const chunk_width: usize = 25; + + var rows: usize = @divTrunc(self.camera.image_height, chunk_height); + if (self.camera.image_height % rows != 0) { + rows += 1; + } - log.debug("rows: {}, row_height: {}, num_threads: {}", .{ rows, row_height, num_threads }); + var cols: usize = @divTrunc(self.camera.image_width, chunk_width); + if (self.camera.image_width % cols != 0) { + cols += 1; + } - var threads = try self.allocator.alloc(ThreadTracker, num_threads); - defer self.allocator.free(threads); + const num_chunks = cols * rows; - for (0..num_threads) |row| { - const ctx = tracer.Context{ .cam = &self.camera, .world = &self.world }; - const t = try std.Thread.spawn( - .{}, + // log.debug("rows: {}, cols: {}, chunk_height: {}, chunk_width: {}, num_chunks: {}, num_threads: {}", .{ + // rows, + // cols, + // chunk_height, + // chunk_width, + // num_chunks, + // self.thread_pool.threads.len, + // }); + + var tasks = try self.allocator.alloc(TaskTracker, num_chunks); + defer self.allocator.free(tasks); + + for (tasks, 0..) |*t, id| { + var row: usize = id / cols; + var col: usize = id - cols * row; + + row *= chunk_height; + col *= chunk_width; + + const c_height = IntervalUsize{ .min = row, .max = row + chunk_height }; + const c_width = IntervalUsize{ .min = col, .max = col + chunk_width + 1 }; + + const ctx = tracer.Context{ + .cam = &self.camera, + .world = &self.world, + .height = c_height, + .width = c_width, + }; + + log.debug("Spawning chunk: {}, row start: {}, col start: {}", .{ id, row, col }); + + try self.thread_pool.spawn( renderThread, - .{ - ctx, - &threads[row].done, - row, - row_height, - }, + .{ ctx, t, id }, ); - threads[row].thread = t; } const stderr = std.io.getStdErr(); @@ -80,47 +119,52 @@ pub const Raytracer = struct { .terminal = stderr, .supports_ansi_escape_codes = true, }; - var node = progress.start("Rendered Chunks", num_threads); + + var node = progress.start("Rendered Chunks", num_chunks); node.setCompletedItems(0); node.context.refresh(); while (true) { var done = true; - for (0..num_threads) |id| { - const thead_done = threads[id].done.load(.Acquire); - if (thead_done and !threads[id].marked_as_done) { - threads[id].thread.join(); - threads[id].marked_as_done = true; + for (0..num_chunks) |id| { + const task_done = tasks[id].done.load(.acquire); + if (task_done and !tasks[id].marked_as_done) { + // threads[id].thread.join(); + tasks[id].marked_as_done = true; node.completeOne(); - } else if (!thead_done) { + try self.camera.image.writeToFilePath("./out/out.png", .{ .png = .{} }); + node.context.refresh(); + } else if (!task_done) { done = false; } } - if (done) break; + if (done or !self.thread_pool.is_running) break; } - // node.end(); + node.end(); return self.camera.image; } }; -pub fn renderThread(ctx: tracer.Context, done: *std.atomic.Value(bool), row: usize, row_height: usize) void { +pub fn renderThread(ctx: tracer.Context, task: *TaskTracker, id: usize) void { spall.init_thread(); defer spall.deinit_thread(); - const height = IntervalUsize{ .min = row_height * row, .max = row_height * row + row_height }; - const width = IntervalUsize{ .min = 0, .max = ctx.cam.image_width }; - // log.debug("Started Render Thread {}", .{row}); - const s = spall.trace(@src(), "Render Thread {}", .{row}); - defer s.end(); + // const s = spall.trace(@src(), "Render Thread {}", .{row}); + // defer s.end(); - tracer.trace(ctx, height, width); + tracer.trace(ctx); + + { + task.done.store(true, .release); + } - done.store(true, .Release); + // log.info("Chunk {} rendered", .{id}); + _ = id; } diff --git a/src/tracer.zig b/src/tracer.zig index d11c850..f719354 100644 --- a/src/tracer.zig +++ b/src/tracer.zig @@ -19,6 +19,8 @@ const log = std.log.scoped(.tracer); pub const Context = struct { cam: *Camera, world: *hittable.HittableList, + height: IntervalUsize, + width: IntervalUsize, }; pub fn rayColor(r: *Ray, world: *hittable.HittableList, depth: usize) zm.Vec { @@ -38,12 +40,12 @@ pub fn rayColor(r: *Ray, world: *hittable.HittableList, depth: usize) zm.Vec { return zm.f32x4s(1.0 - a) * zm.f32x4s(1.0) + zm.f32x4s(a) * zm.f32x4(0.5, 0.7, 1.0, 1.0); } -pub fn trace(ctx: Context, height: IntervalUsize, width: IntervalUsize) void { - var height_iter = height.iter(); +pub fn trace(ctx: Context) void { + var height_iter = ctx.height.iter(); while (height_iter.nextInc()) |j| { if (j >= ctx.cam.image_height) break; - var width_iter = width.iter(); + var width_iter = ctx.width.iter(); while (width_iter.nextExc()) |i| { var col = zm.f32x4(0.0, 0.0, 0.0, 1.0); for (0..ctx.cam.samples_per_pixel) |_| {