this repo has no description
1const std = @import("std");
2pub const 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 Scene = @import("Scene.zig");
16pub const texture = @import("texture.zig");
17pub const tracer = @import("tracer.zig");
18pub const util = @import("util.zig");
19
20const log = std.log.scoped(.rayray);
21
22pub const TaskTracker = struct {
23 marked_as_done: bool = false,
24 done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
25 thread_id: std.Thread.Id = 0,
26};
27
28pub const Raytracer = struct {
29 const Self = @This();
30
31 allocator: std.mem.Allocator,
32 thread_pool: *std.Thread.Pool,
33
34 scene: Scene,
35 cols: usize,
36 rows: usize,
37 num_chunks: usize,
38 num_threads: usize,
39 chunks_per_thread: usize,
40
41 const chunk_height: usize = 25;
42 const chunk_width: usize = 25;
43
44 pub fn init(allocator: std.mem.Allocator, scene: Scene) !Self {
45 var thread_pool = try allocator.create(std.Thread.Pool);
46 try thread_pool.init(.{ .allocator = allocator });
47
48 const num_threads = blk: {
49 const count = try std.Thread.getCpuCount();
50 if (count > 1) {
51 break :blk count;
52 } else break :blk 1;
53 };
54
55 var rows: usize = @divTrunc(scene.camera.image_height, chunk_height);
56 if (scene.camera.image_height % rows != 0) {
57 rows += 1;
58 }
59
60 var cols: usize = @divTrunc(scene.camera.image_width, chunk_width);
61 if (scene.camera.image_width % cols != 0) {
62 cols += 1;
63 }
64
65 const num_chunks = cols * rows;
66
67 log.debug("with: {}, height: {}, rows: {}, cols: {}, chunk_height: {}, chunk_width: {}, num_chunks: {}, num_threads: {}", .{
68 scene.camera.image_width,
69 scene.camera.image_height,
70 rows,
71 cols,
72 chunk_height,
73 chunk_width,
74 num_chunks,
75 num_threads,
76 });
77
78 return .{
79 .allocator = allocator,
80 .thread_pool = thread_pool,
81 .scene = scene,
82 .cols = cols,
83 .rows = rows,
84 .num_chunks = num_chunks,
85 .num_threads = num_threads,
86 .chunks_per_thread = num_chunks / num_threads,
87 };
88 }
89
90 pub fn deinit(self: *Self) void {
91 self.scene.deinit();
92 self.thread_pool.deinit();
93 self.allocator.destroy(self.thread_pool);
94 }
95
96 pub fn render(self: *Self) !zigimg.Image {
97 var root_node = std.Progress.start(.{
98 .root_name = "Ray Tracer",
99 .estimated_total_items = 4,
100 });
101
102 var bvh_node = root_node.start("Createing BVH", 0);
103
104 var world_bvh = try BVH.init(self.allocator, self.scene.world, build_options.max_depth);
105
106 bvh_node.end();
107 // root_node.setCompletedItems(0);
108
109 var create_pixels_node = root_node.start("Create pixel array", 0);
110
111 const pixels: []zmath.Vec = try self.allocator.alloc(zmath.Vec, self.scene.camera.image_height * self.scene.camera.image_width);
112 defer self.allocator.free(pixels);
113 // const l = pixels.ptr;
114
115 create_pixels_node.end();
116 // root_node.setCompletedItems(1);
117
118 var task_node = root_node.start("Creating render tasks", 0);
119
120 const tasks: []TaskTracker = try self.allocator.alloc(TaskTracker, self.num_chunks);
121 defer self.allocator.free(tasks);
122
123 for (tasks, 0..) |*t, id| {
124 // const row: usize = @divTrunc(id, cols) * chunk_height;
125 // const col: usize = (id - cols * @divTrunc(id, cols)) * chunk_width;
126 const row: usize = (id / self.cols) * chunk_height;
127 const col: usize = (id % self.cols) * chunk_width;
128
129 const c_height = IntervalUsize{ .min = row, .max = row + chunk_height };
130 const c_width = IntervalUsize{ .min = col, .max = col + chunk_width + 1 };
131
132 const ctx = try self.allocator.create(tracer.Context);
133
134 ctx.* = tracer.Context{
135 .pixels = pixels,
136 .cam = &self.scene.camera,
137 .world = &world_bvh,
138 .height = c_height,
139 .width = c_width,
140 };
141
142 try self.thread_pool.spawn(
143 renderThread,
144 .{ ctx, t },
145 );
146 }
147
148 task_node.end();
149 // root_node.setCompletedItems(2);
150
151 var render_node = root_node.start("Rendering", self.num_chunks);
152
153 try self.awaitTasks(&render_node, tasks);
154
155 log.info("Rendering done!", .{});
156
157 render_node.end();
158 // root_node.setCompletedItems(4);
159
160 var image_node = root_node.start("Creating Image", 0);
161 defer image_node.end();
162
163 for (pixels, 0..) |pix, p| {
164 const y = p / self.scene.camera.image_width;
165 const x = p % self.scene.camera.image_width;
166 if (pix[0] < 0 or pix[1] < 0 or pix[2] < 0) {
167 // std.log.debug("wrong ({}, {}) {}", .{ x, y, pix });
168 try self.scene.camera.setPixel(x, y, zigimg.color.Rgba32.initRgb(255, 0, 0));
169 continue;
170 }
171 self.scene.camera.setPixel(x, y, vecToRgba(pix, self.scene.camera.samples_per_pixel_v)) catch continue;
172 }
173
174 return self.scene.camera.image;
175 }
176
177 fn awaitTasks(self: *Self, render_node: *std.Progress.Node, tasks: []TaskTracker) !void {
178 var thread_to_idx = std.ArrayList(std.Thread.Id).init(self.allocator);
179 defer thread_to_idx.deinit();
180
181 var nodes = std.ArrayList(std.Progress.Node).init(self.allocator);
182 defer nodes.deinit();
183
184 var completed_chunks: u64 = 0;
185 var i: usize = 0;
186 while (true) {
187 var done = true;
188
189 for (tasks) |*t| {
190 const task_done = t.done.load(.acquire);
191
192 if (task_done and !t.marked_as_done) {
193 t.marked_as_done = true;
194
195 const idx = blk: {
196 for (thread_to_idx.items, 0..) |value, idx| {
197 if (value == t.thread_id) break :blk idx;
198 }
199
200 try thread_to_idx.append(t.thread_id);
201
202 const node_msg = try std.fmt.allocPrint(self.allocator, "Render Thread #{}", .{i});
203 defer self.allocator.free(node_msg);
204 try nodes.append(render_node.start(node_msg, self.chunks_per_thread));
205
206 i += 1;
207 std.debug.assert(i <= self.num_threads);
208 break :blk i - 1;
209 };
210 nodes.items[idx].completeOne();
211
212 // if (i == 1) continue;
213 completed_chunks += 1;
214 render_node.setCompletedItems(completed_chunks);
215
216 if (build_options.save_during_render and
217 completed_chunks % self.thread_pool.threads.len == 0)
218 try self.scene.writeToFilePath(build_options.output, .{ .png = .{} });
219 } else if (!task_done) {
220 done = false;
221 }
222 }
223
224 if (done or !self.thread_pool.is_running) break;
225 }
226
227 std.debug.assert(completed_chunks == self.num_chunks);
228 }
229};
230
231fn renderThread(ctx: *tracer.Context, task: *TaskTracker) void {
232 defer task.done.store(true, .release);
233 task.thread_id = std.Thread.getCurrentId();
234 tracer.trace(ctx);
235}
236
237const zero = zmath.f32x4s(0.0);
238const nearly_one = zmath.f32x4s(0.999);
239const v256 = zmath.f32x4s(256);
240
241inline fn vecToRgba(v: zmath.Vec, samples_per_pixel: zmath.Vec) zigimg.color.Rgba32 {
242 const rgba = zmath.clampFast(
243 @sqrt(v / samples_per_pixel),
244 zero,
245 nearly_one,
246 ) * v256; // linear to gamma
247
248 return zigimg.color.Rgba32.initRgba(
249 @intFromFloat(rgba[0]),
250 @intFromFloat(rgba[1]),
251 @intFromFloat(rgba[2]),
252 @intFromFloat(rgba[3]),
253 );
254}