Multi thread
This commit is contained in:
parent
1f5e37de40
commit
62e4438b7d
226
src/camera.zig
226
src/camera.zig
@ -17,16 +17,77 @@ const unit_vector = mat_math.unit_vector;
|
||||
const length = mat_math.length;
|
||||
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 {
|
||||
aspect_ratio: f64,
|
||||
image_width: i64,
|
||||
samples_per_pixel: i64,
|
||||
max_depth: i64,
|
||||
image_width: usize,
|
||||
samples_per_pixel: usize,
|
||||
max_depth: usize,
|
||||
vfov: f64,
|
||||
defocus_angle: f64,
|
||||
focus_dist: f64,
|
||||
world: *const HittableList,
|
||||
|
||||
image_height: i64,
|
||||
image_height: usize,
|
||||
center: vec3,
|
||||
pixel_samples_scale: f64,
|
||||
pixel00_loc: vec3,
|
||||
@ -39,11 +100,72 @@ pub const Camera = struct {
|
||||
v: vec3,
|
||||
w: vec3,
|
||||
|
||||
pub fn new() Camera {
|
||||
const aspect_ratio: f64 = 16.0 / 9.0;
|
||||
const image_width: i64 = 512; // Possible 128, 256, 512, 1024, 1280, 1920, 2560, 3840, 7680
|
||||
const samples_per_pixel = 50;
|
||||
const max_depth = 10;
|
||||
pub fn render(self: Camera, writer: anytype) !void {
|
||||
_camera = &self;
|
||||
|
||||
var semaphore = Semaphore{};
|
||||
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 lookfrom = vec3{ 13, 2, 3 };
|
||||
const lookat = vec3{ 0, 0, 0 };
|
||||
@ -53,7 +175,6 @@ pub const Camera = struct {
|
||||
const focus_dist = 10;
|
||||
|
||||
const camera_center = lookfrom;
|
||||
const image_height: i64 = image_width / aspect_ratio;
|
||||
const pixel_samples_scale = 1.0 / @as(f64, @floatFromInt(samples_per_pixel));
|
||||
|
||||
// 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 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_v = v * toVec3(defocus_radius);
|
||||
|
||||
@ -88,6 +209,7 @@ pub const Camera = struct {
|
||||
.vfov = vfov,
|
||||
.focus_dist = focus_dist,
|
||||
.defocus_angle = defocus_angle,
|
||||
.world = world,
|
||||
|
||||
.u = u,
|
||||
.v = v,
|
||||
@ -103,50 +225,32 @@ pub const Camera = struct {
|
||||
.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 {
|
||||
while (true) {
|
||||
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) {
|
||||
return vec3{ 0, 0, 0 };
|
||||
}
|
||||
@ -186,6 +290,10 @@ fn writeColor(color: vec3, writer: anytype) !void {
|
||||
var g_float = color[1];
|
||||
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);
|
||||
g_float = utils.linear_to_gamma(g_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 });
|
||||
}
|
||||
|
||||
fn pbar(value: i64, max: i64) void {
|
||||
fn pbar(value: usize, max: usize) void {
|
||||
const used_char = "-";
|
||||
const number_of_char = 60;
|
||||
const percent_done: i64 = if (value == max - 1) 100 else @divFloor(value * 100, max);
|
||||
const full_char: i64 = @divFloor(number_of_char * percent_done, 100);
|
||||
const percent_done: usize = if (value == max - 1) 100 else @divFloor(value * 100, max);
|
||||
const full_char: usize = @divFloor(number_of_char * percent_done, 100);
|
||||
|
||||
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) {
|
||||
print("\n", .{});
|
||||
|
57
src/main.zig
57
src/main.zig
@ -32,21 +32,20 @@ pub fn main() !void {
|
||||
defer file.close();
|
||||
var buffered_writer = std.io.bufferedWriter(file.writer());
|
||||
const writer = buffered_writer.writer();
|
||||
const camera = Camera.new();
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||
defer arena.deinit();
|
||||
errdefer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const world = try generateRandomScene(alloc);
|
||||
const camera = Camera.new(&world);
|
||||
|
||||
const start_time: i64 = std.time.milliTimestamp();
|
||||
try camera.render(world, &writer);
|
||||
try camera.render(&writer);
|
||||
const end_time: i64 = std.time.milliTimestamp();
|
||||
const total_time: f64 = @as(f64, @floatFromInt(end_time - start_time)) / 1000;
|
||||
|
||||
print("Rendering took {} s\n", .{total_time});
|
||||
print("Potential FPS: {}\n", .{1 / total_time});
|
||||
print("Rendering took {d} s\n\n", .{total_time});
|
||||
|
||||
// Flush the buffered writer to ensure all data is written to the file
|
||||
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 {
|
||||
var spheres = std.ArrayList(Hittable).init(alloc);
|
||||
|
||||
@ -111,10 +149,13 @@ pub fn generateRandomScene(alloc: std.mem.Allocator) !HittableList {
|
||||
});
|
||||
|
||||
// Smaller spheres
|
||||
var a: i32 = -11;
|
||||
var a: i32 = -6;
|
||||
while (a < 11) : (a += 1) {
|
||||
var b: i32 = -11;
|
||||
while (b < 11) : (b += 1) {
|
||||
var b: i32 = -6;
|
||||
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 center = vec3{
|
||||
@as(f64, @floatFromInt(a)) + 0.9 * utils.rand_01(),
|
||||
|
@ -45,6 +45,10 @@ pub const Interval = struct {
|
||||
|
||||
pub fn toVec3(x: anytype) vec3 {
|
||||
switch (@TypeOf(x)) {
|
||||
usize => {
|
||||
const x_float = @as(f64, @floatFromInt(x));
|
||||
return vec3{ x_float, x_float, x_float };
|
||||
},
|
||||
comptime_float => {
|
||||
const x_float = @as(f64, x);
|
||||
return vec3{ x_float, x_float, x_float };
|
||||
@ -63,10 +67,7 @@ pub fn toVec3(x: anytype) vec3 {
|
||||
@Vector(3, f64) => {
|
||||
return x;
|
||||
},
|
||||
usize => {
|
||||
const x_float = @as(f64, @floatFromInt(@as(i64, @intCast(x))));
|
||||
return vec3{ x_float, x_float, x_float };
|
||||
},
|
||||
|
||||
else => {
|
||||
@panic("Unknow type passed through toVec3\n\n");
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user