I'm trying to write a raytracer in Rust. I'm having difficulty getting the for loops to run in parallel. I'm not sure where the problem is, but I can't seem to get anything on the screen. Is this the correct approach or am I completely heading in the wrong direction?
I've tried running the for loops without multi-threading and it does correctly produce output. I've also added loggers to the consumer loop and I'm getting the correct values as well. It just doesn't seem to update the window.
#[derive(Clone, Copy)]
pub struct Pixel {
pub x: usize,
pub y: usize,
pub color: Vec3,
}
let mut buffer : Vec<u32> = vec![0; WIDTH * HEIGHT];
let (tx, rx) = mpsc::channel()
for x in 0..HEIGHT {
let tx_t = tx.clone();
thread::spawn(move || {
for y in 0..WIDTH {
let mut color = cast_ray(x, y); // returns vec3
let pixel = Pixel { x: x, y: y, color: color };
tx_t.send(pixel).unwrap();
}
});
}
for received in rx {
buffer[received.x * WIDTH + received.y] = received.color.x << 16 | received.color.y << 8 | received.color.z;
}
while window.is_open() && !window.is_key_down(Key::Escape) {
window.update_with_buffer(&buffer).unwrap();
}
I'm expecting a few spheres or color to appear on the screen, but it's just black.
Related
I want to calculate per pixel color when drawing text.
draw_text doesn't take a closure ( lambda) for calculating the color so i thought to make my own canvas and make a custom draw_pixel.
sounds good but when for the life of me i cannot get a simple color to pixel conversion to compile here.
for example below, the c cannot be turned into a Pixel. but when i am writing code outside this, such as code external to imageproc, rgb to pixel works just fine.
pub struct CustomBlend<I>(pub I );
impl<I: GenericImage> Canvas for CustomBlend<I> {
type Pixel = I::Pixel;
fn dimensions(&self) -> (u32, u32) {
self.0.dimensions()
}
fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel {
self.0.get_pixel(x, y)
}
fn draw_pixel(&mut self, x: u32, y: u32, color: Self::Pixel) {
let mut pix = self.0.get_pixel(x, y);
let c = Rgb([1u8,1u8,1u8]);
self.0.put_pixel(x, y,c );
}
}
I got this code but it takes a screenshot of each screen separately, and I need to take the two screens together .
use screenshots::Screen;
use std::fs;
use std::fs::File;
use std::fmt::Display;
fn main() {
let screens = Screen::all();
unsafe {
for screen in screens {
let mut image = screen.capture().unwrap();
let mut buffer = image.buffer();
fs::write(format!("{}.png", screen.id.to_string()), &buffer).unwrap();
}
}
}
like this screenshot
There is no built-in mechanism for this in screenshots.
You can, however, easily query the image coordinates from the Screen object and then combine it yourself:
use std::io::Cursor;
use image::{GenericImage, ImageFormat, RgbaImage};
use screenshots::Screen;
struct ScreenImage {
screen: screenshots::Screen,
image: screenshots::Image,
}
fn main() {
// Capture all screens
let screen_images = Screen::all()
.into_iter()
.map(|screen| {
let image = screen.capture().unwrap();
ScreenImage { screen, image }
})
.collect::<Vec<_>>();
// Compute coordinates of combined image
let x_min = screen_images.iter().map(|s| s.screen.x).min().unwrap();
let y_min = screen_images.iter().map(|s| s.screen.y).min().unwrap();
let x_max = screen_images
.iter()
.map(|s| s.screen.x + s.image.width() as i32)
.max()
.unwrap();
let y_max = screen_images
.iter()
.map(|s| s.screen.y + s.image.height() as i32)
.max()
.unwrap();
// Compute size and offset of combined image
let offset = (x_min, y_min);
let size = ((x_max - x_min) as u32, (y_max - y_min) as u32);
println!("Total screenshot size: {:?}", size);
println!("Offset: {:?}", offset);
// Allocate combined image
let mut img = RgbaImage::new(size.0, size.1);
for screen_image in screen_images {
let screenshot = image::io::Reader::new(Cursor::new(screen_image.image.buffer()))
.with_guessed_format()
.unwrap()
.decode()
.unwrap();
img.copy_from(
&screenshot,
(screen_image.screen.x - offset.0) as u32,
(screen_image.screen.y - offset.1) as u32,
)
.unwrap();
}
img.save_with_format("Screenshot.png", ImageFormat::Png)
.unwrap();
}
I on purpose moved my screens in awkward positions to test the program. So I got:
Total screenshot size: (4480, 1846)
Offset: (-1920, 0)
Good evening!
I'm trying to write a very simple terminal application that draws two textboxes on screen, accepting input on one and showing output on the other, using Rust and tui-rs. The first part works perfectly, but my problems arose when i tried to draw two blocks at the same time: for some reason, it only shows the second block (in order of drawing) and if i move my mouse, it flickers between the two in a weird way. My best guess is that this is due to my drawing implementation, which somehow "clears" the screen whenever it needs to draw something, but if that's the case, i couldn't find any doc on it, and i wouldn't know how to go about working around this. I've provided some code that should be enough to replicate the issue on a smaller scale.
#![allow(unused_imports)]
#![allow(unused_variables)]
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use std::io
use tui::{
backend::CrosstermBackend,
layout::Rect,
widgets::{Block, Borders},
Terminal,
};
struct FirstStruct {}
impl FirstStruct {
pub fn draw(&self, term: &mut Terminal<CrosstermBackend<io::Stdout>>) -> io::Result<()> {
term.draw(|f| {
let size = f.size();
let (w, h) = (size.width / 2, size.height);
let (x, y) = (size.x, size.y);
let rect = Rect::new(x, y, w, h);
let block = Block::default()
.title("One")
.borders(Borders::ALL);
f.render_widget(block, rect)
})?;
Ok(())
}
}
struct SecondStruct { }
impl SecondStruct {
pub fn draw(&self, term: &mut Terminal<CrosstermBackend<io::Stdout>>) -> io::Result<()> {
term.draw(|f| {
let size = f.size();
let (w, h) = (size.width / 2, size.height);
let (x, y) = (size.x + w, size.y);
let rect = Rect::new(x, y, w, h);
let block = Block::default()
.title("Two")
.borders(Borders::ALL);
f.render_widget(block, rect)
})?;
Ok(())
}
}
fn main() -> io::Result<()>{
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let first = FirstStruct {};
let second = SecondStruct {};
let mut running = true;
while running {
if let Event::Key(key) = event::read()? {
running = false;
}
second.draw(&mut terminal)?;
first.draw(&mut terminal)?;
}
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
Ok(())
}
Does anybody know how i can fix this issue? Thanks in advance!!
Every time you call Terminal::draw(), you must draw everything that you want to be visible at once. Instead of passing Terminal to your own draw functions, pass the Frame that you get from Terminal::draw(). That is, replace
second.draw(&mut terminal)?;
first.draw(&mut terminal)?;
with
terminal.draw(|f| {
first.draw(f)?;
second.draw(f)?;
});
and change the signature of FirstStruct and SecondStruct to match.
Also, it would be more usual to, instead of computing the rectangle for each widget in the individual functions, decide at the top level (using Layout, perhaps) and pass Rects down to the drawing functions. That way, they can be positioned differently in different situations. What you have will work, but it's not as easy to change.
Layout code from the documentation's example, adjusted to your situation:
terminal.draw(|f| {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Percentage(50),
Constraint::Percentage(50),
].as_ref()
)
.split(f.size());
first.draw(f, chunks[0])?;
second.draw(f, chunks[1])?;
});
I have the function get_screen that's specified in a separate module from main.rs. It takes two 2D vectors (one that's 1920x1080 and called screen and another one that's even larger called world) and maps a portion of the world vector to the screen vector. This is the function signature when I first made it:
pub fn get_screen(
screen: &mut Vec<Vec<[u8; 4]>>,
world: &Vec<Vec<Chunk>>,
camera_coords: (isize, isize),
screen_width: usize,
screen_height: usize,
chunk_width: usize,
chunk_height: usize,
)
I had serious issues with execution time, but I optimized it from 14ms down to 3ms by using #[inline].
I then moved the world vector to its own struct (alongside some other related variables like chunk width/height) and made the get_screen function into a method in the new world struct. This is what the function signature looked like after that change:
pub fn get_screen(
&self,
screen: &mut Vec<Vec<[u8; 4]>>,
camera_coords: (isize, isize),
screen_width: usize,
screen_height: usize,
)
Then the execution time increases back to 14ms. I've tried enabling lto=true in Cargo.toml and switching to #[inline(always)] to enforce it, but it seems like the compiler refuses to optimize this function the way it used to.
I attempted to remove the get_screen method from the struct and run it as its own function like before and that seems to fix it, but only if I don't pass anything from the struct. If I attempt to pass even a usize from the world struct to the separate get_screen function, then the execution time increases from 3ms back to 14ms.
To show an example of what I mean, if I pass nothing directly from the world struct and instead pass it a cloned version of the 2D struct in world and the hardcoded chunk_width/chunk_height:
gen::get_screen(
&mut screen.buf,
&cloned_world_data,
camera_coords,
SCREEN_WIDTH,
SCREEN_HEIGHT,
CHUNK_WIDTH,
CHUNK_HEIGHT,
);
It runs in 3.3ms. When I pass the usize fields chunk_width/chunk_height directly from the world struct:
gen::get_screen(
&mut screen.buf,
&cloned_world_data,
camera_coords,
SCREEN_WIDTH,
SCREEN_HEIGHT,
world.chunk_width,
world.chunk_height,
);
it takes 14.55ms to run
What's up here? How can I get my get_screen function to compile inline while using my World struct? Preferably I'd like to be able to re-add it to my World struct as a method instead of keeping it separate.
Here is a minimal example:
use std::time::Instant;
const SCREEN_HEIGHT: usize = 1080; //528;
const SCREEN_WIDTH: usize = 1920; //960;
const CHUNK_WIDTH: usize = 256;
const CHUNK_HEIGHT: usize = 256;
const GEN_RANGE: isize = 25; //how far out to gen chunks
fn main() {
let batch_size = 1_000;
struct_test(batch_size);
separate_test(batch_size);
}
fn struct_test(batch_size: u32) {
let world = World::new(CHUNK_WIDTH, CHUNK_HEIGHT, GEN_RANGE); //generate world
let mut screen = vec![vec!([0; 4]; SCREEN_WIDTH); SCREEN_HEIGHT];
let camera_coords: (isize, isize) = (0, 0); //set camera location
let start = Instant::now();
for _ in 0..batch_size {
get_screen(
&mut screen,
&world.data,
camera_coords,
SCREEN_WIDTH,
SCREEN_HEIGHT,
world.chunk_width,
world.chunk_height,
); //gets visible pixels from world as 2d vec
}
println!(
"struct: {:?} {:?}",
start.elapsed(),
start.elapsed() / batch_size
);
}
fn separate_test(batch_size: u32) {
let world = World::new(CHUNK_WIDTH, CHUNK_HEIGHT, GEN_RANGE); //generate world
let cloned_world_data = world.data.clone();
let mut screen = vec![vec!([0; 4]; SCREEN_WIDTH); SCREEN_HEIGHT];
let camera_coords: (isize, isize) = (0, 0); //set camera location
let start = Instant::now();
for _ in 0..batch_size {
get_screen(
&mut screen,
&cloned_world_data,
camera_coords,
SCREEN_WIDTH,
SCREEN_HEIGHT,
CHUNK_WIDTH,
CHUNK_HEIGHT,
); //gets visible pixels from world as 2d vec
}
println!(
"separate: {:?} {:?}",
start.elapsed(),
start.elapsed() / batch_size
);
}
///gets all visible pixels on screen relative camera position in world
#[inline(always)] //INLINE STOPPED WORKING??
pub fn get_screen(
screen: &mut Vec<Vec<[u8; 4]>>,
world: &Vec<Vec<Chunk>>,
camera_coords: (isize, isize),
screen_width: usize,
screen_height: usize,
chunk_width: usize,
chunk_height: usize,
) {
let camera = get_local_coords(&world, camera_coords, chunk_width, chunk_height); //gets loaded coords of camera in loaded chunks
(camera.1 - screen_height as isize / 2..camera.1 + screen_height as isize / 2)
.enumerate()
.for_each(|(py, y)| {
//for screen pixel index and particle in range of camera loaded y
let (cy, ly) = get_local_pair(y, chunk_height); //calculate chunk y and inner y from loaded y
if let Some(c_row) = world.get(cy) {
//if chunk row at loaded chunk y exists
(camera.0 - screen_width as isize / 2..camera.0 + screen_width as isize / 2)
.enumerate()
.for_each(|(px, x)| {
//for screen pixel index and particle in range of camera loaded x
let (cx, lx) = get_local_pair(x, chunk_width); //get loaded chunk x and inner x from loaded x
if let Some(c) = c_row.get(cx) {
screen[py][px] = c.data[ly][lx];
}
//if chunk in row then copy color of target particle in chunk
else {
screen[py][px] = [0; 4]
} //if target chunk doesn't exist color black
})
} else {
screen[py].iter_mut().for_each(|px| *px = [0; 4])
} //if target chunk row doesn't exist color row black
});
}
///calculates local coordinates in world vec from your global position
///returns negative if above/left of rendered area
pub fn get_local_coords(
world: &Vec<Vec<Chunk>>,
coords: (isize, isize),
chunk_width: usize,
chunk_height: usize,
) -> (isize, isize) {
let (wx, wy) = world[0][0].chunk_coords; //gets coords of first chunk in rendered vec
let lx = coords.0 - (wx * chunk_width as isize); //calculates local x coord based off world coords of first chunk
let ly = (wy * chunk_height as isize) - coords.1; //calculates local y coord based off world coords of first chunk
(lx, ly)
}
pub fn get_local_pair(coord: isize, chunk: usize) -> (usize, usize) {
(coord as usize / chunk, coord as usize % chunk)
}
///contains chunk data
#[derive(Clone)]
pub struct Chunk {
//world chunk object
pub chunk_coords: (isize, isize), //chunk coordinates
pub data: Vec<Vec<[u8; 4]>>, //chunk Particle data
}
impl Chunk {
///generates chunk
fn new(chunk_coords: (isize, isize), chunk_width: usize, chunk_height: usize) -> Self {
let data = vec![vec!([0; 4]; chunk_width); chunk_height];
Self { chunk_coords, data }
}
}
pub struct World {
pub data: Vec<Vec<Chunk>>,
pub chunk_width: usize,
pub chunk_height: usize,
}
impl World {
pub fn new(chunk_width: usize, chunk_height: usize, gen_range: isize) -> Self {
let mut data = Vec::new(); //creates empty vec to hold world
for (yi, world_chunk_y) in (gen_range * -1..gen_range + 1).rev().enumerate() {
//for y index, y in gen range counting down
data.push(Vec::new()); //push new row
for world_chunk_x in gen_range * -1..gen_range + 1 {
//for chunk in gen range of row
data[yi].push(Chunk::new(
(world_chunk_x, world_chunk_y),
chunk_width,
chunk_height,
)); //gen new chunk and put it there
}
}
Self {
data,
chunk_width,
chunk_height,
}
}
}
Probably, when you use world.chunk_width and world.chunk_height as parameters the compiler does not consider these parameters as constants and then actually generates division and modulus operations.
On the other hand, when you provide constants for these parameters, they can be propagated in the algorithm (constant folding) and some expensive operations (division, modulus) are not performed (or transformed into bit-shifts/masks).
Copying/pasting your code in godbolt (compiler explorer), making separate_test() and struct_test() public, and compiling with -C opt-level=3 confirms this since div instructions are present in the generated code for struct_test() but not for separate_test().
I'm trying to implement the BVH algorithm in my Rust ray tracer, but I'm having trouble with lifetimes and ownership. I have a trait Hittable that a bunch of different things implement -- Sphere, Triangle, Mesh, etc. And so I have a Vec<Box<dyn Hittable>>, which I want to turn into a tree of this struct:
pub struct BvhNode {
bounding_box: BoundingBox,
left: Box<dyn Hittable>,
right: Box<dyn Hittable>,
}
And so I have this recursive algorithm that almost works, if not for lifetime issues. My function looks like
pub fn new(objects: Vec<Box<dyn Hittable>>, start: usize, end: usize, t0: f64, t1: f64) -> Self {
let r = util::rand();
let comp = if r < 1. / 3. {
util::box_x_compare
} else if r < 2. / 3. {
util::box_y_compare
} else {
util::box_z_compare
}; // which axis to compare along (random for now)
let num_obj = end - start;
let mut left: Box<dyn Hittable>;
let mut right: Box<dyn Hittable>;
if num_obj == 1 {
left = objects[start];
right = objects[start];
} else if num_obj == 2 {
if comp(&&objects[start], &&objects[start + 1]) != Ordering::Greater {
left = objects[start];
right = objects[start + 1];
} else {
left = objects[start + 1];
right = objects[start];
}
} else {
let mut slice: Vec<&Box<dyn Hittable>> = Vec::new();
for i in start..end { // make a copy to sort
slice.push(&objects[i]);
}
slice.sort_by(comp);
let mid = start + num_obj / 2;
let l = BvhNode::new(objects, start, mid, t0, t1);
let r = BvhNode::new(objects, mid, end, t0, t1);
left = Box::new(l.clone());
right = Box::new(r.clone());
}
let left_box = left.get_bounding_box(t0, t1);
let right_box = right.get_bounding_box(t0, t1);
if left_box.is_none() || right_box.is_none() {
println!("Error: No bounding box in Bvh Node");
panic!();
}
Self { left, right, bounding_box: BoundingBox::new(Point3::origin(), Point3::origin()) }
}
First, I ran into some issues with trying to "move out of a vec", which I can't do, so I tried to implement Clone on all of the types that implement Hittable. It works for almost all of them, but my Triangle struct
pub struct Triangle <'b> {
mat: &'b Box<dyn Material>,
bounding_box: Option<BoundingBox>,
p: Vec<Point3<f64>>,
n: Vec<Vector3<f64>>,
uv: Vec<Vector2<f64>>,
}
contains reference to the material of its associated mesh, which clone doesn't like. I could make a clone of the Material itself, but it could potentially be very large and I don't want to have thousands of copies of it for each triangle in a mesh.
I feel like there has to be a better way to design my system, like get rid of references in Triangle so it can easily be copied. Once this structure is created, I won't need the Vec anymore, so being able to move the objects to out of it inside of copying them would also work, but I don't see a way to do that either.
In case it helps, the full file is on GitHub here
I was able to solve this by changing the objects Vec to &mut Vec<Box<dyn Hittable>> and then using objects.remove() instead of indexing into it. I had to change the algorithm slightly to handle the new way, but it should work.