this repo has no description
at main 8.4 kB view raw
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}