lumenpyx/
lib.rs

1use glium;
2use glium::glutin::surface::WindowSurface;
3use glium::implement_vertex;
4use glium::Frame;
5use glium::Surface;
6use parley::fontique::Collection;
7use parley::FontContext;
8use parley::LayoutContext;
9use primitives::Texture;
10use swash::scale::ScaleContext;
11/// This module contains all the window and display setup functions
12pub use winit;
13use winit::event_loop::EventLoop;
14/// This module contains all the objects that can be drawn in the program
15pub mod primitives;
16/// This module contains the full screen quad for custom shaders
17pub mod shaders;
18use shaders::*;
19/// This module contains all the objects that can be drawn in the program
20/// As well as containing the trait that all drawable objects must implement
21pub mod drawable_object;
22use drawable_object::*;
23use rustc_hash::FxHashMap;
24pub mod animation;
25pub mod blending;
26/// This module contains all the lights that can be used in the program
27/// As well as containing the trait that all lights must implement
28pub mod lights;
29pub mod text;
30
31const HANDLE_STRING_ID: &str = "wdAYG8&DWtyiwDhukhjwda";
32
33// include the whole lumenpyx.wiki folder into the documentation
34#[doc = include_str!("../lumenpyx wiki/Home.md")]
35#[doc = include_str!("../lumenpyx wiki/Common-problems-and-their-solutions.md")]
36#[doc = include_str!("../lumenpyx wiki/Creating-custom-drawable-objects.md")]
37#[doc = include_str!("../lumenpyx wiki/Creating-Custom-Lights.md")]
38#[doc = include_str!("../lumenpyx wiki/Creating-Custom-Renderables.md")]
39#[doc = include_str!("../lumenpyx wiki/Rendering-a-Sprite.md")]
40#[doc = include_str!("../lumenpyx wiki/Technical-Documentation.md")]
41
42pub(crate) const DEFAULT_BEHAVIOR: glium::uniforms::SamplerBehavior =
43    glium::uniforms::SamplerBehavior {
44        minify_filter: glium::uniforms::MinifySamplerFilter::Nearest,
45        magnify_filter: glium::uniforms::MagnifySamplerFilter::Nearest,
46        max_anisotropy: 1,
47        wrap_function: (
48            glium::uniforms::SamplerWrapFunction::Mirror,
49            glium::uniforms::SamplerWrapFunction::Mirror,
50            glium::uniforms::SamplerWrapFunction::Mirror,
51        ),
52        depth_texture_comparison: None,
53    };
54
55/// The debug option is used to determine what to display on the screen
56pub enum DebugOption {
57    /// Display the final image
58    None,
59    /// Display the albedo texture
60    Albedo,
61    /// Display the height texture
62    Height,
63    /// Display the roughness texture
64    Roughness,
65    /// Display the normal texture
66    Normal,
67    /// Display the internal shadow strength texture
68    ShadowStrength,
69}
70
71impl Default for DebugOption {
72    fn default() -> Self {
73        DebugOption::None
74    }
75}
76
77#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
78pub struct TextureHandle {
79    id: u32,
80}
81
82/// The main struct that contains the window and display
83pub struct LumenpyxProgram {
84    /// The window that the program is running in (this is a winit window)
85    pub window: winit::window::Window,
86    /// The display that the program is running in
87    pub display: glium::Display<WindowSurface>,
88    /// The indices for the program (there are no indices, but glium requires this to be here)
89    pub indices: glium::index::NoIndices,
90    shaders: FxHashMap<String, glium::Program>,
91    cache: LumenpyxCache,
92    next_texture_id: u32,
93    dimensions: [u32; 2],
94    pub debug: DebugOption,
95    pub render_settings: RenderSettings,
96    font_context: Option<FontContext>,
97    scale_context: Option<ScaleContext>,
98    layout_context: Option<LayoutContext>,
99}
100
101impl LumenpyxProgram {
102    /// Create a new program with the given resolution and name
103    pub fn new(resolution: [u32; 2], name: &str) -> (LumenpyxProgram, EventLoop<()>) {
104        let (event_loop, window, display, indices) = setup_program();
105        event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
106
107        let mut program = LumenpyxProgram {
108            window,
109            display,
110            indices,
111            shaders: FxHashMap::default(),
112            next_texture_id: 0,
113            cache: LumenpyxCache::default(),
114            dimensions: resolution,
115            debug: DebugOption::None,
116            render_settings: RenderSettings {
117                shadows: true,
118                reflections: true,
119                render_resolution: None,
120                blur_reflections: false,
121                blur_strength: 0.01,
122            },
123            font_context: None,
124            scale_context: None,
125            layout_context: None,
126        };
127
128        program.set_name(name);
129
130        shaders::load_all_system_shaders(&mut program);
131
132        (program, event_loop)
133    }
134
135    /// Add a shader to the program with the given name
136    pub fn add_shader(&mut self, program: glium::Program, name: &str) {
137        self.shaders.insert(name.to_string(), program);
138    }
139
140    /// Get a shader from the program with the given name
141    pub fn get_shader(&self, name: &str) -> Option<&glium::Program> {
142        self.shaders.get(name)
143    }
144
145    /// Add a texture to the program with the given name
146    pub fn add_texture(&mut self, texture: glium::texture::Texture2d, name: &str) {
147        self.cache.insert(name.to_string(), texture);
148    }
149
150    /// Get a texture from the program with the given name
151    pub fn get_texture(&self, name: &str) -> Option<&glium::texture::Texture2d> {
152        self.cache.get_texture(name)
153    }
154
155    /// Get a texture from a texture handle
156    pub fn get_texture_from_handle(
157        &self,
158        handle: &TextureHandle,
159    ) -> Option<&glium::texture::Texture2d> {
160        self.cache
161            .get_texture(&format!("{}_{}", HANDLE_STRING_ID, handle.id))
162    }
163
164    /// Remove a shader from the program
165    pub fn remove_shader(&mut self, name: &str) {
166        self.shaders.remove(name);
167    }
168
169    /// Set the name of the window
170    pub fn set_name(&mut self, name: &str) {
171        self.window.set_title(name);
172    }
173
174    /// Set the debug option of the program
175    pub fn set_debug(&mut self, debug: DebugOption) {
176        self.debug = debug;
177    }
178
179    /// Set the render settings of the program
180    pub fn set_render_settings(&mut self, settings: RenderSettings) {
181        self.render_settings = settings;
182    }
183
184    /// Set the resolution of the program
185    pub fn set_resolution(&mut self, resolution: [u32; 2]) {
186        self.dimensions = resolution;
187    }
188
189    /// Get the resolution of the program
190    pub fn get_resolution(&self) -> [u32; 2] {
191        self.dimensions
192    }
193
194    /// run the program with the given update function
195    pub fn run<F>(&mut self, event_loop: EventLoop<()>, mut update: F)
196    where
197        F: FnMut(&mut Self),
198    {
199        event_loop
200            .run(move |ev, window_target| match ev {
201                winit::event::Event::WindowEvent { event, .. } => match event {
202                    winit::event::WindowEvent::CloseRequested => {
203                        window_target.exit();
204                    }
205                    winit::event::WindowEvent::Resized(physical_size) => {
206                        self.display.resize(physical_size.into());
207                    }
208                    winit::event::WindowEvent::RedrawRequested => {
209                        update(self);
210                    }
211                    _ => (),
212                },
213                winit::event::Event::AboutToWait => {
214                    // RedrawRequested will only when we resize the window, so we need to manually
215                    // request it.
216                    self.window.request_redraw();
217                }
218                _ => (),
219            })
220            .expect("Failed to run event loop");
221    }
222
223    pub fn get_dimensions(&self) -> [u32; 2] {
224        self.dimensions
225    }
226
227    pub(crate) fn get_render_resolution(&self) -> [u32; 2] {
228        self.render_settings
229            .render_resolution
230            .unwrap_or(self.dimensions)
231    }
232
233    pub(crate) fn adjust_transform_for_drawable(
234        &self,
235        mut transform: &Transform,
236        camera: &Camera,
237    ) -> Transform {
238        let render_resolution = self.get_render_resolution();
239        let mut new_transform = transform.clone();
240
241        let mut scale = transform.get_scale();
242        // scale off the resolution
243        if render_resolution[0] > render_resolution[1] {
244            scale[0] *= render_resolution[1] as f32 / render_resolution[0] as f32;
245        } else {
246            scale[1] *= render_resolution[0] as f32 / render_resolution[1] as f32;
247        }
248        new_transform.set_scale(scale[0], scale[1], scale[2]);
249
250        let (mut x, mut y, z) = (transform.get_x(), transform.get_y(), transform.get_z());
251        // adjust off the camera no need to translate the z, it would just mess up the height map's interaction with the light
252        x -= camera.position[0];
253        y -= camera.position[1];
254
255        x /= render_resolution[0] as f32;
256        y /= render_resolution[1] as f32;
257
258        // because its -1 to 1, we need to multiply by 2
259        x *= 2.0;
260        y *= 2.0;
261
262        new_transform.translate(x, y, z);
263
264        new_transform
265    }
266
267    /// Add a texture to the program without a name, returns a handle to the texture
268    pub fn add_not_named_texture(&mut self, texture: glium::texture::Texture2d) -> TextureHandle {
269        let id = self.next_texture_id;
270        self.next_texture_id += 1;
271
272        self.cache
273            .insert(format!("{}_{}", HANDLE_STRING_ID, id), texture);
274
275        TextureHandle { id }
276    }
277
278    pub fn remove_texture(&mut self, handle: &TextureHandle) {
279        self.cache
280            .hashmap
281            .remove(&format!("{}_{}", HANDLE_STRING_ID, handle.id));
282    }
283
284    pub fn set_font_collection(
285        &mut self,
286        font_collection: Collection,
287        lumenpyx_program: &mut crate::LumenpyxProgram,
288    ) {
289        if let Some(font_context) = &mut lumenpyx_program.font_context {
290            font_context.collection = font_collection;
291        } else {
292            let mut new_font_context = FontContext::default();
293            new_font_context.collection = font_collection;
294            lumenpyx_program.font_context = Some(new_font_context);
295        }
296    }
297
298    pub fn add_font_to_collection(&mut self, font: Vec<u8>) {
299        if let Some(font_context) = &mut self.font_context {
300            font_context.collection.register_fonts(font);
301        } else {
302            let mut new_font_context = FontContext::default();
303            new_font_context.collection.register_fonts(font);
304            self.font_context = Some(new_font_context);
305        }
306    }
307
308    pub fn get_font_collection(&self) -> Option<&Collection> {
309        if let Some(font_context) = &self.font_context {
310            Some(&font_context.collection)
311        } else {
312            None
313        }
314    }
315
316    pub fn get_font_context(&self) -> Option<&FontContext> {
317        self.font_context.as_ref()
318    }
319
320    pub fn get_font_context_mut(&mut self) -> Option<&mut FontContext> {
321        self.font_context.as_mut()
322    }
323}
324
325struct LumenpyxCache {
326    hashmap: FxHashMap<String, glium::Texture2d>,
327}
328
329impl Default for LumenpyxCache {
330    fn default() -> Self {
331        LumenpyxCache {
332            hashmap: FxHashMap::default(),
333        }
334    }
335}
336
337impl LumenpyxCache {
338    fn insert(&mut self, name: String, texture: glium::Texture2d) {
339        self.hashmap.insert(name, texture);
340    }
341
342    fn get_texture(&self, name: &str) -> Option<&glium::Texture2d> {
343        let texture = self.hashmap.get(name);
344
345        if let Some(texture) = texture {
346            Some(texture)
347        } else {
348            None
349        }
350    }
351}
352
353/// The transform struct is used to determine the position and scale of an object
354#[derive(Copy, Clone)]
355pub struct Transform {
356    matrix: [[f32; 4]; 4],
357    rotation: f32,
358}
359
360impl Default for Transform {
361    fn default() -> Self {
362        Transform::new([0.0, 0.0, 0.0])
363    }
364}
365
366impl Transform {
367    pub fn from_matrix(matrix: [[f32; 4]; 4]) -> Transform {
368        Transform {
369            matrix,
370            rotation: 0.0,
371        }
372    }
373
374    pub fn new(pos: [f32; 3]) -> Transform {
375        Transform {
376            matrix: [
377                [1.0, 0.0, 0.0, 0.0],
378                [0.0, 1.0, 0.0, 0.0],
379                [0.0, 0.0, 1.0, 0.0],
380                [pos[0], pos[1], pos[2], 1.0],
381            ],
382            rotation: 0.0,
383        }
384    }
385
386    /// Get the matrix of the transform
387    pub fn get_matrix(&self) -> [[f32; 4]; 4] {
388        let rotation_matrix = [
389            [self.rotation.cos(), -self.rotation.sin(), 0.0, 0.0],
390            [self.rotation.sin(), self.rotation.cos(), 0.0, 0.0],
391            [0.0, 0.0, 1.0, 0.0],
392            [0.0, 0.0, 0.0, 1.0],
393        ];
394
395        let mut self_matrix = self.matrix;
396        self_matrix[3][2] = 0.0;
397
398        multiply_matrix(rotation_matrix, self_matrix)
399    }
400
401    /// Set the matrix of the transform
402    pub fn translate(&mut self, x: f32, y: f32, z: f32) {
403        self.matrix[3][0] = x;
404        self.matrix[3][1] = y;
405        self.matrix[3][2] = z;
406    }
407
408    /// set the scale of the transform
409    pub fn set_scale(&mut self, x: f32, y: f32, z: f32) {
410        self.matrix[0][0] = x;
411        self.matrix[1][1] = y;
412        self.matrix[2][2] = z;
413    }
414
415    /// get the scale of the transform
416    pub fn get_scale(&self) -> [f32; 3] {
417        [self.matrix[0][0], self.matrix[1][1], self.matrix[2][2]]
418    }
419
420    /// set the x position of the transform
421    pub fn set_x(&mut self, x: f32) {
422        self.matrix[3][0] = x;
423    }
424
425    /// get the x position of the transform
426    pub fn get_x(&self) -> f32 {
427        self.matrix[3][0]
428    }
429
430    /// set the y position of the transform
431    pub fn set_y(&mut self, y: f32) {
432        self.matrix[3][1] = y;
433    }
434
435    /// get the y position of the transform
436    pub fn get_y(&self) -> f32 {
437        self.matrix[3][1]
438    }
439
440    /// set the z position of the transform
441    pub fn set_z(&mut self, z: f32) {
442        self.matrix[3][2] = z;
443    }
444
445    /// get the z position of the transform
446    pub fn get_z(&self) -> f32 {
447        self.matrix[3][2]
448    }
449
450    /// set the rotation of the transform
451    pub fn set_rotation(&mut self, angle: f32) {
452        self.rotation = angle;
453    }
454
455    pub fn get_rotation(&self) -> f32 {
456        self.rotation
457    }
458
459    pub fn add_parent(&self, parent: &Transform) -> Transform {
460        let mut new_transform = Transform::new([0.0, 0.0, 0.0]);
461
462        new_transform.set_x(self.get_x() + parent.get_x());
463        new_transform.set_y(self.get_y() + parent.get_y());
464        new_transform.set_z(self.get_z() + parent.get_z());
465
466        new_transform.set_rotation(self.get_rotation() + parent.get_rotation());
467
468        new_transform.set_scale(
469            self.get_scale()[0] * parent.get_scale()[0],
470            self.get_scale()[1] * parent.get_scale()[1],
471            self.get_scale()[2] * parent.get_scale()[2],
472        );
473
474        new_transform
475    }
476}
477
478fn multiply_matrix(matrix1: [[f32; 4]; 4], matrix2: [[f32; 4]; 4]) -> [[f32; 4]; 4] {
479    let mut new_matrix = [[0.0; 4]; 4];
480
481    for i in 0..4 {
482        for j in 0..4 {
483            for k in 0..4 {
484                new_matrix[i][j] = new_matrix[i][j] + matrix1[i][k] * matrix2[k][j];
485            }
486        }
487    }
488
489    new_matrix
490}
491
492/// a is the parent
493impl<'a, 'b> std::ops::Add<&'b Transform> for &'a Transform {
494    type Output = Transform;
495
496    fn add(self, other: &'b Transform) -> Transform {
497        let mut new_transform = Transform::new([0.0, 0.0, 0.0]);
498
499        new_transform.translate(
500            self.get_x() + other.get_x(),
501            self.get_y() + other.get_y(),
502            self.get_z() + other.get_z(),
503        );
504
505        new_transform.set_rotation(self.get_rotation() + other.get_rotation());
506
507        new_transform.set_scale(
508            self.get_x() * other.get_x(),
509            self.get_y() * other.get_y(),
510            self.get_z() * other.get_z(),
511        );
512
513        new_transform
514    }
515}
516
517/// The vertex struct for the program.
518/// This doesn't need to be messed with unless you are making a custom shader
519/// that doesn't use the full screen quad.
520#[derive(Copy, Clone)]
521pub struct Vertex {
522    position: [f32; 2],
523    tex_coords: [f32; 2],
524}
525implement_vertex!(Vertex, position, tex_coords);
526
527/// Setup the program with the window and display
528pub(crate) fn setup_program() -> (
529    EventLoop<()>,
530    winit::window::Window,
531    glium::Display<WindowSurface>,
532    glium::index::NoIndices,
533) {
534    // this is just a wrapper for the setup_window function for now
535    let (event_loop, display, window, indices) = setup_window();
536
537    (event_loop, window, display, indices)
538}
539
540fn load_image(path: &str) -> glium::texture::RawImage2d<f32> {
541    let img = image::open(path).expect(format!("Failed to load image at path {}", path,).as_str());
542    img.flipv();
543    let path = format!("{}", path);
544    let image = image::load(
545        std::io::Cursor::new(std::fs::read(path).expect("Failed to read image")),
546        image::ImageFormat::Png,
547    )
548    .expect("Failed to load image")
549    .to_rgba32f();
550    let image_dimensions = image.dimensions();
551    let image = glium::texture::RawImage2d::from_raw_rgba_reversed(&image, image_dimensions);
552    image
553}
554
555fn setup_window() -> (
556    EventLoop<()>,
557    glium::Display<WindowSurface>,
558    winit::window::Window,
559    glium::index::NoIndices,
560) {
561    // 1. The **winit::EventLoop** for handling events.
562    let event_loop = winit::event_loop::EventLoopBuilder::new().build();
563
564    match event_loop {
565        Ok(event_loop) => {
566            // 2. Create a glutin context and glium Display
567            let (window, display) =
568                glium::backend::glutin::SimpleWindowBuilder::new().build(&event_loop);
569
570            let indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList);
571
572            (event_loop, display, window, indices)
573        }
574        Err(error) => panic!("Failed to create event loop: {}", error),
575    }
576}
577
578/// The camera struct is used to determine the position of the camera
579#[derive(Copy, Clone)]
580pub struct Camera {
581    pub position: [f32; 3],
582}
583
584impl Camera {
585    /// the z position of the camera is just for reflection purposes
586    pub fn new(position: [f32; 3]) -> Camera {
587        Camera { position }
588    }
589}
590
591pub struct RenderSettings {
592    shadows: bool,
593    reflections: bool,
594    /// The resolution that the program is rendering at
595    /// this is different from the window resolution,
596    /// the program will only show window resolution pixels,
597    /// set the render resolution to a higher value for reflecting things that are off screen
598    render_resolution: Option<[u32; 2]>,
599    blur_reflections: bool,
600    blur_strength: f32,
601}
602
603impl Default for RenderSettings {
604    fn default() -> Self {
605        RenderSettings {
606            shadows: true,
607            reflections: true,
608            render_resolution: None,
609            blur_reflections: false,
610            blur_strength: 0.01,
611        }
612    }
613}
614
615impl RenderSettings {
616    pub fn with_shadows(mut self, shadows: bool) -> Self {
617        self.shadows = shadows;
618        self
619    }
620
621    pub fn with_reflections(mut self, reflections: bool) -> Self {
622        self.reflections = reflections;
623        self
624    }
625
626    pub fn with_render_resolution(mut self, resolution: [u32; 2]) -> Self {
627        self.render_resolution = Some(resolution);
628        self
629    }
630
631    pub fn with_blur_reflections(mut self, blur: bool) -> Self {
632        self.blur_reflections = blur;
633        self
634    }
635
636    // default is 0.01
637    pub fn with_blur_strength(mut self, strength: f32) -> Self {
638        self.blur_strength = strength;
639        self
640    }
641}
642
643/// Draw everything to the screen
644pub fn draw_all(
645    lights: Vec<&dyn lights::LightDrawable>,
646    drawables: Vec<&dyn Drawable>,
647    program: &mut LumenpyxProgram,
648    camera: &Camera,
649) {
650    // this is kind of inefficient, but it works for now
651    for drawable in &drawables {
652        drawable.try_load_shaders(program);
653    }
654    for light in &lights {
655        light.try_load_shaders(program);
656    }
657    load_all_textures(program);
658
659    /*
660    STEP 1:
661        render every albedo to a texture
662        render every height to a texture
663        render every roughness to a texture
664        render every normal to a texture
665
666        find the difference between the last frame and this frame
667        use this to color the different pixels with the shadow strength
668    STEP 2:
669        take the textures and feed it into a lighting shader
670        we do this for every light and then blend the results together
671    STEP 3:
672        take the result and feed it into a reflection shader
673        it uses screen space reflections and lerps between the reflection and the original image based on the roughness
674    STEP 4:
675        upscale the result to the screen size
676    */
677
678    let reflected_texture = program
679        .get_texture("reflected_texture")
680        .expect("Failed to get reflected texture");
681
682    let (
683        albedo_texture,
684        normal_texture,
685        height_texture,
686        roughness_texture,
687        shadow_strength_texture,
688    ) = draw_all_no_post(drawables, program, camera);
689
690    let lit_texture = draw_lighting(
691        lights,
692        program,
693        camera,
694        &albedo_texture,
695        &height_texture,
696        &roughness_texture,
697        &shadow_strength_texture,
698    );
699
700    let display = &program.display;
701    let debug = &program.debug;
702    let render_settings = &program.render_settings;
703    let render_resolution = render_settings
704        .render_resolution
705        .unwrap_or(program.dimensions);
706    if render_resolution < program.dimensions {
707        panic!("Render resolution must be greater than or equal to the window resolution");
708    }
709    if render_settings.reflections {
710        let roughness = glium::uniforms::Sampler(roughness_texture, DEFAULT_BEHAVIOR);
711        let height = glium::uniforms::Sampler(height_texture, DEFAULT_BEHAVIOR);
712        let normal = glium::uniforms::Sampler(normal_texture, DEFAULT_BEHAVIOR);
713        let lit_sampler = if render_settings.shadows {
714            glium::uniforms::Sampler(lit_texture, DEFAULT_BEHAVIOR)
715        } else {
716            glium::uniforms::Sampler(albedo_texture, DEFAULT_BEHAVIOR)
717        };
718
719        let mut reflected_framebuffer =
720            glium::framebuffer::SimpleFrameBuffer::new(display, reflected_texture)
721                .expect("Failed to create reflected frame buffer");
722
723        draw_reflections(
724            camera,
725            lit_sampler,
726            height,
727            roughness,
728            normal,
729            &mut reflected_framebuffer,
730            &program,
731        );
732    }
733
734    {
735        let reflected_texture = if render_settings.reflections {
736            reflected_texture
737        } else if render_settings.shadows {
738            &lit_texture
739        } else {
740            &albedo_texture
741        };
742
743        let finished_texture = match debug {
744            DebugOption::None => glium::uniforms::Sampler(reflected_texture, DEFAULT_BEHAVIOR),
745            DebugOption::Albedo => glium::uniforms::Sampler(albedo_texture, DEFAULT_BEHAVIOR),
746            DebugOption::Height => glium::uniforms::Sampler(height_texture, DEFAULT_BEHAVIOR),
747            DebugOption::Roughness => glium::uniforms::Sampler(roughness_texture, DEFAULT_BEHAVIOR),
748            DebugOption::Normal => glium::uniforms::Sampler(normal_texture, DEFAULT_BEHAVIOR),
749            DebugOption::ShadowStrength => {
750                glium::uniforms::Sampler(shadow_strength_texture, DEFAULT_BEHAVIOR)
751            }
752        };
753
754        draw_upscale(finished_texture, &program, program.dimensions);
755    }
756}
757
758fn load_all_textures(program: &mut LumenpyxProgram) {
759    let display = &program.display;
760    let render_settings = &program.render_settings;
761    let render_resolution = render_settings
762        .render_resolution
763        .unwrap_or(program.dimensions);
764    if render_resolution < program.dimensions {
765        panic!("Render resolution must be greater than or equal to the window resolution");
766    }
767
768    let albedo_texture = program.cache.get_texture("albedo_texture");
769    if albedo_texture.is_none() {
770        let albedo_texture_owned = glium::texture::Texture2d::empty_with_format(
771            display,
772            glium::texture::UncompressedFloatFormat::U8U8U8U8,
773            glium::texture::MipmapsOption::NoMipmap,
774            render_resolution[0],
775            render_resolution[1],
776        )
777        .expect("Failed to create albedo texture");
778
779        program
780            .cache
781            .insert("albedo_texture".to_string(), albedo_texture_owned);
782    }
783
784    let height_texture = program.cache.get_texture("height_texture");
785    if height_texture.is_none() {
786        let height_texture_owned = glium::texture::Texture2d::empty_with_format(
787            display,
788            glium::texture::UncompressedFloatFormat::U8U8U8U8,
789            glium::texture::MipmapsOption::NoMipmap,
790            render_resolution[0],
791            render_resolution[1],
792        )
793        .expect("Failed to create height texture");
794
795        program
796            .cache
797            .insert("height_texture".to_string(), height_texture_owned);
798    }
799
800    let normal_texture = program.cache.get_texture("normal_texture");
801    if normal_texture.is_none() {
802        let normal_texture_owned = glium::texture::Texture2d::empty_with_format(
803            display,
804            glium::texture::UncompressedFloatFormat::U8U8U8U8,
805            glium::texture::MipmapsOption::NoMipmap,
806            render_resolution[0],
807            render_resolution[1],
808        )
809        .expect("Failed to create normal texture");
810
811        program
812            .cache
813            .insert("normal_texture".to_string(), normal_texture_owned);
814    }
815
816    let roughness_texture = program.cache.get_texture("roughness_texture");
817    if roughness_texture.is_none() {
818        let roughness_texture_owned = glium::texture::Texture2d::empty_with_format(
819            display,
820            glium::texture::UncompressedFloatFormat::U8U8U8U8,
821            glium::texture::MipmapsOption::NoMipmap,
822            render_resolution[0],
823            render_resolution[1],
824        )
825        .expect("Failed to create roughness texture");
826
827        program
828            .cache
829            .insert("roughness_texture".to_string(), roughness_texture_owned);
830    }
831
832    let shadow_strength_texture = program.cache.get_texture("shadow_strength_texture");
833    if shadow_strength_texture.is_none() {
834        let shadow_strength_texture_owned = glium::texture::Texture2d::empty_with_format(
835            display,
836            glium::texture::UncompressedFloatFormat::U8U8U8U8,
837            glium::texture::MipmapsOption::NoMipmap,
838            render_resolution[0],
839            render_resolution[1],
840        )
841        .expect("Failed to create shadow strength texture");
842
843        program.cache.insert(
844            "shadow_strength_texture".to_string(),
845            shadow_strength_texture_owned,
846        );
847    }
848
849    let last_drawable_texture = program.cache.get_texture("last_drawable_texture");
850    if last_drawable_texture.is_none() {
851        let last_drawable_texture_owned = glium::texture::Texture2d::empty_with_format(
852            display,
853            glium::texture::UncompressedFloatFormat::U8U8U8U8,
854            glium::texture::MipmapsOption::NoMipmap,
855            render_resolution[0],
856            render_resolution[1],
857        )
858        .expect("Failed to create last drawable texture");
859
860        program.cache.insert(
861            "last_drawable_texture".to_string(),
862            last_drawable_texture_owned,
863        );
864    }
865
866    let reflected_texture = program.get_texture("reflected_texture");
867    if reflected_texture.is_none() {
868        let reflected_texture_owned = glium::texture::Texture2d::empty_with_format(
869            display,
870            glium::texture::UncompressedFloatFormat::U8U8U8U8,
871            glium::texture::MipmapsOption::NoMipmap,
872            render_resolution[0],
873            render_resolution[1],
874        )
875        .expect("Failed to create reflected frame buffer");
876
877        program
878            .cache
879            .insert("reflected_texture".to_string(), reflected_texture_owned);
880    }
881
882    let reflection_texture = program.get_texture("reflection_texture");
883    if reflection_texture.is_none() {
884        let reflection_texture_owned = glium::texture::Texture2d::empty_with_format(
885            display,
886            glium::texture::UncompressedFloatFormat::U8U8U8U8,
887            glium::texture::MipmapsOption::NoMipmap,
888            render_resolution[0],
889            render_resolution[1],
890        )
891        .expect("Failed to create reflection frame buffer");
892
893        program
894            .cache
895            .insert("reflection_texture".to_string(), reflection_texture_owned);
896    }
897
898    let lit_texture = program.get_texture("lit_texture");
899    if lit_texture.is_none() {
900        let lit_texture_owned = glium::texture::Texture2d::empty_with_format(
901            display,
902            glium::texture::UncompressedFloatFormat::U8U8U8U8,
903            glium::texture::MipmapsOption::NoMipmap,
904            render_resolution[0],
905            render_resolution[1],
906        )
907        .expect("Failed to create lit frame buffer");
908
909        program
910            .cache
911            .insert("lit_texture".to_string(), lit_texture_owned);
912    }
913}
914
915fn draw_all_no_post<'a>(
916    drawables: Vec<&dyn Drawable>,
917    program: &'a LumenpyxProgram,
918    camera: &Camera,
919) -> (
920    &'a glium::Texture2d,
921    &'a glium::Texture2d,
922    &'a glium::Texture2d,
923    &'a glium::Texture2d,
924    &'a glium::Texture2d,
925) {
926    let display = &program.display;
927    let render_settings = &program.render_settings;
928    let render_resolution = render_settings
929        .render_resolution
930        .unwrap_or(program.dimensions);
931    if render_resolution < program.dimensions {
932        panic!("Render resolution must be greater than or equal to the window resolution");
933    }
934
935    let albedo_texture = program
936        .get_texture("albedo_texture")
937        .expect("Failed to get albedo texture");
938
939    let height_texture = program
940        .cache
941        .get_texture("height_texture")
942        .expect("Failed to get height texture");
943
944    let normal_texture = program
945        .cache
946        .get_texture("normal_texture")
947        .expect("Failed to get normal texture");
948
949    let roughness_texture = program
950        .cache
951        .get_texture("roughness_texture")
952        .expect("Failed to get roughness texture");
953
954    let shadow_strength_texture = program
955        .cache
956        .get_texture("shadow_strength_texture")
957        .expect("Failed to get shadow strength texture");
958
959    {
960        let last_drawable_texture = program
961            .cache
962            .get_texture("last_drawable_texture")
963            .expect("Failed to get last drawable texture");
964
965        let mut last_drawable_framebuffer =
966            glium::framebuffer::SimpleFrameBuffer::new(display, last_drawable_texture)
967                .expect("Failed to create last drawable framebuffer");
968
969        last_drawable_framebuffer.clear_color(0.0, 0.0, 0.0, 0.0);
970
971        let last_drawable_sampler =
972            glium::uniforms::Sampler(last_drawable_texture, DEFAULT_BEHAVIOR);
973
974        let this_drawable_sampler = glium::uniforms::Sampler(albedo_texture, DEFAULT_BEHAVIOR);
975
976        let mut albedo_framebuffer =
977            glium::framebuffer::SimpleFrameBuffer::new(display, albedo_texture)
978                .expect("Failed to create albedo framebuffer");
979
980        albedo_framebuffer.clear_color(0.0, 0.0, 0.0, 0.0);
981
982        let mut height_framebuffer =
983            glium::framebuffer::SimpleFrameBuffer::new(display, height_texture)
984                .expect("Failed to create height framebuffer");
985
986        height_framebuffer.clear_color(0.0, 0.0, 0.0, 0.0);
987
988        let mut roughness_framebuffer =
989            glium::framebuffer::SimpleFrameBuffer::new(display, roughness_texture)
990                .expect("Failed to create roughness framebuffer");
991
992        roughness_framebuffer.clear_color(0.0, 0.0, 0.0, 0.0);
993
994        let mut normal_framebuffer =
995            glium::framebuffer::SimpleFrameBuffer::new(display, normal_texture)
996                .expect("Failed to create normal framebuffer");
997
998        normal_framebuffer.clear_color(0.0, 0.0, 1.0, 0.0);
999
1000        let mut shadow_strength_framebuffer =
1001            glium::framebuffer::SimpleFrameBuffer::new(display, shadow_strength_texture)
1002                .expect("Failed to create shadow strength framebuffer");
1003
1004        shadow_strength_framebuffer.clear_color(0.0, 0.0, 0.0, 0.0);
1005
1006        for drawable in &drawables {
1007            let new_transform =
1008                program.adjust_transform_for_drawable(&drawable.get_transform(), camera);
1009
1010            drawable.draw_albedo(program, &new_transform, &mut albedo_framebuffer);
1011            if render_settings.shadows {
1012                let shadow_strength = drawable.get_recieve_shadows_strength();
1013
1014                shaders::draw_recieve_shadows(
1015                    &mut shadow_strength_framebuffer,
1016                    &program,
1017                    shadow_strength,
1018                    last_drawable_sampler,
1019                    this_drawable_sampler,
1020                );
1021
1022                // copy the albedo to the last drawable framebuffer
1023                albedo_framebuffer.blit_whole_color_to(
1024                    &last_drawable_framebuffer,
1025                    &glium::BlitTarget {
1026                        left: 0,
1027                        bottom: 0,
1028                        width: render_resolution[0] as i32,
1029                        height: render_resolution[1] as i32,
1030                    },
1031                    glium::uniforms::MagnifySamplerFilter::Nearest,
1032                );
1033            }
1034        }
1035
1036        if render_settings.shadows || render_settings.reflections {
1037            for drawable in &drawables {
1038                let new_transform =
1039                    program.adjust_transform_for_drawable(&drawable.get_transform(), camera);
1040
1041                drawable.draw_height(program, &new_transform, &mut height_framebuffer);
1042            }
1043        }
1044
1045        if program.render_settings.reflections {
1046            for drawable in &drawables {
1047                let new_transform =
1048                    program.adjust_transform_for_drawable(&drawable.get_transform(), camera);
1049
1050                drawable.draw_roughness(program, &new_transform, &mut roughness_framebuffer);
1051            }
1052        }
1053
1054        if program.render_settings.reflections {
1055            for drawable in &drawables {
1056                let new_transform =
1057                    program.adjust_transform_for_drawable(&drawable.get_transform(), camera);
1058
1059                drawable.draw_normal(program, &new_transform, &mut normal_framebuffer);
1060            }
1061        }
1062    }
1063
1064    (
1065        albedo_texture,
1066        normal_texture,
1067        height_texture,
1068        roughness_texture,
1069        shadow_strength_texture,
1070    )
1071}
1072
1073fn draw_lighting<'a>(
1074    lights: Vec<&dyn lights::LightDrawable>,
1075    program: &'a LumenpyxProgram,
1076    camera: &Camera,
1077    albedo_texture: &glium::Texture2d,
1078    height_texture: &glium::Texture2d,
1079    roughness_texture: &glium::Texture2d,
1080    shadow_strength_texture: &glium::Texture2d,
1081) -> &'a glium::Texture2d {
1082    let display = &program.display;
1083    let render_settings = &program.render_settings;
1084    let render_resolution = render_settings
1085        .render_resolution
1086        .unwrap_or(program.dimensions);
1087    if render_resolution < program.dimensions {
1088        panic!("Render resolution must be greater than or equal to the window resolution");
1089    }
1090
1091    let lit_texture = program
1092        .get_texture("lit_texture")
1093        .expect("Failed to get lit texture");
1094
1095    if render_settings.shadows {
1096        let albedo = glium::uniforms::Sampler(albedo_texture, DEFAULT_BEHAVIOR);
1097        let height_sampler = glium::uniforms::Sampler(height_texture, DEFAULT_BEHAVIOR);
1098        let roughness_sampler = glium::uniforms::Sampler(roughness_texture, DEFAULT_BEHAVIOR);
1099        let shadow_strength_sampler =
1100            glium::uniforms::Sampler(shadow_strength_texture, DEFAULT_BEHAVIOR);
1101
1102        let mut lit_framebuffer = glium::framebuffer::SimpleFrameBuffer::new(display, lit_texture)
1103            .expect("Failed to create lit frame buffer");
1104        lit_framebuffer.clear_color(0.0, 0.0, 0.0, 0.0);
1105
1106        for light in lights {
1107            let new_transform =
1108                program.adjust_transform_for_drawable(&light.get_transform(), camera);
1109
1110            light.draw(
1111                program,
1112                new_transform.get_matrix(),
1113                &mut lit_framebuffer,
1114                height_sampler,
1115                albedo,
1116                roughness_sampler,
1117                shadow_strength_sampler,
1118            );
1119        }
1120    }
1121
1122    lit_texture
1123}