this repo has no description
at 1cd5e1ab441ef6a246a03a423071f2efe5d8cb20 179 lines 5.7 kB view raw
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}