From b30cdd6f0b79948bcdf10dd23fb096c291b34327 Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Sat, 24 Aug 2024 15:11:16 +0300 Subject: [PATCH 1/7] experimental occlusion test without occlusion queries --- fyrox-impl/src/renderer/gbuffer/mod.rs | 28 +- .../src/renderer/shaders/visibility_fs.glsl | 31 ++ .../src/renderer/shaders/visibility_vs.glsl | 12 + fyrox-impl/src/renderer/visibility.rs | 410 +++++++++++++++++- 4 files changed, 473 insertions(+), 8 deletions(-) create mode 100644 fyrox-impl/src/renderer/shaders/visibility_fs.glsl create mode 100644 fyrox-impl/src/renderer/shaders/visibility_vs.glsl diff --git a/fyrox-impl/src/renderer/gbuffer/mod.rs b/fyrox-impl/src/renderer/gbuffer/mod.rs index 77925ce91..eaa4ed5aa 100644 --- a/fyrox-impl/src/renderer/gbuffer/mod.rs +++ b/fyrox-impl/src/renderer/gbuffer/mod.rs @@ -29,6 +29,7 @@ //! Every alpha channel is used for layer blending for terrains. This is inefficient, but for //! now I don't know better solution. +use crate::renderer::visibility::OcclusionTester; use crate::{ core::{ algebra::{Matrix4, Vector2}, @@ -65,6 +66,7 @@ use crate::{ mesh::{surface::SurfaceData, RenderPath}, }, }; +use fxhash::FxHashSet; use fyrox_core::math::Matrix4Ext; use std::{cell::RefCell, rc::Rc}; @@ -78,6 +80,7 @@ pub struct GBuffer { cube: GeometryBuffer, decal_shader: DecalShader, render_pass_name: ImmutableString, + occlusion_tester: OcclusionTester, } pub(crate) struct GBufferRenderContext<'a, 'b> { @@ -247,6 +250,7 @@ impl GBuffer { )?, decal_framebuffer, render_pass_name: ImmutableString::new("GBuffer"), + occlusion_tester: OcclusionTester::new(state, width, height, 512)?, }) } @@ -303,6 +307,24 @@ impl GBuffer { .. } = args; + let initial_view_projection = camera.view_projection_matrix(); + + let mut objects = FxHashSet::default(); + for bundle in bundle_storage.bundles.iter() { + for instance in bundle.instances.iter() { + objects.insert(instance.node_handle); + } + } + let objects = objects.into_iter().collect::>(); + let visibility = self.occlusion_tester.check( + camera.global_position(), + &initial_view_projection, + state, + &self.framebuffer, + graph, + &objects, + )?; + let viewport = Rect::new(0, 0, self.width, self.height); self.framebuffer.clear( state, @@ -312,8 +334,6 @@ impl GBuffer { Some(0), ); - let initial_view_projection = camera.view_projection_matrix(); - let inv_view = camera.inv_view_matrix().unwrap(); let camera_up = inv_view.up(); @@ -349,6 +369,10 @@ impl GBuffer { }; for instance in bundle.instances.iter() { + if !visibility.get(&instance.node_handle).map_or(true, |v| *v) { + continue; + } + let apply_uniforms = |mut program_binding: GpuProgramBinding| { let view_projection = if instance.depth_offset != 0.0 { let mut projection = camera.projection_matrix(); diff --git a/fyrox-impl/src/renderer/shaders/visibility_fs.glsl b/fyrox-impl/src/renderer/shaders/visibility_fs.glsl new file mode 100644 index 000000000..57f302d9d --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/visibility_fs.glsl @@ -0,0 +1,31 @@ +uniform int tileSize; +uniform sampler2D tileBuffer; +uniform float frameBufferHeight; + +out vec4 FragColor; + +flat in int instanceId; + +void main() +{ + int x = int(gl_FragCoord.x) / tileSize; + int y = int(frameBufferHeight - gl_FragCoord.y) / tileSize; + + // TODO: Replace with binary search. + // TODO: Handle empty pixels. + int bitIndex = -1; + for (int i = 0; i < 32; ++i) { + int objectIndex = int(texelFetch(tileBuffer, ivec2(x + i, y), 0).x); + if (objectIndex == instanceId) { + bitIndex = i; + break; + } + } + + if (bitIndex < 0) { + discard; + } + + float value = float(1 << bitIndex); + FragColor = vec4(value, 0, 0, 0); +} \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/visibility_vs.glsl b/fyrox-impl/src/renderer/shaders/visibility_vs.glsl new file mode 100644 index 000000000..8904c6a97 --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/visibility_vs.glsl @@ -0,0 +1,12 @@ +layout (location = 0) in vec3 vertexPosition; + +uniform mat4 viewProjection; +uniform sampler2D instanceMatrices; + +flat out int instanceId; + +void main() +{ + instanceId = gl_InstanceID; + gl_Position = (viewProjection * S_FetchMatrix(instanceMatrices, gl_InstanceID)) * vec4(vertexPosition, 1.0); +} \ No newline at end of file diff --git a/fyrox-impl/src/renderer/visibility.rs b/fyrox-impl/src/renderer/visibility.rs index ddf83c861..4a7541e0c 100644 --- a/fyrox-impl/src/renderer/visibility.rs +++ b/fyrox-impl/src/renderer/visibility.rs @@ -20,17 +20,42 @@ //! Volumetric visibility cache based on occlusion query. +#![allow(missing_docs)] // TODO + use crate::{ - core::{algebra::Vector3, pool::Handle}, + core::{ + algebra::{Matrix4, Vector2, Vector3}, + array_as_u8_slice, + arrayvec::ArrayVec, + math::{OptionRect, Rect}, + pool::Handle, + ImmutableString, + }, graph::BaseSceneGraph, - renderer::framework::{ - error::FrameworkError, - query::{Query, QueryKind, QueryResult}, - state::PipelineState, + renderer::{ + framework::{ + error::FrameworkError, + framebuffer::{ + Attachment, AttachmentKind, BlendParameters, DrawParameters, FrameBuffer, + }, + geometry_buffer::{GeometryBuffer, GeometryBufferKind}, + gpu_program::{GpuProgram, UniformLocation}, + gpu_texture::{ + Coordinate, GpuTexture, GpuTextureKind, MagnificationFilter, MinificationFilter, + PixelKind, WrapMode, + }, + query::{Query, QueryKind, QueryResult}, + state::{BlendEquation, BlendFactor, BlendFunc, BlendMode, ColorMask, PipelineState}, + }, + storage::MatrixStorage, }, - scene::{graph::Graph, node::Node}, + scene::{graph::Graph, mesh::surface::SurfaceData, node::Node}, }; +use bytemuck::{Pod, Zeroable}; use fxhash::FxHashMap; +use fyrox_core::color::Color; +use std::cmp::Ordering; +use std::{cell::RefCell, rc::Rc}; #[derive(Debug)] struct PendingQuery { @@ -287,3 +312,376 @@ impl VisibilityCache { }); } } + +struct Shader { + program: GpuProgram, + view_projection: UniformLocation, + tile_size: UniformLocation, + tile_buffer: UniformLocation, + instance_matrices: UniformLocation, + frame_buffer_height: UniformLocation, +} + +impl Shader { + fn new(state: &PipelineState) -> Result { + let fragment_source = include_str!("shaders/visibility_fs.glsl"); + let vertex_source = include_str!("shaders/visibility_vs.glsl"); + let program = + GpuProgram::from_source(state, "VisibilityShader", vertex_source, fragment_source)?; + Ok(Self { + view_projection: program + .uniform_location(state, &ImmutableString::new("viewProjection"))?, + tile_size: program.uniform_location(state, &ImmutableString::new("tileSize"))?, + frame_buffer_height: program + .uniform_location(state, &ImmutableString::new("frameBufferHeight"))?, + tile_buffer: program.uniform_location(state, &ImmutableString::new("tileBuffer"))?, + instance_matrices: program + .uniform_location(state, &ImmutableString::new("instanceMatrices"))?, + program, + }) + } +} + +pub struct OcclusionTester { + framebuffer: FrameBuffer, + visibility_mask: Rc>, + instance_matrices_buffer: MatrixStorage, + tile_buffer: Rc>, + frame_size: Vector2, + cube: GeometryBuffer, + shader: Shader, + tile_size: usize, + w_tiles: usize, + h_tiles: usize, +} + +#[derive(Default, Pod, Zeroable, Copy, Clone)] +#[repr(C)] +struct GpuTile { + objects: [u32; 32], +} + +#[derive(Clone)] +struct Object { + index: u32, + depth: f32, +} + +#[derive(Default, Clone)] +struct Tile { + objects: Vec, +} + +const MAX_BITS: usize = u32::BITS as usize; + +impl OcclusionTester { + pub fn new( + state: &PipelineState, + width: usize, + height: usize, + tile_size: usize, + ) -> Result { + let mut depth_stencil_texture = GpuTexture::new( + state, + GpuTextureKind::Rectangle { width, height }, + PixelKind::D24S8, + MinificationFilter::Nearest, + MagnificationFilter::Nearest, + 1, + None, + )?; + depth_stencil_texture + .bind_mut(state, 0) + .set_wrap(Coordinate::S, WrapMode::ClampToEdge) + .set_wrap(Coordinate::T, WrapMode::ClampToEdge); + + let visibility_mask = GpuTexture::new( + state, + GpuTextureKind::Rectangle { width, height }, + PixelKind::R32F, + MinificationFilter::Nearest, + MagnificationFilter::Nearest, + 1, + None, + )?; + + let w_tiles = width / tile_size + 1; + let h_tiles = height / tile_size + 1; + let tile_buffer = GpuTexture::new( + state, + GpuTextureKind::Rectangle { + width: w_tiles * MAX_BITS, + height: h_tiles, + }, + PixelKind::R32UI, + MinificationFilter::Nearest, + MagnificationFilter::Nearest, + 1, + None, + )?; + + let depth_stencil = Rc::new(RefCell::new(depth_stencil_texture)); + let visibility_mask = Rc::new(RefCell::new(visibility_mask)); + let tile_buffer = Rc::new(RefCell::new(tile_buffer)); + + Ok(Self { + framebuffer: FrameBuffer::new( + state, + Some(Attachment { + kind: AttachmentKind::DepthStencil, + texture: depth_stencil, + }), + vec![Attachment { + kind: AttachmentKind::Color, + texture: visibility_mask.clone(), + }], + )?, + visibility_mask, + frame_size: Vector2::new(width, height), + cube: GeometryBuffer::from_surface_data( + &SurfaceData::make_cube(Matrix4::identity()), + GeometryBufferKind::StaticDraw, + state, + )?, + instance_matrices_buffer: MatrixStorage::new(state)?, + shader: Shader::new(state)?, + tile_size, + w_tiles, + tile_buffer, + h_tiles, + }) + } + + fn read_visibility_mask(&self, state: &PipelineState) -> Vec { + // TODO: Replace with double buffering to prevent GPU stalls. + self.visibility_mask + .borrow_mut() + .bind_mut(state, 0) + .read_pixels_of_type(state) + } + + fn visibility_map( + &self, + state: &PipelineState, + objects: &[Handle], + tiles: &[Tile], + ) -> FxHashMap, bool> { + // TODO: This must be done using compute shader, but it is not available on WebGL2 so this + // code should still be used on WASM targets. + let visibility_buffer = self.read_visibility_mask(state); + let mut visibility_map = FxHashMap::default(); + for (index, pixel) in visibility_buffer.into_iter().enumerate() { + let x = index % self.frame_size.x; + let y = index / self.frame_size.y; + let tx = x / self.tile_size; + let ty = y / self.tile_size; + let bits = pixel as u32; + if let Some(tile) = tiles.get(ty * self.w_tiles + tx) { + 'bit_loop: for bit in 0..u32::BITS { + if let Some(object) = tile.objects.get(bit as usize) { + let is_visible = (bits & bit) != 0; + let visibility = visibility_map + .entry(objects[object.index as usize]) + .or_insert(is_visible); + if is_visible { + *visibility = true; + } + } else { + break 'bit_loop; + } + } + } + } + visibility_map + } + + fn screen_space_to_tile_space( + &self, + pos: Vector2, + viewport: &Rect, + ) -> Vector2 { + let x = (pos.x.clamp( + viewport.position.x as f32, + (viewport.position.x + viewport.size.x) as f32, + ) / (self.tile_size as f32)) as usize; + let y = (pos.y.clamp( + viewport.position.y as f32, + (viewport.position.y + viewport.size.y) as f32, + ) / (self.tile_size as f32)) as usize; + Vector2::new(x, y) + } + + fn prepare_tiles( + &self, + state: &PipelineState, + graph: &Graph, + observer_position: Vector3, + view_projection: &Matrix4, + viewport: Rect, + objects: &[Handle], + ) -> Result, FrameworkError> { + let mut tiles = vec![Tile::default(); self.w_tiles * self.h_tiles]; + + for (object_index, object) in objects.iter().enumerate() { + let aabb = graph[*object].world_bounding_box(); + let mut rect_builder = OptionRect::default(); + for corner in aabb.corners() { + let ndc_space = view_projection.transform_point(&corner.into()); + let screen_space_corner = Vector2::new( + (ndc_space.x + 1.0) * (viewport.size.x as f32) / 2.0 + + viewport.position.x as f32, + (ndc_space.y + 1.0) * (viewport.size.y as f32) / 2.0 + + viewport.position.y as f32, + ); + rect_builder.push(screen_space_corner); + } + let rect = rect_builder.unwrap(); + + let average_depth = observer_position.metric_distance(&aabb.center()); + let min = self.screen_space_to_tile_space(rect.left_top_corner(), &viewport); + let max = self.screen_space_to_tile_space(rect.right_bottom_corner(), &viewport); + let size = (max - min).sup(&Vector2::repeat(1)); + for y in min.y..(min.y + size.y) { + for x in min.x..(min.x + size.x) { + let tile = &mut tiles[y * self.w_tiles + x]; + tile.objects.push(Object { + index: object_index as u32, + depth: average_depth, + }); + } + } + } + + let mut gpu_tiles = Vec::with_capacity(tiles.len()); + for tile in tiles.iter_mut() { + tile.objects + .sort_by(|a, b| a.depth.partial_cmp(&b.depth).unwrap_or(Ordering::Less)); + + let mut gpu_tile = GpuTile { + objects: tile + .objects + .iter() + .map(|obj| obj.index) + .chain([u32::MAX; MAX_BITS]) + .take(MAX_BITS) + .collect::>() + .into_inner() + .unwrap(), + }; + + gpu_tile.objects.sort(); + + gpu_tiles.push(gpu_tile); + } + + self.tile_buffer.borrow_mut().bind_mut(state, 0).set_data( + GpuTextureKind::Rectangle { + width: self.w_tiles * MAX_BITS, + height: self.h_tiles, + }, + PixelKind::R32UI, + 1, + Some(array_as_u8_slice(&gpu_tiles)), + )?; + + Ok(tiles) + } + + pub fn check( + &mut self, + observer_position: Vector3, + view_projection: &Matrix4, + state: &PipelineState, + prev_framebuffer: &FrameBuffer, + graph: &Graph, + objects: &[Handle], + ) -> Result, bool>, FrameworkError> { + let w = self.frame_size.x as i32; + let h = self.frame_size.y as i32; + let viewport = Rect::new(0, 0, w, h); + + self.framebuffer.clear( + state, + viewport, + Some(Color::TRANSPARENT), + Some(1.0), + Some(0), + ); + + state.blit_framebuffer( + prev_framebuffer.id(), + self.framebuffer.id(), + 0, + 0, + w, + h, + 0, + 0, + w, + h, + false, + true, + false, + ); + + let tiles = self.prepare_tiles( + state, + graph, + observer_position, + view_projection, + viewport, + objects, + )?; + + self.instance_matrices_buffer.upload( + state, + objects.iter().map(|h| { + let mut aabb = graph[*h].world_bounding_box(); + aabb.inflate(Vector3::repeat(0.01)); + let s = aabb.max - aabb.min; + Matrix4::new_translation(&aabb.center()) * Matrix4::new_nonuniform_scaling(&s) + }), + 0, + )?; + + let shader = &self.shader; + self.framebuffer.draw_instances( + objects.len(), + &self.cube, + state, + viewport, + &self.shader.program, + &DrawParameters { + cull_face: None, + color_write: ColorMask::all(true), + depth_write: false, + stencil_test: None, + depth_test: true, + blend: Some(BlendParameters { + func: BlendFunc::new(BlendFactor::One, BlendFactor::One), + equation: BlendEquation { + rgb: BlendMode::Add, + alpha: BlendMode::Add, + }, + }), + stencil_op: Default::default(), + }, + |mut program_binding| { + program_binding + .set_texture(&shader.tile_buffer, &self.tile_buffer) + .set_texture( + &shader.instance_matrices, + self.instance_matrices_buffer.texture(), + ) + .set_i32(&shader.tile_size, self.tile_size as i32) + .set_f32(&shader.frame_buffer_height, self.frame_size.y as f32) + .set_matrix4(&shader.view_projection, view_projection); + }, + ); + + Ok(self.visibility_map(state, objects, &tiles)) + } +} + +#[cfg(test)] +mod tests {} From c9cd50f327cabf7664d4baa625ca5488a3e87dc9 Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Sat, 24 Aug 2024 22:49:10 +0300 Subject: [PATCH 2/7] various fixes - fixed aabb projection - improved perf - fixed shader - reduced tile size --- fyrox-impl/src/renderer/gbuffer/mod.rs | 2 +- .../src/renderer/shaders/visibility_fs.glsl | 14 +-- fyrox-impl/src/renderer/visibility.rs | 104 +++++++++++------- 3 files changed, 70 insertions(+), 50 deletions(-) diff --git a/fyrox-impl/src/renderer/gbuffer/mod.rs b/fyrox-impl/src/renderer/gbuffer/mod.rs index eaa4ed5aa..fac576c75 100644 --- a/fyrox-impl/src/renderer/gbuffer/mod.rs +++ b/fyrox-impl/src/renderer/gbuffer/mod.rs @@ -250,7 +250,7 @@ impl GBuffer { )?, decal_framebuffer, render_pass_name: ImmutableString::new("GBuffer"), - occlusion_tester: OcclusionTester::new(state, width, height, 512)?, + occlusion_tester: OcclusionTester::new(state, width, height, 32)?, }) } diff --git a/fyrox-impl/src/renderer/shaders/visibility_fs.glsl b/fyrox-impl/src/renderer/shaders/visibility_fs.glsl index 57f302d9d..297c4ba2c 100644 --- a/fyrox-impl/src/renderer/shaders/visibility_fs.glsl +++ b/fyrox-impl/src/renderer/shaders/visibility_fs.glsl @@ -1,5 +1,5 @@ uniform int tileSize; -uniform sampler2D tileBuffer; +uniform usampler2D tileBuffer; uniform float frameBufferHeight; out vec4 FragColor; @@ -12,20 +12,18 @@ void main() int y = int(frameBufferHeight - gl_FragCoord.y) / tileSize; // TODO: Replace with binary search. - // TODO: Handle empty pixels. int bitIndex = -1; for (int i = 0; i < 32; ++i) { - int objectIndex = int(texelFetch(tileBuffer, ivec2(x + i, y), 0).x); - if (objectIndex == instanceId) { + uint objectIndex = uint(texelFetch(tileBuffer, ivec2(x * tileSize + i, y), 0).x); + if (objectIndex == uint(instanceId)) { bitIndex = i; break; } } if (bitIndex < 0) { - discard; + FragColor = vec4(0.0, 0.0, 0.0, 0.0); + } else { + FragColor = vec4(float(1 << bitIndex), 0.0, 0.0, 0.0); } - - float value = float(1 << bitIndex); - FragColor = vec4(value, 0, 0, 0); } \ No newline at end of file diff --git a/fyrox-impl/src/renderer/visibility.rs b/fyrox-impl/src/renderer/visibility.rs index 4a7541e0c..e4656e3fc 100644 --- a/fyrox-impl/src/renderer/visibility.rs +++ b/fyrox-impl/src/renderer/visibility.rs @@ -24,10 +24,11 @@ use crate::{ core::{ - algebra::{Matrix4, Vector2, Vector3}, + algebra::{Matrix4, Vector2, Vector3, Vector4}, array_as_u8_slice, arrayvec::ArrayVec, - math::{OptionRect, Rect}, + color::Color, + math::{aabb::AxisAlignedBoundingBox, OptionRect, Rect}, pool::Handle, ImmutableString, }, @@ -53,9 +54,7 @@ use crate::{ }; use bytemuck::{Pod, Zeroable}; use fxhash::FxHashMap; -use fyrox_core::color::Color; -use std::cmp::Ordering; -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, cmp::Ordering, rc::Rc}; #[derive(Debug)] struct PendingQuery { @@ -355,25 +354,47 @@ pub struct OcclusionTester { h_tiles: usize, } -#[derive(Default, Pod, Zeroable, Copy, Clone)] +#[derive(Default, Pod, Zeroable, Copy, Clone, Debug)] #[repr(C)] struct GpuTile { objects: [u32; 32], } -#[derive(Clone)] +#[derive(Clone, Debug)] struct Object { index: u32, depth: f32, } -#[derive(Default, Clone)] +#[derive(Default, Clone, Debug)] struct Tile { objects: Vec, } const MAX_BITS: usize = u32::BITS as usize; +fn screen_space_rect( + aabb: AxisAlignedBoundingBox, + view_projection: &Matrix4, + viewport: &Rect, +) -> Rect { + let mut rect_builder = OptionRect::default(); + for corner in aabb.corners() { + let clip_space = view_projection * Vector4::new(corner.x, corner.y, corner.z, 1.0); + let ndc_space = if clip_space.w != 0.0 { + clip_space.xyz() / clip_space.w + } else { + clip_space.xyz() + }; + let screen_space_corner = Vector2::new( + ((ndc_space.x + 1.0) / 2.0) * (viewport.size.x as f32) + viewport.position.x as f32, + ((1.0 - ndc_space.y) / 2.0) * (viewport.size.y as f32) + viewport.position.y as f32, + ); + rect_builder.push(screen_space_corner); + } + rect_builder.unwrap() +} + impl OcclusionTester { pub fn new( state: &PipelineState, @@ -452,14 +473,18 @@ impl OcclusionTester { }) } + #[inline(never)] fn read_visibility_mask(&self, state: &PipelineState) -> Vec { // TODO: Replace with double buffering to prevent GPU stalls. + state.finish(); + self.visibility_mask .borrow_mut() .bind_mut(state, 0) - .read_pixels_of_type(state) + .get_image(0, state) } + #[inline(never)] fn visibility_map( &self, state: &PipelineState, @@ -469,20 +494,19 @@ impl OcclusionTester { // TODO: This must be done using compute shader, but it is not available on WebGL2 so this // code should still be used on WASM targets. let visibility_buffer = self.read_visibility_mask(state); - let mut visibility_map = FxHashMap::default(); - for (index, pixel) in visibility_buffer.into_iter().enumerate() { - let x = index % self.frame_size.x; - let y = index / self.frame_size.y; - let tx = x / self.tile_size; + let mut objects_visibility = vec![false; objects.len()]; + for y in 0..self.frame_size.y { let ty = y / self.tile_size; - let bits = pixel as u32; - if let Some(tile) = tiles.get(ty * self.w_tiles + tx) { + let offset = y * self.frame_size.x; + let tile_offset = ty * self.w_tiles; + for x in 0..self.frame_size.x { + let tx = x / self.tile_size; + let bits = visibility_buffer[offset + x] as u32; + let tile = &tiles[tile_offset + tx]; 'bit_loop: for bit in 0..u32::BITS { if let Some(object) = tile.objects.get(bit as usize) { + let visibility = &mut objects_visibility[object.index as usize]; let is_visible = (bits & bit) != 0; - let visibility = visibility_map - .entry(objects[object.index as usize]) - .or_insert(is_visible); if is_visible { *visibility = true; } @@ -492,6 +516,11 @@ impl OcclusionTester { } } } + let visibility_map = objects + .iter() + .cloned() + .zip(objects_visibility.iter().cloned()) + .collect::, bool>>(); visibility_map } @@ -517,29 +546,26 @@ impl OcclusionTester { graph: &Graph, observer_position: Vector3, view_projection: &Matrix4, - viewport: Rect, + viewport: &Rect, objects: &[Handle], ) -> Result, FrameworkError> { let mut tiles = vec![Tile::default(); self.w_tiles * self.h_tiles]; for (object_index, object) in objects.iter().enumerate() { - let aabb = graph[*object].world_bounding_box(); - let mut rect_builder = OptionRect::default(); - for corner in aabb.corners() { - let ndc_space = view_projection.transform_point(&corner.into()); - let screen_space_corner = Vector2::new( - (ndc_space.x + 1.0) * (viewport.size.x as f32) / 2.0 - + viewport.position.x as f32, - (ndc_space.y + 1.0) * (viewport.size.y as f32) / 2.0 - + viewport.position.y as f32, - ); - rect_builder.push(screen_space_corner); - } - let rect = rect_builder.unwrap(); + let node_ref = &graph[*object]; + let world_bounding_box = node_ref.world_bounding_box(); + let aabb = if world_bounding_box.is_valid() { + world_bounding_box + } else { + node_ref + .local_bounding_box() + .transform(&node_ref.global_transform()) + }; + let rect = screen_space_rect(aabb, view_projection, viewport); let average_depth = observer_position.metric_distance(&aabb.center()); - let min = self.screen_space_to_tile_space(rect.left_top_corner(), &viewport); - let max = self.screen_space_to_tile_space(rect.right_bottom_corner(), &viewport); + let min = self.screen_space_to_tile_space(rect.left_top_corner(), viewport); + let max = self.screen_space_to_tile_space(rect.right_bottom_corner(), viewport); let size = (max - min).sup(&Vector2::repeat(1)); for y in min.y..(min.y + size.y) { for x in min.x..(min.x + size.x) { @@ -629,15 +655,14 @@ impl OcclusionTester { graph, observer_position, view_projection, - viewport, + &viewport, objects, )?; self.instance_matrices_buffer.upload( state, objects.iter().map(|h| { - let mut aabb = graph[*h].world_bounding_box(); - aabb.inflate(Vector3::repeat(0.01)); + let aabb = graph[*h].world_bounding_box(); let s = aabb.max - aabb.min; Matrix4::new_translation(&aabb.center()) * Matrix4::new_nonuniform_scaling(&s) }), @@ -682,6 +707,3 @@ impl OcclusionTester { Ok(self.visibility_map(state, objects, &tiles)) } } - -#[cfg(test)] -mod tests {} From 08b50aef9dd93f0e5fd2a006bd77bc3d3d2fef9b Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Mon, 26 Aug 2024 11:43:53 +0300 Subject: [PATCH 3/7] finished first iteration of occlusion culling algorithm - it has perf issues and that's for next iteration --- fyrox-impl/src/renderer/debug_renderer.rs | 75 +++--- fyrox-impl/src/renderer/gbuffer/mod.rs | 8 +- fyrox-impl/src/renderer/mod.rs | 20 +- .../src/renderer/shaders/visibility_fs.glsl | 10 +- .../src/renderer/shaders/visibility_vs.glsl | 9 +- fyrox-impl/src/renderer/visibility.rs | 226 +++++++++++++----- 6 files changed, 240 insertions(+), 108 deletions(-) diff --git a/fyrox-impl/src/renderer/debug_renderer.rs b/fyrox-impl/src/renderer/debug_renderer.rs index d543d3c5a..6e266c173 100644 --- a/fyrox-impl/src/renderer/debug_renderer.rs +++ b/fyrox-impl/src/renderer/debug_renderer.rs @@ -23,24 +23,26 @@ //! shapes, contact information (normals, positions, etc.), paths build by navmesh and so //! on. It contains implementations to draw most common shapes (line, box, oob, frustum, etc). -use crate::core::sstorage::ImmutableString; -use crate::renderer::framework::geometry_buffer::ElementRange; use crate::{ - core::{algebra::Vector3, math::Rect, scope_profile}, - renderer::framework::{ - error::FrameworkError, - framebuffer::{DrawParameters, FrameBuffer}, - geometry_buffer::{ - AttributeDefinition, AttributeKind, BufferBuilder, ElementKind, GeometryBuffer, - GeometryBufferBuilder, GeometryBufferKind, + core::{algebra::Vector3, math::Rect, scope_profile, sstorage::ImmutableString}, + renderer::{ + framework::{ + error::FrameworkError, + framebuffer::{DrawParameters, FrameBuffer}, + geometry_buffer::{ + AttributeDefinition, AttributeKind, BufferBuilder, ElementKind, ElementRange, + GeometryBuffer, GeometryBufferBuilder, GeometryBufferKind, + }, + gpu_program::{GpuProgram, UniformLocation}, + state::PipelineState, }, - gpu_program::{GpuProgram, UniformLocation}, - state::PipelineState, + RenderPassStatistics, }, - renderer::RenderPassStatistics, - scene::{camera::Camera, debug::SceneDrawingContext}, + scene::debug::Line, }; use bytemuck::{Pod, Zeroable}; +use fyrox_core::color::Color; +use rapier2d::na::Matrix4; #[repr(C)] #[derive(Copy, Pod, Zeroable, Clone)] @@ -62,6 +64,22 @@ pub(crate) struct DebugShader { wvp_matrix: UniformLocation, } +/// "Draws" a rectangle into a list of lines. +pub fn draw_rect(rect: &Rect, lines: &mut Vec, color: Color) { + for (a, b) in [ + (rect.left_top_corner(), rect.right_top_corner()), + (rect.right_top_corner(), rect.right_bottom_corner()), + (rect.right_bottom_corner(), rect.left_bottom_corner()), + (rect.left_bottom_corner(), rect.left_top_corner()), + ] { + lines.push(Line { + begin: a.to_homogeneous(), + end: b.to_homogeneous(), + color, + }); + } +} + impl DebugShader { fn new(state: &PipelineState) -> Result { let fragment_source = include_str!("shaders/debug_fs.glsl"); @@ -104,23 +122,13 @@ impl DebugRenderer { }) } - pub(crate) fn render( - &mut self, - state: &PipelineState, - viewport: Rect, - framebuffer: &mut FrameBuffer, - drawing_context: &SceneDrawingContext, - camera: &Camera, - ) -> Result { - scope_profile!(); - - let mut statistics = RenderPassStatistics::default(); - + /// Uploads the new set of lines to GPU. + pub fn set_lines(&mut self, state: &PipelineState, lines: &[Line]) { self.vertices.clear(); self.line_indices.clear(); let mut i = 0; - for line in drawing_context.lines.iter() { + for line in lines.iter() { let color = line.color.into(); self.vertices.push(Vertex { position: line.begin, @@ -135,6 +143,18 @@ impl DebugRenderer { } self.geometry.set_buffer_data(state, 0, &self.vertices); self.geometry.bind(state).set_lines(&self.line_indices); + } + + pub(crate) fn render( + &mut self, + state: &PipelineState, + viewport: Rect, + framebuffer: &mut FrameBuffer, + view_projection: Matrix4, + ) -> Result { + scope_profile!(); + + let mut statistics = RenderPassStatistics::default(); statistics += framebuffer.draw( &self.geometry, @@ -152,8 +172,7 @@ impl DebugRenderer { }, ElementRange::Full, |mut program_binding| { - program_binding - .set_matrix4(&self.shader.wvp_matrix, &camera.view_projection_matrix()); + program_binding.set_matrix4(&self.shader.wvp_matrix, &view_projection); }, )?; diff --git a/fyrox-impl/src/renderer/gbuffer/mod.rs b/fyrox-impl/src/renderer/gbuffer/mod.rs index fac576c75..add30d590 100644 --- a/fyrox-impl/src/renderer/gbuffer/mod.rs +++ b/fyrox-impl/src/renderer/gbuffer/mod.rs @@ -29,6 +29,7 @@ //! Every alpha channel is used for layer blending for terrains. This is inefficient, but for //! now I don't know better solution. +use crate::renderer::debug_renderer::DebugRenderer; use crate::renderer::visibility::OcclusionTester; use crate::{ core::{ @@ -90,8 +91,6 @@ pub(crate) struct GBufferRenderContext<'a, 'b> { pub bundle_storage: &'a RenderDataBundleStorage, pub texture_cache: &'a mut TextureCache, pub shader_cache: &'a mut ShaderCache, - #[allow(dead_code)] - pub environment_dummy: Rc>, pub white_dummy: Rc>, pub normal_dummy: Rc>, pub black_dummy: Rc>, @@ -99,6 +98,8 @@ pub(crate) struct GBufferRenderContext<'a, 'b> { pub use_parallax_mapping: bool, pub graph: &'b Graph, pub matrix_storage: &'a mut MatrixStorageCache, + #[allow(dead_code)] + pub screen_space_debug_renderer: &'a mut DebugRenderer, } impl GBuffer { @@ -250,7 +251,7 @@ impl GBuffer { )?, decal_framebuffer, render_pass_name: ImmutableString::new("GBuffer"), - occlusion_tester: OcclusionTester::new(state, width, height, 32)?, + occlusion_tester: OcclusionTester::new(state, width, height, 64)?, }) } @@ -323,6 +324,7 @@ impl GBuffer { &self.framebuffer, graph, &objects, + None, )?; let viewport = Rect::new(0, 0, self.width, self.height); diff --git a/fyrox-impl/src/renderer/mod.rs b/fyrox-impl/src/renderer/mod.rs index 90d21f127..43a3d22c2 100644 --- a/fyrox-impl/src/renderer/mod.rs +++ b/fyrox-impl/src/renderer/mod.rs @@ -670,6 +670,9 @@ pub struct Renderer { quality_settings: QualitySettings, /// Debug renderer instance can be used for debugging purposes pub debug_renderer: DebugRenderer, + /// Screen space debug renderer instance can be used for debugging purposes to draw lines directly + /// on screen. It is useful to debug some rendering algorithms. + pub screen_space_debug_renderer: DebugRenderer, /// A set of associated data for each scene that was rendered. pub scene_data_map: FxHashMap, AssociatedSceneData>, backbuffer_clear_color: Color, @@ -1290,6 +1293,7 @@ impl Renderer { ui_renderer: UiRenderer::new(&state)?, quality_settings: settings, debug_renderer: DebugRenderer::new(&state)?, + screen_space_debug_renderer: DebugRenderer::new(&state)?, scene_data_map: Default::default(), backbuffer_clear_color: Color::BLACK, texture_cache: Default::default(), @@ -1657,7 +1661,6 @@ impl Renderer { bundle_storage: &bundle_storage, texture_cache: &mut self.texture_cache, shader_cache: &mut self.shader_cache, - environment_dummy: self.environment_dummy.clone(), use_parallax_mapping: self.quality_settings.use_parallax_mapping, normal_dummy: self.normal_dummy.clone(), white_dummy: self.white_dummy.clone(), @@ -1665,6 +1668,7 @@ impl Renderer { volume_dummy: self.volume_dummy.clone(), graph, matrix_storage: &mut self.matrix_storage, + screen_space_debug_renderer: &mut self.screen_space_debug_renderer, })?; state.set_polygon_fill_mode(PolygonFace::FrontAndBack, PolygonFillMode::Fill); @@ -1807,12 +1811,13 @@ impl Renderer { } // Render debug geometry in the LDR frame buffer. + self.debug_renderer + .set_lines(state, &scene.drawing_context.lines); scene_associated_data.statistics += self.debug_renderer.render( state, viewport, &mut scene_associated_data.ldr_scene_framebuffer, - &scene.drawing_context, - camera, + camera.view_projection_matrix(), )?; for render_pass in self.scene_render_passes.iter() { @@ -1925,6 +1930,15 @@ impl Renderer { })?; } + let screen_matrix = + Matrix4::new_orthographic(0.0, backbuffer_width, backbuffer_height, 0.0, -1.0, 1.0); + self.screen_space_debug_renderer.render( + &self.state, + window_viewport, + &mut self.backbuffer, + screen_matrix, + )?; + Ok(()) } diff --git a/fyrox-impl/src/renderer/shaders/visibility_fs.glsl b/fyrox-impl/src/renderer/shaders/visibility_fs.glsl index 297c4ba2c..f575f6d45 100644 --- a/fyrox-impl/src/renderer/shaders/visibility_fs.glsl +++ b/fyrox-impl/src/renderer/shaders/visibility_fs.glsl @@ -4,18 +4,18 @@ uniform float frameBufferHeight; out vec4 FragColor; -flat in int instanceId; +flat in uint objectIndex; void main() { - int x = int(gl_FragCoord.x) / tileSize; - int y = int(frameBufferHeight - gl_FragCoord.y) / tileSize; + int x = int(gl_FragCoord.x - 0.5) / tileSize; + int y = int(frameBufferHeight - gl_FragCoord.y - 0.5) / tileSize; // TODO: Replace with binary search. int bitIndex = -1; for (int i = 0; i < 32; ++i) { - uint objectIndex = uint(texelFetch(tileBuffer, ivec2(x * tileSize + i, y), 0).x); - if (objectIndex == uint(instanceId)) { + uint pixelObjectIndex = uint(texelFetch(tileBuffer, ivec2(x * 32 + i, y), 0).x); + if (pixelObjectIndex == objectIndex) { bitIndex = i; break; } diff --git a/fyrox-impl/src/renderer/shaders/visibility_vs.glsl b/fyrox-impl/src/renderer/shaders/visibility_vs.glsl index 8904c6a97..8858e5c1e 100644 --- a/fyrox-impl/src/renderer/shaders/visibility_vs.glsl +++ b/fyrox-impl/src/renderer/shaders/visibility_vs.glsl @@ -1,12 +1,11 @@ -layout (location = 0) in vec3 vertexPosition; +layout (location = 0) in vec4 vertexPosition; uniform mat4 viewProjection; -uniform sampler2D instanceMatrices; -flat out int instanceId; +flat out uint objectIndex; void main() { - instanceId = gl_InstanceID; - gl_Position = (viewProjection * S_FetchMatrix(instanceMatrices, gl_InstanceID)) * vec4(vertexPosition, 1.0); + objectIndex = uint(vertexPosition.w); + gl_Position = viewProjection * vec4(vertexPosition.xyz, 1.0); } \ No newline at end of file diff --git a/fyrox-impl/src/renderer/visibility.rs b/fyrox-impl/src/renderer/visibility.rs index e4656e3fc..5ff4d6ae3 100644 --- a/fyrox-impl/src/renderer/visibility.rs +++ b/fyrox-impl/src/renderer/visibility.rs @@ -28,29 +28,35 @@ use crate::{ array_as_u8_slice, arrayvec::ArrayVec, color::Color, - math::{aabb::AxisAlignedBoundingBox, OptionRect, Rect}, + math::{aabb::AxisAlignedBoundingBox, OptionRect, Rect, TriangleDefinition}, pool::Handle, ImmutableString, }, graph::BaseSceneGraph, renderer::{ + debug_renderer::{self, DebugRenderer}, framework::{ error::FrameworkError, framebuffer::{ - Attachment, AttachmentKind, BlendParameters, DrawParameters, FrameBuffer, + Attachment, AttachmentKind, BlendParameters, CullFace, DrawParameters, FrameBuffer, + }, + geometry_buffer::{ + AttributeDefinition, AttributeKind, BufferBuilder, ElementKind, ElementRange, + GeometryBuffer, GeometryBufferBuilder, GeometryBufferKind, }, - geometry_buffer::{GeometryBuffer, GeometryBufferKind}, gpu_program::{GpuProgram, UniformLocation}, gpu_texture::{ Coordinate, GpuTexture, GpuTextureKind, MagnificationFilter, MinificationFilter, PixelKind, WrapMode, }, query::{Query, QueryKind, QueryResult}, - state::{BlendEquation, BlendFactor, BlendFunc, BlendMode, ColorMask, PipelineState}, + state::{ + BlendEquation, BlendFactor, BlendFunc, BlendMode, ColorMask, CompareFunc, + PipelineState, + }, }, - storage::MatrixStorage, }, - scene::{graph::Graph, mesh::surface::SurfaceData, node::Node}, + scene::{graph::Graph, node::Node}, }; use bytemuck::{Pod, Zeroable}; use fxhash::FxHashMap; @@ -317,7 +323,6 @@ struct Shader { view_projection: UniformLocation, tile_size: UniformLocation, tile_buffer: UniformLocation, - instance_matrices: UniformLocation, frame_buffer_height: UniformLocation, } @@ -334,8 +339,6 @@ impl Shader { frame_buffer_height: program .uniform_location(state, &ImmutableString::new("frameBufferHeight"))?, tile_buffer: program.uniform_location(state, &ImmutableString::new("tileBuffer"))?, - instance_matrices: program - .uniform_location(state, &ImmutableString::new("instanceMatrices"))?, program, }) } @@ -344,14 +347,13 @@ impl Shader { pub struct OcclusionTester { framebuffer: FrameBuffer, visibility_mask: Rc>, - instance_matrices_buffer: MatrixStorage, tile_buffer: Rc>, frame_size: Vector2, - cube: GeometryBuffer, shader: Shader, tile_size: usize, w_tiles: usize, h_tiles: usize, + cubes: GeometryBuffer, } #[derive(Default, Pod, Zeroable, Copy, Clone, Debug)] @@ -366,9 +368,10 @@ struct Object { depth: f32, } -#[derive(Default, Clone, Debug)] +#[derive(Clone, Debug)] struct Tile { objects: Vec, + bounds: Rect, } const MAX_BITS: usize = u32::BITS as usize; @@ -395,6 +398,20 @@ fn screen_space_rect( rect_builder.unwrap() } +#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)] +#[repr(C)] +struct Vertex { + pub position: Vector4, +} + +impl Vertex { + fn new(x: f32, y: f32, z: f32, w: f32) -> Self { + Self { + position: Vector4::new(x, y, z, w), + } + } +} + impl OcclusionTester { pub fn new( state: &PipelineState, @@ -445,6 +462,19 @@ impl OcclusionTester { let visibility_mask = Rc::new(RefCell::new(visibility_mask)); let tile_buffer = Rc::new(RefCell::new(tile_buffer)); + let cubes = GeometryBufferBuilder::new(ElementKind::Triangle) + .with_buffer_builder( + BufferBuilder::new::(GeometryBufferKind::DynamicDraw, None).with_attribute( + AttributeDefinition { + location: 0, + kind: AttributeKind::Float4, + normalized: false, + divisor: 0, + }, + ), + ) + .build(state)?; + Ok(Self { framebuffer: FrameBuffer::new( state, @@ -459,17 +489,12 @@ impl OcclusionTester { )?, visibility_mask, frame_size: Vector2::new(width, height), - cube: GeometryBuffer::from_surface_data( - &SurfaceData::make_cube(Matrix4::identity()), - GeometryBufferKind::StaticDraw, - state, - )?, - instance_matrices_buffer: MatrixStorage::new(state)?, shader: Shader::new(state)?, tile_size, w_tiles, tile_buffer, h_tiles, + cubes, }) } @@ -487,26 +512,30 @@ impl OcclusionTester { #[inline(never)] fn visibility_map( &self, + observer_position: Vector3, state: &PipelineState, objects: &[Handle], tiles: &[Tile], + graph: &Graph, ) -> FxHashMap, bool> { // TODO: This must be done using compute shader, but it is not available on WebGL2 so this // code should still be used on WASM targets. let visibility_buffer = self.read_visibility_mask(state); let mut objects_visibility = vec![false; objects.len()]; for y in 0..self.frame_size.y { - let ty = y / self.tile_size; - let offset = y * self.frame_size.x; - let tile_offset = ty * self.w_tiles; + let img_y = self.frame_size.y.saturating_sub(1) - y; + let tile_y = y / self.tile_size; + let row_start_index = img_y * self.frame_size.x; + let tile_row_start_index = tile_y * self.w_tiles; for x in 0..self.frame_size.x { - let tx = x / self.tile_size; - let bits = visibility_buffer[offset + x] as u32; - let tile = &tiles[tile_offset + tx]; + let tile_x = x / self.tile_size; + let bits = visibility_buffer[row_start_index + x] as u32; + let tile = &tiles[tile_row_start_index + tile_x]; 'bit_loop: for bit in 0..u32::BITS { if let Some(object) = tile.objects.get(bit as usize) { let visibility = &mut objects_visibility[object.index as usize]; - let is_visible = (bits & bit) != 0; + let mask = 1 << bit; + let is_visible = (bits & mask) == mask; if is_visible { *visibility = true; } @@ -516,11 +545,23 @@ impl OcclusionTester { } } } - let visibility_map = objects + + let mut visibility_map = objects .iter() .cloned() .zip(objects_visibility.iter().cloned()) .collect::, bool>>(); + + for (object, visibility) in visibility_map.iter_mut() { + let object_ref = &graph[*object]; + if object_ref + .world_bounding_box() + .is_contains_point(observer_position) + { + *visibility = true; + } + } + visibility_map } @@ -548,27 +589,38 @@ impl OcclusionTester { view_projection: &Matrix4, viewport: &Rect, objects: &[Handle], + debug_renderer: Option<&mut DebugRenderer>, ) -> Result, FrameworkError> { - let mut tiles = vec![Tile::default(); self.w_tiles * self.h_tiles]; - + let mut tiles = Vec::with_capacity(self.w_tiles * self.h_tiles); + for y in 0..self.h_tiles { + for x in 0..self.w_tiles { + tiles.push(Tile { + objects: vec![], + bounds: Rect::new( + (x * self.tile_size) as f32, + (y * self.tile_size) as f32, + self.tile_size as f32, + self.tile_size as f32, + ), + }) + } + } + let mut lines = Vec::new(); for (object_index, object) in objects.iter().enumerate() { let node_ref = &graph[*object]; - let world_bounding_box = node_ref.world_bounding_box(); - let aabb = if world_bounding_box.is_valid() { - world_bounding_box - } else { - node_ref - .local_bounding_box() - .transform(&node_ref.global_transform()) - }; + let aabb = node_ref.world_bounding_box(); let rect = screen_space_rect(aabb, view_projection, viewport); + if debug_renderer.is_some() { + debug_renderer::draw_rect(&rect, &mut lines, Color::WHITE); + } + let average_depth = observer_position.metric_distance(&aabb.center()); let min = self.screen_space_to_tile_space(rect.left_top_corner(), viewport); let max = self.screen_space_to_tile_space(rect.right_bottom_corner(), viewport); - let size = (max - min).sup(&Vector2::repeat(1)); - for y in min.y..(min.y + size.y) { - for x in min.x..(min.x + size.x) { + let size = max - min; + for y in min.y..=(min.y + size.y) { + for x in min.x..=(min.x + size.x) { let tile = &mut tiles[y * self.w_tiles + x]; tile.objects.push(Object { index: object_index as u32, @@ -578,12 +630,24 @@ impl OcclusionTester { } } + if let Some(debug_renderer) = debug_renderer { + for tile in tiles.iter() { + debug_renderer::draw_rect( + &tile.bounds, + &mut lines, + Color::COLORS[tile.objects.len() + 2], + ); + } + + debug_renderer.set_lines(state, &lines); + } + let mut gpu_tiles = Vec::with_capacity(tiles.len()); for tile in tiles.iter_mut() { tile.objects .sort_by(|a, b| a.depth.partial_cmp(&b.depth).unwrap_or(Ordering::Less)); - let mut gpu_tile = GpuTile { + gpu_tiles.push(GpuTile { objects: tile .objects .iter() @@ -593,11 +657,7 @@ impl OcclusionTester { .collect::>() .into_inner() .unwrap(), - }; - - gpu_tile.objects.sort(); - - gpu_tiles.push(gpu_tile); + }); } self.tile_buffer.borrow_mut().bind_mut(state, 0).set_data( @@ -621,6 +681,7 @@ impl OcclusionTester { prev_framebuffer: &FrameBuffer, graph: &Graph, objects: &[Handle], + debug_renderer: Option<&mut DebugRenderer>, ) -> Result, bool>, FrameworkError> { let w = self.frame_size.x as i32; let h = self.frame_size.y as i32; @@ -657,27 +718,67 @@ impl OcclusionTester { view_projection, &viewport, objects, + debug_renderer, )?; - self.instance_matrices_buffer.upload( - state, - objects.iter().map(|h| { - let aabb = graph[*h].world_bounding_box(); - let s = aabb.max - aabb.min; - Matrix4::new_translation(&aabb.center()) * Matrix4::new_nonuniform_scaling(&s) - }), - 0, - )?; + let mut vertex_buffer = Vec::with_capacity(objects.len() * 8); + let mut triangles = Vec::with_capacity(objects.len() * 12); + for (object_index, object) in objects.iter().enumerate() { + let object_index = object_index as f32; + let mut aabb = graph[*object].world_bounding_box(); + aabb.inflate(Vector3::repeat(0.05)); + let s = aabb.max - aabb.min; + let matrix = + Matrix4::new_translation(&aabb.center()) * Matrix4::new_nonuniform_scaling(&s); + let base_index = vertex_buffer.len(); + let vertices = [ + Vertex::new(0.5, 0.5, 0.5, object_index), + Vertex::new(0.5, -0.5, 0.5, object_index), + Vertex::new(-0.5, -0.5, 0.5, object_index), + Vertex::new(-0.5, 0.5, 0.5, object_index), + Vertex::new(0.5, 0.5, -0.5, object_index), + Vertex::new(0.5, -0.5, -0.5, object_index), + Vertex::new(-0.5, -0.5, -0.5, object_index), + Vertex::new(-0.5, 0.5, -0.5, object_index), + ]; + for mut vertex in vertices { + let xyz = matrix.transform_point(&vertex.position.xyz().into()).coords; + vertex.position.x = xyz.x; + vertex.position.y = xyz.y; + vertex.position.z = xyz.z; + vertex_buffer.push(vertex); + } + let local_triangles = [ + TriangleDefinition([0, 1, 3]), + TriangleDefinition([1, 2, 3]), + TriangleDefinition([7, 5, 4]), + TriangleDefinition([7, 6, 5]), + TriangleDefinition([4, 1, 0]), + TriangleDefinition([1, 4, 5]), + TriangleDefinition([7, 3, 2]), + TriangleDefinition([2, 6, 7]), + TriangleDefinition([0, 3, 4]), + TriangleDefinition([7, 4, 3]), + TriangleDefinition([5, 2, 1]), + TriangleDefinition([2, 5, 6]), + ]; + for triangle in local_triangles { + triangles.push(triangle.add(base_index as u32)); + } + } + self.cubes.set_buffer_data(state, 0, &vertex_buffer); + self.cubes.bind(state).set_triangles(&triangles); + + state.set_depth_func(CompareFunc::LessOrEqual); let shader = &self.shader; - self.framebuffer.draw_instances( - objects.len(), - &self.cube, + self.framebuffer.draw( + &self.cubes, state, viewport, &self.shader.program, &DrawParameters { - cull_face: None, + cull_face: Some(CullFace::Front), color_write: ColorMask::all(true), depth_write: false, stencil_test: None, @@ -691,19 +792,16 @@ impl OcclusionTester { }), stencil_op: Default::default(), }, + ElementRange::Full, |mut program_binding| { program_binding .set_texture(&shader.tile_buffer, &self.tile_buffer) - .set_texture( - &shader.instance_matrices, - self.instance_matrices_buffer.texture(), - ) .set_i32(&shader.tile_size, self.tile_size as i32) .set_f32(&shader.frame_buffer_height, self.frame_size.y as f32) .set_matrix4(&shader.view_projection, view_projection); }, - ); + )?; - Ok(self.visibility_map(state, objects, &tiles)) + Ok(self.visibility_map(observer_position, state, objects, &tiles, graph)) } } From 79413a95fd937b58d9c76a9f19bcbe946c0d2fb8 Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Mon, 26 Aug 2024 20:39:47 +0300 Subject: [PATCH 4/7] optimize visibility buffer on gpu before reading it on cpu - collapses fullscreen visibility buffer into (w_tiles X h_tiles) buffer, and essentially moves 98%+ of computational complexity to GPU --- fyrox-impl/src/renderer/gbuffer/mod.rs | 3 + fyrox-impl/src/renderer/mod.rs | 1 + .../shaders/visibility_optimizer_fs.glsl | 24 +++ .../shaders/visibility_optimizer_vs.glsl | 8 + fyrox-impl/src/renderer/visibility.rs | 169 +++++++++++++++--- 5 files changed, 176 insertions(+), 29 deletions(-) create mode 100644 fyrox-impl/src/renderer/shaders/visibility_optimizer_fs.glsl create mode 100644 fyrox-impl/src/renderer/shaders/visibility_optimizer_vs.glsl diff --git a/fyrox-impl/src/renderer/gbuffer/mod.rs b/fyrox-impl/src/renderer/gbuffer/mod.rs index add30d590..a8bc20c43 100644 --- a/fyrox-impl/src/renderer/gbuffer/mod.rs +++ b/fyrox-impl/src/renderer/gbuffer/mod.rs @@ -100,6 +100,7 @@ pub(crate) struct GBufferRenderContext<'a, 'b> { pub matrix_storage: &'a mut MatrixStorageCache, #[allow(dead_code)] pub screen_space_debug_renderer: &'a mut DebugRenderer, + pub unit_quad: &'a GeometryBuffer, } impl GBuffer { @@ -305,6 +306,7 @@ impl GBuffer { volume_dummy, graph, matrix_storage, + unit_quad, .. } = args; @@ -325,6 +327,7 @@ impl GBuffer { graph, &objects, None, + unit_quad, )?; let viewport = Rect::new(0, 0, self.width, self.height); diff --git a/fyrox-impl/src/renderer/mod.rs b/fyrox-impl/src/renderer/mod.rs index 43a3d22c2..8514adf1a 100644 --- a/fyrox-impl/src/renderer/mod.rs +++ b/fyrox-impl/src/renderer/mod.rs @@ -1669,6 +1669,7 @@ impl Renderer { graph, matrix_storage: &mut self.matrix_storage, screen_space_debug_renderer: &mut self.screen_space_debug_renderer, + unit_quad: &self.quad, })?; state.set_polygon_fill_mode(PolygonFace::FrontAndBack, PolygonFillMode::Fill); diff --git a/fyrox-impl/src/renderer/shaders/visibility_optimizer_fs.glsl b/fyrox-impl/src/renderer/shaders/visibility_optimizer_fs.glsl new file mode 100644 index 000000000..4f9a14b7d --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/visibility_optimizer_fs.glsl @@ -0,0 +1,24 @@ +uniform int tileSize; +uniform sampler2D visibilityBuffer; + +out uint optimizedVisibilityMask; + +void main() +{ + int tileX = int(gl_FragCoord.x); + int tileY = int(gl_FragCoord.y); + + int beginX = tileX * tileSize; + int beginY = tileY * tileSize; + + int endX = (tileX + 1) * tileSize; + int endY = (tileY + 1) * tileSize; + + int visibilityMask = 0; + for (int y = beginY; y < endY; ++y) { + for (int x = beginX; x < endX; ++x) { + visibilityMask |= int(texelFetch(visibilityBuffer, ivec2(x, y), 0).r); + } + } + optimizedVisibilityMask = uint(visibilityMask); +} \ No newline at end of file diff --git a/fyrox-impl/src/renderer/shaders/visibility_optimizer_vs.glsl b/fyrox-impl/src/renderer/shaders/visibility_optimizer_vs.glsl new file mode 100644 index 000000000..221d9758d --- /dev/null +++ b/fyrox-impl/src/renderer/shaders/visibility_optimizer_vs.glsl @@ -0,0 +1,8 @@ +layout (location = 0) in vec3 vertexPosition; + +uniform mat4 viewProjection; + +void main() +{ + gl_Position = viewProjection * vec4(vertexPosition, 1.0); +} \ No newline at end of file diff --git a/fyrox-impl/src/renderer/visibility.rs b/fyrox-impl/src/renderer/visibility.rs index 5ff4d6ae3..b547bac9a 100644 --- a/fyrox-impl/src/renderer/visibility.rs +++ b/fyrox-impl/src/renderer/visibility.rs @@ -22,6 +22,7 @@ #![allow(missing_docs)] // TODO +use crate::renderer::make_viewport_matrix; use crate::{ core::{ algebra::{Matrix4, Vector2, Vector3, Vector4}, @@ -344,6 +345,123 @@ impl Shader { } } +struct VisibilityOptimizerShader { + program: GpuProgram, + view_projection: UniformLocation, + tile_size: UniformLocation, + visibility_buffer: UniformLocation, +} + +impl VisibilityOptimizerShader { + fn new(state: &PipelineState) -> Result { + let fragment_source = include_str!("shaders/visibility_optimizer_fs.glsl"); + let vertex_source = include_str!("shaders/visibility_optimizer_vs.glsl"); + let program = GpuProgram::from_source( + state, + "VisibilityOptimizerShader", + vertex_source, + fragment_source, + )?; + Ok(Self { + view_projection: program + .uniform_location(state, &ImmutableString::new("viewProjection"))?, + tile_size: program.uniform_location(state, &ImmutableString::new("tileSize"))?, + visibility_buffer: program + .uniform_location(state, &ImmutableString::new("visibilityBuffer"))?, + program, + }) + } +} + +struct VisibilityBufferOptimizer { + framebuffer: FrameBuffer, + optimized_visibility_buffer: Rc>, + shader: VisibilityOptimizerShader, + w_tiles: usize, + h_tiles: usize, +} + +impl VisibilityBufferOptimizer { + fn new(state: &PipelineState, w_tiles: usize, h_tiles: usize) -> Result { + let optimized_visibility_buffer = GpuTexture::new( + state, + GpuTextureKind::Rectangle { + width: w_tiles, + height: h_tiles, + }, + PixelKind::R32UI, + MinificationFilter::Nearest, + MagnificationFilter::Nearest, + 1, + None, + )?; + + let optimized_visibility_buffer = Rc::new(RefCell::new(optimized_visibility_buffer)); + + Ok(Self { + framebuffer: FrameBuffer::new( + state, + None, + vec![Attachment { + kind: AttachmentKind::Color, + texture: optimized_visibility_buffer.clone(), + }], + )?, + optimized_visibility_buffer, + shader: VisibilityOptimizerShader::new(state)?, + w_tiles, + h_tiles, + }) + } + + fn read_visibility_mask(&self, state: &PipelineState) -> Vec { + self.optimized_visibility_buffer + .borrow_mut() + .bind_mut(state, 0) + .get_image(0, state) + } + + fn optimize( + &mut self, + state: &PipelineState, + visibility_buffer: &Rc>, + unit_quad: &GeometryBuffer, + tile_size: i32, + ) -> Result, FrameworkError> { + let viewport = Rect::new(0, 0, self.w_tiles as i32, self.h_tiles as i32); + + self.framebuffer + .clear(state, viewport, Some(Color::TRANSPARENT), None, None); + + let matrix = make_viewport_matrix(viewport); + + self.framebuffer.draw( + unit_quad, + state, + viewport, + &self.shader.program, + &DrawParameters { + cull_face: None, + color_write: ColorMask::all(true), + depth_write: false, + stencil_test: None, + depth_test: false, + blend: None, + stencil_op: Default::default(), + }, + ElementRange::Full, + |mut program_binding| { + program_binding + .set_matrix4(&self.shader.view_projection, &matrix) + .set_texture(&self.shader.visibility_buffer, visibility_buffer) + .set_i32(&self.shader.tile_size, tile_size); + }, + )?; + + Ok(self.read_visibility_mask(state)) + } +} + pub struct OcclusionTester { framebuffer: FrameBuffer, visibility_mask: Rc>, @@ -354,6 +472,7 @@ pub struct OcclusionTester { w_tiles: usize, h_tiles: usize, cubes: GeometryBuffer, + visibility_buffer_optimizer: VisibilityBufferOptimizer, } #[derive(Default, Pod, Zeroable, Copy, Clone, Debug)] @@ -495,43 +614,34 @@ impl OcclusionTester { tile_buffer, h_tiles, cubes, + visibility_buffer_optimizer: VisibilityBufferOptimizer::new(state, w_tiles, h_tiles)?, }) } - #[inline(never)] - fn read_visibility_mask(&self, state: &PipelineState) -> Vec { - // TODO: Replace with double buffering to prevent GPU stalls. - state.finish(); - - self.visibility_mask - .borrow_mut() - .bind_mut(state, 0) - .get_image(0, state) - } - #[inline(never)] fn visibility_map( - &self, + &mut self, observer_position: Vector3, state: &PipelineState, objects: &[Handle], tiles: &[Tile], graph: &Graph, - ) -> FxHashMap, bool> { - // TODO: This must be done using compute shader, but it is not available on WebGL2 so this - // code should still be used on WASM targets. - let visibility_buffer = self.read_visibility_mask(state); + unit_quad: &GeometryBuffer, + ) -> Result, bool>, FrameworkError> { + let visibility_buffer = self.visibility_buffer_optimizer.optimize( + state, + &self.visibility_mask, + unit_quad, + self.tile_size as i32, + )?; + let mut objects_visibility = vec![false; objects.len()]; - for y in 0..self.frame_size.y { - let img_y = self.frame_size.y.saturating_sub(1) - y; - let tile_y = y / self.tile_size; - let row_start_index = img_y * self.frame_size.x; - let tile_row_start_index = tile_y * self.w_tiles; - for x in 0..self.frame_size.x { - let tile_x = x / self.tile_size; - let bits = visibility_buffer[row_start_index + x] as u32; - let tile = &tiles[tile_row_start_index + tile_x]; - 'bit_loop: for bit in 0..u32::BITS { + for y in 0..self.h_tiles { + let img_y = self.h_tiles.saturating_sub(1) - y; + for x in 0..self.w_tiles { + let tile = &tiles[y * self.w_tiles + x]; + let bits = visibility_buffer[img_y * self.w_tiles + x]; + for bit in 0..u32::BITS { if let Some(object) = tile.objects.get(bit as usize) { let visibility = &mut objects_visibility[object.index as usize]; let mask = 1 << bit; @@ -540,7 +650,7 @@ impl OcclusionTester { *visibility = true; } } else { - break 'bit_loop; + break; } } } @@ -562,7 +672,7 @@ impl OcclusionTester { } } - visibility_map + Ok(visibility_map) } fn screen_space_to_tile_space( @@ -682,6 +792,7 @@ impl OcclusionTester { graph: &Graph, objects: &[Handle], debug_renderer: Option<&mut DebugRenderer>, + unit_quad: &GeometryBuffer, ) -> Result, bool>, FrameworkError> { let w = self.frame_size.x as i32; let h = self.frame_size.y as i32; @@ -802,6 +913,6 @@ impl OcclusionTester { }, )?; - Ok(self.visibility_map(observer_position, state, objects, &tiles, graph)) + self.visibility_map(observer_position, state, objects, &tiles, graph, unit_quad) } } From d06e9e91deef7b38c9350c0046aa3cc8133be4fe Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Tue, 27 Aug 2024 11:55:33 +0300 Subject: [PATCH 5/7] fixed aabb -> screen projection - handle cases when points go behind near clipping plane --- fyrox-impl/src/renderer/visibility.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/fyrox-impl/src/renderer/visibility.rs b/fyrox-impl/src/renderer/visibility.rs index b547bac9a..1ffb2fd28 100644 --- a/fyrox-impl/src/renderer/visibility.rs +++ b/fyrox-impl/src/renderer/visibility.rs @@ -503,15 +503,16 @@ fn screen_space_rect( let mut rect_builder = OptionRect::default(); for corner in aabb.corners() { let clip_space = view_projection * Vector4::new(corner.x, corner.y, corner.z, 1.0); - let ndc_space = if clip_space.w != 0.0 { - clip_space.xyz() / clip_space.w - } else { - clip_space.xyz() - }; + let ndc_space = clip_space.xyz() / clip_space.w.abs(); + let mut normalized_screen_space = + Vector2::new((ndc_space.x + 1.0) / 2.0, (1.0 - ndc_space.y) / 2.0); + normalized_screen_space.x = normalized_screen_space.x.clamp(0.0, 1.0); + normalized_screen_space.y = normalized_screen_space.y.clamp(0.0, 1.0); let screen_space_corner = Vector2::new( - ((ndc_space.x + 1.0) / 2.0) * (viewport.size.x as f32) + viewport.position.x as f32, - ((1.0 - ndc_space.y) / 2.0) * (viewport.size.y as f32) + viewport.position.y as f32, + (normalized_screen_space.x * viewport.size.x as f32) + viewport.position.x as f32, + (normalized_screen_space.y * viewport.size.y as f32) + viewport.position.y as f32, ); + rect_builder.push(screen_space_corner); } rect_builder.unwrap() From dbe704c75e3a37543d064ffd5e6f5f7296ca0291 Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Tue, 27 Aug 2024 11:56:32 +0300 Subject: [PATCH 6/7] use instancing instead of manual vertex buffer formation - saves some time on cpu + gpu --- .../src/renderer/shaders/visibility_vs.glsl | 7 +- fyrox-impl/src/renderer/visibility.rs | 127 +++++------------- 2 files changed, 39 insertions(+), 95 deletions(-) diff --git a/fyrox-impl/src/renderer/shaders/visibility_vs.glsl b/fyrox-impl/src/renderer/shaders/visibility_vs.glsl index 8858e5c1e..a4e900189 100644 --- a/fyrox-impl/src/renderer/shaders/visibility_vs.glsl +++ b/fyrox-impl/src/renderer/shaders/visibility_vs.glsl @@ -1,11 +1,12 @@ -layout (location = 0) in vec4 vertexPosition; +layout (location = 0) in vec3 vertexPosition; uniform mat4 viewProjection; +uniform sampler2D matrices; flat out uint objectIndex; void main() { - objectIndex = uint(vertexPosition.w); - gl_Position = viewProjection * vec4(vertexPosition.xyz, 1.0); + objectIndex = uint(gl_InstanceID); + gl_Position = (viewProjection * S_FetchMatrix(matrices, gl_InstanceID)) * vec4(vertexPosition, 1.0); } \ No newline at end of file diff --git a/fyrox-impl/src/renderer/visibility.rs b/fyrox-impl/src/renderer/visibility.rs index 1ffb2fd28..cc72af5a8 100644 --- a/fyrox-impl/src/renderer/visibility.rs +++ b/fyrox-impl/src/renderer/visibility.rs @@ -22,14 +22,13 @@ #![allow(missing_docs)] // TODO -use crate::renderer::make_viewport_matrix; use crate::{ core::{ algebra::{Matrix4, Vector2, Vector3, Vector4}, array_as_u8_slice, arrayvec::ArrayVec, color::Color, - math::{aabb::AxisAlignedBoundingBox, OptionRect, Rect, TriangleDefinition}, + math::{aabb::AxisAlignedBoundingBox, OptionRect, Rect}, pool::Handle, ImmutableString, }, @@ -41,10 +40,7 @@ use crate::{ framebuffer::{ Attachment, AttachmentKind, BlendParameters, CullFace, DrawParameters, FrameBuffer, }, - geometry_buffer::{ - AttributeDefinition, AttributeKind, BufferBuilder, ElementKind, ElementRange, - GeometryBuffer, GeometryBufferBuilder, GeometryBufferKind, - }, + geometry_buffer::{ElementRange, GeometryBuffer, GeometryBufferKind}, gpu_program::{GpuProgram, UniformLocation}, gpu_texture::{ Coordinate, GpuTexture, GpuTextureKind, MagnificationFilter, MinificationFilter, @@ -56,8 +52,10 @@ use crate::{ PipelineState, }, }, + make_viewport_matrix, + storage::MatrixStorage, }, - scene::{graph::Graph, node::Node}, + scene::{graph::Graph, mesh::surface::SurfaceData, node::Node}, }; use bytemuck::{Pod, Zeroable}; use fxhash::FxHashMap; @@ -325,6 +323,7 @@ struct Shader { tile_size: UniformLocation, tile_buffer: UniformLocation, frame_buffer_height: UniformLocation, + matrices: UniformLocation, } impl Shader { @@ -340,6 +339,7 @@ impl Shader { frame_buffer_height: program .uniform_location(state, &ImmutableString::new("frameBufferHeight"))?, tile_buffer: program.uniform_location(state, &ImmutableString::new("tileBuffer"))?, + matrices: program.uniform_location(state, &ImmutableString::new("matrices"))?, program, }) } @@ -471,8 +471,9 @@ pub struct OcclusionTester { tile_size: usize, w_tiles: usize, h_tiles: usize, - cubes: GeometryBuffer, + cube: GeometryBuffer, visibility_buffer_optimizer: VisibilityBufferOptimizer, + matrix_storage: MatrixStorage, } #[derive(Default, Pod, Zeroable, Copy, Clone, Debug)] @@ -518,18 +519,10 @@ fn screen_space_rect( rect_builder.unwrap() } -#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)] -#[repr(C)] -struct Vertex { - pub position: Vector4, -} - -impl Vertex { - fn new(x: f32, y: f32, z: f32, w: f32) -> Self { - Self { - position: Vector4::new(x, y, z, w), - } - } +fn inflated_world_aabb(graph: &Graph, object: Handle) -> AxisAlignedBoundingBox { + let mut aabb = graph[object].world_bounding_box(); + aabb.inflate(Vector3::repeat(0.05)); + aabb } impl OcclusionTester { @@ -582,19 +575,6 @@ impl OcclusionTester { let visibility_mask = Rc::new(RefCell::new(visibility_mask)); let tile_buffer = Rc::new(RefCell::new(tile_buffer)); - let cubes = GeometryBufferBuilder::new(ElementKind::Triangle) - .with_buffer_builder( - BufferBuilder::new::(GeometryBufferKind::DynamicDraw, None).with_attribute( - AttributeDefinition { - location: 0, - kind: AttributeKind::Float4, - normalized: false, - divisor: 0, - }, - ), - ) - .build(state)?; - Ok(Self { framebuffer: FrameBuffer::new( state, @@ -614,8 +594,13 @@ impl OcclusionTester { w_tiles, tile_buffer, h_tiles, - cubes, + cube: GeometryBuffer::from_surface_data( + &SurfaceData::make_cube(Matrix4::identity()), + GeometryBufferKind::StaticDraw, + state, + )?, visibility_buffer_optimizer: VisibilityBufferOptimizer::new(state, w_tiles, h_tiles)?, + matrix_storage: MatrixStorage::new(state)?, }) } @@ -664,11 +649,7 @@ impl OcclusionTester { .collect::, bool>>(); for (object, visibility) in visibility_map.iter_mut() { - let object_ref = &graph[*object]; - if object_ref - .world_bounding_box() - .is_contains_point(observer_position) - { + if inflated_world_aabb(graph, *object).is_contains_point(observer_position) { *visibility = true; } } @@ -833,64 +814,26 @@ impl OcclusionTester { debug_renderer, )?; - let mut vertex_buffer = Vec::with_capacity(objects.len() * 8); - let mut triangles = Vec::with_capacity(objects.len() * 12); - for (object_index, object) in objects.iter().enumerate() { - let object_index = object_index as f32; - let mut aabb = graph[*object].world_bounding_box(); - aabb.inflate(Vector3::repeat(0.05)); - let s = aabb.max - aabb.min; - let matrix = - Matrix4::new_translation(&aabb.center()) * Matrix4::new_nonuniform_scaling(&s); - let base_index = vertex_buffer.len(); - let vertices = [ - Vertex::new(0.5, 0.5, 0.5, object_index), - Vertex::new(0.5, -0.5, 0.5, object_index), - Vertex::new(-0.5, -0.5, 0.5, object_index), - Vertex::new(-0.5, 0.5, 0.5, object_index), - Vertex::new(0.5, 0.5, -0.5, object_index), - Vertex::new(0.5, -0.5, -0.5, object_index), - Vertex::new(-0.5, -0.5, -0.5, object_index), - Vertex::new(-0.5, 0.5, -0.5, object_index), - ]; - for mut vertex in vertices { - let xyz = matrix.transform_point(&vertex.position.xyz().into()).coords; - vertex.position.x = xyz.x; - vertex.position.y = xyz.y; - vertex.position.z = xyz.z; - vertex_buffer.push(vertex); - } - - let local_triangles = [ - TriangleDefinition([0, 1, 3]), - TriangleDefinition([1, 2, 3]), - TriangleDefinition([7, 5, 4]), - TriangleDefinition([7, 6, 5]), - TriangleDefinition([4, 1, 0]), - TriangleDefinition([1, 4, 5]), - TriangleDefinition([7, 3, 2]), - TriangleDefinition([2, 6, 7]), - TriangleDefinition([0, 3, 4]), - TriangleDefinition([7, 4, 3]), - TriangleDefinition([5, 2, 1]), - TriangleDefinition([2, 5, 6]), - ]; - for triangle in local_triangles { - triangles.push(triangle.add(base_index as u32)); - } - } - self.cubes.set_buffer_data(state, 0, &vertex_buffer); - self.cubes.bind(state).set_triangles(&triangles); + self.matrix_storage.upload( + state, + objects.iter().map(|h| { + let aabb = inflated_world_aabb(graph, *h); + let s = aabb.max - aabb.min; + Matrix4::new_translation(&aabb.center()) * Matrix4::new_nonuniform_scaling(&s) + }), + 0, + )?; state.set_depth_func(CompareFunc::LessOrEqual); let shader = &self.shader; - self.framebuffer.draw( - &self.cubes, + self.framebuffer.draw_instances( + objects.len(), + &self.cube, state, viewport, &self.shader.program, &DrawParameters { - cull_face: Some(CullFace::Front), + cull_face: Some(CullFace::Back), color_write: ColorMask::all(true), depth_write: false, stencil_test: None, @@ -904,15 +847,15 @@ impl OcclusionTester { }), stencil_op: Default::default(), }, - ElementRange::Full, |mut program_binding| { program_binding .set_texture(&shader.tile_buffer, &self.tile_buffer) + .set_texture(&shader.matrices, self.matrix_storage.texture()) .set_i32(&shader.tile_size, self.tile_size as i32) .set_f32(&shader.frame_buffer_height, self.frame_size.y as f32) .set_matrix4(&shader.view_projection, view_projection); }, - )?; + ); self.visibility_map(observer_position, state, objects, &tiles, graph, unit_quad) } From 353a7e78fc26233d66fa1d92c7dc8eb5233e125f Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Tue, 27 Aug 2024 12:42:32 +0300 Subject: [PATCH 7/7] switch to enable/disable occlusion culling --- fyrox-impl/src/renderer/gbuffer/mod.rs | 47 +++++++++++++++----------- fyrox-impl/src/renderer/mod.rs | 11 +++++- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/fyrox-impl/src/renderer/gbuffer/mod.rs b/fyrox-impl/src/renderer/gbuffer/mod.rs index a8bc20c43..6c9f471b3 100644 --- a/fyrox-impl/src/renderer/gbuffer/mod.rs +++ b/fyrox-impl/src/renderer/gbuffer/mod.rs @@ -31,6 +31,7 @@ use crate::renderer::debug_renderer::DebugRenderer; use crate::renderer::visibility::OcclusionTester; +use crate::renderer::QualitySettings; use crate::{ core::{ algebra::{Matrix4, Vector2}, @@ -95,7 +96,7 @@ pub(crate) struct GBufferRenderContext<'a, 'b> { pub normal_dummy: Rc>, pub black_dummy: Rc>, pub volume_dummy: Rc>, - pub use_parallax_mapping: bool, + pub quality_settings: &'a QualitySettings, pub graph: &'b Graph, pub matrix_storage: &'a mut MatrixStorageCache, #[allow(dead_code)] @@ -299,7 +300,7 @@ impl GBuffer { bundle_storage, texture_cache, shader_cache, - use_parallax_mapping, + quality_settings, white_dummy, normal_dummy, black_dummy, @@ -312,23 +313,27 @@ impl GBuffer { let initial_view_projection = camera.view_projection_matrix(); - let mut objects = FxHashSet::default(); - for bundle in bundle_storage.bundles.iter() { - for instance in bundle.instances.iter() { - objects.insert(instance.node_handle); + let visibility = if quality_settings.use_occlusion_culling { + let mut objects = FxHashSet::default(); + for bundle in bundle_storage.bundles.iter() { + for instance in bundle.instances.iter() { + objects.insert(instance.node_handle); + } } - } - let objects = objects.into_iter().collect::>(); - let visibility = self.occlusion_tester.check( - camera.global_position(), - &initial_view_projection, - state, - &self.framebuffer, - graph, - &objects, - None, - unit_quad, - )?; + let objects = objects.into_iter().collect::>(); + self.occlusion_tester.check( + camera.global_position(), + &initial_view_projection, + state, + &self.framebuffer, + graph, + &objects, + None, + unit_quad, + )? + } else { + Default::default() + }; let viewport = Rect::new(0, 0, self.width, self.height); self.framebuffer.clear( @@ -374,7 +379,9 @@ impl GBuffer { }; for instance in bundle.instances.iter() { - if !visibility.get(&instance.node_handle).map_or(true, |v| *v) { + if quality_settings.use_occlusion_culling + && !visibility.get(&instance.node_handle).map_or(true, |v| *v) + { continue; } @@ -401,7 +408,7 @@ impl GBuffer { camera_up_vector: &camera_up, camera_side_vector: &camera_side, z_near: camera.projection().z_near(), - use_pom: use_parallax_mapping, + use_pom: quality_settings.use_parallax_mapping, light_position: &Default::default(), blend_shapes_storage: blend_shapes_storage.as_ref(), blend_shapes_weights: &instance.blend_shapes_weights, diff --git a/fyrox-impl/src/renderer/mod.rs b/fyrox-impl/src/renderer/mod.rs index 8514adf1a..cb685e6ed 100644 --- a/fyrox-impl/src/renderer/mod.rs +++ b/fyrox-impl/src/renderer/mod.rs @@ -254,6 +254,11 @@ pub struct QualitySettings { /// Whether to use bloom effect. pub use_bloom: bool, + + /// Whether to use occlusion culling technique or not. Warning: this is experimental feature + /// that wasn't properly tested yet! + #[serde(default)] + pub use_occlusion_culling: bool, } impl Default for QualitySettings { @@ -290,6 +295,7 @@ impl QualitySettings { use_bloom: true, + use_occlusion_culling: false, use_parallax_mapping: true, csm_settings: Default::default(), @@ -323,6 +329,7 @@ impl QualitySettings { use_bloom: true, + use_occlusion_culling: false, use_parallax_mapping: true, csm_settings: CsmSettings { @@ -361,6 +368,7 @@ impl QualitySettings { use_bloom: true, + use_occlusion_culling: false, use_parallax_mapping: false, csm_settings: CsmSettings { @@ -399,6 +407,7 @@ impl QualitySettings { use_bloom: false, + use_occlusion_culling: false, use_parallax_mapping: false, csm_settings: CsmSettings { @@ -1661,7 +1670,7 @@ impl Renderer { bundle_storage: &bundle_storage, texture_cache: &mut self.texture_cache, shader_cache: &mut self.shader_cache, - use_parallax_mapping: self.quality_settings.use_parallax_mapping, + quality_settings: &self.quality_settings, normal_dummy: self.normal_dummy.clone(), white_dummy: self.white_dummy.clone(), black_dummy: self.black_dummy.clone(),