• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ekarpp / lumo / 14235426927

03 Apr 2025 03:53AM UTC coverage: 72.653%. First build
14235426927

push

github

ekarpp
0.6.1

* Generalize thread pool
* Paralellize material parsing for scenes
* Implement variable wavelength index of refraction (and dispersion)
* Implement efficiency optimized russian roulette
* Add batching to samplers
* Restricts mediums to scene boundaries
* Fix cylinder bounding box
* Fix instance sample on normals
* Update github actions

290 of 821 new or added lines in 36 files covered. (35.32%)

6740 of 9277 relevant lines covered (72.65%)

39613243.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/renderer.rs
1
use crate::{
2
    formatting, rng::Xorshift, Vec2, Float, ToneMap, SamplerType
3
};
4
use crate::tracer::{
5
    Camera, Film, FilmSample,
6
    Integrator, Scene, FilmTile
7
};
8
use crate::pool::{Executor, ThreadPool};
9
use crate::math::vec2::UVec2;
10
use std::{
11
    sync::Arc, io::Write, time::{Duration, Instant}
12
};
13
use itertools::Itertools;
14

15
const TILE_SIZE: u64 = 16;
16
// should be a square for samplers
17
pub const SAMPLES_INCREMENT: u64 = 256;
18
const PROGRESS_BAR_LEN: usize = 16;
19

20
const DEFAULT_NUM_SAMPLES: u64 = 1;
21
const DEFAULT_THREADS: usize = 4;
22

23
mod task;
24

25
/// Configures the image to be rendered
26
pub struct Renderer {
27
    scene: Arc<Scene>,
28
    camera: Arc<Camera>,
29
    resolution: UVec2,
30
    num_samples: u64,
31
    integrator: Integrator,
32
    tone_map: ToneMap,
33
    sampler: SamplerType,
34
    threads: usize,
35
    seed: u64,
36
}
37

38
impl Renderer {
39
    /// Constructs a new renderer.
40
    pub fn new(mut scene: Scene, camera: Camera) -> Self {
×
41
        scene.build();
×
42
        assert!(scene.num_lights() != 0);
×
43

44
        let scene = Arc::new(scene);
×
45
        let camera = Arc::new(camera);
×
46

×
47
        let resolution = camera.get_resolution();
×
48
        let num_samples = DEFAULT_NUM_SAMPLES;
×
49

×
50
        let seed = crate::rng::gen_seed();
×
51

×
52
        Self {
×
53
            scene,
×
54
            camera,
×
55
            resolution,
×
56
            num_samples,
×
57
            seed,
×
58
            threads: DEFAULT_THREADS,
×
59
            sampler: SamplerType::default(),
×
60
            integrator: Integrator::default(),
×
61
            tone_map: ToneMap::default(),
×
62
        }
×
63
    }
×
64

65
    /// Sets the tone mapping algorithm used
66
    pub fn tone_map(mut self, tone_map: ToneMap) -> Self {
×
67
        self.tone_map = tone_map;
×
68
        self
×
69
    }
×
70

71
    /// Sets number of samples per pixel
72
    pub fn samples(mut self, samples: u64) -> Self {
×
73
        self.num_samples = samples;
×
74
        self
×
75
    }
×
76

77
    /// Sets the integrator used to render the image
78
    pub fn integrator(mut self, integrator: Integrator) -> Self {
×
79
        self.integrator = integrator;
×
80
        self
×
81
    }
×
82

83
    /// Set the seed used for random number generation
84
    pub fn seed(mut self, seed: u64) -> Self {
×
85
        self.seed = seed;
×
86
        self
×
87
    }
×
88

89
    /// Set the sampler used in rendering
90
    pub fn sampler(mut self, sampler: SamplerType) -> Self {
×
91
        self.sampler = sampler;
×
92
        self
×
93
    }
×
94

95
    /// Sets the number of threads used for rendering
96
    pub fn threads(mut self, threads: usize) -> Self {
×
97
        self.threads = threads;
×
98
        self
×
99
    }
×
100

101
    fn output_begin(&self, film: &Film) {
×
102
        #[cfg(debug_assertions)]
×
103
        println!("Debug assertions enabled");
×
104

105
        if matches!(self.integrator, Integrator::BDPathTrace)
×
106
            && self.scene.medium.is_some() {
×
107
                println!("Volumetric mediums not currently supported with BDPT, \
×
108
                          rendering anyways");
×
109
            }
×
110
        println!("Starting to render the scene:\n\
×
111
                  \t Resolution: {} x {}\n\
×
112
                  \t Samples: {}\n\
×
113
                  \t Shadow rays: {}\n\
×
114
                  \t Integrator: {}\n\
×
115
                  \t Primitives: {}\n\
×
116
                  \t Lights: {}\n\
×
117
                  \t Sampler: {}\n\
×
118
                  \t Tone map: {}\n\
×
119
                  \t Film: [{}] \n\
×
120
                  \t Seed: {}\n\
×
121
                  \t Threads: {}",
×
122
                 self.resolution.x, self.resolution.y,
123
                 self.num_samples,
124
                 if matches!(self.integrator, Integrator::BDPathTrace) {
×
125
                     1
×
126
                 } else {
127
                     self.scene.num_shadow_rays()
×
128
                 },
129
                 self.integrator,
130
                 self.scene.num_primitives(),
×
131
                 self.scene.num_lights(),
×
132
                 self.sampler,
×
133
                 self.tone_map,
×
134
                 film,
×
135
                 self.seed,
×
136
                 self.threads,
×
137
        );
×
138
    }
×
139

140
    fn output_progress(
×
141
        &self,
×
142
        rays_traced: u64,
×
143
        rays_total: u64,
×
144
        dt: Duration,
×
145
    ) {
×
146
        let bar_step = rays_total / PROGRESS_BAR_LEN as u64;
×
147
        let steps = rays_traced / bar_step;
×
148
        let prog = rays_total as Float / rays_traced as Float;
×
149
        print!("\r{}", " ".repeat(PROGRESS_BAR_LEN + 32));
×
150
        print!("\r[{}{}] (~{} left)",
×
151
               "+".repeat(steps as usize),
×
152
               "-".repeat(PROGRESS_BAR_LEN - steps as usize),
×
153
               formatting::fmt_elapsed(dt.mul_f64(prog as f64 - 1.0)),
×
154
        );
×
155
        let _ = std::io::stdout().flush();
×
156
    }
×
157

158
    /// Starts the rendering process and returns the rendered image
159
    pub fn render(&mut self) -> Film {
×
160
        let start = Instant::now();
×
161

×
162
        let mut film = self.camera.create_film(self.num_samples);
×
163
        self.output_begin(&film);
×
164

×
NEW
165
        let mut rng = Xorshift::new(self.seed);
×
NEW
166
        let executor = task::RenderTaskExecutor::new(
×
167
            Arc::clone(&self.camera),
×
168
            Arc::clone(&self.scene),
×
NEW
169
            self.sampler.clone(),
×
NEW
170
            self.integrator.clone(),
×
NEW
171
            self.tone_map.clone(),
×
NEW
172
        );
×
NEW
173

×
NEW
174
        let pool = ThreadPool::new(
×
NEW
175
            self.threads,
×
NEW
176
            executor,
×
177
        );
×
178

×
179
        let tiles_x = self.resolution.x.div_ceil(TILE_SIZE);
×
180
        let tiles_y = self.resolution.y.div_ceil(TILE_SIZE);
×
181

×
182
        let mut samples_taken = 0;
×
183
        while samples_taken < self.num_samples {
×
184
            let prev = samples_taken;
×
NEW
185
            let batch = samples_taken / SAMPLES_INCREMENT;
×
186
            samples_taken += SAMPLES_INCREMENT;
×
187
            samples_taken = samples_taken.min(self.num_samples);
×
188
            let samples = samples_taken - prev;
×
189

×
190
            (0..tiles_y).cartesian_product(0..tiles_x).for_each(|(y, x): (u64, u64)| {
×
191
                let px_min = UVec2::new(x, y) * TILE_SIZE;
×
192
                let px_max = (px_min + TILE_SIZE).min(self.resolution);
×
193
                let tile = film.create_tile(px_min, px_max);
×
194

×
NEW
195
                let task = task::RenderTask::new(
×
NEW
196
                    tile,
×
NEW
197
                    batch,
×
NEW
198
                    samples,
×
NEW
199
                    self.num_samples,
×
NEW
200
                    rng.gen_u64()
×
NEW
201
                );
×
202
                pool.publish(task);
×
203
            });
×
204
        }
×
205

206
        pool.all_published();
×
207

×
NEW
208
        let camera_rays_total = self.num_samples
×
209
            * self.resolution.x
×
210
            * self.resolution.y;
×
NEW
211
        let mut camera_rays_traced = 0;
×
NEW
212
        let mut rays_total = 0;
×
213
        let mut finished = 0;
×
214
        let mut tiles_added = 0;
×
215
        while finished < self.threads {
×
216
            let result = pool.pop_result();
×
NEW
217
            if let Some(result) = result {
×
NEW
218
                let tile = result.tile;
×
219
                film.add_tile(tile);
×
NEW
220
                camera_rays_traced += result.num_camera_rays;
×
NEW
221
                rays_total += result.num_rays;
×
222
                tiles_added += 1;
×
223
                let output = tiles_added % self.resolution.x == 0
×
224
                    || tiles_added == self.threads as u64;
×
225
                if output {
×
NEW
226
                    self.output_progress(
×
NEW
227
                        camera_rays_traced,
×
NEW
228
                        camera_rays_total,
×
NEW
229
                        start.elapsed()
×
NEW
230
                    );
×
231
                }
×
232
            } else {
×
233
                finished += 1;
×
234
            }
×
235
        }
236

NEW
237
        println!("\rFinished rendering in {} ({} camera rays, {} total rays)",
×
238
                 formatting::fmt_elapsed(start.elapsed()),
×
NEW
239
                 formatting::fmt_si(camera_rays_total),
×
NEW
240
                 formatting::fmt_si(rays_total)
×
241
        );
×
242

×
243
        film
×
244
    }
×
245
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc