this repo has no description
1const std = @import("std");
2const build_options = @import("build-options");
3
4pub const zmath = @import("zmath");
5
6const zigimg = @import("zigimg");
7const color = zigimg.color;
8
9pub const BVH = @import("BVH.zig");
10pub const Camera = @import("Camera.zig");
11pub const hittable = @import("hittable.zig");
12pub const interval = @import("interval.zig");
13const IntervalUsize = interval.IntervalUsize;
14pub const material = @import("material.zig");
15pub const tracer = @import("tracer.zig");
16pub const util = @import("util.zig");
17
18const log = std.log.scoped(.rayray);
19
20pub const TaskTracker = struct {
21 marked_as_done: bool = false,
22 done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
23 thread_id: std.Thread.Id = 0,
24};
25
26pub const Raytracer = struct {
27 const Self = @This();
28
29 allocator: std.mem.Allocator,
30 thread_pool: *std.Thread.Pool,
31
32 camera: Camera,
33 world: BVH,
34
35 pub fn init(allocator: std.mem.Allocator, world: hittable.HittableList, camera_opts: Camera.Options) !Self {
36 var thread_pool = try allocator.create(std.Thread.Pool);
37 try thread_pool.init(.{ .allocator = allocator });
38
39 return .{
40 .allocator = allocator,
41 .thread_pool = thread_pool,
42 .camera = try Camera.init(allocator, camera_opts),
43 .world = try BVH.init(allocator, world, build_options.max_depth),
44 };
45 }
46
47 pub fn deinit(self: *Self) void {
48 self.camera.deinit();
49 self.world.deinit();
50
51 self.thread_pool.deinit();
52 self.allocator.destroy(self.thread_pool);
53 }
54
55 pub fn render(self: *Self) !zigimg.Image {
56 const chunk_height: usize = 25;
57 const chunk_width: usize = 25;
58
59 var rows: usize = @divTrunc(self.camera.image_height, chunk_height);
60 if (self.camera.image_height % rows != 0) {
61 rows += 1;
62 }
63
64 var cols: usize = @divTrunc(self.camera.image_width, chunk_width);
65 if (self.camera.image_width % cols != 0) {
66 cols += 1;
67 }
68
69 const num_chunks = cols * rows;
70
71 const num_threads = blk: {
72 const count = try std.Thread.getCpuCount();
73 if (count > 1) {
74 break :blk count;
75 } else break :blk 1;
76 };
77
78 log.debug("rows: {}, cols: {}, chunk_height: {}, chunk_width: {}, num_chunks: {}, num_threads: {}", .{
79 rows,
80 cols,
81 chunk_height,
82 chunk_width,
83 num_chunks,
84 num_threads,
85 });
86
87 const tasks = try self.allocator.alloc(TaskTracker, num_chunks);
88 defer self.allocator.free(tasks);
89
90 for (tasks, 0..) |*t, id| {
91 const row: usize = @divTrunc(id, cols) * chunk_height;
92 const col: usize = (id - cols * @divTrunc(id, cols)) * chunk_width;
93
94 const c_height = IntervalUsize{ .min = row, .max = row + chunk_height };
95 const c_width = IntervalUsize{ .min = col, .max = col + chunk_width + 1 };
96
97 const ctx = tracer.Context{
98 .cam = &self.camera,
99 .world = &self.world,
100 .height = c_height,
101 .width = c_width,
102 };
103
104 try self.thread_pool.spawn(
105 renderThread,
106 .{ ctx, t, id },
107 );
108 }
109
110 // const stderr = std.io.getStdErr();
111
112 // var progress = std.Progress{
113 // .terminal = stderr,
114 // .supports_ansi_escape_codes = true,
115 // };
116 // var node = progress.start("Rendered Chunks", num_chunks);
117 // node.setCompletedItems(0);
118 // node.context.refresh();
119
120 var thread_to_idx = std.ArrayList(std.Thread.Id).init(self.allocator);
121 defer thread_to_idx.deinit();
122
123 var root_node = std.Progress.start(.{
124 .root_name = "Ray Tracer",
125 .estimated_total_items = num_chunks,
126 });
127 var nodes = std.ArrayList(std.Progress.Node).init(self.allocator);
128 defer nodes.deinit();
129
130 for (0..num_threads) |_| {
131 try nodes.append(root_node.start("Chunks Rendered", num_chunks / num_threads));
132 }
133
134 var completed_chunks: u64 = 0;
135 var i: usize = 0;
136 while (true) {
137 var done = true;
138
139 for (tasks) |*t| {
140 const task_done = t.done.load(.acquire);
141
142 if (task_done and !t.marked_as_done) {
143 t.marked_as_done = true;
144
145 const idx = blk: {
146 for (thread_to_idx.items, 0..) |value, idx| {
147 if (value == t.thread_id) break :blk idx;
148 }
149 try thread_to_idx.append(t.thread_id);
150 const idx = i;
151 i += 1;
152 break :blk idx;
153 };
154 nodes.items[idx].completeOne();
155
156 completed_chunks += 1;
157 root_node.setCompletedItems(completed_chunks);
158 // if (completed_chunks % self.thread_pool.threads.len == 0) try self.camera.image.writeToFilePath("./out/out.png", .{ .png = .{} });
159 } else if (!task_done) {
160 done = false;
161 }
162 }
163
164 if (done or !self.thread_pool.is_running) break;
165 }
166
167 // node.end();
168
169 return self.camera.image;
170 }
171};
172
173pub fn renderThread(ctx: tracer.Context, task: *TaskTracker, id: usize) void {
174 defer task.done.store(true, .release);
175 task.thread_id = std.Thread.getCurrentId();
176 _ = id;
177 tracer.trace(ctx);
178}