Skip to content

Commit 5caac0f

Browse files
authored
docs: add codegen examples (#14)
* docs: add codegen examples * more instructions * update benchmark instruction
1 parent 871893c commit 5caac0f

File tree

10 files changed

+285
-20
lines changed

10 files changed

+285
-20
lines changed

Cargo.lock

Lines changed: 56 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
1-
[package]
2-
name = "raytracer"
3-
version = "0.1.0"
4-
authors = ["Alex Chi <[email protected]>"]
5-
edition = "2018"
1+
[workspace]
62

7-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8-
9-
[dependencies]
10-
image = "0.23"
11-
indicatif = "0.15"
12-
threadpool = "1.8"
13-
imageproc = "0.21"
14-
rusttype = "0.9"
3+
members = [
4+
"raytracer",
5+
"raytracer_codegen",
6+
]

README.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,25 @@ TA Wenxin Zheng 会有更详细的说明。
2929
* **Track 1: New Features** 完成 Rest of Your Life 的剩余部分,重构代码并渲染带玻璃球的 Cornell Box。
3030
* **Track 2: More Features** 完成 Next Week 中除 Motion Blur 外的部分,渲染噪点较少的最终场景。
3131
* **Track 3: Reduce Contention** 此项工作的前提条件是完成多线程渲染。在多线程环境中,clone / drop Arc 可能会导致性能下降。因此,我们要尽量减少 Arc 的使用。这项任务的目标是,仅在线程创建的时候 clone Arc;其他地方不出现 Arc,将 Arc 改为引用。
32-
* **Track 4: Static Dispatch** 调用 `Box<dyn trait>` / `Arc<dyn trait>` / `&dyn trait` 中的函数时会产生额外的开销。我们可以通过泛型来解决这个问题。这个任务的目标是,定义新的泛型材质、变换和物体,仅在 `HitRecord`, `ScatterRecord` (这个在 Rest of Your Life 的剩余部分中出现), `HittableList``BVHNode` 中使用 `dyn`
33-
* **Track 5: Code Generation** 此项工作的前提条件是完成 BVH。目前,`BVHNode` 是在运行时构造的。这个过程其实可以在编译期完成。我们可以通过过程宏生成所有的物体,并构造静态的 `BVHNode`,从而提升渲染效率。
32+
* **Track 4: Static Dispatch** 调用 `Box<dyn trait>` / `Arc<dyn trait>` / `&dyn trait` 中的函数时会产生额外的开销。我们可以通过泛型来解决这个问题。
33+
* 这个任务的目标是,通过定义新的泛型材质、变换和物体,比如 `LambertianStatic<T>`,并在场景中使用他们,从而减少动态调用的开销。你也可以另开一个模块定义和之前的材质同名的 struct。
34+
* 你可以在 `material.rs` 里找到泛型的相关用法。
35+
* 仅在 `HitRecord`, `ScatterRecord` (这个在 Rest of Your Life 的剩余部分中出现), `HittableList``BVHNode` 中使用 `dyn`
36+
* 如果感兴趣,可以探索如何使用 `macro_rules` 来减少几乎相同的代码写两遍的冗余。
37+
* **Track 5: Code Generation** 此项工作的前提条件是完成 BVH。
38+
* 目前,`BVHNode` 是在运行时构造的。这个过程其实可以在编译期完成。我们可以通过过程宏生成所有的物体,并构造静态的 `BVHNode`,从而提升渲染效率。
39+
* 为了使用过程宏,在这个工程中,我们已经重新组织了目录结构。请参考[这个 PR](https://github.com/skyzh/raytracer-tutorial/pull/14)进行修改。
40+
* 你可以使用 `cargo expand` 来查看过程宏处理过后的代码。你也可以在编译过程中直接输出过程宏生成的代码。
41+
* `codegen` 部分不需要通过 clippy。
42+
* 如果感兴趣,你也可以探索给过程宏传参的方法。e.g. 通过 `make_spheres_impl! { 100 }` 生成可以产生 100 个球的函数。
3443
* **Track 6: PDF Static Dispatch** 此项工作的前提条件是完成 Rest of your Life 的剩余部分。PDF 中需要处理的物体使用泛型完成,去除代码路径中的 `&dyn`
35-
* **Track 7: More Code Generation** 在过程宏中,读取文件,直接从 yaml 或 JSON 文件生成场景对应的程序。在 `data` 文件夹中给出了一些例子。
44+
* **Track 7: More Code Generation** 在过程宏中,读取文件,直接从 yaml 或 JSON 文件生成场景对应的程序。
45+
*`data` 文件夹中给出了一些例子。
46+
* 例子中 `BVHNode` 里的 `bounding_box` 是冗余数据。你可以不使用这个数据。
3647
* **Track 8: Advanced Features** 增加对 Transform 的 PDF 支持。
37-
* 完成 Track 3 前请备份代码 (比如记录 git 的 commit id)。完成 Track 4, 5, 6 时请保留原先的场景和程序,在此基础上添加新的内容。完成后编写 benchmark,对比修改前后效率的提升。你可以使用 `criterion` crate 做 benchmark。benchmark 的内容可以是往构造好的场景中随机打光线,记录打一条光线所需的时间。
48+
* 如果你有多余的时间,你可以通过 benchmark 来测试实现功能前后的区别。
49+
* 完成 Track 3 前请备份代码 (比如记录 git 的 commit id)。完成 Track 4, 5, 6 时请保留原先的场景和程序,在此基础上添加新的内容。
50+
* 你可以使用 `criterion` crate 做 benchmark。benchmark 的内容可以是往构造好的场景中随机打光线,记录打一条光线所需的时间。
3851

3952
## More Information
4053

raytracer/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "raytracer"
3+
version = "0.1.0"
4+
authors = ["Alex Chi <[email protected]>"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
image = "0.23"
11+
indicatif = "0.15"
12+
threadpool = "1.8"
13+
imageproc = "0.21"
14+
rusttype = "0.9"
15+
raytracer_codegen = { path = "../raytracer_codegen" }

src/main.rs renamed to raytracer/src/main.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
#[allow(clippy::float_cmp)]
1+
#![allow(clippy::float_cmp)]
2+
#![feature(box_syntax)]
3+
4+
mod material;
5+
mod scene;
26
mod vec3;
7+
38
use image::{ImageBuffer, Rgb, RgbImage};
49
use indicatif::ProgressBar;
510
use rusttype::Font;
11+
use scene::example_scene;
612
use std::sync::mpsc::channel;
713
use std::sync::Arc;
814
use threadpool::ThreadPool;
915
pub use vec3::Vec3;
1016

1117
const AUTHOR: &str = "Alex Chi";
1218

13-
struct World {
19+
pub struct World {
1420
pub height: u32,
1521
}
1622

@@ -82,7 +88,7 @@ fn main() {
8288
let bar = ProgressBar::new(n_jobs as u64);
8389

8490
// use Arc to pass one instance of World to multiple threads
85-
let world = Arc::new(World { height });
91+
let world = Arc::new(example_scene());
8692

8793
for i in 0..n_jobs {
8894
let tx = tx.clone();

raytracer/src/material.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#![allow(dead_code)]
2+
#![allow(clippy::boxed_local)]
3+
// You SHOULD remove above line in your code.
4+
5+
// This file shows necessary examples of how to complete Track 4 and 5.
6+
7+
pub trait Texture {}
8+
pub trait Material {}
9+
10+
/// `Lambertian` now takes a generic parameter `T`.
11+
/// This reduces the overhead of using `Box<dyn Texture>`
12+
#[derive(Clone)]
13+
pub struct Lambertian<T: Texture> {
14+
pub albedo: T,
15+
}
16+
17+
impl<T: Texture> Lambertian<T> {
18+
pub fn new(albedo: T) -> Self {
19+
Self { albedo }
20+
}
21+
}
22+
23+
impl<T: Texture> Material for Lambertian<T> {}
24+
25+
pub trait Hitable {}
26+
pub struct AABB;
27+
28+
/// This BVHNode should be constructed statically.
29+
/// You should use procedural macro to generate code like this:
30+
/// ```
31+
/// let bvh = BVHNode::construct(
32+
/// box BVHNode::construct(
33+
/// box Sphere { .. }
34+
/// box Sphere { .. }
35+
/// ),
36+
/// box BVHNode::construct(
37+
/// box Sphere { .. }
38+
/// box Sphere { .. }
39+
/// )
40+
/// )
41+
/// ```
42+
/// And you can put that `bvh` into your `HittableList`.
43+
pub struct BVHNode<L: Hitable, R: Hitable> {
44+
left: Box<L>,
45+
right: Box<R>,
46+
bounding_box: AABB,
47+
}
48+
49+
impl<L: Hitable, R: Hitable> BVHNode<L, R> {
50+
pub fn construct(_left: Box<L>, _right: Box<R>) -> Self {
51+
unimplemented!()
52+
}
53+
}

raytracer/src/scene.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#![allow(dead_code)]
2+
// You SHOULD remove above line in your code.
3+
4+
use crate::Vec3;
5+
use crate::World;
6+
use raytracer_codegen::make_spheres_impl;
7+
8+
// Call the procedural macro, which will become `make_spheres` function.
9+
make_spheres_impl! {}
10+
11+
// These three structs are just written here to make it compile.
12+
// You should `use` your own structs in this file.
13+
// e.g. replace next two lines with
14+
// `use crate::materials::{DiffuseLight, ConstantTexture}`
15+
pub struct ConstantTexture(Vec3);
16+
pub struct DiffuseLight(ConstantTexture);
17+
18+
pub struct Sphere {
19+
center: Vec3,
20+
radius: f64,
21+
material: DiffuseLight,
22+
}
23+
24+
pub fn example_scene() -> World {
25+
let mut spheres: Vec<Box<Sphere>> = make_spheres(); // Now `spheres` stores two spheres.
26+
let mut hittable_list = vec![];
27+
// You can now add spheres to your own world
28+
hittable_list.append(&mut spheres);
29+
30+
hittable_list.clear();
31+
World { height: 512 }
32+
}
File renamed without changes.

raytracer_codegen/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "raytracer_codegen"
3+
version = "0.1.0"
4+
authors = ["Alex Chi <[email protected]>"]
5+
edition = "2018"
6+
7+
[lib]
8+
proc-macro = true
9+
10+
[dependencies]
11+
rand = { version = "0.7", features = ["small_rng"] }
12+
quote = "1.0"
13+
syn = "1.0"
14+
proc-macro2 = "1.0"

raytracer_codegen/src/lib.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#![allow(clippy::all)]
2+
3+
extern crate proc_macro;
4+
use proc_macro2::TokenStream;
5+
use quote::quote;
6+
7+
/// You can replace this `Vec` with your own version, e.g. the one
8+
/// you wrote in raytracer, if you want.
9+
#[derive(Copy, Clone, Debug)]
10+
struct Vec3(f64, f64, f64);
11+
12+
/// You can replace this `Sphere` with your own version, e.g. the one
13+
/// you wrote in raytracer, if you want.
14+
#[derive(Copy, Clone, Debug)]
15+
struct Sphere {
16+
pub pos: Vec3,
17+
pub size: f64,
18+
pub color: Vec3,
19+
}
20+
21+
/// This function generates code for one Sphere.
22+
/// Enable `box` feature in `main.rs` with `#![feature(box_syntax)]`,
23+
/// or replace `box` with `Box::new`
24+
fn sphere_code(sphere: &Sphere) -> TokenStream {
25+
let Vec3(x, y, z) = sphere.pos;
26+
let Vec3(r, g, b) = sphere.color;
27+
let size = sphere.size * 0.9;
28+
29+
// Create a code snippet of `Sphere`.
30+
// Note that the `Sphere` in `quote` is the one in your ray tracer,
31+
// not the one you defined in this module.
32+
quote! {
33+
box Sphere {
34+
center: Vec3::new(#x, #y, #z),
35+
radius: #size,
36+
material: DiffuseLight(
37+
ConstantTexture(
38+
Vec3::new(#r, #g, #b)
39+
)
40+
)
41+
}
42+
}
43+
}
44+
45+
/// This function generates the final `make_spheres` function.
46+
#[proc_macro]
47+
pub fn make_spheres_impl(_item: proc_macro::TokenStream) -> proc_macro::TokenStream {
48+
let spheres = vec![
49+
&Sphere {
50+
pos: Vec3(0.0, 0.0, 0.0),
51+
size: 1.0,
52+
color: Vec3(1.0, 1.0, 1.0),
53+
},
54+
&Sphere {
55+
pos: Vec3(0.0, 0.0, 0.0),
56+
size: 1.0,
57+
color: Vec3(1.0, 1.0, 1.0),
58+
},
59+
];
60+
61+
let mut tokens = vec![];
62+
63+
for sphere in spheres {
64+
let sc = sphere_code(sphere);
65+
tokens.push(quote! {
66+
spheres.push(#sc);
67+
});
68+
}
69+
70+
let x_final: TokenStream = tokens.into_iter().collect();
71+
72+
let result = proc_macro::TokenStream::from(quote! {
73+
fn make_spheres() -> Vec<Box<Sphere>> {
74+
let mut spheres = vec![];
75+
#x_final
76+
spheres
77+
}
78+
});
79+
80+
// uncomment this statement if you want to inspect result
81+
// println!("{}", result);
82+
83+
result
84+
}

0 commit comments

Comments
 (0)