Multi thread

This commit is contained in:
Adrien Bouvais 2024-08-22 15:14:34 +02:00
parent 1f5e37de40
commit 62e4438b7d
3 changed files with 222 additions and 70 deletions

View File

@ -17,16 +17,77 @@ const unit_vector = mat_math.unit_vector;
const length = mat_math.length; const length = mat_math.length;
const cross = mat_math.cross; const cross = mat_math.cross;
const AtomicOrder = @import("std").builtin.AtomicOrder;
const Thread = std.Thread;
const Semaphore = std.Thread.Semaphore;
const Pixel = struct {
w: usize,
h: usize,
color: vec3,
};
const ThreadInfo = struct {
thread_index: usize,
semaphore_ptr: *Semaphore,
};
// Resolution parameters
const aspect_ratio: f64 = 16.0 / 9.0;
const image_width: usize = 2560; // Possible 128, 256, 512, 1024, 1280, 1920, 2560, 3840, 7680
const image_height: usize = image_width / aspect_ratio;
const samples_per_pixel = 500;
const max_depth = 50;
const n_threads_to_spawn = 100;
var completion_count: usize = 0;
var next_entry_to_do: usize = 0;
var entry_count: usize = 0;
var entry_buffer: [image_width * image_height]Pixel = undefined;
var _camera: *const Camera = undefined;
fn addWork(semaphore_ptr: *Semaphore) void {
const entry_index = @atomicLoad(usize, &entry_count, AtomicOrder.seq_cst);
// SeqCst guarantees that the msg write above is visible before the entry_count write below can be seen.
@atomicStore(usize, &entry_count, entry_index + 1, AtomicOrder.seq_cst);
semaphore_ptr.post(); // Wake up all threads
}
fn doWork(info: *ThreadInfo) void {
while (true) {
if (@atomicLoad(usize, &next_entry_to_do, AtomicOrder.seq_cst) < @atomicLoad(usize, &entry_count, AtomicOrder.seq_cst)) {
const entry_index = @atomicRmw(usize, &next_entry_to_do, .Add, 1, AtomicOrder.seq_cst);
const pixel = entry_buffer[entry_index];
var pixel_color = vec3{ 0, 0, 0 };
for (0.._camera.samples_per_pixel) |_| {
const r = _camera.get_ray(pixel.h, pixel.w);
pixel_color += ray_color(r, _camera.max_depth, _camera.world);
}
entry_buffer[entry_index].color = pixel_color;
_ = @atomicRmw(usize, &completion_count, .Add, 1, AtomicOrder.seq_cst);
} else {
info.semaphore_ptr.wait(); // Put all threads to sleep
}
}
}
pub const Camera = struct { pub const Camera = struct {
aspect_ratio: f64, aspect_ratio: f64,
image_width: i64, image_width: usize,
samples_per_pixel: i64, samples_per_pixel: usize,
max_depth: i64, max_depth: usize,
vfov: f64, vfov: f64,
defocus_angle: f64, defocus_angle: f64,
focus_dist: f64, focus_dist: f64,
world: *const HittableList,
image_height: i64, image_height: usize,
center: vec3, center: vec3,
pixel_samples_scale: f64, pixel_samples_scale: f64,
pixel00_loc: vec3, pixel00_loc: vec3,
@ -39,11 +100,72 @@ pub const Camera = struct {
v: vec3, v: vec3,
w: vec3, w: vec3,
pub fn new() Camera { pub fn render(self: Camera, writer: anytype) !void {
const aspect_ratio: f64 = 16.0 / 9.0; _camera = &self;
const image_width: i64 = 512; // Possible 128, 256, 512, 1024, 1280, 1920, 2560, 3840, 7680
const samples_per_pixel = 50; var semaphore = Semaphore{};
const max_depth = 10; var infos: [n_threads_to_spawn]ThreadInfo = undefined;
for (&infos, 0..) |*info, thread_index| {
info.thread_index = thread_index;
info.semaphore_ptr = &semaphore;
const handle = try Thread.spawn(.{}, doWork, .{info});
handle.detach();
}
// Write the PPM header
try writer.print("P3\n{} {}\n255\n", .{ self.image_width, self.image_height });
for (0..self.image_width) |w| {
for (0..self.image_height) |h| {
entry_buffer[w * self.image_height + h] = Pixel{ .w = w, .h = h, .color = vec3{ -1, -1, -1 } };
addWork(&semaphore);
}
}
print("\nStarting rendering using {d} threads\n", .{n_threads_to_spawn});
while (entry_count != @atomicLoad(usize, &completion_count, AtomicOrder.seq_cst)) {
pbar(@atomicLoad(usize, &completion_count, AtomicOrder.seq_cst), self.image_height * self.image_width);
}
pbar(100, 100);
for (0..self.image_height) |h| {
for (0..self.image_width) |w| {
try writeColor(self.get_pixel_color(w, h) * toVec3(self.pixel_samples_scale), writer);
}
}
}
fn get_pixel_color(self: Camera, w: usize, h: usize) vec3 {
const pixel = entry_buffer[w * self.image_height + h];
if ((pixel.w == w) and (pixel.h == h)) {
return pixel.color;
}
print("Pixel with different coordinates w: {}, h: {}, pixel.w: {}, pixel.h: {}", .{ w, h, pixel.w, pixel.h });
return vec3{ 0, 0, 0 };
}
fn get_ray(self: Camera, h: usize, w: usize) Ray {
// Construct a camera ray originating from the origin and directed at randomly sampled
// point around the pixel location i, j.
const offset = sample_square();
const pixel_sample = self.pixel00_loc +
(toVec3(h) + toVec3(offset[0])) * self.pixel_delta_v +
(toVec3(w) + toVec3(offset[1])) * self.pixel_delta_u;
const ray_origin = if (self.defocus_angle <= 0) self.center else self.defocus_disk_sample();
const ray_direction = pixel_sample - ray_origin;
return Ray{ .orig = ray_origin, .dir = ray_direction };
}
fn defocus_disk_sample(self: Camera) vec3 {
const p = random_in_unit_disk();
return self.center + (toVec3(p[0]) * self.defocus_disk_u) + (toVec3(p[1]) * self.defocus_disk_v);
}
pub fn new(world: *const HittableList) Camera {
const vfov = 20; const vfov = 20;
const lookfrom = vec3{ 13, 2, 3 }; const lookfrom = vec3{ 13, 2, 3 };
const lookat = vec3{ 0, 0, 0 }; const lookat = vec3{ 0, 0, 0 };
@ -53,7 +175,6 @@ pub const Camera = struct {
const focus_dist = 10; const focus_dist = 10;
const camera_center = lookfrom; const camera_center = lookfrom;
const image_height: i64 = image_width / aspect_ratio;
const pixel_samples_scale = 1.0 / @as(f64, @floatFromInt(samples_per_pixel)); const pixel_samples_scale = 1.0 / @as(f64, @floatFromInt(samples_per_pixel));
// Camera // Camera
@ -76,7 +197,7 @@ pub const Camera = struct {
const viewport_upper_left = camera_center - toVec3(focus_dist) * w - viewport_u / toVec3(2) - viewport_v / toVec3(2); const viewport_upper_left = camera_center - toVec3(focus_dist) * w - viewport_u / toVec3(2) - viewport_v / toVec3(2);
const pixel00_loc = viewport_upper_left + toVec3(0.5) * (pixel_delta_u + pixel_delta_v); const pixel00_loc = viewport_upper_left + toVec3(0.5) * (pixel_delta_u + pixel_delta_v);
const defocus_radius = focus_dist * @tan(utils.degrees_to_radians(defocus_angle / 2)); const defocus_radius = focus_dist * @tan(utils.degrees_to_radians(defocus_angle / 2.0));
const defocus_disk_u = u * toVec3(defocus_radius); const defocus_disk_u = u * toVec3(defocus_radius);
const defocus_disk_v = v * toVec3(defocus_radius); const defocus_disk_v = v * toVec3(defocus_radius);
@ -88,6 +209,7 @@ pub const Camera = struct {
.vfov = vfov, .vfov = vfov,
.focus_dist = focus_dist, .focus_dist = focus_dist,
.defocus_angle = defocus_angle, .defocus_angle = defocus_angle,
.world = world,
.u = u, .u = u,
.v = v, .v = v,
@ -103,50 +225,32 @@ pub const Camera = struct {
.pixel_delta_v = pixel_delta_v, .pixel_delta_v = pixel_delta_v,
}; };
} }
pub fn render(self: Camera, world: HittableList, writer: anytype) !void {
// Write the PPM header
try writer.print("P3\n{} {}\n255\n", .{ self.image_width, self.image_height });
// Write the pixel data
for (0..@as(usize, @intCast(self.image_height))) |i| {
const h = @as(i64, @intCast(i));
pbar(h, self.image_height);
for (0..@as(usize, @intCast(self.image_width))) |j| {
const w = @as(i64, @intCast(j));
var pixel_color = vec3{ 0, 0, 0 };
for (0..@as(usize, @intCast(self.samples_per_pixel))) |_| {
const r = self.get_ray(h, w);
pixel_color += ray_color(r, self.max_depth, world);
}
try writeColor(pixel_color * toVec3(self.pixel_samples_scale), writer);
}
}
}
fn get_ray(self: Camera, h: i64, w: i64) Ray {
// Construct a camera ray originating from the origin and directed at randomly sampled
// point around the pixel location i, j.
const offset = sample_square();
const pixel_sample = self.pixel00_loc +
(toVec3((@as(f64, @floatFromInt(h))) + offset[0]) * self.pixel_delta_v) +
(toVec3((@as(f64, @floatFromInt(w))) + offset[1]) * self.pixel_delta_u);
const ray_origin = if (self.defocus_angle <= 0) self.center else self.defocus_disk_sample();
const ray_direction = pixel_sample - ray_origin;
return Ray{ .orig = ray_origin, .dir = ray_direction };
}
fn defocus_disk_sample(self: Camera) vec3 {
const p = random_in_unit_disk();
return self.center + (toVec3(p[0]) * self.defocus_disk_u) + (toVec3(p[1]) * self.defocus_disk_v);
}
}; };
fn splitPixels(allocator: std.mem.Allocator, pixels: []Pixel, num_sublists: usize) ![][]Pixel {
const len = pixels.len;
if (num_sublists == 0 or num_sublists > len) {
return error.InvalidNumberOfSublists;
}
const sublist_size = len / num_sublists;
const remainder = len % num_sublists;
var result = try allocator.alloc([]Pixel, num_sublists);
var start: usize = 0;
for (0..num_sublists) |i| {
var end = start + sublist_size;
if (i < remainder) {
end += 1;
}
result[i] = pixels[start..end];
start = end;
}
return result;
}
fn random_in_unit_disk() vec3 { fn random_in_unit_disk() vec3 {
while (true) { while (true) {
const p = vec3{ utils.rand_mm(-1, 1), utils.rand_mm(-1, 1), 0 }; const p = vec3{ utils.rand_mm(-1, 1), utils.rand_mm(-1, 1), 0 };
@ -155,7 +259,7 @@ fn random_in_unit_disk() vec3 {
} }
} }
fn ray_color(ray: Ray, depth: i64, world: HittableList) vec3 { fn ray_color(ray: Ray, depth: usize, world: *const HittableList) vec3 {
if (depth <= 0) { if (depth <= 0) {
return vec3{ 0, 0, 0 }; return vec3{ 0, 0, 0 };
} }
@ -186,6 +290,10 @@ fn writeColor(color: vec3, writer: anytype) !void {
var g_float = color[1]; var g_float = color[1];
var b_float = color[2]; var b_float = color[2];
if ((r_float < -0.999) or (g_float < -0.999) or (b_float < -0.999)) {
@panic("-1 color detected");
}
r_float = utils.linear_to_gamma(r_float); r_float = utils.linear_to_gamma(r_float);
g_float = utils.linear_to_gamma(g_float); g_float = utils.linear_to_gamma(g_float);
b_float = utils.linear_to_gamma(b_float); b_float = utils.linear_to_gamma(b_float);
@ -198,11 +306,11 @@ fn writeColor(color: vec3, writer: anytype) !void {
try writer.print("{} {} {}\n", .{ r, g, b }); try writer.print("{} {} {}\n", .{ r, g, b });
} }
fn pbar(value: i64, max: i64) void { fn pbar(value: usize, max: usize) void {
const used_char = "-"; const used_char = "-";
const number_of_char = 60; const number_of_char = 60;
const percent_done: i64 = if (value == max - 1) 100 else @divFloor(value * 100, max); const percent_done: usize = if (value == max - 1) 100 else @divFloor(value * 100, max);
const full_char: i64 = @divFloor(number_of_char * percent_done, 100); const full_char: usize = @divFloor(number_of_char * percent_done, 100);
print("\r|", .{}); print("\r|", .{});
@ -215,7 +323,9 @@ fn pbar(value: i64, max: i64) void {
} }
} }
print("| {}%", .{percent_done}); print("| {}% |", .{percent_done});
print(" {} ", .{value});
print("/ {}", .{max});
if (percent_done == 100) { if (percent_done == 100) {
print("\n", .{}); print("\n", .{});

View File

@ -32,21 +32,20 @@ pub fn main() !void {
defer file.close(); defer file.close();
var buffered_writer = std.io.bufferedWriter(file.writer()); var buffered_writer = std.io.bufferedWriter(file.writer());
const writer = buffered_writer.writer(); const writer = buffered_writer.writer();
const camera = Camera.new();
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); errdefer arena.deinit();
const alloc = arena.allocator(); const alloc = arena.allocator();
const world = try generateRandomScene(alloc); const world = try generateRandomScene(alloc);
const camera = Camera.new(&world);
const start_time: i64 = std.time.milliTimestamp(); const start_time: i64 = std.time.milliTimestamp();
try camera.render(world, &writer); try camera.render(&writer);
const end_time: i64 = std.time.milliTimestamp(); const end_time: i64 = std.time.milliTimestamp();
const total_time: f64 = @as(f64, @floatFromInt(end_time - start_time)) / 1000; const total_time: f64 = @as(f64, @floatFromInt(end_time - start_time)) / 1000;
print("Rendering took {} s\n", .{total_time}); print("Rendering took {d} s\n\n", .{total_time});
print("Potential FPS: {}\n", .{1 / total_time});
// Flush the buffered writer to ensure all data is written to the file // Flush the buffered writer to ensure all data is written to the file
try buffered_writer.flush(); try buffered_writer.flush();
@ -98,6 +97,45 @@ pub fn run_open_image() !void {
} }
} }
pub fn generateSameScene(alloc: std.mem.Allocator) !HittableList {
var spheres = std.ArrayList(Hittable).init(alloc);
// Ground sphere
try spheres.append(.{
.sphere = Sphere{
.center = vec3{ 0, -1000, 0 },
.radius = 1000,
.material = Material{ .lambertian = Lambertian{ .albedo = vec3{ 0.5, 0.5, 0.5 } } },
},
});
// Three large spheres
try spheres.append(.{
.sphere = Sphere{
.center = vec3{ 0, 1, 0 },
.radius = 1.0,
.material = Material{ .dielectric = Dielectric{ .refraction_index = 1.5 } },
},
});
try spheres.append(.{
.sphere = Sphere{
.center = vec3{ -4, 1, 0 },
.radius = 1.0,
.material = Material{ .lambertian = Lambertian{ .albedo = vec3{ 0.4, 0.2, 0.1 } } },
},
});
try spheres.append(.{
.sphere = Sphere{
.center = vec3{ 4, 1, 0 },
.radius = 1.0,
.material = Material{ .metal = Metal{ .albedo = vec3{ 0.7, 0.6, 0.5 }, .fuzz = 0.0 } },
},
});
// Convert ArrayList to a slice and create HittableList
return HittableList{ .list = spheres.items };
}
pub fn generateRandomScene(alloc: std.mem.Allocator) !HittableList { pub fn generateRandomScene(alloc: std.mem.Allocator) !HittableList {
var spheres = std.ArrayList(Hittable).init(alloc); var spheres = std.ArrayList(Hittable).init(alloc);
@ -111,10 +149,13 @@ pub fn generateRandomScene(alloc: std.mem.Allocator) !HittableList {
}); });
// Smaller spheres // Smaller spheres
var a: i32 = -11; var a: i32 = -6;
while (a < 11) : (a += 1) { while (a < 11) : (a += 1) {
var b: i32 = -11; var b: i32 = -6;
while (b < 11) : (b += 1) { while (b < 6) : (b += 1) {
if ((-1 < a) and (a < 1) or ((-1 < b) and (b < 1))) {
continue;
}
const choose_mat = utils.rand_01(); const choose_mat = utils.rand_01();
const center = vec3{ const center = vec3{
@as(f64, @floatFromInt(a)) + 0.9 * utils.rand_01(), @as(f64, @floatFromInt(a)) + 0.9 * utils.rand_01(),

View File

@ -45,6 +45,10 @@ pub const Interval = struct {
pub fn toVec3(x: anytype) vec3 { pub fn toVec3(x: anytype) vec3 {
switch (@TypeOf(x)) { switch (@TypeOf(x)) {
usize => {
const x_float = @as(f64, @floatFromInt(x));
return vec3{ x_float, x_float, x_float };
},
comptime_float => { comptime_float => {
const x_float = @as(f64, x); const x_float = @as(f64, x);
return vec3{ x_float, x_float, x_float }; return vec3{ x_float, x_float, x_float };
@ -63,10 +67,7 @@ pub fn toVec3(x: anytype) vec3 {
@Vector(3, f64) => { @Vector(3, f64) => {
return x; return x;
}, },
usize => {
const x_float = @as(f64, @floatFromInt(@as(i64, @intCast(x))));
return vec3{ x_float, x_float, x_float };
},
else => { else => {
@panic("Unknow type passed through toVec3\n\n"); @panic("Unknow type passed through toVec3\n\n");
}, },