mod grid;
mod optimizer;
use crate::{
core::{
algebra::{Matrix4, Vector2, Vector3},
array_as_u8_slice,
color::Color,
math::{aabb::AxisAlignedBoundingBox, Rect, Vector3Ext},
pool::Handle,
ImmutableString,
},
graph::BaseSceneGraph,
renderer::{
cache::uniform::UniformBufferCache,
debug_renderer::{self, DebugRenderer},
framework::{
buffer::BufferUsage,
error::FrameworkError,
framebuffer::{
Attachment, AttachmentKind, FrameBuffer, ResourceBindGroup, ResourceBinding,
},
geometry_buffer::GeometryBuffer,
gpu_program::{GpuProgram, UniformLocation},
gpu_texture::{GpuTexture, GpuTextureKind, PixelKind},
server::GraphicsServer,
uniform::StaticUniformBuffer,
BlendEquation, BlendFactor, BlendFunc, BlendMode, BlendParameters, ColorMask,
CompareFunc, CullFace, DrawParameters, GeometryBufferExt,
},
occlusion::{
grid::{GridCache, Visibility},
optimizer::VisibilityBufferOptimizer,
},
storage::MatrixStorage,
},
scene::{graph::Graph, mesh::surface::SurfaceData, node::Node},
};
use bytemuck::{Pod, Zeroable};
use fyrox_graphics::framebuffer::BufferLocation;
use std::{cell::RefCell, rc::Rc};
struct Shader {
program: Box<dyn GpuProgram>,
tile_buffer: UniformLocation,
matrices: UniformLocation,
uniform_buffer_binding: usize,
}
impl Shader {
fn new(server: &dyn GraphicsServer) -> Result<Self, FrameworkError> {
let fragment_source = include_str!("../shaders/visibility_fs.glsl");
let vertex_source = include_str!("../shaders/visibility_vs.glsl");
let program = server.create_program("VisibilityShader", vertex_source, fragment_source)?;
Ok(Self {
uniform_buffer_binding: program
.uniform_block_index(&ImmutableString::new("Uniforms"))?,
tile_buffer: program.uniform_location(&ImmutableString::new("tileBuffer"))?,
matrices: program.uniform_location(&ImmutableString::new("matrices"))?,
program,
})
}
}
pub struct OcclusionTester {
framebuffer: Box<dyn FrameBuffer>,
visibility_mask: Rc<RefCell<dyn GpuTexture>>,
tile_buffer: Rc<RefCell<dyn GpuTexture>>,
frame_size: Vector2<usize>,
shader: Shader,
tile_size: usize,
w_tiles: usize,
h_tiles: usize,
cube: Box<dyn GeometryBuffer>,
visibility_buffer_optimizer: VisibilityBufferOptimizer,
matrix_storage: MatrixStorage,
objects_to_test: Vec<Handle<Node>>,
view_projection: Matrix4<f32>,
observer_position: Vector3<f32>,
pub grid_cache: GridCache,
tiles: TileBuffer,
}
const MAX_BITS: usize = u32::BITS as usize;
#[derive(Default, Pod, Zeroable, Copy, Clone, Debug)]
#[repr(C)]
struct Tile {
count: u32,
objects: [u32; MAX_BITS],
}
impl Tile {
fn add(&mut self, index: u32) {
let count = self.count as usize;
if count < self.objects.len() {
self.objects[count] = index;
self.count += 1;
}
}
}
#[derive(Default, Debug)]
struct TileBuffer {
tiles: Vec<Tile>,
}
impl TileBuffer {
fn new(width: usize, height: usize) -> Self {
Self {
tiles: vec![Default::default(); width * height],
}
}
fn clear(&mut self) {
for tile in self.tiles.iter_mut() {
tile.count = 0;
}
}
}
fn inflated_world_aabb(graph: &Graph, object: Handle<Node>) -> Option<AxisAlignedBoundingBox> {
let mut aabb = graph
.try_get(object)
.map(|node_ref| node_ref.world_bounding_box())?;
aabb.inflate(Vector3::repeat(0.01));
Some(aabb)
}
impl OcclusionTester {
pub fn new(
server: &dyn GraphicsServer,
width: usize,
height: usize,
tile_size: usize,
) -> Result<Self, FrameworkError> {
let depth_stencil = server.create_2d_render_target(PixelKind::D24S8, width, height)?;
let visibility_mask = server.create_2d_render_target(PixelKind::RGBA8, width, height)?;
let w_tiles = width / tile_size + 1;
let h_tiles = height / tile_size + 1;
let tile_buffer =
server.create_2d_render_target(PixelKind::R32UI, w_tiles * (MAX_BITS + 1), h_tiles)?;
Ok(Self {
framebuffer: server.create_frame_buffer(
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),
shader: Shader::new(server)?,
tile_size,
w_tiles,
tile_buffer,
h_tiles,
cube: <dyn GeometryBuffer>::from_surface_data(
&SurfaceData::make_cube(Matrix4::identity()),
BufferUsage::StaticDraw,
server,
)?,
visibility_buffer_optimizer: VisibilityBufferOptimizer::new(server, w_tiles, h_tiles)?,
matrix_storage: MatrixStorage::new(server)?,
objects_to_test: Default::default(),
view_projection: Default::default(),
observer_position: Default::default(),
grid_cache: GridCache::new(Vector3::repeat(1)),
tiles: TileBuffer::new(w_tiles, h_tiles),
})
}
pub fn try_query_visibility_results(&mut self, graph: &Graph) {
let Some(visibility_buffer) = self.visibility_buffer_optimizer.read_visibility_mask()
else {
return;
};
let mut objects_visibility = vec![false; self.objects_to_test.len()];
for y in 0..self.h_tiles {
let img_y = self.h_tiles.saturating_sub(1) - y;
let tile_offset = y * self.w_tiles;
let img_offset = img_y * self.w_tiles;
for x in 0..self.w_tiles {
let tile = &self.tiles.tiles[tile_offset + x];
let bits = visibility_buffer[img_offset + x];
let count = tile.count as usize;
for bit in 0..count {
let object_index = tile.objects[bit];
let visibility = &mut objects_visibility[object_index as usize];
let mask = 1 << bit;
let is_visible = (bits & mask) == mask;
if is_visible {
*visibility = true;
}
}
}
}
let cell = self.grid_cache.get_or_insert_cell(self.observer_position);
for (obj, vis) in self.objects_to_test.iter().zip(objects_visibility.iter()) {
cell.mark(*obj, (*vis).into());
}
for (object, visibility) in cell.iter_mut() {
let Some(aabb) = inflated_world_aabb(graph, *object) else {
continue;
};
if aabb.is_contains_point(self.observer_position) {
*visibility = Visibility::Visible;
}
}
}
fn screen_space_to_tile_space(&self, pos: Vector2<f32>) -> Vector2<usize> {
let x = (pos.x / (self.tile_size as f32)) as usize;
let y = (pos.y / (self.tile_size as f32)) as usize;
Vector2::new(x, y)
}
fn prepare_tiles(
&mut self,
graph: &Graph,
viewport: &Rect<i32>,
debug_renderer: Option<&mut DebugRenderer>,
) -> Result<(), FrameworkError> {
self.tiles.clear();
let mut lines = Vec::new();
for (object_index, object) in self.objects_to_test.iter().enumerate() {
let object_index = object_index as u32;
let Some(node_ref) = graph.try_get(*object) else {
continue;
};
let aabb = node_ref.world_bounding_box();
let rect = aabb.project(&self.view_projection, viewport);
if debug_renderer.is_some() {
debug_renderer::draw_rect(&rect, &mut lines, Color::WHITE);
}
let min = self.screen_space_to_tile_space(rect.left_top_corner());
let max = self.screen_space_to_tile_space(rect.right_bottom_corner());
for y in min.y..=max.y {
let offset = y * self.w_tiles;
for x in min.x..=max.x {
self.tiles.tiles[offset + x].add(object_index);
}
}
}
if let Some(debug_renderer) = debug_renderer {
for (tile_index, tile) in self.tiles.tiles.iter().enumerate() {
let x = (tile_index % self.w_tiles) * self.tile_size;
let y = (tile_index / self.w_tiles) * self.tile_size;
let bounds = Rect::new(
x as f32,
y as f32,
self.tile_size as f32,
self.tile_size as f32,
);
debug_renderer::draw_rect(
&bounds,
&mut lines,
Color::COLORS[tile.objects.len() + 2],
);
}
debug_renderer.set_lines(&lines);
}
self.tile_buffer.borrow_mut().set_data(
GpuTextureKind::Rectangle {
width: self.w_tiles * (MAX_BITS + 1),
height: self.h_tiles,
},
PixelKind::R32UI,
1,
Some(array_as_u8_slice(self.tiles.tiles.as_slice())),
)?;
Ok(())
}
fn upload_data<'a>(
&mut self,
graph: &Graph,
objects_to_test: impl Iterator<Item = &'a Handle<Node>>,
prev_framebuffer: &dyn FrameBuffer,
observer_position: Vector3<f32>,
view_projection: Matrix4<f32>,
) {
self.view_projection = view_projection;
self.observer_position = observer_position;
let w = self.frame_size.x as i32;
let h = self.frame_size.y as i32;
prev_framebuffer.blit_to(
&*self.framebuffer,
0,
0,
w,
h,
0,
0,
w,
h,
false,
true,
false,
);
self.objects_to_test.clear();
if let Some(cell) = self.grid_cache.cell(self.observer_position) {
for object in objects_to_test {
if cell.needs_occlusion_query(*object) {
self.objects_to_test.push(*object);
}
}
}
self.objects_to_test.sort_unstable_by_key(|a| {
(graph[*a].global_position().sqr_distance(&observer_position) * 1000.0) as u64
});
}
pub fn try_run_visibility_test<'a>(
&mut self,
graph: &Graph,
debug_renderer: Option<&mut DebugRenderer>,
unit_quad: &dyn GeometryBuffer,
objects_to_test: impl Iterator<Item = &'a Handle<Node>>,
prev_framebuffer: &dyn FrameBuffer,
observer_position: Vector3<f32>,
view_projection: Matrix4<f32>,
uniform_buffer_cache: &mut UniformBufferCache,
) -> Result<(), FrameworkError> {
if self.visibility_buffer_optimizer.is_reading_from_gpu() {
return Ok(());
}
self.upload_data(
graph,
objects_to_test,
prev_framebuffer,
observer_position,
view_projection,
);
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(viewport, Some(Color::TRANSPARENT), None, None);
self.prepare_tiles(graph, &viewport, debug_renderer)?;
self.matrix_storage
.upload(self.objects_to_test.iter().filter_map(|h| {
let aabb = inflated_world_aabb(graph, *h)?;
let s = aabb.max - aabb.min;
Some(Matrix4::new_translation(&aabb.center()) * Matrix4::new_nonuniform_scaling(&s))
}))?;
let shader = &self.shader;
self.framebuffer.draw_instances(
self.objects_to_test.len(),
&*self.cube,
viewport,
&*self.shader.program,
&DrawParameters {
cull_face: Some(CullFace::Back),
color_write: ColorMask::all(true),
depth_write: false,
stencil_test: None,
depth_test: Some(CompareFunc::LessOrEqual),
blend: Some(BlendParameters {
func: BlendFunc::new(BlendFactor::One, BlendFactor::One),
equation: BlendEquation {
rgb: BlendMode::Add,
alpha: BlendMode::Add,
},
}),
stencil_op: Default::default(),
scissor_box: None,
},
&[ResourceBindGroup {
bindings: &[
ResourceBinding::texture(&self.tile_buffer, &shader.tile_buffer),
ResourceBinding::texture(self.matrix_storage.texture(), &shader.matrices),
ResourceBinding::Buffer {
buffer: uniform_buffer_cache.write(
StaticUniformBuffer::<256>::new()
.with(&self.view_projection)
.with(&(self.tile_size as i32))
.with(&(self.frame_size.y as f32)),
)?,
binding: BufferLocation::Auto {
shader_location: self.shader.uniform_buffer_binding,
},
data_usage: Default::default(),
},
],
}],
);
self.visibility_buffer_optimizer.optimize(
&self.visibility_mask,
unit_quad,
self.tile_size as i32,
uniform_buffer_cache,
)?;
Ok(())
}
}