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