19 ms·
Writing a small ray tracer in Rust and Zig
- olodus 7y agoI really don't think these languages should be set opposite to one another as much as they do. I mean, I get why they are. They take up almost the same position and have quite different ways to view security and lang design. And they both try to grow and compete. But still. I think there are some space for them both. I would really think it would be cool to spend my working hours programming Rust for safety and switching to Zig whenever I have to code an unsafe block (all of it compiling to webasm :o )
- flohofwoe 7y agoIn my mind/opinion, Rust is a potential replacement for C++, while Zig is a potential replacement for C, and both have their place in the world. Rust feels more restrictive, but that may be the right approach for building large software projects with big, "diverse" (in terms of skill level) teams. Zig is smaller and feels more nimble, and might be better suited for smaller teams working on smaller projects, and for getting results faster, while avoiding most of C's darker corners.
- oconnor663 7y ago> But rendering in separate threads turned out to be (unsurprisingly) harder than the way I would do it in C++...It was a bit frustrating to figure out how to accomplish this. Googling yielded a few stack overflow posts with similar questions, and were answered by people basically saying use my crate! Based on some discussion in r/rust (https://www.reddit.com/r/rust/comments/c7t5za/writing_a_small_ray_tracer_in_rust_and_zig/ https://www.reddit.com/r/rust/comments/c7t5za/writing_a_smal...) I went ahead and added a Rayon-based answer to that SO question (https://stackoverflow.com/a/56840441/823869 https://stackoverflow.com/a/56840441/823869). That's been the de facto standard for data parallelism in Rust for the last few years. But the article highlights that discovering the de facto standards is still a challenge for new Rust users -- does anyone know of a well-maintained list of the 10-20 most critical crates that new users should familiarize themselves with after reading The Book? Things like Rayon and lazy_static. The ranked search results at https://crates.io/crates?sort=recent-downloads https://crates.io/crates?sort=recent-downloads are almost good enough, but they include a lot of transitive dependencies that new users shouldn't care about. (I.e. `regex` is a very important crate, but `aho-corasick` is usually only downloaded as a dependency of `regex`.)
- steveklabnik 7y agohttps://rust-lang-nursery.github.io/rust-cookbook/ https://rust-lang-nursery.github.io/rust-cookbook/ is sorta kinda this, sorta
- oconnor663 7y agoOh nice.
- OskarS 7y agoI will say, the one example they have there which is sort-of analogous to "render each pixel of this image in parallel" is the "draw a julia set" one [0], and it's a very bad way of convincing a C/C++ programmer that Rust is good at this sort of thing. Even if the "loop over all rows in the main thread, adding to the pool a lambda that loops over each column" is somehow optimized in a good data-parallel way (I doubt it compares favorably performance-wise with "#pragma omp parallel for"), the lambdas then push each finished pixel into a channel along with their coordinates. The main thread then has to literally loop through every pixel and read from the channel for each and every one. The natural way to do that in C/C++ is to just write the pixel to the bitmap in each thread. There are no race conditions here (everything is embarassingly parallel), just write the resulting pixel to the bitmap and be done with it. The only reason to have that channel with all that overhead (and that final synchronization on the main thread) is to satisfy the borrow checker, which is just silly in this case. It adds a tremendous amount of overhead just to make it idiomatic Rust. It's true that you can do it the "C++ way" in Rust using unsafe and raw pointers (and there's probably crates that can do the "parallel for" in a way that compares well with OpenMP), but as a graphics programmer who's done a lot of this sort of thing, that piece of code made a very bad first impression of Rust as a high-performance language. EDIT: also, the description is wrong. It says: "ThreadPool::execute receives each pixel as a separate job". No it doesn't, it receives each scanline as a separate job. It might be better if the pool had each pixel as a separate job, but that's not what the code is doing. [0]: https://rust-lang-nursery.github.io/rust-cookbook/concurrency/threads.html#draw-fractal-dispatching-work-to-a-thread-pool https://rust-lang-nursery.github.io/rust-cookbook/concurrenc...
- keyle 7y agoThat was a really fun read... Now if the author could do it in nim and v-lang...
- tntn 7y ago> I wrapped my objects in atomic reference counters, and wrapped my pixel buffer in a mutex Rust people, is there a way to tell the compiler that each thread gets its own elements? Do you really have to either (unnecessarily) add a lock or reach for unsafe?
- saagarjha 7y agoI wonder if there’s a way to borrow noncontiguous slices.
- sitkack 7y agounsafe is the tool u are looking for.
- saagarjha 7y ago:(
- sitkack 7y agoAll of Rust's safe abstractions are on top of unsafe. It isn't a bad thing, it just need to be used with rigor. Splitting a single slice into two mutable slices is done via https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut https://doc.rust-lang.org/std/primitive.slice.html#method.sp... if you want more than that, you will need to roll your own. I think it would be a great exercise to implement what you are asking for, the docs link directly to the source. https://doc.rust-lang.org/src/core/slice/mod.rs.html#991-1001 https://doc.rust-lang.org/src/core/slice/mod.rs.html#991-100...
- Tuna-Fish 7y agoNo, there are plenty of safe ways to achieve this in the standard library. The chunks and split families of functions on slices are all designed to do pretty much exactly this.
- 7y ago
- forrestthewoods 7y agoWhat a delightful post. Author wrote a very nice description of things they learned from a little weekend project. No preaching or ranting or opinionating. Just “I did a thing and here’s what I learned”. That’s easily my favorite type of blog post. Thanks for sharing! ️
- skrebbel 7y agoThis is an awesome post. At the risk of shedding it to bikes, one point that the author makes is that Zig's lack of operator overloading makes him write vector math like this: if (discriminant > 0.0) { // I stared at this monster for a while to ensure I got it right return uv.sub(n.mul(dt)).mul(ni_over_nt).sub(n.mul(math.sqrt(discriminant))); } He signs off with: > How do C programmers manage? The answer is simple: we assign names to intermediate results. Now, I have absolutely no idea what that expression computes, because I suck at graphics programming and math in general. Please pretend that these are proper mathy terms: if (discriminant > 0.0) { const banana = uv.sub(n.mul(dt)) const apple = banana.mul(ni_over_nt) const pear = n.mul(math.sqrt(discriminant) return apple.sub(pear) } I'm convinced that there's a proper mathy/lighting-y word for each component in that expression. Of course this approach totally breaks down if you're copying expressions from papers or articles without understanding why they are correct (which is how I do all my graphics programming). I do find that naming variables is often a great way to force myself to grok what's going on.
- geokon 7y agoIn my experience writing math code the intermediary values get quite goofy Ex: orthogonal-vector-to-plane-bisecting-input-vector-and-first-column-vector But I rather use crazy descriptive names than hiding it away. Otherwise it gets quite incomprehensible when you reread it 3 months later Anyone else hit this problem? I suspect most people just reference a paper or book and use the letters to match the source ( x/y/n/m/etc. )
- skrebbel 7y agoYep, agree! I think extremely long variable names (when used locally), if necessary, are wildly underrated. I mean, we all know horror stories like SimpleBeanFactoryAwareAspectInstanceFactory, but they are really design problems much more than naming problems. They gave long variables a bad rep, undeservedly so. Inside an expression, names like that truly do wonders.
- dagw 7y agoI suspect most people just reference a paper or book and use the letters to match the source ( x/y/n/m/etc. ) If you do this (and I'll certainly admit that I do this as well) make sure you link to the paper in question somewhere in either comments or the documentation so that future developers can find the canonical reference to what the variables mean.
- MrGilbert 7y agoReally cool post. I always dreamed of writing a small realtime, raytraced game, with lights etc. Basically a roguelike in 3D. I never managed to finish it, and this project reminds me of it.
- gopuseyuj 7y agoi earn $400 hourly on the internet. She has been with out artwork for five months however final month her charge emerge as $12747 really on foot on the internet for some hours. study greater on this net internet sit...www.prizebest.com
- mrec 7y agoI didn't quite grok this bit: > The ability to return values from if expressions and blocks is awesome and I don’t know how I’ve managed to live up until now without it. Instead of conditionally assigning to a bunch of variables it is better to return them from an if expression instead. The example shown (aside from the fact it's assigning a tuple, which is a different point) would naturally be a ternary in C/C++. Does the awesomeness kick in for more complicated examples where you want intermediate vars etc in the two branches?
- monocasa 7y agoYeah, exactly. YOu know how it becomes a pain to make a const variable that needs some setup in C and C++? This lets you const all the things.
- flohofwoe 7y agoYes, anything which is more complex than a very simple if-else. The advantage is even more obvious with switch-expressions, e.g.: https://ziglang.org/documentation/master/#switch https://ziglang.org/documentation/master/#switch https://doc.rust-lang.org/reference/expressions/match-expr.html https://doc.rust-lang.org/reference/expressions/match-expr.h...
- gameswithgo 7y agoThere are lots of little nice things: * you can more easily handle more than 2 options * you can more easily have a bunch of code in each case * you don't have a new syntax for the compiler or programmer to have to know about, everything is just consistently an expression It is nice, and those of us who say that are usually aware of various alternative workarounds with ternaries etc.
- jorangreef 7y agoZig is worth supporting on Patreon: https://www.patreon.com/andrewrk https://www.patreon.com/andrewrk