diff --git a/Cargo.toml b/Cargo.toml index 057b1fb76..7ef61297e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,10 @@ tokio = { version = "1.16", features = ["macros", "rt-multi-thread"] } name = "triangle" path = "examples/triangle/src/main.rs" +[[example]] +name = "screen" +path = "examples/screen/src/main.rs" + [[example]] name = "mandelbrot" path = "examples/mandelbrot/src/main.rs" diff --git a/examples/README.md b/examples/README.md index 6a02861c5..31b3386cf 100644 --- a/examples/README.md +++ b/examples/README.md @@ -35,87 +35,91 @@ All of the examples builds to both native (desktop, mobile or whatever target sp Because they should run in a browser and to keep the same code for native and wasm, all loading happens async. If your application is native only, you can avoid the async runtime (`tokio` or `async-std`) and use `Loader::load_blocking` instead of `Loader::load_async`. -## Triangle [[code](https://github.com/asny/three-d/tree/master/examples/triangle/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/triangle.html)] +## Triangle [[code](https://github.com/asny/three-d/tree/master/examples/triangle/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/triangle.html)] This is the recomended starting point for a gentle introduction to `three-d`. -![Triangle example](https://asny.github.io/three-d/0.11/triangle.png) +![Triangle example](https://asny.github.io/three-d/0.12/triangle.png) -## Mandelbrot [[code](https://github.com/asny/three-d/tree/master/examples/mandelbrot/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/mandelbrot.html)] +## Mandelbrot [[code](https://github.com/asny/three-d/tree/master/examples/mandelbrot/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/mandelbrot.html)] -![Mandelbrot example](https://asny.github.io/three-d/0.11/mandelbrot.png) +![Mandelbrot example](https://asny.github.io/three-d/0.12/mandelbrot.png) -## Shapes2D [[code](https://github.com/asny/three-d/tree/master/examples/shapes2d/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/shapes2d.html)] +## Shapes2D [[code](https://github.com/asny/three-d/tree/master/examples/shapes2d/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/shapes2d.html)] -![Shapes2d example](https://asny.github.io/three-d/0.11/shapes2d.png) +![Shapes2d example](https://asny.github.io/three-d/0.12/shapes2d.png) -## Shapes [[code](https://github.com/asny/three-d/tree/master/examples/shapes/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/shapes.html)] +## Shapes [[code](https://github.com/asny/three-d/tree/master/examples/shapes/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/shapes.html)] -![Shapes example](https://asny.github.io/three-d/0.11/shapes.png) +![Shapes example](https://asny.github.io/three-d/0.12/shapes.png) -## Sprites [[code](https://github.com/asny/three-d/tree/master/examples/sprites/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/sprites.html)] +## Screen [[code](https://github.com/asny/three-d/tree/master/examples/screen/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/screen.html)] -![Sprites example](https://asny.github.io/three-d/0.11/sprites.png) +![Screen example](https://asny.github.io/three-d/0.12/screen.png) -## Texture [[code](https://github.com/asny/three-d/tree/master/examples/texture/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/texture.html)] +## Sprites [[code](https://github.com/asny/three-d/tree/master/examples/sprites/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/sprites.html)] -![Texture example](https://asny.github.io/three-d/0.11/texture.png) +![Sprites example](https://asny.github.io/three-d/0.12/sprites.png) -## Picking [[code](https://github.com/asny/three-d/tree/master/examples/picking/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/picking.html)] +## Texture [[code](https://github.com/asny/three-d/tree/master/examples/texture/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/texture.html)] -![Picking example](https://asny.github.io/three-d/0.11/picking.png) +![Texture example](https://asny.github.io/three-d/0.12/texture.png) -## Environment [[code](https://github.com/asny/three-d/tree/master/examples/environment/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/environment.html)] +## Picking [[code](https://github.com/asny/three-d/tree/master/examples/picking/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/picking.html)] -![Environment example](https://asny.github.io/three-d/0.11/environment.png) +![Picking example](https://asny.github.io/three-d/0.12/picking.png) -## PBR [[code](https://github.com/asny/three-d/tree/master/examples/pbr/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/pbr.html)] +## Environment [[code](https://github.com/asny/three-d/tree/master/examples/environment/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/environment.html)] -![PBR example](https://asny.github.io/three-d/0.11/pbr.png) +![Environment example](https://asny.github.io/three-d/0.12/environment.png) -## Lighting [[code](https://github.com/asny/three-d/tree/master/examples/lighting/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/lighting.html)] +## PBR [[code](https://github.com/asny/three-d/tree/master/examples/pbr/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/pbr.html)] -![Lighting example](https://asny.github.io/three-d/0.11/lighting.png) +![PBR example](https://asny.github.io/three-d/0.12/pbr.png) -## Lights [[code](https://github.com/asny/three-d/tree/master/examples/lights/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/lights.html)] +## Lighting [[code](https://github.com/asny/three-d/tree/master/examples/lighting/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/lighting.html)] -![Lights example](https://asny.github.io/three-d/0.11/lights.png) +![Lighting example](https://asny.github.io/three-d/0.12/lighting.png) -## Image [[code](https://github.com/asny/three-d/tree/master/examples/image/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/image.html)] +## Lights [[code](https://github.com/asny/three-d/tree/master/examples/lights/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/lights.html)] -![Image example](https://asny.github.io/three-d/0.11/image.png) +![Lights example](https://asny.github.io/three-d/0.12/lights.png) -## Fog [[code](https://github.com/asny/three-d/tree/master/examples/fog/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/fog.html)] +## Image [[code](https://github.com/asny/three-d/tree/master/examples/image/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/image.html)] -![Fog example](https://asny.github.io/three-d/0.11/fog.png) +![Image example](https://asny.github.io/three-d/0.12/image.png) -## Fireworks [[code](https://github.com/asny/three-d/tree/master/examples/fireworks/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/fireworks.html)] +## Fog [[code](https://github.com/asny/three-d/tree/master/examples/fog/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/fog.html)] -![Fireworks example](https://asny.github.io/three-d/0.11/fireworks.png) +![Fog example](https://asny.github.io/three-d/0.12/fog.png) -## Statues [[code](https://github.com/asny/three-d/tree/master/examples/statues/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/statues.html)] +## Fireworks [[code](https://github.com/asny/three-d/tree/master/examples/fireworks/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/fireworks.html)] -![Statues example](https://asny.github.io/three-d/0.11/statues.png) +![Fireworks example](https://asny.github.io/three-d/0.12/fireworks.png) -## Wireframe [[code](https://github.com/asny/three-d/tree/master/examples/wireframe/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/wireframe.html)] +## Statues [[code](https://github.com/asny/three-d/tree/master/examples/statues/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/statues.html)] -![Wireframe example](https://asny.github.io/three-d/0.11/wireframe.png) +![Statues example](https://asny.github.io/three-d/0.12/statues.png) -## Forest [[code](https://github.com/asny/three-d/tree/master/examples/forest/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/forest.html)] +## Wireframe [[code](https://github.com/asny/three-d/tree/master/examples/wireframe/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/wireframe.html)] -![Forest example](https://asny.github.io/three-d/0.11/forest.png) +![Wireframe example](https://asny.github.io/three-d/0.12/wireframe.png) -## Volume [[code](https://github.com/asny/three-d/tree/master/examples/volume/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/volume.html)] +## Forest [[code](https://github.com/asny/three-d/tree/master/examples/forest/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/forest.html)] -![Volume example](https://asny.github.io/three-d/0.11/volume.png) +![Forest example](https://asny.github.io/three-d/0.12/forest.png) -## Normals [[code](https://github.com/asny/three-d/tree/master/examples/normals/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/normals.html)] +## Volume [[code](https://github.com/asny/three-d/tree/master/examples/volume/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/volume.html)] -![Normals example](https://asny.github.io/three-d/0.11/normals.png) +![Volume example](https://asny.github.io/three-d/0.12/volume.png) -## Logo [[code](https://github.com/asny/three-d/tree/master/examples/logo/src/main.rs)] [[demo](https://asny.github.io/three-d/0.11/logo.html)] +## Normals [[code](https://github.com/asny/three-d/tree/master/examples/normals/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/normals.html)] -![Logo example](https://asny.github.io/three-d/0.11/logo.png) +![Normals example](https://asny.github.io/three-d/0.12/normals.png) + +## Logo [[code](https://github.com/asny/three-d/tree/master/examples/logo/src/main.rs)] [[demo](https://asny.github.io/three-d/0.12/logo.html)] + +![Logo example](https://asny.github.io/three-d/0.12/logo.png) ## Headless [[code](https://github.com/asny/three-d/tree/master/examples/headless/src/main.rs)] diff --git a/examples/environment/src/main.rs b/examples/environment/src/main.rs index 9897ae710..0fd382da8 100644 --- a/examples/environment/src/main.rs +++ b/examples/environment/src/main.rs @@ -57,7 +57,7 @@ pub async fn run() { let mut color = [1.0; 4]; window .render_loop(move |mut frame_input| { - let mut panel_width = 0; + let mut panel_width = 0.0; gui.update(&mut frame_input, |gui_context| { use three_d::egui::*; SidePanel::left("side_panel").show(gui_context, |ui| { @@ -66,15 +66,16 @@ pub async fn run() { ui.add(Slider::new(&mut model.material.roughness, 0.0..=1.0).text("Roughness")); ui.color_edit_button_rgba_unmultiplied(&mut color); }); - panel_width = gui_context.used_size().x as u32; + panel_width = gui_context.used_size().x as f64; }) .unwrap(); model.material.albedo = Color::from_rgba_slice(&color); let viewport = Viewport { - x: panel_width as i32, + x: (panel_width * frame_input.device_pixel_ratio) as i32, y: 0, - width: frame_input.viewport.width - panel_width, + width: frame_input.viewport.width + - (panel_width * frame_input.device_pixel_ratio) as u32, height: frame_input.viewport.height, }; camera.set_viewport(viewport).unwrap(); @@ -82,17 +83,14 @@ pub async fn run() { .handle_events(&mut camera, &mut frame_input.events) .unwrap(); - Screen::write( - &context, - ClearState::color_and_depth(0.5, 0.5, 0.5, 1.0, 1.0), - || { - skybox.render(&camera, &[&light])?; - model.render(&camera, &[&light])?; - gui.render()?; - Ok(()) - }, - ) - .unwrap(); + frame_input + .screen() + .clear(ClearState::color_and_depth(0.5, 0.5, 0.5, 1.0, 1.0)) + .unwrap() + .render(&camera, &[&skybox as &dyn Object, &model], &[&light]) + .unwrap() + .write(|| gui.render()) + .unwrap(); FrameOutput::default() }) diff --git a/examples/fireworks/src/main.rs b/examples/fireworks/src/main.rs index 68ce4c089..1f11adb67 100644 --- a/examples/fireworks/src/main.rs +++ b/examples/fireworks/src/main.rs @@ -42,7 +42,6 @@ impl Material for FireworksMaterial { }, depth_test: DepthTest::Always, write_mask: WriteMask::COLOR, - clip: Clip::Disabled, } } fn is_transparent(&self) -> bool { @@ -138,14 +137,18 @@ pub fn run() { particles.update(&data).unwrap(); } - Screen::write(&context, ClearState::color(0.0, 0.0, 0.0, 1.0), || { - let f = particles.time / explosion_time.max(0.0); - fireworks_material.fade = 1.0 - f * f * f * f; - fireworks_material.color = colors[color_index]; - particles.render_with_material(&fireworks_material, &camera, &[])?; - Ok(()) - }) - .unwrap(); + frame_input + .screen() + .clear(ClearState::color(0.0, 0.0, 0.0, 1.0)) + .unwrap() + .write(|| { + let f = particles.time / explosion_time.max(0.0); + fireworks_material.fade = 1.0 - f * f * f * f; + fireworks_material.color = colors[color_index]; + particles.render_with_material(&fireworks_material, &camera, &[])?; + Ok(()) + }) + .unwrap(); FrameOutput::default() }) diff --git a/examples/fog/src/main.rs b/examples/fog/src/main.rs index 7a5598793..e90f77ead 100644 --- a/examples/fog/src/main.rs +++ b/examples/fog/src/main.rs @@ -80,23 +80,27 @@ pub async fn run() { ); } - Screen::write(&context, ClearState::default(), || { - monkey - .as_ref() - .unwrap() - .render(&camera, &[&ambient, &directional])?; - if fog_enabled { - if let Some(ref depth_texture) = depth_texture { - fog_effect.apply( - &camera, - depth_texture, - frame_input.accumulated_time as f32, - )?; + frame_input + .screen() + .clear(ClearState::default()) + .unwrap() + .write(|| { + monkey + .as_ref() + .unwrap() + .render(&camera, &[&ambient, &directional])?; + if fog_enabled { + if let Some(ref depth_texture) = depth_texture { + fog_effect.apply( + &camera, + depth_texture, + frame_input.accumulated_time as f32, + )?; + } } - } - Ok(()) - }) - .unwrap(); + Ok(()) + }) + .unwrap(); FrameOutput::default() }) diff --git a/examples/forest/src/main.rs b/examples/forest/src/main.rs index e81c45378..19bdd327b 100644 --- a/examples/forest/src/main.rs +++ b/examples/forest/src/main.rs @@ -131,20 +131,14 @@ pub async fn run() { .unwrap(); if redraw { - Screen::write( - &context, - ClearState::color_and_depth(0.8, 0.8, 0.8, 1.0, 1.0), - || { - render_pass( - &camera, - &models.iter().map(|m| m as &dyn Object).collect::>(), - &[&ambient, &directional], - )?; - imposters.render(&camera, &[])?; - Ok(()) - }, - ) - .unwrap(); + let mut models = models.iter().map(|m| m as &dyn Object).collect::>(); + models.push(&imposters); + frame_input + .screen() + .clear(ClearState::color_and_depth(0.8, 0.8, 0.8, 1.0, 1.0)) + .unwrap() + .render(&camera, &models, &[&ambient, &directional]) + .unwrap(); } FrameOutput { diff --git a/examples/headless/src/main.rs b/examples/headless/src/main.rs index 6ca55a9f5..88e542201 100644 --- a/examples/headless/src/main.rs +++ b/examples/headless/src/main.rs @@ -64,27 +64,33 @@ fn main() { // Render three frames for frame_index in 0..3 { - // Create a render target (a combination of a color and a depth texture) to write into and clear the color and depth - RenderTarget::new(&context, &mut texture, &mut depth_texture) - .unwrap() - .write(ClearState::color_and_depth(0.8, 0.8, 0.8, 1.0, 1.0), || { - // Set the current transformation of the triangle - model.set_transformation(Mat4::from_angle_y(radians( - (frame_index as f32 * 0.6) as f32, - ))); + // Set the current transformation of the triangle + model.set_transformation(Mat4::from_angle_y(radians( + (frame_index as f32 * 0.6) as f32, + ))); - // Render the triangle with the per vertex colors defined at construction - model.render(&camera, &[]) - }) - .unwrap(); + // Create a render target (a combination of a color and a depth texture) to write into + let pixels = RenderTarget::new( + texture.as_color_target(None), + depth_texture.as_depth_target(), + ) + .unwrap() + // Clear color and depth of the render target + .clear(ClearState::color_and_depth(0.8, 0.8, 0.8, 1.0, 1.0)) + .unwrap() + // Render the triangle with the per vertex colors defined at construction + .render(&camera, &[&model], &[]) + .unwrap() + // Read out the colors from the render target + .read_color() + .unwrap(); // Save the rendered image - let pixels = texture.read(viewport).unwrap(); Saver::save_pixels( format!("headless-{}.png", frame_index), &pixels, - viewport.width, - viewport.height, + texture.width(), + texture.height(), ) .unwrap(); } diff --git a/examples/image/src/main.rs b/examples/image/src/main.rs index 6691d1570..2c306b52e 100644 --- a/examples/image/src/main.rs +++ b/examples/image/src/main.rs @@ -34,7 +34,7 @@ pub async fn run() { let mut texture_transform_y = 0.0; window .render_loop(move |mut frame_input| { - let mut panel_width = 0; + let mut panel_width = 0.0; gui.update(&mut frame_input, |gui_context| { use three_d::egui::*; SidePanel::left("side_panel").show(gui_context, |ui| { @@ -53,7 +53,7 @@ pub async fn run() { .text("Texture transform y"), ); }); - panel_width = gui_context.used_size().x as u32; + panel_width = gui_context.used_size().x as f64; }) .unwrap(); @@ -63,20 +63,25 @@ pub async fn run() { ); let viewport = Viewport { - x: panel_width as i32, + x: (panel_width * frame_input.device_pixel_ratio) as i32, y: 0, - width: frame_input.viewport.width - panel_width, + width: frame_input.viewport.width + - (panel_width * frame_input.device_pixel_ratio) as u32, height: frame_input.viewport.height, }; - Screen::write(&context, ClearState::default(), || { - image_effect.use_texture("image", &image)?; - image_effect.use_uniform("parameter", tone_mapping)?; - image_effect.apply(RenderStates::default(), viewport)?; - gui.render()?; - Ok(()) - }) - .unwrap(); + frame_input + .screen() + .clear(ClearState::default()) + .unwrap() + .write(|| { + image_effect.use_texture("image", &image)?; + image_effect.use_uniform("parameter", tone_mapping)?; + image_effect.apply(RenderStates::default(), viewport)?; + gui.render()?; + Ok(()) + }) + .unwrap(); FrameOutput::default() }) diff --git a/examples/lighting/src/main.rs b/examples/lighting/src/main.rs index 541720675..ce836ebdd 100644 --- a/examples/lighting/src/main.rs +++ b/examples/lighting/src/main.rs @@ -115,7 +115,7 @@ pub async fn run() { window .render_loop(move |mut frame_input| { let mut change = frame_input.first_frame; - let mut panel_width = frame_input.viewport.width; + let mut panel_width = 0.0; change |= gui .update(&mut frame_input, |gui_context| { use three_d::egui::*; @@ -224,14 +224,15 @@ pub async fn run() { ); ui.radio_value(&mut deferred_pipeline.debug_type, DebugType::ORM, "ORM"); }); - panel_width = gui_context.used_size().x as u32; + panel_width = gui_context.used_size().x as f64; }) .unwrap(); let viewport = Viewport { - x: panel_width as i32, + x: (panel_width * frame_input.device_pixel_ratio) as i32, y: 0, - width: frame_input.viewport.width - panel_width, + width: frame_input.viewport.width + - (panel_width * frame_input.device_pixel_ratio) as u32, height: frame_input.viewport.height, }; change |= camera.set_viewport(viewport).unwrap(); @@ -287,74 +288,96 @@ pub async fn run() { ]; // Light pass - Screen::write(&context, ClearState::default(), || { - match current_pipeline { - Pipeline::Forward => { - match deferred_pipeline.debug_type { - DebugType::NORMAL => { - plane.render_with_material( - &NormalMaterial::from_physical_material(&plane.material), + frame_input + .screen() + .clear(ClearState::default()) + .unwrap() + .write(|| { + match current_pipeline { + Pipeline::Forward => { + match deferred_pipeline.debug_type { + DebugType::NORMAL => { + plane.render_with_material( + &NormalMaterial::from_physical_material(&plane.material), + &camera, + &lights, + )?; + model.render_with_material( + &NormalMaterial::from_physical_material(&model.material), + &camera, + &lights, + )?; + } + DebugType::DEPTH => { + let depth_material = DepthMaterial::default(); + plane.render_with_material( + &depth_material, + &camera, + &lights, + )?; + model.render_with_material( + &depth_material, + &camera, + &lights, + )?; + } + DebugType::ORM => { + plane.render_with_material( + &ORMMaterial::from_physical_material(&plane.material), + &camera, + &lights, + )?; + model.render_with_material( + &ORMMaterial::from_physical_material(&model.material), + &camera, + &lights, + )?; + } + DebugType::POSITION => { + let position_material = PositionMaterial::default(); + plane.render_with_material( + &position_material, + &camera, + &lights, + )?; + model.render_with_material( + &position_material, + &camera, + &lights, + )?; + } + DebugType::UV => { + let uv_material = UVMaterial::default(); + plane.render_with_material(&uv_material, &camera, &lights)?; + model.render_with_material(&uv_material, &camera, &lights)?; + } + DebugType::COLOR => { + plane.render_with_material( + &ColorMaterial::from_physical_material(&plane.material), + &camera, + &lights, + )?; + model.render_with_material( + &ColorMaterial::from_physical_material(&model.material), + &camera, + &lights, + )?; + } + DebugType::NONE => forward_pipeline.render_pass( &camera, + &[&plane, &model], &lights, - )?; - model.render_with_material( - &NormalMaterial::from_physical_material(&model.material), - &camera, - &lights, - )?; - } - DebugType::DEPTH => { - let depth_material = DepthMaterial::default(); - plane.render_with_material(&depth_material, &camera, &lights)?; - model.render_with_material(&depth_material, &camera, &lights)?; - } - DebugType::ORM => { - plane.render_with_material( - &ORMMaterial::from_physical_material(&plane.material), - &camera, - &lights, - )?; - model.render_with_material( - &ORMMaterial::from_physical_material(&model.material), - &camera, - &lights, - )?; - } - DebugType::POSITION => { - let position_material = PositionMaterial::default(); - plane.render_with_material(&position_material, &camera, &lights)?; - model.render_with_material(&position_material, &camera, &lights)?; - } - DebugType::UV => { - let uv_material = UVMaterial::default(); - plane.render_with_material(&uv_material, &camera, &lights)?; - model.render_with_material(&uv_material, &camera, &lights)?; - } - DebugType::COLOR => { - plane.render_with_material( - &ColorMaterial::from_physical_material(&plane.material), - &camera, - &lights, - )?; - model.render_with_material( - &ColorMaterial::from_physical_material(&model.material), - &camera, - &lights, - )?; - } - DebugType::NONE => { - forward_pipeline.render_pass(&camera, &[&plane, &model], &lights)? - } - }; - } - Pipeline::Deferred => { - deferred_pipeline.lighting_pass(&camera, &lights)?; + )?, + }; + } + Pipeline::Deferred => { + deferred_pipeline.lighting_pass(&camera, &lights)?; + } } - } - gui.render()?; - Ok(()) - }) - .unwrap(); + gui.render()?; + Ok(()) + }) + .unwrap(); FrameOutput::default() }) diff --git a/examples/lights/src/main.rs b/examples/lights/src/main.rs index 70776de24..f39923420 100644 --- a/examples/lights/src/main.rs +++ b/examples/lights/src/main.rs @@ -145,7 +145,7 @@ pub async fn run() { let mut color = [1.0; 4]; window .render_loop(move |mut frame_input| { - let mut panel_width = frame_input.viewport.width; + let mut panel_width = 0.0; gui.update(&mut frame_input, |gui_context| { use three_d::egui::*; SidePanel::left("side_panel").show(gui_context, |ui| { @@ -162,7 +162,7 @@ pub async fn run() { ); ui.color_edit_button_rgba_unmultiplied(&mut color); }); - panel_width = gui_context.used_size().x as u32; + panel_width = gui_context.used_size().x as f64; }) .unwrap(); while lights.len() < light_count { @@ -184,15 +184,14 @@ pub async fn run() { ); light.update(0.00005 * size.magnitude() * frame_input.elapsed_time as f32); } - - camera - .set_viewport(Viewport { - x: panel_width as i32, - y: 0, - width: frame_input.viewport.width - panel_width, - height: frame_input.viewport.height, - }) - .unwrap(); + let viewport = Viewport { + x: (panel_width * frame_input.device_pixel_ratio) as i32, + y: 0, + width: frame_input.viewport.width + - (panel_width * frame_input.device_pixel_ratio) as u32, + height: frame_input.viewport.height, + }; + camera.set_viewport(viewport).unwrap(); control .handle_events(&mut camera, &mut frame_input.events) @@ -205,10 +204,11 @@ pub async fn run() { ) .unwrap(); - Screen::write( - &context, - ClearState::color_and_depth(0.2, 0.2, 0.8, 1.0, 1.0), - || { + frame_input + .screen() + .clear(ClearState::color_and_depth(0.2, 0.2, 0.8, 1.0, 1.0)) + .unwrap() + .write(|| { for light in lights.iter() { light.render(&camera)?; } @@ -221,9 +221,8 @@ pub async fn run() { )?; gui.render()?; Ok(()) - }, - ) - .unwrap(); + }) + .unwrap(); FrameOutput::default() }) diff --git a/examples/logo/src/main.rs b/examples/logo/src/main.rs index af67a07e4..f53a1b187 100644 --- a/examples/logo/src/main.rs +++ b/examples/logo/src/main.rs @@ -79,18 +79,12 @@ pub async fn run() { window .render_loop(move |frame_input: FrameInput| { camera.set_viewport(frame_input.viewport).unwrap(); - Screen::write( - &context, - ClearState::color_and_depth(1.0, 1.0, 1.0, 1.0, 1.0), - || { - /*model.set_transformation(Mat4::from_angle_y(radians( - (frame_input.accumulated_time * 0.0002) as f32, - )));*/ - model.render(&camera, &[&light])?; - Ok(()) - }, - ) - .unwrap(); + frame_input + .screen() + .clear(ClearState::color_and_depth(1.0, 1.0, 1.0, 1.0, 1.0)) + .unwrap() + .render(&camera, &[&model], &[&light]) + .unwrap(); FrameOutput::default() }) diff --git a/examples/mandelbrot/src/main.rs b/examples/mandelbrot/src/main.rs index 48e2e0f4d..402dfbfa1 100644 --- a/examples/mandelbrot/src/main.rs +++ b/examples/mandelbrot/src/main.rs @@ -110,10 +110,12 @@ pub fn main() { } if redraw { - Screen::write(&context, ClearState::color(0.0, 1.0, 1.0, 1.0), || { - mesh.render(&camera, &[]) - }) - .unwrap(); + frame_input + .screen() + .clear(ClearState::color(0.0, 1.0, 1.0, 1.0)) + .unwrap() + .render(&camera, &[&mesh], &[]) + .unwrap(); } FrameOutput { diff --git a/examples/normals/src/main.rs b/examples/normals/src/main.rs index ab86c366a..8e7906514 100644 --- a/examples/normals/src/main.rs +++ b/examples/normals/src/main.rs @@ -86,19 +86,21 @@ pub async fn run() { .unwrap(); // Draw - Screen::write( - &context, - ClearState::color_and_depth(0.5, 0.5, 0.5, 1.0, 1.0), - || { - let lights: [&dyn Light; 2] = [&ambient, &directional]; - model_with_computed_tangents.render(&camera, &lights)?; - model_with_loaded_tangents.render(&camera, &lights)?; - instanced_model_with_computed_tangents.render(&camera, &lights)?; - instanced_model_with_loaded_tangents.render(&camera, &lights)?; - Ok(()) - }, - ) - .unwrap(); + frame_input + .screen() + .clear(ClearState::color_and_depth(0.5, 0.5, 0.5, 1.0, 1.0)) + .unwrap() + .render( + &camera, + &[ + &model_with_computed_tangents, + &model_with_loaded_tangents, + &instanced_model_with_computed_tangents, + &instanced_model_with_loaded_tangents, + ], + &[&ambient, &directional], + ) + .unwrap(); FrameOutput::default() }) .unwrap(); diff --git a/examples/pbr/src/main.rs b/examples/pbr/src/main.rs index b9ec535dc..6fab1530d 100644 --- a/examples/pbr/src/main.rs +++ b/examples/pbr/src/main.rs @@ -57,7 +57,7 @@ pub async fn run() { let mut emissive_map_enabled = true; window .render_loop(move |mut frame_input| { - let mut panel_width = 0; + let mut panel_width = 0.0; gui.update(&mut frame_input, |gui_context| { use three_d::egui::*; SidePanel::left("side_panel").show(gui_context, |ui| { @@ -68,14 +68,15 @@ pub async fn run() { ui.checkbox(&mut occlusion_map_enabled, "Occlusion map"); ui.checkbox(&mut emissive_map_enabled, "Emissive map"); }); - panel_width = gui_context.used_size().x as u32; + panel_width = gui_context.used_size().x as f64; }) .unwrap(); let viewport = Viewport { - x: panel_width as i32, + x: (panel_width * frame_input.device_pixel_ratio) as i32, y: 0, - width: frame_input.viewport.width - panel_width, + width: frame_input.viewport.width + - (panel_width * frame_input.device_pixel_ratio) as u32, height: frame_input.viewport.height, }; camera.set_viewport(viewport).unwrap(); @@ -83,11 +84,13 @@ pub async fn run() { .handle_events(&mut camera, &mut frame_input.events) .unwrap(); - Screen::write( - &context, - ClearState::color_and_depth(0.5, 0.5, 0.5, 1.0, 1.0), - || { - skybox.render(&camera, &[])?; + frame_input + .screen() + .clear(ClearState::color_and_depth(0.5, 0.5, 0.5, 1.0, 1.0)) + .unwrap() + .render(&camera, &[&skybox], &[]) + .unwrap() + .write(|| { let material = PhysicalMaterial { name: model.material.name.clone(), albedo: model.material.albedo, @@ -135,9 +138,8 @@ pub async fn run() { model.render_with_material(&material, &camera, &[&light])?; gui.render()?; Ok(()) - }, - ) - .unwrap(); + }) + .unwrap(); FrameOutput::default() }) diff --git a/examples/picking/src/main.rs b/examples/picking/src/main.rs index 7f658db1b..6eac30788 100644 --- a/examples/picking/src/main.rs +++ b/examples/picking/src/main.rs @@ -90,15 +90,12 @@ pub async fn run() { // draw if change { - Screen::write( - &context, - ClearState::color_and_depth(1.0, 1.0, 1.0, 1.0, 1.0), - || { - render_pass(&camera, &[&monkey, &pick_mesh], &[&ambient, &directional])?; - Ok(()) - }, - ) - .unwrap(); + frame_input + .screen() + .clear(ClearState::color_and_depth(1.0, 1.0, 1.0, 1.0, 1.0)) + .unwrap() + .render(&camera, &[&monkey, &pick_mesh], &[&ambient, &directional]) + .unwrap(); } FrameOutput { diff --git a/examples/screen/Cargo.toml b/examples/screen/Cargo.toml new file mode 100644 index 000000000..71cd780d4 --- /dev/null +++ b/examples/screen/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "screen" +version = "0.1.0" +authors = ["Asger Nyman Christiansen "] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +three-d = { path = "../../", default-features = false, features=["glutin-window", "canvas", "egui-gui"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +log = "0.4" +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +console_error_panic_hook = "0.1" +console_log = "0.2" diff --git a/examples/screen/src/lib.rs b/examples/screen/src/lib.rs new file mode 100644 index 000000000..fc3601c11 --- /dev/null +++ b/examples/screen/src/lib.rs @@ -0,0 +1,18 @@ +mod main; + +// Entry point for wasm +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen(start)] +pub fn start() -> Result<(), JsValue> { + console_log::init_with_level(log::Level::Debug).unwrap(); + + use log::info; + info!("Logging works!"); + + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + main::main(); + Ok(()) +} diff --git a/examples/screen/src/main.rs b/examples/screen/src/main.rs new file mode 100644 index 000000000..7315574b0 --- /dev/null +++ b/examples/screen/src/main.rs @@ -0,0 +1,135 @@ +use three_d::*; + +pub fn main() { + let window = Window::new(WindowSettings { + title: "Screen!".to_string(), + max_size: Some((1280, 720)), + ..Default::default() + }) + .unwrap(); + let context = window.gl().unwrap(); + + let mut camera = Camera::new_perspective( + &context, + window.viewport().unwrap(), + vec3(0.0, 0.0, 1.3), + vec3(0.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + degrees(45.0), + 0.1, + 10.0, + ) + .unwrap(); + + let cpu_mesh = CpuMesh { + positions: Positions::F32(vec![ + vec3(0.5, -0.5, 0.0), // bottom right + vec3(-0.5, -0.5, 0.0), // bottom left + vec3(0.0, 0.5, 0.0), // top + ]), + colors: Some(vec![ + Color::new(255, 0, 0, 255), // bottom right + Color::new(0, 255, 0, 255), // bottom left + Color::new(0, 0, 255, 255), // top + ]), + ..Default::default() + }; + + let mut model = Model::new(&context, &cpu_mesh).unwrap(); + + let mut gui = three_d::GUI::new(&context).unwrap(); + let mut viewport_zoom = 1.0; + let mut scissor_zoom = 1.0; + window + .render_loop(move |mut frame_input: FrameInput| { + model.set_transformation(Mat4::from_angle_y(radians( + (frame_input.accumulated_time * 0.005) as f32, + ))); + + let mut panel_width = 0.0; + gui.update(&mut frame_input, |gui_context| { + use three_d::egui::*; + SidePanel::left("side_panel").show(gui_context, |ui| { + use three_d::egui::*; + ui.heading("Debug Panel"); + ui.add(Slider::new(&mut viewport_zoom, 0.01..=1.0).text("Viewport")); + ui.add(Slider::new(&mut scissor_zoom, 0.01..=1.0).text("Scissor")); + }); + panel_width = gui_context.used_size().x as f64; + }) + .unwrap(); + + let viewport = Viewport { + x: (panel_width * frame_input.device_pixel_ratio) as i32, + y: 0, + width: frame_input.viewport.width + - (panel_width * frame_input.device_pixel_ratio) as u32, + height: frame_input.viewport.height, + }; + + // Main view + let viewport_zoomed = zoom(viewport_zoom, viewport); + let scissor_box_zoomed = zoom(scissor_zoom, viewport).into(); + + camera.set_viewport(viewport_zoomed).unwrap(); + frame_input + .screen() + .clear(ClearState::color_and_depth(1.0, 1.0, 1.0, 1.0, 1.0)) + .unwrap() + .clear_partially( + if viewport_zoom < scissor_zoom { + scissor_box_zoomed + } else { + viewport_zoomed.into() + }, + ClearState::color(0.8, 0.8, 0.8, 1.0), + ) + .unwrap() + .clear_partially( + if viewport_zoom > scissor_zoom { + scissor_box_zoomed + } else { + viewport_zoomed.into() + }, + ClearState::color(0.5, 0.5, 0.5, 1.0), + ) + .unwrap() + .render_partially(scissor_box_zoomed, &camera, &[&model], &[]) + .unwrap() + .write(|| gui.render()) + .unwrap(); + + // Secondary view + let secondary_viewport = Viewport { + x: viewport.x, + y: viewport.y, + width: 200, + height: 200, + }; + camera.set_viewport(secondary_viewport).unwrap(); + frame_input + .screen() + .clear_partially( + secondary_viewport.into(), + ClearState::color(0.3, 0.3, 0.3, 1.0), + ) + .unwrap() + .render_partially(secondary_viewport.into(), &camera, &[&model], &[]) + .unwrap(); + + // Returns default frame output to end the frame + FrameOutput::default() + }) + .unwrap(); +} + +fn zoom(zoom: f32, viewport: Viewport) -> Viewport { + let width = (viewport.width as f32 * zoom) as u32; + let height = (viewport.height as f32 * zoom) as u32; + Viewport { + x: ((viewport.width - width) / 2 + viewport.x as u32) as i32, + y: ((viewport.height - height) / 2 + viewport.y as u32) as i32, + width, + height, + } +} diff --git a/examples/shapes/src/main.rs b/examples/shapes/src/main.rs index b292a3714..f86a3f08d 100644 --- a/examples/shapes/src/main.rs +++ b/examples/shapes/src/main.rs @@ -108,27 +108,24 @@ pub fn main() { .handle_events(&mut camera, &mut frame_input.events) .unwrap(); - Screen::write( - &context, - ClearState::color_and_depth(0.8, 0.8, 0.8, 1.0, 1.0), - || { - render_pass( - &camera, - &[ - &sphere, - &cylinder, - &cube, - &axes, - &bounding_box_sphere, - &bounding_box_cube, - &bounding_box_cylinder, - ], - &[&light0, &light1], - )?; - Ok(()) - }, - ) - .unwrap(); + frame_input + .screen() + .clear(ClearState::color_and_depth(0.8, 0.8, 0.8, 1.0, 1.0)) + .unwrap() + .render( + &camera, + &[ + &sphere, + &cylinder, + &cube, + &axes, + &bounding_box_sphere, + &bounding_box_cube, + &bounding_box_cylinder, + ], + &[&light0, &light1], + ) + .unwrap(); FrameOutput::default() }) diff --git a/examples/shapes2d/src/main.rs b/examples/shapes2d/src/main.rs index 2ef49d3e8..a140f2c7e 100644 --- a/examples/shapes2d/src/main.rs +++ b/examples/shapes2d/src/main.rs @@ -76,17 +76,17 @@ pub fn main() { _ => {} } } - Screen::write( - &context, - ClearState::color_and_depth(0.8, 0.8, 0.8, 1.0, 1.0), - || { + frame_input + .screen() + .clear(ClearState::color_and_depth(0.8, 0.8, 0.8, 1.0, 1.0)) + .unwrap() + .write(|| { line.render(frame_input.viewport)?; rectangle.render(frame_input.viewport)?; circle.render(frame_input.viewport)?; Ok(()) - }, - ) - .unwrap(); + }) + .unwrap(); FrameOutput::default() }) diff --git a/examples/sprites/src/main.rs b/examples/sprites/src/main.rs index 72f8d66ea..e9c11c63a 100644 --- a/examples/sprites/src/main.rs +++ b/examples/sprites/src/main.rs @@ -84,33 +84,30 @@ pub async fn run() { .handle_events(&mut camera, &mut frame_input.events) .unwrap(); - Screen::write( - &context, - ClearState::color_and_depth(0.8, 0.8, 0.8, 1.0, 1.0), - || { - render_pass( - &camera, - &[ - &axes, - &Gm { - geometry: &billboards, - material: &material, - }, - &Gm { - geometry: &sprites_up, - material: &material, - }, - &Gm { - geometry: &sprites, - material: &material, - }, - ], - &[&ambient], - )?; - Ok(()) - }, - ) - .unwrap(); + frame_input + .screen() + .clear(ClearState::color_and_depth(0.8, 0.8, 0.8, 1.0, 1.0)) + .unwrap() + .render( + &camera, + &[ + &axes, + &Gm { + geometry: &billboards, + material: &material, + }, + &Gm { + geometry: &sprites_up, + material: &material, + }, + &Gm { + geometry: &sprites, + material: &material, + }, + ], + &[&ambient], + ) + .unwrap(); FrameOutput::default() }) diff --git a/examples/statues/src/main.rs b/examples/statues/src/main.rs index 9d456739f..2d515562d 100644 --- a/examples/statues/src/main.rs +++ b/examples/statues/src/main.rs @@ -149,25 +149,25 @@ pub async fn run() { let mut bounding_box_enabled = false; window .render_loop(move |mut frame_input| { - let mut panel_width = 0; + let mut panel_width = 0.0; gui.update(&mut frame_input, |gui_context| { use three_d::egui::*; SidePanel::left("side_panel").show(gui_context, |ui| { ui.heading("Debug Panel"); - ui.heading("Camera"); - ui.radio_value(&mut camera_type, CameraType::Primary, "Primary"); - ui.radio_value(&mut camera_type, CameraType::Secondary, "Secondary"); + ui.radio_value(&mut camera_type, CameraType::Primary, "Primary camera"); + ui.radio_value(&mut camera_type, CameraType::Secondary, "Secondary camera"); ui.checkbox(&mut bounding_box_enabled, "Bounding boxes"); }); - panel_width = gui_context.used_size().x as u32; + panel_width = gui_context.used_size().x as f64; }) .unwrap(); let viewport = Viewport { - x: panel_width as i32, + x: (panel_width * frame_input.device_pixel_ratio) as i32, y: 0, - width: frame_input.viewport.width - panel_width, + width: frame_input.viewport.width + - (panel_width * frame_input.device_pixel_ratio) as u32, height: frame_input.viewport.height, }; primary_camera.set_viewport(viewport).unwrap(); @@ -177,10 +177,11 @@ pub async fn run() { .unwrap(); // draw - Screen::write( - &context, - ClearState::color_and_depth(0.8, 0.8, 0.7, 1.0, 1.0), - || { + frame_input + .screen() + .clear(ClearState::color_and_depth(0.8, 0.8, 0.7, 1.0, 1.0)) + .unwrap() + .write(|| { let camera = match camera_type { CameraType::Primary => &primary_camera, CameraType::Secondary => &secondary_camera, @@ -198,9 +199,8 @@ pub async fn run() { } gui.render()?; Ok(()) - }, - ) - .unwrap(); + }) + .unwrap(); FrameOutput::default() }) diff --git a/examples/texture/src/main.rs b/examples/texture/src/main.rs index 749e7f00e..81484f466 100644 --- a/examples/texture/src/main.rs +++ b/examples/texture/src/main.rs @@ -87,14 +87,16 @@ pub async fn run() { // draw if redraw { - Screen::write(&context, ClearState::default(), || { - render_pass( + frame_input + .screen() + .clear(ClearState::default()) + .unwrap() + .render( &camera, &[&box_object, &penguin_object, &skybox], &[&ambient, &directional], ) - }) - .unwrap(); + .unwrap(); } FrameOutput { diff --git a/examples/triangle/src/main.rs b/examples/triangle/src/main.rs index b8fafac91..02f4f66f9 100644 --- a/examples/triangle/src/main.rs +++ b/examples/triangle/src/main.rs @@ -51,15 +51,18 @@ pub fn main() { // Ensure the viewport matches the current window viewport which changes if the window is resized camera.set_viewport(frame_input.viewport).unwrap(); - // Start writing to the screen and clears the color and depth - Screen::write(&context, ClearState::color_and_depth(0.8, 0.8, 0.8, 1.0, 1.0), || { - // Set the current transformation of the triangle - model.set_transformation(Mat4::from_angle_y(radians((frame_input.accumulated_time * 0.005) as f32))); + // Set the current transformation of the triangle + model.set_transformation(Mat4::from_angle_y(radians((frame_input.accumulated_time * 0.005) as f32))); - // Render the triangle with the color material which uses the per vertex colors defined at construction - model.render(&camera, &[])?; - Ok(()) - }).unwrap(); + // Get the screen render target to be able to render something on the screen + frame_input.screen() + // Clear the color and depth of the screen render target + .clear(ClearState::color_and_depth(0.8, 0.8, 0.8, 1.0, 1.0)).unwrap() + // Write to the screen render target + .write(|| { + // Render the triangle with the color material which uses the per vertex colors defined at construction + model.render(&camera, &[]) + }).unwrap(); // Returns default frame output to end the frame FrameOutput::default() diff --git a/examples/volume/src/main.rs b/examples/volume/src/main.rs index b214d4aec..7b754f9c3 100644 --- a/examples/volume/src/main.rs +++ b/examples/volume/src/main.rs @@ -66,7 +66,7 @@ pub async fn run() { let mut color = [1.0; 4]; window .render_loop(move |mut frame_input| { - let mut panel_width = 0; + let mut panel_width = 0.0; gui.update(&mut frame_input, |gui_context| { use three_d::egui::*; SidePanel::left("side_panel").show(gui_context, |ui| { @@ -76,15 +76,16 @@ pub async fn run() { ); ui.color_edit_button_rgba_unmultiplied(&mut color); }); - panel_width = gui_context.used_size().x as u32; + panel_width = gui_context.used_size().x as f64; }) .unwrap(); volume.material.color = Color::from_rgba_slice(&color); let viewport = Viewport { - x: panel_width as i32, + x: (panel_width * frame_input.device_pixel_ratio) as i32, y: 0, - width: frame_input.viewport.width - panel_width, + width: frame_input.viewport.width + - (panel_width * frame_input.device_pixel_ratio) as u32, height: frame_input.viewport.height, }; camera.set_viewport(viewport).unwrap(); @@ -93,20 +94,18 @@ pub async fn run() { .unwrap(); // draw - Screen::write( - &context, - ClearState::color_and_depth(0.5, 0.5, 0.5, 1.0, 1.0), - || { - render_pass( - &camera, - &[&volume], - &[&ambient, &directional1, &directional2], - )?; - gui.render()?; - Ok(()) - }, - ) - .unwrap(); + frame_input + .screen() + .clear(ClearState::color_and_depth(0.5, 0.5, 0.5, 1.0, 1.0)) + .unwrap() + .render( + &camera, + &[&volume], + &[&ambient, &directional1, &directional2], + ) + .unwrap() + .write(|| gui.render()) + .unwrap(); FrameOutput::default() }) diff --git a/examples/wireframe/src/main.rs b/examples/wireframe/src/main.rs index 117d621e3..2622a6c79 100644 --- a/examples/wireframe/src/main.rs +++ b/examples/wireframe/src/main.rs @@ -97,19 +97,16 @@ pub async fn run() { .unwrap(); if redraw { - Screen::write( - &context, - ClearState::color_and_depth(1.0, 1.0, 1.0, 1.0, 1.0), - || { - render_pass( - &camera, - &[&model, &vertices, &edges], - &[&ambient, &directional0, &directional1], - )?; - Ok(()) - }, - ) - .unwrap(); + frame_input + .screen() + .clear(ClearState::color_and_depth(1.0, 1.0, 1.0, 1.0, 1.0)) + .unwrap() + .render( + &camera, + &[&model, &vertices, &edges], + &[&ambient, &directional0, &directional1], + ) + .unwrap(); } FrameOutput { diff --git a/src/core.rs b/src/core.rs index a88227653..ab2ae09fd 100644 --- a/src/core.rs +++ b/src/core.rs @@ -66,6 +66,10 @@ mod viewport; #[doc(inline)] pub use viewport::*; +mod scissor_box; +#[doc(inline)] +pub use scissor_box::*; + pub use crate::ThreeDResult; use thiserror::Error; /// diff --git a/src/core/context.rs b/src/core/context.rs index df00b795d..329f3c5b2 100644 --- a/src/core/context.rs +++ b/src/core/context.rs @@ -133,6 +133,185 @@ impl Context { callback(camera2d.as_ref().unwrap()) } + /// + /// Set the scissor test for this context (see [ScissorBox]). + /// + pub fn set_scissor(&self, scissor_box: ScissorBox) { + unsafe { + if scissor_box.width > 0 && scissor_box.height > 0 { + self.enable(crate::context::SCISSOR_TEST); + self.scissor( + scissor_box.x as i32, + scissor_box.y as i32, + scissor_box.width as i32, + scissor_box.height as i32, + ); + } else { + self.disable(crate::context::SCISSOR_TEST); + } + } + } + + /// + /// Set the viewport for this context (See [Viewport]). + /// + pub fn set_viewport(&self, viewport: Viewport) { + unsafe { + self.viewport( + viewport.x, + viewport.y, + viewport.width as i32, + viewport.height as i32, + ); + } + } + + /// + /// Set the face culling for this context (see [Cull]). + /// + pub fn set_cull(&self, cull: Cull) { + unsafe { + match cull { + Cull::None => { + self.disable(crate::context::CULL_FACE); + } + Cull::Back => { + self.enable(crate::context::CULL_FACE); + self.cull_face(crate::context::BACK); + } + Cull::Front => { + self.enable(crate::context::CULL_FACE); + self.cull_face(crate::context::FRONT); + } + Cull::FrontAndBack => { + self.enable(crate::context::CULL_FACE); + self.cull_face(crate::context::FRONT_AND_BACK); + } + } + } + } + + /// + /// Set the write mask for this context (see [WriteMask]). + /// + pub fn set_write_mask(&self, write_mask: WriteMask) { + unsafe { + self.color_mask( + write_mask.red, + write_mask.green, + write_mask.blue, + write_mask.alpha, + ); + self.depth_mask(write_mask.depth); + } + } + + /// + /// Set the depth test for this context (see [DepthTest]). + /// + pub fn set_depth_test(&self, depth_test: DepthTest) { + unsafe { + self.enable(crate::context::DEPTH_TEST); + match depth_test { + DepthTest::Never => { + self.depth_func(crate::context::NEVER); + } + DepthTest::Less => { + self.depth_func(crate::context::LESS); + } + DepthTest::Equal => { + self.depth_func(crate::context::EQUAL); + } + DepthTest::LessOrEqual => { + self.depth_func(crate::context::LEQUAL); + } + DepthTest::Greater => { + self.depth_func(crate::context::GREATER); + } + DepthTest::NotEqual => { + self.depth_func(crate::context::NOTEQUAL); + } + DepthTest::GreaterOrEqual => { + self.depth_func(crate::context::GEQUAL); + } + DepthTest::Always => { + self.depth_func(crate::context::ALWAYS); + } + } + } + } + + /// + /// Set the blend state for this context (see [Blend]). + /// + pub fn set_blend(&self, blend: Blend) { + unsafe { + if let Blend::Enabled { + source_rgb_multiplier, + source_alpha_multiplier, + destination_rgb_multiplier, + destination_alpha_multiplier, + rgb_equation, + alpha_equation, + } = blend + { + self.enable(crate::context::BLEND); + self.blend_func_separate( + Self::blend_const_from_multiplier(source_rgb_multiplier), + Self::blend_const_from_multiplier(destination_rgb_multiplier), + Self::blend_const_from_multiplier(source_alpha_multiplier), + Self::blend_const_from_multiplier(destination_alpha_multiplier), + ); + self.blend_equation_separate( + Self::blend_const_from_equation(rgb_equation), + Self::blend_const_from_equation(alpha_equation), + ); + } else { + self.disable(crate::context::BLEND); + } + } + } + + fn blend_const_from_multiplier(multiplier: BlendMultiplierType) -> u32 { + match multiplier { + BlendMultiplierType::Zero => crate::context::ZERO, + BlendMultiplierType::One => crate::context::ONE, + BlendMultiplierType::SrcColor => crate::context::SRC_COLOR, + BlendMultiplierType::OneMinusSrcColor => crate::context::ONE_MINUS_SRC_COLOR, + BlendMultiplierType::DstColor => crate::context::DST_COLOR, + BlendMultiplierType::OneMinusDstColor => crate::context::ONE_MINUS_DST_COLOR, + BlendMultiplierType::SrcAlpha => crate::context::SRC_ALPHA, + BlendMultiplierType::OneMinusSrcAlpha => crate::context::ONE_MINUS_SRC_ALPHA, + BlendMultiplierType::DstAlpha => crate::context::DST_ALPHA, + BlendMultiplierType::OneMinusDstAlpha => crate::context::ONE_MINUS_DST_ALPHA, + BlendMultiplierType::SrcAlphaSaturate => crate::context::SRC_ALPHA_SATURATE, + } + } + fn blend_const_from_equation(equation: BlendEquationType) -> u32 { + match equation { + BlendEquationType::Add => crate::context::FUNC_ADD, + BlendEquationType::Subtract => crate::context::FUNC_SUBTRACT, + BlendEquationType::ReverseSubtract => crate::context::FUNC_REVERSE_SUBTRACT, + BlendEquationType::Min => crate::context::MIN, + BlendEquationType::Max => crate::context::MAX, + } + } + + /// + /// Set the render states for this context (see [RenderStates]). + /// + pub fn set_render_states(&self, render_states: RenderStates) -> ThreeDResult<()> { + self.set_cull(render_states.cull); + self.set_write_mask(render_states.write_mask); + if render_states.write_mask.depth { + self.set_depth_test(render_states.depth_test); + } else { + unsafe { self.disable(crate::context::DEPTH_TEST) } + } + self.set_blend(render_states.blend); + self.error_check() + } + pub(super) fn error_check(&self) -> ThreeDResult<()> { #[cfg(debug_assertions)] unsafe { @@ -208,6 +387,15 @@ impl Context { } } +impl std::fmt::Debug for Context { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut d = f.debug_struct("Context"); + d.field("programs", &self.programs.borrow().len()); + d.field("effects", &self.effects.borrow().len()); + d.finish() + } +} + impl std::ops::Deref for Context { type Target = crate::context::Context; fn deref(&self) -> &Self::Target { diff --git a/src/core/image_cube_effect.rs b/src/core/image_cube_effect.rs index 55a03ddff..764b466df 100644 --- a/src/core/image_cube_effect.rs +++ b/src/core/image_cube_effect.rs @@ -75,8 +75,7 @@ impl ImageCubeEffect { /// /// Applies the effect defined in the fragment shader source given at construction to the given side of a cube map. - /// Must be called in a render target render function, - /// for example in the callback function of [Screen::write]. + /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. /// pub fn render( &self, diff --git a/src/core/image_effect.rs b/src/core/image_effect.rs index 4aae779d8..8917bebff 100644 --- a/src/core/image_effect.rs +++ b/src/core/image_effect.rs @@ -65,8 +65,7 @@ impl ImageEffect { /// /// Applies the calculations defined in the fragment shader given at construction and output it to the current screen/render target. - /// Must be called in a render target render function, - /// for example in the callback function of [Screen::write]. + /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. /// pub fn render(&self, render_states: RenderStates, viewport: Viewport) -> ThreeDResult<()> { self.program @@ -81,8 +80,7 @@ impl ImageEffect { /// /// Applies the calculations defined in the fragment shader given at construction and output it to the current screen/render target. - /// Must be called in a render target render function, - /// for example in the callback function of [Screen::write]. + /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. /// pub fn apply(&self, render_states: RenderStates, viewport: Viewport) -> ThreeDResult<()> { self.render(render_states, viewport) diff --git a/src/core/program.rs b/src/core/program.rs index 0b7478526..097bf96c0 100644 --- a/src/core/program.rs +++ b/src/core/program.rs @@ -524,8 +524,8 @@ impl Program { viewport: Viewport, count: u32, ) -> ThreeDResult<()> { - Self::set_viewport(&self.context, viewport); - Self::set_states(&self.context, render_states)?; + self.context.set_viewport(viewport); + self.context.set_render_states(render_states)?; self.use_program(); unsafe { self.context @@ -541,7 +541,7 @@ impl Program { /// /// Same as [Program::draw_arrays] except it renders 'instance_count' instances of the same set of triangles. - /// Use the [Program::use_attribute_instanced], [Program::use_attribute_vec2_instanced], [Program::use_attribute_vec3_instanced] and [Program::use_attribute_vec4_instanced] methods to send unique data for each instance to the shader. + /// Use the [Program::use_instance_attribute], method to send unique data for each instance to the shader. /// pub fn draw_arrays_instanced( &self, @@ -550,8 +550,8 @@ impl Program { count: u32, instance_count: u32, ) -> ThreeDResult<()> { - Self::set_viewport(&self.context, viewport); - Self::set_states(&self.context, render_states)?; + self.context.set_viewport(viewport); + self.context.set_render_states(render_states)?; self.use_program(); unsafe { self.context.draw_arrays_instanced( @@ -604,8 +604,8 @@ impl Program { first: u32, count: u32, ) -> ThreeDResult<()> { - Self::set_viewport(&self.context, viewport); - Self::set_states(&self.context, render_states)?; + self.context.set_viewport(viewport); + self.context.set_render_states(render_states)?; self.use_program(); element_buffer.bind(); unsafe { @@ -629,7 +629,7 @@ impl Program { /// /// Same as [Program::draw_elements] except it renders 'instance_count' instances of the same set of triangles. - /// Use the [Program::use_attribute_instanced], [Program::use_attribute_vec2_instanced], [Program::use_attribute_vec3_instanced] and [Program::use_attribute_vec4_instanced] methods to send unique data for each instance to the shader. + /// Use the [Program::use_instance_attribute] method to send unique data for each instance to the shader. /// pub fn draw_elements_instanced( &self, @@ -650,7 +650,7 @@ impl Program { /// /// Same as [Program::draw_subset_of_elements] except it renders 'instance_count' instances of the same set of triangles. - /// Use the [Program::use_attribute_instanced], [Program::use_attribute_vec2_instanced], [Program::use_attribute_vec3_instanced] and [Program::use_attribute_vec4_instanced] methods to send unique data for each instance to the shader. + /// Use the [Program::use_instance_attribute] method to send unique data for each instance to the shader. /// pub fn draw_subset_of_elements_instanced( &self, @@ -661,8 +661,8 @@ impl Program { count: u32, instance_count: u32, ) -> ThreeDResult<()> { - Self::set_viewport(&self.context, viewport); - Self::set_states(&self.context, render_states)?; + self.context.set_viewport(viewport); + self.context.set_render_states(render_states)?; self.use_program(); element_buffer.bind(); unsafe { @@ -718,174 +718,6 @@ impl Program { self.context.use_program(None); } } - - fn set_states(context: &Context, render_states: RenderStates) -> ThreeDResult<()> { - Self::set_cull(context, render_states.cull); - Self::set_write_mask(context, render_states.write_mask); - Self::set_clip(context, render_states.clip); - Self::set_depth( - context, - Some(render_states.depth_test), - render_states.write_mask.depth, - ); - Self::set_blend(context, render_states.blend); - context.error_check() - } - - fn set_clip(context: &Context, clip: Clip) { - unsafe { - if let Clip::Enabled { - x, - y, - width, - height, - } = clip - { - context.enable(crate::context::SCISSOR_TEST); - context.scissor(x as i32, y as i32, width as i32, height as i32); - } else { - context.disable(crate::context::SCISSOR_TEST); - } - } - } - - fn set_viewport(context: &Context, viewport: Viewport) { - unsafe { - context.viewport( - viewport.x, - viewport.y, - viewport.width as i32, - viewport.height as i32, - ); - } - } - - fn set_cull(context: &Context, cull: Cull) { - unsafe { - match cull { - Cull::None => { - context.disable(crate::context::CULL_FACE); - } - Cull::Back => { - context.enable(crate::context::CULL_FACE); - context.cull_face(crate::context::BACK); - } - Cull::Front => { - context.enable(crate::context::CULL_FACE); - context.cull_face(crate::context::FRONT); - } - Cull::FrontAndBack => { - context.enable(crate::context::CULL_FACE); - context.cull_face(crate::context::FRONT_AND_BACK); - } - } - } - } - - fn set_blend(context: &Context, blend: Blend) { - unsafe { - if let Blend::Enabled { - source_rgb_multiplier, - source_alpha_multiplier, - destination_rgb_multiplier, - destination_alpha_multiplier, - rgb_equation, - alpha_equation, - } = blend - { - context.enable(crate::context::BLEND); - context.blend_func_separate( - Self::blend_const_from_multiplier(source_rgb_multiplier), - Self::blend_const_from_multiplier(destination_rgb_multiplier), - Self::blend_const_from_multiplier(source_alpha_multiplier), - Self::blend_const_from_multiplier(destination_alpha_multiplier), - ); - context.blend_equation_separate( - Self::blend_const_from_equation(rgb_equation), - Self::blend_const_from_equation(alpha_equation), - ); - } else { - context.disable(crate::context::BLEND); - } - } - } - - fn blend_const_from_multiplier(multiplier: BlendMultiplierType) -> u32 { - match multiplier { - BlendMultiplierType::Zero => crate::context::ZERO, - BlendMultiplierType::One => crate::context::ONE, - BlendMultiplierType::SrcColor => crate::context::SRC_COLOR, - BlendMultiplierType::OneMinusSrcColor => crate::context::ONE_MINUS_SRC_COLOR, - BlendMultiplierType::DstColor => crate::context::DST_COLOR, - BlendMultiplierType::OneMinusDstColor => crate::context::ONE_MINUS_DST_COLOR, - BlendMultiplierType::SrcAlpha => crate::context::SRC_ALPHA, - BlendMultiplierType::OneMinusSrcAlpha => crate::context::ONE_MINUS_SRC_ALPHA, - BlendMultiplierType::DstAlpha => crate::context::DST_ALPHA, - BlendMultiplierType::OneMinusDstAlpha => crate::context::ONE_MINUS_DST_ALPHA, - BlendMultiplierType::SrcAlphaSaturate => crate::context::SRC_ALPHA_SATURATE, - } - } - - fn blend_const_from_equation(equation: BlendEquationType) -> u32 { - match equation { - BlendEquationType::Add => crate::context::FUNC_ADD, - BlendEquationType::Subtract => crate::context::FUNC_SUBTRACT, - BlendEquationType::ReverseSubtract => crate::context::FUNC_REVERSE_SUBTRACT, - BlendEquationType::Min => crate::context::MIN, - BlendEquationType::Max => crate::context::MAX, - } - } - - pub(crate) fn set_write_mask(context: &Context, write_mask: WriteMask) { - unsafe { - context.color_mask( - write_mask.red, - write_mask.green, - write_mask.blue, - write_mask.alpha, - ); - Self::set_depth(context, None, write_mask.depth); - } - } - - fn set_depth(context: &Context, depth_test: Option, depth_mask: bool) { - unsafe { - if depth_mask == false && depth_test == Some(DepthTest::Always) { - context.disable(crate::context::DEPTH_TEST); - } else { - context.enable(crate::context::DEPTH_TEST); - context.depth_mask(depth_mask); - if let Some(depth_test) = depth_test { - match depth_test { - DepthTest::Never => { - context.depth_func(crate::context::NEVER); - } - DepthTest::Less => { - context.depth_func(crate::context::LESS); - } - DepthTest::Equal => { - context.depth_func(crate::context::EQUAL); - } - DepthTest::LessOrEqual => { - context.depth_func(crate::context::LEQUAL); - } - DepthTest::Greater => { - context.depth_func(crate::context::GREATER); - } - DepthTest::NotEqual => { - context.depth_func(crate::context::NOTEQUAL); - } - DepthTest::GreaterOrEqual => { - context.depth_func(crate::context::GEQUAL); - } - DepthTest::Always => { - context.depth_func(crate::context::ALWAYS); - } - } - } - } - } - } } impl Drop for Program { diff --git a/src/core/render_states.rs b/src/core/render_states.rs index 4aaeaef9d..3ee31a443 100644 --- a/src/core/render_states.rs +++ b/src/core/render_states.rs @@ -12,11 +12,6 @@ pub struct RenderStates { /// pub write_mask: WriteMask, - /// - /// Defines the rectangle of pixels to write to in a render call. If none, all pixels in the current render target are possibly written into. - /// - pub clip: Clip, - /// /// Defines the depth test in a render call. /// The depth test determines whether or not a fragment from the current render call should be discarded @@ -44,7 +39,6 @@ impl Default for RenderStates { write_mask: WriteMask::default(), depth_test: DepthTest::default(), blend: Blend::default(), - clip: Clip::default(), cull: Cull::default(), } } @@ -96,32 +90,6 @@ impl Default for DepthTest { } } -/// -/// Defines the rectangle of pixels to write to in a render call. -/// -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Clip { - /// Only render inside the defined rectangle of the screen/render target. - Enabled { - /// The distance in pixels from the left edge of the screen/render target. - x: u32, - /// The distance in pixels from the top edge of the screen/render target. - y: u32, - /// The width of the rectangle. - width: u32, - /// The height of the rectangle. - height: u32, - }, - /// Render inside the entire screen/render target. - Disabled, -} - -impl Default for Clip { - fn default() -> Self { - Self::Disabled - } -} - /// /// Defines which channels (red, green, blue, alpha and depth) to write to in a render call. /// diff --git a/src/core/render_target.rs b/src/core/render_target.rs index 96c9e0089..71746242a 100644 --- a/src/core/render_target.rs +++ b/src/core/render_target.rs @@ -91,6 +91,42 @@ impl ClearState { depth: Some(depth), } } + + pub(in crate::core) fn apply(&self, context: &Context) { + context.set_write_mask(WriteMask { + red: self.red.is_some(), + green: self.green.is_some(), + blue: self.blue.is_some(), + alpha: self.alpha.is_some(), + depth: self.depth.is_some(), + }); + unsafe { + let clear_color = self.red.is_some() + || self.green.is_some() + || self.blue.is_some() + || self.alpha.is_some(); + if clear_color { + context.clear_color( + self.red.unwrap_or(0.0), + self.green.unwrap_or(0.0), + self.blue.unwrap_or(0.0), + self.alpha.unwrap_or(1.0), + ); + } + if let Some(depth) = self.depth { + context.clear_depth_f32(depth); + } + context.clear(if clear_color && self.depth.is_some() { + crate::context::COLOR_BUFFER_BIT | crate::context::DEPTH_BUFFER_BIT + } else { + if clear_color { + crate::context::COLOR_BUFFER_BIT + } else { + crate::context::DEPTH_BUFFER_BIT + } + }); + } + } } impl Default for ClearState { @@ -109,45 +145,6 @@ pub(in crate::core) fn new_framebuffer( } } -fn clear(context: &Context, clear_state: &ClearState) { - Program::set_write_mask( - context, - WriteMask { - red: clear_state.red.is_some(), - green: clear_state.green.is_some(), - blue: clear_state.blue.is_some(), - alpha: clear_state.alpha.is_some(), - depth: clear_state.depth.is_some(), - }, - ); - let clear_color = clear_state.red.is_some() - || clear_state.green.is_some() - || clear_state.blue.is_some() - || clear_state.alpha.is_some(); - unsafe { - if clear_color { - context.clear_color( - clear_state.red.unwrap_or(0.0), - clear_state.green.unwrap_or(0.0), - clear_state.blue.unwrap_or(0.0), - clear_state.alpha.unwrap_or(1.0), - ); - } - if let Some(depth) = clear_state.depth { - context.clear_depth_f32(depth); - } - context.clear(if clear_color && clear_state.depth.is_some() { - crate::context::COLOR_BUFFER_BIT | crate::context::DEPTH_BUFFER_BIT - } else { - if clear_color { - crate::context::COLOR_BUFFER_BIT - } else { - crate::context::DEPTH_BUFFER_BIT - } - }); - } -} - fn copy_from( context: &Context, color_texture: Option<&Texture2D>, diff --git a/src/core/render_target/render_target2d.rs b/src/core/render_target/render_target2d.rs index 16af58374..c3602c651 100644 --- a/src/core/render_target/render_target2d.rs +++ b/src/core/render_target/render_target2d.rs @@ -1,122 +1,537 @@ use crate::core::render_target::*; /// -/// Adds additional functionality to write to and copy from both a [ColorTargetTexture2D] and -/// a [DepthTargetTexture2D] at the same time. -/// It purely adds functionality, so it can be created each time it is needed, the data is saved in the textures. +/// Adds additional functionality to clear, read from and write to a texture. +/// Use the `as_color_target` function directly on the texture structs (for example [Texture2D]) to construct a color target. +/// Combine this together with a [DepthTarget] with [RenderTarget::new] to be able to write to both a depth and color target at the same time. +/// A color target purely adds functionality, so it can be created each time it is needed, the actual data is saved in the texture. /// -pub struct RenderTarget<'a, 'b> { +/// **Note:** [DepthTest] is disabled if not also writing to a [DepthTarget]. +/// +#[derive(Clone)] +pub struct ColorTarget<'a> { context: Context, - id: Option, - color_texture: Option<&'a Texture2D>, - depth_texture: Option<&'b DepthTargetTexture2D>, + target: CT<'a>, +} + +#[derive(Clone)] +enum CT<'a> { + Texture2D { + texture: &'a Texture2D, + mip_level: Option, + }, + Texture2DArray { + texture: &'a Texture2DArray, + layers: &'a [u32], + mip_level: Option, + }, + TextureCubeMap { + texture: &'a TextureCubeMap, + side: CubeMapSide, + mip_level: Option, + }, } -impl<'a, 'b> RenderTarget<'a, 'b> { +impl<'a> ColorTarget<'a> { + pub(in crate::core) fn new_texture2d( + context: &Context, + texture: &'a Texture2D, + mip_level: Option, + ) -> Self { + ColorTarget { + context: context.clone(), + target: CT::Texture2D { texture, mip_level }, + } + } + + pub(in crate::core) fn new_texture_cube_map( + context: &Context, + texture: &'a TextureCubeMap, + side: CubeMapSide, + mip_level: Option, + ) -> Self { + ColorTarget { + context: context.clone(), + target: CT::TextureCubeMap { + texture, + side, + mip_level, + }, + } + } + + pub(in crate::core) fn new_texture_2d_array( + context: &Context, + texture: &'a Texture2DArray, + layers: &'a [u32], + mip_level: Option, + ) -> Self { + ColorTarget { + context: context.clone(), + target: CT::Texture2DArray { + texture, + layers, + mip_level, + }, + } + } + + /// + /// Clears the color of this color target as defined by the given clear state. + /// + pub fn clear(&self, clear_state: ClearState) -> ThreeDResult<&Self> { + self.clear_partially(self.scissor_box(), clear_state) + } + + /// + /// Clears the color of the part of this color target that is inside the given scissor box. + /// + pub fn clear_partially( + &self, + scissor_box: ScissorBox, + clear_state: ClearState, + ) -> ThreeDResult<&Self> { + self.as_render_target()?.clear_partially( + scissor_box, + ClearState { + depth: None, + ..clear_state + }, + )?; + Ok(self) + } + + /// + /// Writes whatever rendered in the `render` closure into this color target. + /// + pub fn write(&self, render: impl FnOnce() -> ThreeDResult<()>) -> ThreeDResult<&Self> { + self.write_partially(self.scissor_box(), render) + } + + /// + /// Writes whatever rendered in the `render` closure into the part of this color target defined by the scissor box. + /// + pub fn write_partially( + &self, + scissor_box: ScissorBox, + render: impl FnOnce() -> ThreeDResult<()>, + ) -> ThreeDResult<&Self> { + self.as_render_target()? + .write_partially(scissor_box, render)?; + Ok(self) + } + + /// + /// Returns the colors of the pixels in this color target. + /// The number of channels per pixel and the data format for each channel is specified by the generic parameter. + /// + /// **Note:** On web, the data format needs to match the data format of the color texture. + /// + pub fn read(&self) -> ThreeDResult> { + self.read_partially(self.scissor_box()) + } + + /// + /// Returns the colors of the pixels in this color target inside the given scissor box. + /// The number of channels per pixel and the data format for each channel is specified by the generic parameter. + /// + /// **Note:** On web, the data format needs to match the data format of the color texture. + /// + pub fn read_partially( + &self, + scissor_box: ScissorBox, + ) -> ThreeDResult> { + self.as_render_target()?.read_color_partially(scissor_box) + } + + /// + /// Returns the width of the color target in texels. + /// If using the zero mip level of the underlying texture, then this is simply the width of that texture, otherwise it is the width of the given mip level. + /// + pub fn width(&self) -> u32 { + match self.target { + CT::Texture2D { texture, mip_level } => size_with_mip(texture.width(), mip_level), + CT::Texture2DArray { + texture, mip_level, .. + } => size_with_mip(texture.width(), mip_level), + CT::TextureCubeMap { + texture, mip_level, .. + } => size_with_mip(texture.width(), mip_level), + } + } + + /// + /// Returns the height of the color target in texels. + /// If using the zero mip level of the underlying texture, then this is simply the height of that texture, otherwise it is the height of the given mip level. + /// + pub fn height(&self) -> u32 { + match self.target { + CT::Texture2D { texture, mip_level } => size_with_mip(texture.height(), mip_level), + CT::Texture2DArray { + texture, mip_level, .. + } => size_with_mip(texture.height(), mip_level), + CT::TextureCubeMap { + texture, mip_level, .. + } => size_with_mip(texture.height(), mip_level), + } + } + + /// + /// Returns the scissor box that encloses the entire target. + /// + pub fn scissor_box(&self) -> ScissorBox { + ScissorBox::new_at_origo(self.width(), self.height()) + } + + pub(in crate::core) fn as_render_target(&self) -> ThreeDResult> { + RenderTarget::new_color(self.clone()) + } + + fn generate_mip_maps(&self) { + match self.target { + CT::Texture2D { texture, mip_level } => { + if mip_level.is_none() { + texture.generate_mip_maps() + } + } + CT::Texture2DArray { + texture, mip_level, .. + } => { + if mip_level.is_none() { + texture.generate_mip_maps() + } + } + CT::TextureCubeMap { + texture, mip_level, .. + } => { + if mip_level.is_none() { + texture.generate_mip_maps() + } + } + } + } + + fn bind(&self, context: &Context) { + match self.target { + CT::Texture2D { texture, mip_level } => unsafe { + context.draw_buffers(&[crate::context::COLOR_ATTACHMENT0]); + texture.bind_as_color_target(0, mip_level.unwrap_or(0)); + }, + CT::Texture2DArray { + texture, + layers, + mip_level, + } => unsafe { + context.draw_buffers( + &(0..layers.len()) + .map(|i| crate::context::COLOR_ATTACHMENT0 + i as u32) + .collect::>(), + ); + for channel in 0..layers.len() { + texture.bind_as_color_target( + layers[channel], + channel as u32, + mip_level.unwrap_or(0), + ); + } + }, + CT::TextureCubeMap { + texture, + side, + mip_level, + } => unsafe { + context.draw_buffers(&[crate::context::COLOR_ATTACHMENT0]); + texture.bind_as_color_target(side, 0, mip_level.unwrap_or(0)); + }, + } + } +} + +/// +/// Adds additional functionality to clear, read from and write to a texture. +/// Use the `as_depth_target` function directly on the texture structs (for example [DepthTargetTexture2D]) to construct a depth target. +/// Combine this together with a [ColorTarget] with [RenderTarget::new] to be able to write to both a depth and color target at the same time. +/// A depth target purely adds functionality, so it can be created each time it is needed, the actual data is saved in the texture. +/// +#[derive(Clone)] +pub struct DepthTarget<'a> { + context: Context, + target: DT<'a>, +} + +#[derive(Clone)] +enum DT<'a> { + Texture2D { + texture: &'a DepthTargetTexture2D, + }, + Texture2DArray { + texture: &'a DepthTargetTexture2DArray, + layer: u32, + }, + TextureCubeMap { + texture: &'a DepthTargetTextureCubeMap, + side: CubeMapSide, + }, +} + +impl<'a> DepthTarget<'a> { + pub(in crate::core) fn new_texture2d( + context: &Context, + texture: &'a DepthTargetTexture2D, + ) -> Self { + Self { + context: context.clone(), + target: DT::Texture2D { texture }, + } + } + + pub(in crate::core) fn new_texture_cube_map( + context: &Context, + texture: &'a DepthTargetTextureCubeMap, + side: CubeMapSide, + ) -> Self { + Self { + context: context.clone(), + target: DT::TextureCubeMap { texture, side }, + } + } + + pub(in crate::core) fn new_texture_2d_array( + context: &Context, + texture: &'a DepthTargetTexture2DArray, + layer: u32, + ) -> Self { + Self { + context: context.clone(), + target: DT::Texture2DArray { texture, layer }, + } + } + + /// + /// Clears the depth of this depth target as defined by the given clear state. + /// + pub fn clear(&self, clear_state: ClearState) -> ThreeDResult<&Self> { + self.clear_partially(self.scissor_box(), clear_state) + } + + /// + /// Clears the depth of the part of this depth target that is inside the given scissor box. + /// + pub fn clear_partially( + &self, + scissor_box: ScissorBox, + clear_state: ClearState, + ) -> ThreeDResult<&Self> { + self.as_render_target()?.clear_partially( + scissor_box, + ClearState { + depth: clear_state.depth, + ..ClearState::none() + }, + )?; + Ok(self) + } + + /// + /// Writes whatever rendered in the `render` closure into this depth target. + /// + pub fn write(&self, render: impl FnOnce() -> ThreeDResult<()>) -> ThreeDResult<&Self> { + self.write_partially(self.scissor_box(), render) + } + + /// + /// Writes whatever rendered in the `render` closure into the part of this depth target defined by the scissor box. + /// + pub fn write_partially( + &self, + scissor_box: ScissorBox, + render: impl FnOnce() -> ThreeDResult<()>, + ) -> ThreeDResult<&Self> { + self.as_render_target()? + .write_partially(scissor_box, render)?; + Ok(self) + } + + /// + /// Returns the depth values in this depth target. + /// + #[cfg(not(target_arch = "wasm32"))] + pub fn read(&self) -> ThreeDResult> { + self.read_partially(self.scissor_box()) + } + + /// + /// Returns the depth values in this depth target inside the given scissor box. + /// + #[cfg(not(target_arch = "wasm32"))] + pub fn read_partially(&self, scissor_box: ScissorBox) -> ThreeDResult> { + self.as_render_target()?.read_depth_partially(scissor_box) + } + + fn as_render_target(&self) -> ThreeDResult> { + RenderTarget::new_depth(self.clone()) + } + + /// + /// Returns the width of the depth target in texels, which is simply the width of the underlying texture. + /// + pub fn width(&self) -> u32 { + match &self.target { + DT::Texture2D { texture, .. } => texture.width(), + DT::Texture2DArray { texture, .. } => texture.width(), + DT::TextureCubeMap { texture, .. } => texture.width(), + } + } + + /// + /// Returns the height of the depth target in texels, which is simply the height of the underlying texture. + /// + pub fn height(&self) -> u32 { + match &self.target { + DT::Texture2D { texture, .. } => texture.height(), + DT::Texture2DArray { texture, .. } => texture.height(), + DT::TextureCubeMap { texture, .. } => texture.height(), + } + } + + /// + /// Returns the scissor box that encloses the entire target. + /// + pub fn scissor_box(&self) -> ScissorBox { + ScissorBox::new_at_origo(self.width(), self.height()) + } + + fn bind(&self) { + match &self.target { + DT::Texture2D { texture } => { + texture.bind_as_depth_target(); + } + DT::Texture2DArray { texture, layer } => { + texture.bind_as_depth_target(*layer); + } + DT::TextureCubeMap { texture, side } => { + texture.bind_as_depth_target(*side); + } + } + } +} + +use crate::context::Framebuffer; +/// +/// Adds additional functionality to clear, read from and write to the screen (see [RenderTarget::screen]) or a color texture and +/// a depth texture at the same time (see [RenderTarget::new]). +/// If you only want to perform an operation on either a color texture or depth texture, see [ColorTarget] and [DepthTarget] respectively. +/// A render target purely adds functionality, so it can be created each time it is needed, the actual data is saved in the textures. +/// +pub struct RenderTarget<'a> { + id: Option, + color: Option>, + depth: Option>, + context: Context, + width: u32, + height: u32, +} + +impl<'a> RenderTarget<'a> { /// /// Returns the screen render target for this context. /// Write to this render target to draw something on the screen. /// - pub fn screen(context: &Context) -> ThreeDResult { - Ok(Self { + pub fn screen(context: &Context, width: u32, height: u32) -> Self { + Self { context: context.clone(), id: None, - color_texture: None, - depth_texture: None, - }) + color: None, + depth: None, + width, + height, + } } /// - /// Constructs a new render target that enables rendering into the given - /// [DepthTargetTexture2D]. + /// Constructs a new render target that enables rendering into the given [ColorTarget] and [DepthTarget]. /// - pub fn new_depth( - context: &Context, - depth_texture: &'b mut DepthTargetTexture2D, - ) -> ThreeDResult { + pub fn new(color: ColorTarget<'a>, depth: DepthTarget<'a>) -> ThreeDResult { + let width = color.width(); + let height = color.height(); Ok(Self { - context: context.clone(), - id: Some(new_framebuffer(context)?), - color_texture: None, - depth_texture: Some(depth_texture), + context: color.context.clone(), + id: Some(new_framebuffer(&color.context)?), + color: Some(color), + depth: Some(depth), + width, + height, }) } + /// - /// Constructs a new render target that enables rendering into the given - /// [ColorTargetTexture2D] and [DepthTargetTexture2D] textures. + /// Clears the color and depth of this render target as defined by the given clear state. /// - pub fn new( - context: &Context, - color_texture: &'a mut Texture2D, - depth_texture: &'b mut DepthTargetTexture2D, - ) -> ThreeDResult { - Ok(Self { - context: context.clone(), - id: Some(new_framebuffer(context)?), - color_texture: Some(color_texture), - depth_texture: Some(depth_texture), - }) + pub fn clear(&self, clear_state: ClearState) -> ThreeDResult<&Self> { + self.clear_partially(self.scissor_box(), clear_state) } /// - /// Constructs a new render target that enables rendering into the given - /// [ColorTargetTexture2D]. + /// Clears the color and depth of the part of this render target that is inside the given scissor box. /// - pub fn new_color(context: &Context, color_texture: &'a mut Texture2D) -> ThreeDResult { - Self::new_color_internal(context, color_texture) + pub fn clear_partially( + &self, + scissor_box: ScissorBox, + clear_state: ClearState, + ) -> ThreeDResult<&Self> { + self.context.set_scissor(scissor_box); + self.bind(crate::context::DRAW_FRAMEBUFFER)?; + clear_state.apply(&self.context); + self.context.error_check()?; + Ok(self) } - pub(in crate::core) fn new_color_internal( - context: &Context, - color_texture: &'a Texture2D, - ) -> ThreeDResult { - Ok(Self { - context: context.clone(), - id: Some(new_framebuffer(context)?), - color_texture: Some(color_texture), - depth_texture: None, - }) + /// + /// Writes whatever rendered in the `render` closure into this render target. + /// + pub fn write(&self, render: impl FnOnce() -> ThreeDResult<()>) -> ThreeDResult<&Self> { + self.write_partially(self.scissor_box(), render) } /// - /// Renders whatever rendered in the `render` closure into the textures defined at construction. - /// Before writing, the textures are cleared based on the given clear state. + /// Writes whatever rendered in the `render` closure into the part of this render target defined by the scissor box. /// - pub fn write( + pub fn write_partially( &self, - clear_state: ClearState, + scissor_box: ScissorBox, render: impl FnOnce() -> ThreeDResult<()>, - ) -> ThreeDResult<()> { + ) -> ThreeDResult<&Self> { + self.context.set_scissor(scissor_box); self.bind(crate::context::DRAW_FRAMEBUFFER)?; - if self.id.is_some() { - clear( - &self.context, - &ClearState { - red: self.color_texture.as_ref().and(clear_state.red), - green: self.color_texture.as_ref().and(clear_state.green), - blue: self.color_texture.as_ref().and(clear_state.blue), - alpha: self.color_texture.as_ref().and(clear_state.alpha), - depth: self.depth_texture.as_ref().and(clear_state.depth), - }, - ); - } else { - clear(&self.context, &clear_state); - } render()?; - if let Some(ref color_texture) = self.color_texture { - color_texture.generate_mip_maps(); + if let Some(ref color) = self.color { + color.generate_mip_maps(); } - self.context.error_check() + self.context.error_check()?; + Ok(self) + } + + /// + /// Returns the colors of the pixels in this render target. + /// The number of channels per pixel and the data format for each channel is specified by the generic parameter. + /// + /// **Note:** On web, the data format needs to match the data format of the color texture. + /// + pub fn read_color(&self) -> ThreeDResult> { + self.read_color_partially(self.scissor_box()) } /// - /// Returns the values of the pixels in this texture inside the given viewport. + /// Returns the colors of the pixels in this render target inside the given scissor box. /// The number of channels per pixel and the data format for each channel is specified by the generic parameter. /// /// **Note:** On web, the data format needs to match the data format of the color texture. /// - pub fn read_color(&self, viewport: Viewport) -> ThreeDResult> { - if self.id.is_some() && self.color_texture.is_none() { + pub fn read_color_partially( + &self, + scissor_box: ScissorBox, + ) -> ThreeDResult> { + if self.id.is_some() && self.color.is_none() { Err(CoreError::RenderTargetRead("color".to_string()))?; } self.bind(crate::context::DRAW_FRAMEBUFFER)?; @@ -126,13 +541,14 @@ impl<'a, 'b> RenderTarget<'a, 'b> { if data_size / T::size() as usize == 1 { data_size *= 4 / T::size() as usize } - let mut bytes = vec![0u8; viewport.width as usize * viewport.height as usize * data_size]; + let mut bytes = + vec![0u8; scissor_box.width as usize * scissor_box.height as usize * data_size]; unsafe { self.context.read_pixels( - viewport.x as i32, - viewport.y as i32, - viewport.width as i32, - viewport.height as i32, + scissor_box.x as i32, + scissor_box.y as i32, + scissor_box.width as i32, + scissor_box.height as i32, format_from_data_type::(), T::data_type(), crate::context::PixelPackData::Slice(&mut bytes), @@ -142,30 +558,37 @@ impl<'a, 'b> RenderTarget<'a, 'b> { let mut pixels = from_byte_slice(&bytes).to_vec(); flip_y( &mut pixels, - viewport.width as usize, - viewport.height as usize, + scissor_box.width as usize, + scissor_box.height as usize, ); Ok(pixels) } /// - /// Returns the depth values from the screen as a list of 32-bit floats. - /// Only available on desktop. + /// Returns the depth values in this render target. + /// + #[cfg(not(target_arch = "wasm32"))] + pub fn read_depth(&self) -> ThreeDResult> { + self.read_depth_partially(self.scissor_box()) + } + + /// + /// Returns the depth values in this render target inside the given scissor box. /// #[cfg(not(target_arch = "wasm32"))] - pub fn read_depth(&self, viewport: Viewport) -> ThreeDResult> { - if self.id.is_some() && self.depth_texture.is_none() { + pub fn read_depth_partially(&self, scissor_box: ScissorBox) -> ThreeDResult> { + if self.id.is_some() && self.depth.is_none() { Err(CoreError::RenderTargetRead("depth".to_string()))?; } self.bind(crate::context::DRAW_FRAMEBUFFER)?; self.bind(crate::context::READ_FRAMEBUFFER)?; - let mut pixels = vec![0u8; viewport.width as usize * viewport.height as usize * 4]; + let mut pixels = vec![0u8; scissor_box.width as usize * scissor_box.height as usize * 4]; unsafe { self.context.read_pixels( - viewport.x as i32, - viewport.y as i32, - viewport.width as i32, - viewport.height as i32, + scissor_box.x as i32, + scissor_box.y as i32, + scissor_box.width as i32, + scissor_box.height as i32, crate::context::DEPTH_COMPONENT, crate::context::FLOAT, crate::context::PixelPackData::Slice(&mut pixels), @@ -176,22 +599,22 @@ impl<'a, 'b> RenderTarget<'a, 'b> { } /// - /// Copies the content of the color and depth texture to the specified viewport of this render target. + /// Copies the content of the color and depth texture to the specified scissor box of this render target. /// Only copies the channels given by the write mask. /// pub fn copy_from( &self, color_texture: Option<&Texture2D>, depth_texture: Option<&DepthTargetTexture2D>, - viewport: Viewport, + scissor_box: ScissorBox, write_mask: WriteMask, - ) -> ThreeDResult<()> { - self.write(ClearState::none(), || { + ) -> ThreeDResult<&Self> { + self.write(|| { copy_from( &self.context, color_texture, depth_texture, - viewport, + scissor_box.into(), write_mask, ) }) @@ -205,38 +628,69 @@ impl<'a, 'b> RenderTarget<'a, 'b> { &self, color_texture: Option<(&Texture2DArray, u32)>, depth_texture: Option<(&DepthTargetTexture2DArray, u32)>, - viewport: Viewport, + scissor_box: ScissorBox, write_mask: WriteMask, - ) -> ThreeDResult<()> { - self.write(ClearState::none(), || { + ) -> ThreeDResult<&Self> { + self.write(|| { copy_from_array( &self.context, color_texture, depth_texture, - viewport, + scissor_box.into(), write_mask, ) }) } - pub(in crate::core) fn bind(&self, target: u32) -> ThreeDResult<()> { + /// + /// Returns the scissor box that encloses the entire target. + /// + pub fn scissor_box(&self) -> ScissorBox { + ScissorBox::new_at_origo(self.width, self.height) + } + + fn new_color(color: ColorTarget<'a>) -> ThreeDResult { + let width = color.width(); + let height = color.height(); + Ok(Self { + context: color.context.clone(), + id: Some(new_framebuffer(&color.context)?), + color: Some(color), + depth: None, + width, + height, + }) + } + + fn new_depth(depth: DepthTarget<'a>) -> ThreeDResult { + let width = depth.width(); + let height = depth.height(); + Ok(Self { + context: depth.context.clone(), + id: Some(new_framebuffer(&depth.context)?), + depth: Some(depth), + color: None, + width, + height, + }) + } + + fn bind(&self, target: u32) -> ThreeDResult<()> { unsafe { self.context.bind_framebuffer(target, self.id); - if let Some(ref tex) = self.color_texture { - self.context - .draw_buffers(&[crate::context::COLOR_ATTACHMENT0]); - tex.bind_as_color_target(0); - } - if let Some(ref tex) = self.depth_texture { - tex.bind_as_depth_target(); - } + } + if let Some(ref color) = self.color { + color.bind(&self.context); + } + if let Some(ref depth) = self.depth { + depth.bind(); } self.context.framebuffer_check()?; self.context.error_check() } } -impl Drop for RenderTarget<'_, '_> { +impl Drop for RenderTarget<'_> { fn drop(&mut self) { unsafe { if let Some(id) = self.id { @@ -245,3 +699,11 @@ impl Drop for RenderTarget<'_, '_> { } } } + +fn size_with_mip(size: u32, mip: Option) -> u32 { + if let Some(mip) = mip { + size / 2u32.pow(mip) + } else { + size + } +} diff --git a/src/core/render_target/render_target2d_array.rs b/src/core/render_target/render_target2d_array.rs index 3c225c97c..d374f242d 100644 --- a/src/core/render_target/render_target2d_array.rs +++ b/src/core/render_target/render_target2d_array.rs @@ -1,3 +1,4 @@ +#![allow(deprecated)] use crate::core::render_target::*; /// @@ -5,6 +6,7 @@ use crate::core::render_target::*; /// a [DepthTargetTexture2DArray] at the same time. /// It purely adds functionality, so it can be created each time it is needed, the data is saved in the textures. /// +#[deprecated = "use RenderTarget instead"] pub struct RenderTargetArray<'a, 'b> { context: Context, id: crate::context::Framebuffer, @@ -105,7 +107,11 @@ impl<'a, 'b> RenderTargetArray<'a, 'b> { .collect::>(), ); for channel in 0..color_layers.len() { - color_texture.bind_as_color_target(color_layers[channel], channel as u32); + color_texture.bind_as_color_target( + color_layers[channel], + channel as u32, + 0, + ); } } } @@ -127,3 +133,39 @@ impl Drop for RenderTargetArray<'_, '_> { } } } + +fn clear(context: &Context, clear_state: &ClearState) { + context.set_write_mask(WriteMask { + red: clear_state.red.is_some(), + green: clear_state.green.is_some(), + blue: clear_state.blue.is_some(), + alpha: clear_state.alpha.is_some(), + depth: clear_state.depth.is_some(), + }); + let clear_color = clear_state.red.is_some() + || clear_state.green.is_some() + || clear_state.blue.is_some() + || clear_state.alpha.is_some(); + unsafe { + if clear_color { + context.clear_color( + clear_state.red.unwrap_or(0.0), + clear_state.green.unwrap_or(0.0), + clear_state.blue.unwrap_or(0.0), + clear_state.alpha.unwrap_or(1.0), + ); + } + if let Some(depth) = clear_state.depth { + context.clear_depth_f32(depth); + } + context.clear(if clear_color && clear_state.depth.is_some() { + crate::context::COLOR_BUFFER_BIT | crate::context::DEPTH_BUFFER_BIT + } else { + if clear_color { + crate::context::COLOR_BUFFER_BIT + } else { + crate::context::DEPTH_BUFFER_BIT + } + }); + } +} diff --git a/src/core/render_target/render_target_cube_map.rs b/src/core/render_target/render_target_cube_map.rs index 3d1839cc6..31c427a5e 100644 --- a/src/core/render_target/render_target_cube_map.rs +++ b/src/core/render_target/render_target_cube_map.rs @@ -1,3 +1,4 @@ +#![allow(deprecated)] use crate::core::render_target::*; /// @@ -5,6 +6,7 @@ use crate::core::render_target::*; /// a [DepthTargetTextureCubeMap] at the same time. /// It purely adds functionality, so it can be created each time it is needed, the data is saved in the textures. /// +#[deprecated = "use RenderTarget instead"] pub struct RenderTargetCubeMap<'a, 'b> { context: Context, id: crate::context::Framebuffer, @@ -126,3 +128,39 @@ impl Drop for RenderTargetCubeMap<'_, '_> { } } } + +fn clear(context: &Context, clear_state: &ClearState) { + context.set_write_mask(WriteMask { + red: clear_state.red.is_some(), + green: clear_state.green.is_some(), + blue: clear_state.blue.is_some(), + alpha: clear_state.alpha.is_some(), + depth: clear_state.depth.is_some(), + }); + let clear_color = clear_state.red.is_some() + || clear_state.green.is_some() + || clear_state.blue.is_some() + || clear_state.alpha.is_some(); + unsafe { + if clear_color { + context.clear_color( + clear_state.red.unwrap_or(0.0), + clear_state.green.unwrap_or(0.0), + clear_state.blue.unwrap_or(0.0), + clear_state.alpha.unwrap_or(1.0), + ); + } + if let Some(depth) = clear_state.depth { + context.clear_depth_f32(depth); + } + context.clear(if clear_color && clear_state.depth.is_some() { + crate::context::COLOR_BUFFER_BIT | crate::context::DEPTH_BUFFER_BIT + } else { + if clear_color { + crate::context::COLOR_BUFFER_BIT + } else { + crate::context::DEPTH_BUFFER_BIT + } + }); + } +} diff --git a/src/core/render_target/screen.rs b/src/core/render_target/screen.rs index 0457cb6aa..d3fad8fff 100644 --- a/src/core/render_target/screen.rs +++ b/src/core/render_target/screen.rs @@ -1,8 +1,11 @@ +#![allow(deprecated)] + use crate::core::render_target::*; /// /// The screen render target which is essential to get something on the screen (see the [write function](Screen::write)). /// +#[deprecated = "use RenderTarget::screen or FrameInput::screen to get the screen render target"] pub struct Screen {} impl Screen { @@ -16,14 +19,17 @@ impl Screen { clear_state: ClearState, render: F, ) -> ThreeDResult<()> { - RenderTarget::screen(context)?.write(clear_state, render) + RenderTarget::screen(context, 0, 0) + .clear(clear_state)? + .write(render)?; + Ok(()) } /// /// Returns the RGBA color values from the screen as a list of bytes (one byte for each color channel). /// pub fn read_color(context: &Context, viewport: Viewport) -> ThreeDResult> { - RenderTarget::screen(context)?.read_color(viewport) + RenderTarget::screen(context, 0, 0).read_color_partially(viewport.into()) } /// @@ -32,7 +38,7 @@ impl Screen { /// #[cfg(not(target_arch = "wasm32"))] pub fn read_depth(context: &Context, viewport: Viewport) -> ThreeDResult> { - RenderTarget::screen(context)?.read_depth(viewport) + RenderTarget::screen(context, 0, 0).read_depth_partially(viewport.into()) } /// @@ -46,7 +52,13 @@ impl Screen { viewport: Viewport, write_mask: WriteMask, ) -> ThreeDResult<()> { - RenderTarget::screen(context)?.copy_from(color_texture, depth_texture, viewport, write_mask) + RenderTarget::screen(context, 0, 0).copy_from( + color_texture, + depth_texture, + viewport.into(), + write_mask, + )?; + Ok(()) } /// @@ -60,11 +72,12 @@ impl Screen { viewport: Viewport, write_mask: WriteMask, ) -> ThreeDResult<()> { - RenderTarget::screen(context)?.copy_from_array( + RenderTarget::screen(context, 0, 0).copy_from_array( color_texture, depth_texture, - viewport, + viewport.into(), write_mask, - ) + )?; + Ok(()) } } diff --git a/src/core/scissor_box.rs b/src/core/scissor_box.rs new file mode 100644 index 000000000..d5aa1a6ec --- /dev/null +++ b/src/core/scissor_box.rs @@ -0,0 +1,41 @@ +/// +/// Defines the part of the screen or render target that is rendered to. +/// All pixels outside of the scissor box will not be modified. +/// All values should be given in physical pixels. +/// +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ScissorBox { + /// The distance in pixels from the left edge of the target. + pub x: i32, + /// The distance in pixels from the bottom edge of the target. + pub y: i32, + /// The width of the box. + pub width: u32, + /// The height of the box. + pub height: u32, +} + +impl ScissorBox { + /// + /// Creates a new scissor box which starts at origo (x and y are both zero). + /// + pub fn new_at_origo(width: u32, height: u32) -> Self { + Self { + x: 0, + y: 0, + width, + height, + } + } +} + +impl From for ScissorBox { + fn from(viewport: crate::core::Viewport) -> Self { + Self { + x: viewport.x, + y: viewport.y, + width: viewport.width, + height: viewport.height, + } + } +} diff --git a/src/core/texture/depth_target_texture2d.rs b/src/core/texture/depth_target_texture2d.rs index 557502462..5b73531c0 100644 --- a/src/core/texture/depth_target_texture2d.rs +++ b/src/core/texture/depth_target_texture2d.rs @@ -67,22 +67,31 @@ impl DepthTargetTexture2D { Ok(texture) } + /// + /// Returns a [DepthTarget] which can be used to clear, write to and read from this texture. + /// Combine this together with a [ColorTarget] with [RenderTarget::new] to be able to write to both a depth and color target at the same time. + /// + pub fn as_depth_target(&mut self) -> DepthTarget { + DepthTarget::new_texture2d(&self.context, self) + } + /// /// Write the depth of whatever rendered in the `render` closure into the texture. /// Before writing, the texture is cleared based on the given clear state. /// + #[deprecated = "use as_depth_target followed by clear and write"] pub fn write ThreeDResult<()>>( &mut self, clear_state: Option, render: F, ) -> ThreeDResult<()> { - RenderTarget::new_depth(&self.context.clone(), self)?.write( - ClearState { + self.as_depth_target() + .clear(ClearState { depth: clear_state, ..ClearState::none() - }, - render, - ) + })? + .write(render)?; + Ok(()) } /// The width of this texture. diff --git a/src/core/texture/depth_target_texture2d_array.rs b/src/core/texture/depth_target_texture2d_array.rs index 8b3c07a97..01c4da35e 100644 --- a/src/core/texture/depth_target_texture2d_array.rs +++ b/src/core/texture/depth_target_texture2d_array.rs @@ -1,7 +1,7 @@ use crate::core::texture::*; /// -/// An array of 2D depth textures that can be rendered into and read from. See also [RenderTargetArray]. +/// An array of 2D depth textures that can be rendered into and read from. See also [RenderTarget]. /// pub struct DepthTargetTexture2DArray { context: Context, @@ -58,24 +58,31 @@ impl DepthTargetTexture2DArray { } /// - /// Writes the depth of whatever rendered in the `render` closure into the depth texture defined by the input parameter `depth_layer`. + /// Returns a [DepthTarget] which can be used to clear, write to and read from the given layer of this texture. + /// Combine this together with a [ColorTarget] with [RenderTarget::new] to be able to write to both a depth and color target at the same time. + /// + pub fn as_depth_target(&mut self, layer: u32) -> DepthTarget { + DepthTarget::new_texture_2d_array(&self.context, self, layer) + } + + /// + /// Writes the depth of whatever rendered in the `render` closure into the depth texture defined by the input parameter `layer`. /// Before writing, the texture is cleared based on the given clear state. /// + #[deprecated = "use as_depth_target followed by clear and write"] pub fn write ThreeDResult<()>>( &mut self, - depth_layer: u32, + layer: u32, clear_state: Option, render: F, ) -> ThreeDResult<()> { - RenderTargetArray::new_depth(&self.context.clone(), self)?.write( - &[], - depth_layer, - ClearState { + self.as_depth_target(layer) + .clear(ClearState { depth: clear_state, ..ClearState::none() - }, - render, - ) + })? + .write(render)?; + Ok(()) } /// The width of this texture. diff --git a/src/core/texture/depth_target_texture_cube_map.rs b/src/core/texture/depth_target_texture_cube_map.rs index 8291ebf96..4363fd8d2 100644 --- a/src/core/texture/depth_target_texture_cube_map.rs +++ b/src/core/texture/depth_target_texture_cube_map.rs @@ -1,7 +1,7 @@ use crate::core::texture::*; /// -/// A depth texture cube map that can be rendered into and read from. See also [RenderTargetCubeMap]. +/// A depth texture cube map that can be rendered into and read from. See also [RenderTarget]. /// pub struct DepthTargetTextureCubeMap { context: Context, @@ -54,24 +54,32 @@ impl DepthTargetTextureCubeMap { Ok(texture) } + /// + /// Returns a [DepthTarget] which can be used to clear, write to and read from the given side of this texture. + /// Combine this together with a [ColorTarget] with [RenderTarget::new] to be able to write to both a depth and color target at the same time. + /// + pub fn as_depth_target(&mut self, side: CubeMapSide) -> DepthTarget { + DepthTarget::new_texture_cube_map(&self.context, self, side) + } + /// /// Writes the depth of whatever rendered in the `render` closure into the depth texture at the cube map side given by the input parameter `side`. /// Before writing, the texture side is cleared based on the given clear state. /// + #[deprecated = "use as_depth_target followed by clear and write"] pub fn write( &mut self, side: CubeMapSide, clear_state: Option, render: impl FnOnce() -> ThreeDResult<()>, ) -> ThreeDResult<()> { - RenderTargetCubeMap::new_depth(&self.context.clone(), self)?.write( - side, - ClearState { + self.as_depth_target(side) + .clear(ClearState { depth: clear_state, ..ClearState::none() - }, - render, - ) + })? + .write(render)?; + Ok(()) } /// The width of this texture. diff --git a/src/core/texture/texture2d.rs b/src/core/texture/texture2d.rs index 98b1136d0..9394f48fc 100644 --- a/src/core/texture/texture2d.rs +++ b/src/core/texture/texture2d.rs @@ -135,6 +135,18 @@ impl Texture2D { self.context.error_check() } + /// + /// Returns a [ColorTarget] which can be used to clear, write to and read from the given mip level of this texture. + /// Combine this together with a [DepthTarget] with [RenderTarget::new] to be able to write to both a depth and color target at the same time. + /// If `None` is specified as the mip level, the 0 level mip level is used and mip maps are generated after a write operation if a mip map filter is specified. + /// Otherwise, the given mip level is used and no mip maps are generated. + /// + /// **Note:** [DepthTest] is disabled if not also writing to a depth texture. + /// + pub fn as_color_target(&mut self, mip_level: Option) -> ColorTarget { + ColorTarget::new_texture2d(&self.context, self, mip_level) + } + /// /// Renders whatever rendered in the `render` closure into the texture. /// Before writing, the texture is cleared based on the given clear state. @@ -142,12 +154,17 @@ impl Texture2D { /// **Note:** [DepthTest] is disabled if not also writing to a depth texture. /// Use a [RenderTarget] to write to both color and depth. /// + #[deprecated = "use as_color_target followed by clear and write"] pub fn write ThreeDResult<()>>( &mut self, clear_state: ClearState, render: F, ) -> ThreeDResult<()> { - RenderTarget::new_color(&self.context.clone(), self)?.write(clear_state, render) + self.as_color_target(None) + .as_render_target()? + .clear(clear_state)? + .write(render)?; + Ok(()) } /// @@ -156,8 +173,9 @@ impl Texture2D { /// /// **Note:** On web, the data format needs to match the data format of this texture. /// - pub fn read(&self, viewport: Viewport) -> ThreeDResult> { - RenderTarget::new_color_internal(&self.context.clone(), self)?.read_color(viewport) + #[deprecated = "use as_color_target followed by read"] + pub fn read(&mut self, viewport: Viewport) -> ThreeDResult> { + self.as_color_target(None).read_partially(viewport.into()) } /// The width of this texture. @@ -179,14 +197,14 @@ impl Texture2D { } } - pub(in crate::core) fn bind_as_color_target(&self, channel: u32) { + pub(in crate::core) fn bind_as_color_target(&self, channel: u32, mip_level: u32) { unsafe { self.context.framebuffer_texture_2d( crate::context::FRAMEBUFFER, crate::context::COLOR_ATTACHMENT0 + channel, crate::context::TEXTURE_2D, Some(self.id), - 0, + mip_level as i32, ); } } diff --git a/src/core/texture/texture2d_array.rs b/src/core/texture/texture2d_array.rs index e5c53562b..019599b80 100644 --- a/src/core/texture/texture2d_array.rs +++ b/src/core/texture/texture2d_array.rs @@ -3,8 +3,8 @@ use crate::core::texture::*; /// /// A array of 2D color textures that can be rendered into. /// -/// **Note:** [DepthTest] is disabled if not also writing to a depth texture array. -/// Use a [RenderTargetArray] to write to both color and depth. +/// **Note:** [DepthTest] is disabled if not also writing to a [DepthTarget]. +/// Use a [RenderTarget] to write to both color and depth. /// pub struct Texture2DArray { context: Context, @@ -65,30 +65,47 @@ impl Texture2DArray { depth as i32, ); } + texture.generate_mip_maps(); context.error_check()?; Ok(texture) } /// - /// Renders whatever rendered in the `render` closure into the textures defined by the input parameters `color_layers`. - /// Output at location *i* defined in the fragment shader is written to the color texture layer at the *ith* index in `color_layers`. + /// Returns a [ColorTarget] which can be used to clear, write to and read from the given layers and mip level of this texture. + /// Combine this together with a [DepthTarget] with [RenderTarget::new] to be able to write to both a depth and color target at the same time. + /// If `None` is specified as the mip level, the 0 level mip level is used and mip maps are generated after a write operation if a mip map filter is specified. + /// Otherwise, the given mip level is used and no mip maps are generated. + /// + /// **Note:** [DepthTest] is disabled if not also writing to a depth texture. + /// + pub fn as_color_target<'a>( + &'a mut self, + layers: &'a [u32], + mip_level: Option, + ) -> ColorTarget<'a> { + ColorTarget::new_texture_2d_array(&self.context, self, layers, mip_level) + } + + /// + /// Renders whatever rendered in the `render` closure into the textures defined by the input parameters `layers`. + /// Output at location *i* defined in the fragment shader is written to the color texture layer at the *ith* index in `layers`. /// Before writing, the textures are cleared based on the given clear state. /// - /// **Note:** [DepthTest] is disabled if not also writing to a depth texture array. - /// Use a [RenderTargetArray] to write to both color and depth. + /// **Note:** [DepthTest] is disabled if not also writing to a [DepthTarget]. + /// Use a [RenderTarget] to write to both color and depth. /// - pub fn write ThreeDResult<()>>( - &mut self, - color_layers: &[u32], + #[deprecated = "use as_color_target followed by clear and write"] + pub fn write<'a, F: FnOnce() -> ThreeDResult<()>>( + &'a mut self, + layers: &'a [u32], clear_state: ClearState, render: F, ) -> ThreeDResult<()> { - RenderTargetArray::new_color(&self.context.clone(), self)?.write( - color_layers, - 0, - clear_state, - render, - ) + self.as_color_target(layers, None) + .as_render_target()? + .clear(clear_state)? + .write(render)?; + Ok(()) } /// The width of this texture. @@ -116,13 +133,13 @@ impl Texture2DArray { } } - pub(in crate::core) fn bind_as_color_target(&self, layer: u32, channel: u32) { + pub(in crate::core) fn bind_as_color_target(&self, layer: u32, channel: u32, mip_level: u32) { unsafe { self.context.framebuffer_texture_layer( crate::context::DRAW_FRAMEBUFFER, crate::context::COLOR_ATTACHMENT0 + channel, Some(self.id), - 0, + mip_level as i32, layer as i32, ); } diff --git a/src/core/texture/texture3d.rs b/src/core/texture/texture3d.rs index 3523bd408..412c4d703 100644 --- a/src/core/texture/texture3d.rs +++ b/src/core/texture/texture3d.rs @@ -72,7 +72,7 @@ impl Texture3D { let id = generate(context)?; let number_of_mip_maps = calculate_number_of_mip_maps(mip_map_filter, width, height, Some(depth)); - let tex = Self { + let texture = Self { context: context.clone(), id, width, @@ -81,7 +81,7 @@ impl Texture3D { number_of_mip_maps, data_byte_size: std::mem::size_of::(), }; - tex.bind(); + texture.bind(); set_parameters( context, crate::context::TEXTURE_3D, @@ -106,8 +106,9 @@ impl Texture3D { depth as i32, ); } + texture.generate_mip_maps(); context.error_check()?; - Ok(tex) + Ok(texture) } /// diff --git a/src/core/texture/texture_cube_map.rs b/src/core/texture/texture_cube_map.rs index e9e119f42..cdc5adce2 100644 --- a/src/core/texture/texture_cube_map.rs +++ b/src/core/texture/texture_cube_map.rs @@ -345,40 +345,58 @@ impl TextureCubeMap { outColor = vec4(texture(equirectangularMap, uv).rgb, 1.0); }"; let effect = ImageCubeEffect::new(context, fragment_shader_source)?; - let render_target = RenderTargetCubeMap::new_color(context, &mut texture)?; for side in CubeMapSide::iter() { effect.use_texture("equirectangularMap", &map)?; let viewport = Viewport::new_at_origo(texture_size, texture_size); - render_target.write(side, ClearState::default(), || { - effect.render(side, RenderStates::default(), viewport) - })?; + texture + .as_color_target(side, None) + .clear(ClearState::default())? + .write(|| effect.render(side, RenderStates::default(), viewport))?; } } Ok(texture) } + /// + /// Returns a [ColorTarget] which can be used to clear, write to and read from the given side and mip level of this texture. + /// Combine this together with a [DepthTarget] with [RenderTarget::new] to be able to write to both a depth and color target at the same time. + /// If `None` is specified as the mip level, the 0 level mip level is used and mip maps are generated after a write operation if a mip map filter is specified. + /// Otherwise, the given mip level is used and no mip maps are generated. + /// + /// **Note:** [DepthTest] is disabled if not also writing to a depth texture. + /// + pub fn as_color_target<'a>( + &'a mut self, + side: CubeMapSide, + mip_level: Option, + ) -> ColorTarget<'a> { + ColorTarget::new_texture_cube_map(&self.context, self, side, mip_level) + } + /// /// Writes whatever rendered in the `render` closure into the color texture at the cube map side given by the input parameter `side`. /// Before writing, the texture side is cleared based on the given clear state. /// + #[deprecated = "use as_color_target followed by clear and write"] pub fn write( &mut self, side: CubeMapSide, clear_state: ClearState, render: impl FnOnce() -> ThreeDResult<()>, ) -> ThreeDResult<()> { - RenderTargetCubeMap::new_color(&self.context.clone(), self)?.write( - side, - clear_state, - render, - ) + self.as_color_target(side, None) + .as_render_target()? + .clear(clear_state)? + .write(render)?; + Ok(()) } /// /// Writes whatever rendered in the `render` closure into the given mip level of the color texture at the cube map side given by the input parameter `side`. /// Before writing, the texture side is cleared based on the given clear state. /// + #[deprecated = "use as_color_target followed by clear and write"] pub fn write_to_mip_level( &mut self, side: CubeMapSide, @@ -386,12 +404,11 @@ impl TextureCubeMap { clear_state: ClearState, render: impl FnOnce() -> ThreeDResult<()>, ) -> ThreeDResult<()> { - RenderTargetCubeMap::new_color(&self.context.clone(), self)?.write_to_mip_level( - side, - mip_level, - clear_state, - render, - ) + self.as_color_target(side, Some(mip_level)) + .as_render_target()? + .clear(clear_state)? + .write(render)?; + Ok(()) } /// The width of this texture. diff --git a/src/core/viewport.rs b/src/core/viewport.rs index bb6b317cf..323aa4b13 100644 --- a/src/core/viewport.rs +++ b/src/core/viewport.rs @@ -34,3 +34,14 @@ impl Viewport { self.width as f32 / self.height as f32 } } + +impl From for Viewport { + fn from(viewport: crate::core::ScissorBox) -> Self { + Self { + x: viewport.x, + y: viewport.y, + width: viewport.width, + height: viewport.height, + } + } +} diff --git a/src/gui/egui_gui.rs b/src/gui/egui_gui.rs index 65b999dd2..b11b273e2 100644 --- a/src/gui/egui_gui.rs +++ b/src/gui/egui_gui.rs @@ -111,8 +111,8 @@ impl GUI { } /// - /// Render the GUI defined in the [update](Self::update) function. Must be called in a render target render function, - /// for example in the callback function of [Screen::write](crate::Screen::write). + /// Render the GUI defined in the [update](Self::update) function. + /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. /// pub fn render(&mut self) -> ThreeDResult<()> { let (_, shapes) = self.egui_context.end_frame(); @@ -146,18 +146,9 @@ impl GUI { (self.height as f32 * scale).round() as u32, ); - for egui::ClippedMesh(rect, mesh) in clipped_meshes { - let width = rect.max.x - rect.min.x; - let height = rect.max.y - rect.min.y; - let clipping = Clip::Enabled { - x: (rect.min.x * scale) as u32, - y: ((self.height as f32 - rect.max.y) * scale) as u32, - width: (width * scale) as u32, - height: (height * scale) as u32, - }; + for egui::ClippedMesh(_, mesh) in clipped_meshes { self.paint_mesh( viewport, - clipping, self.width, self.height, &mesh, @@ -170,7 +161,6 @@ impl GUI { fn paint_mesh( &self, viewport: Viewport, - clip: Clip, width: u32, height: u32, mesh: &egui::paint::Mesh, @@ -208,7 +198,6 @@ impl GUI { alpha_equation: BlendEquationType::Add, }, depth_test: DepthTest::Always, - clip, ..Default::default() }; diff --git a/src/renderer.rs b/src/renderer.rs index a925acfb2..65bba3ebf 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -6,7 +6,8 @@ pub use crate::core::{ math::*, render_states::*, render_target::*, texture::*, AxisAlignedBoundingBox, Camera, Context, CpuMaterial, CpuMesh, CpuTexture, CpuTexture3D, CpuTextureCube, CpuVolume, - GeometryFunction, Indices, LightingModel, NormalDistributionFunction, Positions, Viewport, + GeometryFunction, Indices, LightingModel, NormalDistributionFunction, Positions, ScissorBox, + Viewport, }; pub mod material; @@ -41,9 +42,106 @@ use thiserror::Error; #[allow(missing_docs)] pub enum RendererError {} +impl<'a> DepthTarget<'a> { + /// + /// Render the objects using the given camera and lights into this depth target. + /// Use an empty array for the `lights` argument, if the objects does not require lights to be rendered. + /// Also, objects outside the camera frustum are not rendered and the objects are rendered in the order given by [cmp_render_order]. + /// + pub fn render( + &self, + camera: &Camera, + objects: &[&dyn Object], + lights: &[&dyn Light], + ) -> ThreeDResult<&Self> { + self.render_partially(self.scissor_box(), camera, objects, lights) + } + + /// + /// Render the objects using the given camera and lights into the part of this depth target defined by the scissor box. + /// Use an empty array for the `lights` argument, if the objects does not require lights to be rendered. + /// Also, objects outside the camera frustum are not rendered and the objects are rendered in the order given by [cmp_render_order]. + /// + pub fn render_partially( + &self, + scissor_box: ScissorBox, + camera: &Camera, + objects: &[&dyn Object], + lights: &[&dyn Light], + ) -> ThreeDResult<&Self> { + self.write_partially(scissor_box, || render_pass(camera, objects, lights))?; + Ok(self) + } +} + +impl<'a> ColorTarget<'a> { + /// + /// Render the objects using the given camera and lights into this color target. + /// Use an empty array for the `lights` argument, if the objects does not require lights to be rendered. + /// Also, objects outside the camera frustum are not rendered and the objects are rendered in the order given by [cmp_render_order]. + /// + pub fn render( + &self, + camera: &Camera, + objects: &[&dyn Object], + lights: &[&dyn Light], + ) -> ThreeDResult<&Self> { + self.render_partially(self.scissor_box(), camera, objects, lights) + } + + /// + /// Render the objects using the given camera and lights into the part of this color target defined by the scissor box. + /// Use an empty array for the `lights` argument, if the objects does not require lights to be rendered. + /// Also, objects outside the camera frustum are not rendered and the objects are rendered in the order given by [cmp_render_order]. + /// + pub fn render_partially( + &self, + scissor_box: ScissorBox, + camera: &Camera, + objects: &[&dyn Object], + lights: &[&dyn Light], + ) -> ThreeDResult<&Self> { + self.write_partially(scissor_box, || render_pass(camera, objects, lights))?; + Ok(self) + } +} + +impl<'a> RenderTarget<'a> { + /// + /// Render the objects using the given camera and lights into this render target. + /// Use an empty array for the `lights` argument, if the objects does not require lights to be rendered. + /// Also, objects outside the camera frustum are not rendered and the objects are rendered in the order given by [cmp_render_order]. + /// + pub fn render( + &self, + camera: &Camera, + objects: &[&dyn Object], + lights: &[&dyn Light], + ) -> ThreeDResult<&Self> { + self.render_partially(self.scissor_box(), camera, objects, lights) + } + + /// + /// Render the objects using the given camera and lights into the part of this render target defined by the scissor box. + /// Use an empty array for the `lights` argument, if the objects does not require lights to be rendered. + /// Also, objects outside the camera frustum are not rendered and the objects are rendered in the order given by [cmp_render_order]. + /// + pub fn render_partially( + &self, + scissor_box: ScissorBox, + camera: &Camera, + objects: &[&dyn Object], + lights: &[&dyn Light], + ) -> ThreeDResult<&Self> { + self.write_partially(scissor_box, || render_pass(camera, objects, lights))?; + Ok(self) + } +} + /// -/// Render the objects. Also avoids rendering objects outside the camera frustum and render the objects in the order given by [cmp_render_order]. -/// Must be called in a render target render function, for example in the callback function of [Screen::write]. +/// Render the objects using the given camera and lights. If the objects materials doesn't require lighting, you can use `&[]` as the `lights` argument. +/// Also, objects outside the camera frustum are not rendered and the objects are rendered in the order given by [cmp_render_order]. +/// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. /// pub fn render_pass( camera: &Camera, @@ -167,23 +265,18 @@ pub fn ray_intersect( }, ..Default::default() }; - { - let render_target = RenderTarget::new(context, &mut texture, &mut depth_texture)?; - render_target.write( - ClearState { - red: Some(1.0), - depth: Some(1.0), - ..ClearState::none() - }, - || { - for geometry in geometries { - geometry.render_with_material(&depth_material, &camera, &[])?; - } - Ok(()) - }, - )?; - } - let depth = texture.read::(viewport)?[0]; + let depth = RenderTarget::new( + texture.as_color_target(None), + depth_texture.as_depth_target(), + )? + .clear(ClearState::color_and_depth(1.0, 1.0, 1.0, 1.0, 1.0))? + .write(|| { + for geometry in geometries { + geometry.render_with_material(&depth_material, &camera, &[])?; + } + Ok(()) + })? + .read_color()?[0]; Ok(if depth < 1.0 { Some(position + direction * depth * max_depth) } else { diff --git a/src/renderer/deferred_pipeline.rs b/src/renderer/deferred_pipeline.rs index 5e257a845..9fc76500f 100644 --- a/src/renderer/deferred_pipeline.rs +++ b/src/renderer/deferred_pipeline.rs @@ -30,7 +30,7 @@ pub struct DeferredPipeline { pub debug_type: DebugType, camera: Camera, geometry_pass_texture: Option, - geometry_pass_depth_texture: Option, + geometry_pass_depth_texture: Option, } impl DeferredPipeline { @@ -62,11 +62,10 @@ impl DeferredPipeline { Wrapping::ClampToEdge, Wrapping::ClampToEdge, )?), - geometry_pass_depth_texture: Some(DepthTargetTexture2DArray::new( + geometry_pass_depth_texture: Some(DepthTargetTexture2D::new( context, 1, 1, - 1, Wrapping::ClampToEdge, Wrapping::ClampToEdge, DepthFormat::Depth32F, @@ -116,21 +115,26 @@ impl DeferredPipeline { Wrapping::ClampToEdge, Wrapping::ClampToEdge, )?); - self.geometry_pass_depth_texture = Some(DepthTargetTexture2DArray::new( + self.geometry_pass_depth_texture = Some(DepthTargetTexture2D::new( &self.context, viewport.width, viewport.height, - 1, Wrapping::ClampToEdge, Wrapping::ClampToEdge, DepthFormat::Depth32F, )?); - RenderTargetArray::new( - &self.context, - self.geometry_pass_texture.as_mut().unwrap(), - self.geometry_pass_depth_texture.as_mut().unwrap(), + RenderTarget::new( + self.geometry_pass_texture + .as_mut() + .unwrap() + .as_color_target(&[0, 1, 2], None), + self.geometry_pass_depth_texture + .as_mut() + .unwrap() + .as_depth_target(), )? - .write(&[0, 1, 2], 0, ClearState::default(), || { + .clear(ClearState::default())? + .write(|| { for (geometry, material) in objects .iter() .filter(|(g, _)| self.camera.in_frustum(&g.aabb())) @@ -145,8 +149,7 @@ impl DeferredPipeline { /// /// Uses the geometry and surface material parameters written in the last [DeferredPipeline::render_pass] call /// and all of the given lights to render the objects. - /// Must be called in a render target render function, - /// for example in the callback function of [Screen::write]. + /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. /// pub fn lighting_pass(&mut self, camera: &Camera, lights: &[&dyn Light]) -> ThreeDResult<()> { let render_states = RenderStates { @@ -169,7 +172,7 @@ impl DeferredPipeline { light.use_uniforms(effect, i as u32)?; } effect.use_texture_array("gbuffer", self.geometry_pass_texture())?; - effect.use_depth_texture_array("depthMap", self.geometry_pass_depth_texture_array())?; + effect.use_depth_texture("depthMap", self.geometry_pass_depth_texture())?; effect.use_uniform_if_required( "viewProjectionInverse", (camera.projection() * camera.view()).invert().unwrap(), @@ -191,33 +194,7 @@ impl DeferredPipeline { } /// Returns the geometry pass depth texture - pub fn geometry_pass_depth_texture_array(&self) -> &DepthTargetTexture2DArray { + pub fn geometry_pass_depth_texture(&self) -> &DepthTargetTexture2D { self.geometry_pass_depth_texture.as_ref().unwrap() } - - /// Returns the geometry pass depth texture - pub fn geometry_pass_depth_texture(&self) -> DepthTargetTexture2D { - let depth_array: &DepthTargetTexture2DArray = - self.geometry_pass_depth_texture.as_ref().unwrap(); - let mut depth_texture = DepthTargetTexture2D::new( - &self.context, - depth_array.width(), - depth_array.height(), - Wrapping::ClampToEdge, - Wrapping::ClampToEdge, - DepthFormat::Depth32F, - ) - .unwrap(); - - RenderTarget::new_depth(&self.context, &mut depth_texture) - .unwrap() - .copy_from_array( - None, - Some((&depth_array, 0)), - Viewport::new_at_origo(depth_array.width(), depth_array.height()), - WriteMask::default(), - ) - .unwrap(); - depth_texture - } } diff --git a/src/renderer/effect/fog.rs b/src/renderer/effect/fog.rs index ca52013b3..8c119d7dc 100644 --- a/src/renderer/effect/fog.rs +++ b/src/renderer/effect/fog.rs @@ -40,8 +40,7 @@ impl FogEffect { /// /// Apply the fog effect on the current render target based on the given depth map. - /// Must be called in a render target render function, - /// for example in the callback function of [Screen::write]. + /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. /// pub fn apply( &self, diff --git a/src/renderer/effect/fxaa.rs b/src/renderer/effect/fxaa.rs index 8726c5ddf..c32911e25 100644 --- a/src/renderer/effect/fxaa.rs +++ b/src/renderer/effect/fxaa.rs @@ -20,8 +20,7 @@ impl FXAAEffect { /// /// Applies the FXAA effect to the image in the given texture and writes the result to the given viewport of the current render target. - /// Must be called in a render target render function, - /// for example in the callback function of [Screen::write]. + /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. /// pub fn apply(&self, viewport: Viewport, texture: &Texture2D) -> ThreeDResult<()> { let render_states = RenderStates { diff --git a/src/renderer/forward_pipeline.rs b/src/renderer/forward_pipeline.rs index e15a82637..899c7b0fa 100644 --- a/src/renderer/forward_pipeline.rs +++ b/src/renderer/forward_pipeline.rs @@ -21,7 +21,7 @@ impl ForwardPipeline { /// /// Render the objects. Also avoids rendering objects outside the camera frustum and render the objects in the order given by [cmp_render_order]. - /// Must be called in a render target render function, for example in the callback function of [Screen::write]. + /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. /// pub fn render_pass( &self, @@ -34,7 +34,7 @@ impl ForwardPipeline { /// /// Render the distance from the camera to the objects in each pixel into a depth texture. Also, do not render transparent objects and objects outside the camera frustum. - /// Must be called in a render target render function, where a depth texture is bound, for example in the callback function of [Screen::write] or [DepthTargetTexture2D::write]. + /// Must be called in the callback given as input to a [RenderTarget] or [DepthTarget] write method. /// pub fn depth_pass(&self, camera: &Camera, objects: &[&dyn Object]) -> ThreeDResult<()> { let depth_material = DepthMaterial { @@ -69,7 +69,10 @@ impl ForwardPipeline { Wrapping::ClampToEdge, DepthFormat::Depth32F, )?; - depth_texture.write(Some(1.0), || self.depth_pass(&camera, objects))?; + depth_texture + .as_depth_target() + .clear(ClearState::default())? + .write(|| self.depth_pass(&camera, objects))?; Ok(depth_texture) } } diff --git a/src/renderer/geometry.rs b/src/renderer/geometry.rs index dd3116f69..c7006ef2e 100644 --- a/src/renderer/geometry.rs +++ b/src/renderer/geometry.rs @@ -38,9 +38,8 @@ use crate::renderer::*; pub trait Geometry { /// /// Render the geometry with the given material. - /// Must be called in a render target render function, - /// for example in the callback function of [Screen::write]. - /// You can use an empty array for the `lights` argument, if you know the object does not require lights to be rendered. + /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. + /// Use an empty array for the `lights` argument, if the objects does not require lights to be rendered. /// fn render_with_material( &self, @@ -136,8 +135,7 @@ impl Geometry for std::rc::Rc> { pub trait Geometry2D { /// /// Render the object with the given material. - /// Must be called in a render target render function, - /// for example in the callback function of [Screen::write](crate::Screen::write). + /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. /// fn render_with_material(&self, material: &dyn Material, viewport: Viewport) -> ThreeDResult<()>; diff --git a/src/renderer/light/directional_light.rs b/src/renderer/light/directional_light.rs index 206df52ed..2643c03fa 100644 --- a/src/renderer/light/directional_light.rs +++ b/src/renderer/light/directional_light.rs @@ -96,15 +96,18 @@ impl DirectionalLight { }, ..Default::default() }; - shadow_texture.write(Some(1.0), || { - for geometry in geometries - .iter() - .filter(|g| shadow_camera.in_frustum(&g.aabb())) - { - geometry.render_with_material(&depth_material, &shadow_camera, &[])?; - } - Ok(()) - })?; + shadow_texture + .as_depth_target() + .clear(ClearState::default())? + .write(|| { + for geometry in geometries + .iter() + .filter(|g| shadow_camera.in_frustum(&g.aabb())) + { + geometry.render_with_material(&depth_material, &shadow_camera, &[])?; + } + Ok(()) + })?; self.shadow_texture = Some(shadow_texture); self.shadow_matrix = shadow_matrix(&shadow_camera); Ok(()) diff --git a/src/renderer/light/environment.rs b/src/renderer/light/environment.rs index f8c13bb4b..e0cd170c2 100644 --- a/src/renderer/light/environment.rs +++ b/src/renderer/light/environment.rs @@ -58,13 +58,13 @@ impl Environment { include_str!("shaders/irradiance.frag") ); let effect = ImageCubeEffect::new(context, &fragment_shader_source)?; - let render_target = RenderTargetCubeMap::new_color(context, &mut irradiance_map)?; for side in CubeMapSide::iter() { effect.use_texture_cube("environmentMap", environment_map)?; let viewport = Viewport::new_at_origo(irradiance_size, irradiance_size); - render_target.write(side, ClearState::default(), || { - effect.render(side, RenderStates::default(), viewport) - })?; + irradiance_map + .as_color_target(side, None) + .clear(ClearState::default())? + .write(|| effect.render(side, RenderStates::default(), viewport))?; } } @@ -89,22 +89,21 @@ impl Environment { include_str!("shaders/light_shared.frag"), include_str!("shaders/prefilter.frag") ); - let program = ImageCubeEffect::new(context, &fragment_shader_source)?; - let render_target = RenderTargetCubeMap::new_color(context, &mut prefilter_map)?; - + let effect = ImageCubeEffect::new(context, &fragment_shader_source)?; let max_mip_levels = 5; for mip in 0..max_mip_levels { let roughness = mip as f32 / (max_mip_levels as f32 - 1.0); - let viewport = Viewport::new_at_origo( - prefilter_size / 2u32.pow(mip), - prefilter_size / 2u32.pow(mip), - ); for side in CubeMapSide::iter() { - program.use_texture_cube("environmentMap", environment_map)?; - program.use_uniform("roughness", &roughness)?; - program.use_uniform("resolution", &(environment_map.width() as f32))?; - render_target.write_to_mip_level(side, mip, ClearState::default(), || { - program.render(side, RenderStates::default(), viewport) + effect.use_texture_cube("environmentMap", environment_map)?; + effect.use_uniform("roughness", &roughness)?; + effect.use_uniform("resolution", &(environment_map.width() as f32))?; + let color_target = prefilter_map.as_color_target(side, Some(mip)); + color_target.clear(ClearState::default())?.write(|| { + effect.render( + side, + RenderStates::default(), + Viewport::new_at_origo(color_target.width(), color_target.height()), + ) })?; } } @@ -132,9 +131,10 @@ impl Environment { ), )?; let viewport = Viewport::new_at_origo(brdf_map.width(), brdf_map.height()); - brdf_map.write(ClearState::default(), || { - effect.apply(RenderStates::default(), viewport) - })?; + brdf_map + .as_color_target(None) + .clear(ClearState::default())? + .write(|| effect.apply(RenderStates::default(), viewport))?; Ok(Self { irradiance_map, diff --git a/src/renderer/light/spot_light.rs b/src/renderer/light/spot_light.rs index 46bae1cff..cd8521ed6 100644 --- a/src/renderer/light/spot_light.rs +++ b/src/renderer/light/spot_light.rs @@ -110,15 +110,18 @@ impl SpotLight { }, ..Default::default() }; - shadow_texture.write(Some(1.0), || { - for geometry in geometries - .iter() - .filter(|g| shadow_camera.in_frustum(&g.aabb())) - { - geometry.render_with_material(&depth_material, &shadow_camera, &[])?; - } - Ok(()) - })?; + shadow_texture + .as_depth_target() + .clear(ClearState::default())? + .write(|| { + for geometry in geometries + .iter() + .filter(|g| shadow_camera.in_frustum(&g.aabb())) + { + geometry.render_with_material(&depth_material, &shadow_camera, &[])?; + } + Ok(()) + })?; self.shadow_texture = Some(shadow_texture); Ok(()) } diff --git a/src/renderer/material/shaders/deferred_lighting.frag b/src/renderer/material/shaders/deferred_lighting.frag index d5b558ec9..fc4f6dfe0 100644 --- a/src/renderer/material/shaders/deferred_lighting.frag +++ b/src/renderer/material/shaders/deferred_lighting.frag @@ -1,6 +1,6 @@ uniform sampler2DArray gbuffer; -uniform sampler2DArray depthMap; +uniform sampler2D depthMap; uniform mat4 viewProjectionInverse; uniform float zNear; uniform float zFar; @@ -13,7 +13,7 @@ layout (location = 0) out vec4 outColor; void main() { - float depth = texture(depthMap, vec3(uv,0)).r; + float depth = texture(depthMap, uv).r; if(depth > 0.99999) { discard; diff --git a/src/renderer/object.rs b/src/renderer/object.rs index b5975adf2..5d5f60401 100644 --- a/src/renderer/object.rs +++ b/src/renderer/object.rs @@ -52,9 +52,8 @@ use crate::renderer::*; pub trait Object: Geometry { /// /// Render the object. - /// Must be called in a render target render function, - /// for example in the callback function of [Screen::write](crate::Screen::write). - /// You can use an empty array for the `lights` argument, if you know the object does not require lights to be rendered. + /// Use an empty array for the `lights` argument, if the objects does not require lights to be rendered. + /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. /// fn render(&self, camera: &Camera, lights: &[&dyn Light]) -> ThreeDResult<()>; @@ -122,8 +121,7 @@ impl Object for std::rc::Rc> { pub trait Object2D: Geometry2D { /// /// Render the object. - /// Must be called in a render target render function, - /// for example in the callback function of [Screen::write](crate::Screen::write). + /// Must be called in the callback given as input to a [RenderTarget], [ColorTarget] or [DepthTarget] write method. /// fn render(&self, viewport: Viewport) -> ThreeDResult<()>; diff --git a/src/renderer/object/imposters.rs b/src/renderer/object/imposters.rs index 8717a64c1..6aae33350 100644 --- a/src/renderer/object/imposters.rs +++ b/src/renderer/object/imposters.rs @@ -170,35 +170,28 @@ impl ImpostersMaterial { Wrapping::ClampToEdge, Wrapping::ClampToEdge, )?; - let mut depth_texture = DepthTargetTexture2DArray::new( + let mut depth_texture = DepthTargetTexture2D::new( &self.context, texture_width, texture_height, - NO_VIEW_ANGLES, Wrapping::ClampToEdge, Wrapping::ClampToEdge, DepthFormat::Depth32F, )?; - { - let render_target = - RenderTargetArray::new(&self.context, &mut self.texture, &mut depth_texture)?; - for i in 0..NO_VIEW_ANGLES { - let angle = i as f32 * 2.0 * PI / NO_VIEW_ANGLES as f32; - camera.set_view( - center + width * vec3(f32::cos(angle), 0.0, f32::sin(angle)), - center, - vec3(0.0, 1.0, 0.0), - )?; - render_target.write( - &[i], - 0, - ClearState::color_and_depth(0.0, 0.0, 0.0, 0.0, 1.0), - || { - render_pass(&camera, objects, lights)?; - Ok(()) - }, - )?; - } + for i in 0..NO_VIEW_ANGLES { + let layers = [i]; + let angle = i as f32 * 2.0 * PI / NO_VIEW_ANGLES as f32; + camera.set_view( + center + width * vec3(f32::cos(angle), 0.0, f32::sin(angle)), + center, + vec3(0.0, 1.0, 0.0), + )?; + RenderTarget::new( + self.texture.as_color_target(&layers, None), + depth_texture.as_depth_target(), + )? + .clear(ClearState::color_and_depth(0.0, 0.0, 0.0, 0.0, 1.0))? + .render(&camera, objects, lights)?; } } Ok(()) diff --git a/src/window.rs b/src/window.rs index cac9e3bb4..1d0a7ce0b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -221,6 +221,8 @@ pub struct Modifiers { pub command: bool, } +use crate::core::*; + /// /// Input from the window to the rendering (and whatever else needs it) each frame. /// @@ -236,7 +238,7 @@ pub struct FrameInput { pub accumulated_time: f64, /// Viewport of the window in physical pixels (the size of the [screen](crate::Screen)). - pub viewport: crate::core::Viewport, + pub viewport: Viewport, /// Width of the window in logical pixels. pub window_width: u32, @@ -249,6 +251,23 @@ pub struct FrameInput { /// Whether or not this is the first frame. pub first_frame: bool, + + /// The graphics context for the window. + pub context: Context, +} + +impl FrameInput { + /// + /// Returns the screen render target, which is used for drawing to the screen, for this window. + /// Same as + /// + /// ```notrust + /// RenderTarget::screen(&frame_input.context, frame_input.viewport.width, frame_input.viewport.height) + /// ``` + /// + pub fn screen(&self) -> RenderTarget { + RenderTarget::screen(&self.context, self.viewport.width, self.viewport.height) + } } /// diff --git a/src/window/canvas.rs b/src/window/canvas.rs index 420a8752a..7a323e104 100644 --- a/src/window/canvas.rs +++ b/src/window/canvas.rs @@ -164,6 +164,7 @@ impl Window { let mut last_time = performance.now(); let mut accumulated_time = 0.0; let mut first_frame = true; + let context = self.gl()?; let input = Input::new(self.window.clone()); self.add_context_menu_event_listener()?; @@ -192,6 +193,7 @@ impl Window { let canvas = self.canvas.as_ref().unwrap(); let (width, height) = (canvas.width(), canvas.height()); let frame_input = FrameInput { + context: context.clone(), events, elapsed_time, accumulated_time, diff --git a/src/window/glutin_window.rs b/src/window/glutin_window.rs index 31821dc1f..bfdf75138 100644 --- a/src/window/glutin_window.rs +++ b/src/window/glutin_window.rs @@ -106,7 +106,6 @@ impl Window { let mut modifiers = Modifiers::default(); let mut first_frame = true; let mut mouse_pressed = None; - #[cfg(feature = "image-io")] let context = self.gl.clone(); self.event_loop.run(move |event, _, control_flow| { match event { @@ -141,6 +140,7 @@ impl Window { window_height: height, device_pixel_ratio: device_pixel_ratio, first_frame: first_frame, + context: context.clone(), }; first_frame = false; events.clear(); @@ -170,11 +170,10 @@ impl Window { #[cfg(feature = "image-io")] if let Some(ref path) = frame_output.screenshot { - let pixels = crate::Screen::read_color( - &context, - Viewport::new_at_origo(physical_width, physical_height), - ) - .unwrap(); + let pixels = + RenderTarget::screen(&context, physical_width, physical_height) + .read_color() + .unwrap(); crate::Saver::save_pixels(path, &pixels, physical_width, physical_height) .unwrap(); }