this repo has no description

restructured the code

+222 -92
+6 -5
src/hittable.zig
··· 2 2 3 3 const zm = @import("zmath"); 4 4 5 + const IntervalF32 = @import("interval.zig").IntervalF32; 5 6 const Ray = @import("ray.zig"); 6 7 pub const Sphere = @import("hittable/sphere.zig"); 7 8 ··· 28 29 return .{ .sphere = sphere }; 29 30 } 30 31 31 - pub fn hit(self: *Hittable, r: *Ray, ray_tmin: f32, ray_tmax: f32) ?HitRecord { 32 + pub fn hit(self: *Hittable, r: *Ray, ray_t: IntervalF32) ?HitRecord { 32 33 switch (self.*) { 33 34 .sphere => |*sphere| { 34 - return sphere.hit(r, ray_tmin, ray_tmax); 35 + return sphere.hit(r, ray_t); 35 36 }, 36 37 } 37 38 ··· 56 57 try self.list.append(item); 57 58 } 58 59 59 - pub fn hit(self: *HittableList, r: *Ray, ray_tmin: f32, ray_tmax: f32) ?HitRecord { 60 + pub fn hit(self: *HittableList, r: *Ray, ray_t: IntervalF32) ?HitRecord { 60 61 var rec: ?HitRecord = null; 61 62 var hit_anything = false; 62 - var closest_so_far = ray_tmax; 63 + var closest_so_far = ray_t.max; 63 64 64 65 for (self.list.items) |*object| { 65 - if (object.hit(r, ray_tmin, closest_so_far)) |new_rec| { 66 + if (object.hit(r, IntervalF32.init(ray_t.min, closest_so_far))) |new_rec| { 66 67 rec = new_rec; 67 68 hit_anything = true; 68 69 closest_so_far = new_rec.t;
+4 -3
src/hittable/sphere.zig
··· 1 1 const zm = @import("zmath"); 2 2 3 + const IntervalF32 = @import("../interval.zig").IntervalF32; 3 4 const Ray = @import("../ray.zig"); 4 5 const HitRecord = @import("../hittable.zig").HitRecord; 5 6 ··· 8 9 center: zm.Vec, 9 10 radius: f32, 10 11 11 - pub fn hit(self: *Sphere, r: *Ray, ray_tmin: f32, ray_tmax: f32) ?HitRecord { 12 + pub fn hit(self: *Sphere, r: *Ray, ray_t: IntervalF32) ?HitRecord { 12 13 const oc = r.orig - self.center; 13 14 const a = zm.lengthSq3(r.dir)[0]; 14 15 const half_b = zm.dot3(oc, r.dir)[0]; ··· 21 22 22 23 // Find the nearest root that lies in the acceptable range 23 24 var root = (-half_b - sqrtd) / a; 24 - if (root <= ray_tmin or ray_tmax <= root) { 25 + if (!ray_t.surrounds(root)) { 25 26 root = (-half_b + sqrtd) / a; 26 - if (root <= ray_tmin or ray_tmax <= root) return null; 27 + if (!ray_t.surrounds(root)) return null; 27 28 } 28 29 29 30 var rec = HitRecord{
+106
src/interval.zig
··· 1 + const std = @import("std"); 2 + 3 + pub const IntervalU8 = Interval(u8); 4 + pub const IntervalU16 = Interval(u16); 5 + pub const IntervalU32 = Interval(u32); 6 + pub const IntervalU64 = Interval(u64); 7 + pub const IntervalUsize = Interval(usize); 8 + 9 + pub const IntervalI8 = Interval(i8); 10 + pub const IntervalI16 = Interval(i16); 11 + pub const IntervalI32 = Interval(i32); 12 + pub const IntervalI64 = Interval(i64); 13 + pub const IntervalIsize = Interval(isize); 14 + 15 + pub const IntervalF32 = Interval(f32); 16 + pub const IntervalF64 = Interval(f64); 17 + 18 + pub const IntervalIteratorType = enum { 19 + inclusive, 20 + exclusive, 21 + }; 22 + 23 + pub fn Interval(comptime T: type) type { 24 + if (@typeInfo(T) == .Int) { 25 + return struct { 26 + const Self = @This(); 27 + 28 + pub const empty: Self = .{ .min = std.math.inf(T), .max = -std.math.inf(T) }; 29 + pub const universe: Self = .{ .min = -std.math.inf(T), .max = std.math.inf(T) }; 30 + 31 + pub const Iterator = struct { 32 + interval: Self, 33 + current: T, 34 + 35 + lower_boundry: IntervalIteratorType = .inclusive, 36 + upper_boundry: IntervalIteratorType = .exclusive, 37 + 38 + pub fn init( 39 + interval: Self, 40 + lower_boundry: IntervalIteratorType, 41 + upper_boundry: IntervalIteratorType, 42 + ) Iterator { 43 + return .{ 44 + .interval = interval, 45 + .current = if (lower_boundry == .inclusive) interval.min else interval.min + 1, 46 + .lower_boundry = lower_boundry, 47 + .upper_boundry = upper_boundry, 48 + }; 49 + } 50 + 51 + pub fn next(self: *Iterator) ?T { 52 + self.current += 1; 53 + if (self.current < self.interval.max or (self.current == self.interval.max and self.upper_boundry == .inclusive)) { 54 + return self.current; 55 + } else return null; 56 + } 57 + }; 58 + 59 + min: T, 60 + max: T, 61 + 62 + pub fn init(min: T, max: T) Interval { 63 + return .{ .min = min, .max = max }; 64 + } 65 + 66 + pub fn contains(self: *const Self, x: T) bool { 67 + return self.min <= x and x <= self.max; 68 + } 69 + 70 + pub fn surrounds(self: *const Self, x: T) bool { 71 + return self.min < x and x < self.max; 72 + } 73 + 74 + pub fn iter(self: *const Self) Iterator { 75 + return Iterator{ 76 + .interval = self.*, 77 + .current = self.min, 78 + }; 79 + } 80 + }; 81 + } else if (@typeInfo(T) == .Float) { 82 + return struct { 83 + pub const empty: @This() = .{ .min = std.math.inf(T), .max = -std.math.inf(T) }; 84 + pub const universe: @This() = .{ .min = -std.math.inf(T), .max = std.math.inf(T) }; 85 + 86 + const Self = @This(); 87 + 88 + min: T, 89 + max: T, 90 + 91 + pub fn init(min: T, max: T) Self { 92 + return .{ .min = min, .max = max }; 93 + } 94 + 95 + pub fn contains(self: *const Self, x: T) bool { 96 + return self.min <= x and x <= self.max; 97 + } 98 + 99 + pub fn surrounds(self: *const Self, x: T) bool { 100 + return self.min < x and x < self.max; 101 + } 102 + }; 103 + } else { 104 + @compileError("Interval only supports Int and Float Types!"); 105 + } 106 + }
-25
src/ray.zig
··· 2 2 3 3 const zm = @import("zmath"); 4 4 5 - const hittable = @import("hittable.zig"); 6 5 const Ray = @This(); 7 6 8 7 orig: zm.Vec, ··· 18 17 pub fn at(self: *Ray, t: f32) zm.Vec { 19 18 return self.orig + zm.f32x4s(t) * self.dir; 20 19 } 21 - 22 - pub fn color(r: *Ray, world: *hittable.HittableList) zm.Vec { 23 - if (world.hit(r, 0, std.math.inf(f32))) |rec| { 24 - return zm.f32x4s(0.5) * (rec.normal + zm.f32x4(1, 1, 1, 1)); 25 - } 26 - 27 - const unit_direction = zm.normalize3(r.dir); 28 - const a = 0.5 * (unit_direction[1] + 1.0); 29 - return zm.f32x4s(1.0 - a) * zm.f32x4s(1.0) + zm.f32x4s(a) * zm.f32x4(0.5, 0.7, 1.0, 1.0); 30 - } 31 - 32 - fn hitSphere(center: zm.Vec, radius: f32, r: *Ray) f32 { 33 - const oc = r.orig - center; 34 - const a = zm.lengthSq3(r.dir)[0]; 35 - const half_b = zm.dot3(oc, r.dir)[0]; 36 - const c = zm.dot3(oc, oc)[0] - radius * radius; 37 - const discriminant = half_b * half_b - a * c; 38 - 39 - if (discriminant < 0) { 40 - return -1.0; 41 - } else { 42 - return (-half_b - @sqrt(discriminant)) / a; 43 - } 44 - }
+26 -59
src/rayray.zig
··· 5 5 const color = zigimg.color; 6 6 const zm = @import("zmath"); 7 7 8 - const Camera = @import("camera.zig"); 8 + pub const Camera = @import("camera.zig"); 9 9 pub const hittable = @import("hittable.zig"); 10 - const Ray = @import("ray.zig"); 10 + pub const renderer = @import("renderer.zig"); 11 11 12 12 const log = std.log.scoped(.rayray); 13 + 14 + const ThreadTracker = struct { 15 + thread: std.Thread, 16 + done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), 17 + marked_as_done: bool = false, 18 + }; 13 19 14 20 pub const Raytracer = struct { 15 21 const Self = @This(); ··· 48 54 49 55 log.debug("rows: {}, row_height: {}, num_threads: {}", .{ rows, row_height, num_threads }); 50 56 51 - var threads = try self.allocator.alloc(TaskTracker, num_threads); 57 + var threads = try self.allocator.alloc(ThreadTracker, num_threads); 52 58 defer self.allocator.free(threads); 53 59 54 60 const finished_threads = try self.allocator.alloc(bool, num_threads); 55 61 56 62 for (0..num_threads) |row| { 57 - const t = try std.Thread.spawn(.{}, render_thread, .{ &self.camera, &self.world, row, row_height, &threads[row].done }); 63 + const ctx = renderer.Context{ .cam = &self.camera, .world = &self.world, .done = &threads[row].done }; 64 + const t = try std.Thread.spawn(.{}, renderer.renderThread, .{ ctx, row, row_height }); 58 65 threads[row].thread = t; 59 66 } 60 67 61 - const stderr = std.io.getStdErr(); 62 - defer stderr.close(); 68 + // const stderr = std.io.getStdErr(); 69 + // // defer stderr.close(); 63 70 64 - var progress = std.Progress{ 65 - .terminal = stderr, 66 - .supports_ansi_escape_codes = true, 67 - }; 68 - var node = progress.start("Rendering Completed", num_threads); 69 - node.activate(); 71 + // var progress = std.Progress{ 72 + // .terminal = stderr, 73 + // .supports_ansi_escape_codes = true, 74 + // }; 75 + // var node = progress.start("Rendering Completed", num_threads); 76 + // node.activate(); 70 77 71 78 while (true) { 72 79 var done = true; 73 - node.activate(); 74 80 75 81 for (0..num_threads) |id| { 76 - if (threads[id].done and !threads[id].marked_as_done) { 82 + if (threads[id].done.load(.Acquire) and !threads[id].marked_as_done) { 77 83 threads[id].thread.join(); 78 84 threads[id].marked_as_done = true; 79 85 finished_threads[id] = true; 80 - node.completeOne(); 81 - } else if (!threads[id].done) { 86 + // node.completeOne(); 87 + } else if (!threads[id].done.load(.Acquire)) { 82 88 done = false; 83 89 } 84 90 } 85 91 92 + // node.context.refresh(); 93 + 86 94 if (done) break; 87 95 } 88 96 97 + // node.end(); 98 + 89 99 return self.camera.image; 90 100 } 91 - 92 - fn render_thread(cam: *Camera, world: *hittable.HittableList, row: usize, height: usize, done: *bool) void { 93 - spall.init_thread(); 94 - defer spall.deinit_thread(); 95 - 96 - log.debug("Started Render Thread {}", .{row}); 97 - 98 - const s = spall.trace(@src(), "Render Thread {}", .{row}); 99 - defer s.end(); 100 - 101 - for (0..height) |ij| { 102 - const j = ij + height * row; 103 - if (j >= cam.image_height) break; 104 - 105 - for (0..cam.image_width) |i| { 106 - const pixel_center = cam.pixel00_loc + (zm.f32x4s(@as(f32, @floatFromInt(i))) * cam.pixel_delta_u) + (zm.f32x4s(@as(f32, @floatFromInt(j))) * cam.pixel_delta_v); 107 - const ray_direction = pixel_center - cam.camera_center; 108 - var ray = Ray.init(cam.camera_center, ray_direction); 109 - const col = vecToRgba(ray.color(world)); 110 - 111 - cam.setPixel(i, j, col) catch break; 112 - } 113 - } 114 - 115 - done.* = true; 116 - 117 - // log.debug("Render Thread {} is done", .{row}); 118 - } 119 101 }; 120 - 121 - const TaskTracker = struct { 122 - thread: std.Thread, 123 - done: bool = false, 124 - marked_as_done: bool = false, 125 - }; 126 - 127 - fn vecToRgba(v: zm.Vec) color.Rgba32 { 128 - const r: u8 = @intFromFloat(255.999 * v[0]); 129 - const g: u8 = @intFromFloat(255.999 * v[1]); 130 - const b: u8 = @intFromFloat(255.999 * v[2]); 131 - const a: u8 = @intFromFloat(255.999 * v[3]); 132 - 133 - return color.Rgba32.initRgba(r, g, b, a); 134 - }
+80
src/renderer.zig
··· 1 + const std = @import("std"); 2 + 3 + const spall = @import("spall"); 4 + const zigimg = @import("zigimg"); 5 + const color = zigimg.color; 6 + const zm = @import("zmath"); 7 + 8 + pub const Camera = @import("camera.zig"); 9 + pub const interval = @import("interval.zig"); 10 + pub const IntervalUsize = interval.IntervalUsize; 11 + pub const IntervalF32 = interval.IntervalF32; 12 + pub const hittable = @import("hittable.zig"); 13 + pub const Ray = @import("ray.zig"); 14 + 15 + const log = std.log.scoped(.renderer); 16 + 17 + pub const Context = struct { 18 + cam: *Camera, 19 + world: *hittable.HittableList, 20 + done: *std.atomic.Value(bool), 21 + }; 22 + 23 + pub fn rayColor(r: *Ray, world: *hittable.HittableList) zm.Vec { 24 + if (world.hit(r, IntervalF32.init(0, std.math.inf(f32)))) |rec| { 25 + return zm.f32x4s(0.5) * (rec.normal + zm.f32x4(1, 1, 1, 1)); 26 + } 27 + 28 + const unit_direction = zm.normalize3(r.dir); 29 + const a = 0.5 * (unit_direction[1] + 1.0); 30 + return zm.f32x4s(1.0 - a) * zm.f32x4s(1.0) + zm.f32x4s(a) * zm.f32x4(0.5, 0.7, 1.0, 1.0); 31 + } 32 + 33 + pub fn render(ctx: Context, height: IntervalUsize, width: IntervalUsize) void { 34 + var height_iter = height.iter(); 35 + height_iter.upper_boundry = .inclusive; 36 + 37 + while (height_iter.next()) |j| { 38 + if (j >= ctx.cam.image_height) break; 39 + 40 + var width_iter = width.iter(); 41 + height_iter.upper_boundry = .inclusive; 42 + 43 + while (width_iter.next()) |i| inner: { 44 + if (i >= ctx.cam.image_width) break :inner; 45 + 46 + const pixel_center = ctx.cam.pixel00_loc + (zm.f32x4s(@as(f32, @floatFromInt(i))) * ctx.cam.pixel_delta_u) + (zm.f32x4s(@as(f32, @floatFromInt(j))) * ctx.cam.pixel_delta_v); 47 + const ray_direction = pixel_center - ctx.cam.camera_center; 48 + var ray = Ray.init(ctx.cam.camera_center, ray_direction); 49 + const col = vecToRgba(rayColor(&ray, ctx.world)); 50 + 51 + ctx.cam.setPixel(i, j, col) catch break; 52 + } 53 + } 54 + 55 + ctx.done.store(true, .Release); 56 + } 57 + 58 + pub fn renderThread(ctx: Context, row: usize, row_height: usize) void { 59 + spall.init_thread(); 60 + defer spall.deinit_thread(); 61 + 62 + const height = IntervalUsize{ .min = row_height * row, .max = row_height * row + row_height }; 63 + const width = IntervalUsize{ .min = 0, .max = ctx.cam.image_width }; 64 + 65 + log.debug("Started Render Thread {}", .{row}); 66 + 67 + const s = spall.trace(@src(), "Render Thread {}", .{row}); 68 + defer s.end(); 69 + 70 + render(ctx, height, width); 71 + } 72 + 73 + fn vecToRgba(v: zm.Vec) color.Rgba32 { 74 + const r: u8 = @intFromFloat(255.999 * v[0]); 75 + const g: u8 = @intFromFloat(255.999 * v[1]); 76 + const b: u8 = @intFromFloat(255.999 * v[2]); 77 + const a: u8 = @intFromFloat(255.999 * v[3]); 78 + 79 + return color.Rgba32.initRgba(r, g, b, a); 80 + }