Implement better chunk culling

This commit is contained in:
Thinkofname 2016-03-24 23:27:22 +00:00
parent 7692c54cf7
commit de673f1ee1
3 changed files with 208 additions and 48 deletions

View File

@ -8,6 +8,7 @@ use world;
use render;
use resources;
use model;
use types::bit::Set;
const NUM_WORKERS: usize = 8;
@ -16,9 +17,6 @@ pub struct ChunkBuilder {
free_builders: Vec<(usize, Vec<u8>)>,
built_recv: mpsc::Receiver<(usize, BuildReply)>,
sections: Vec<(i32, i32, i32)>,
next_collection: f64,
models: Arc<RwLock<model::Factory>>,
resources: Arc<RwLock<resources::Manager>>,
resource_version: usize,
@ -43,17 +41,13 @@ impl ChunkBuilder {
threads: threads,
free_builders: free,
built_recv: built_recv,
sections: vec![],
next_collection: 0.0,
models: models,
resources: resources.clone(),
resource_version: 0xFFFF,
}
}
pub fn tick(&mut self, world: &mut world::World, renderer: &mut render::Renderer, delta: f64) {
use std::cmp::Ordering;
pub fn tick(&mut self, world: &mut world::World, renderer: &mut render::Renderer, _delta: f64) {
{
let rm = self.resources.read().unwrap();
if rm.version() != self.resource_version {
@ -65,8 +59,9 @@ impl ChunkBuilder {
while let Ok((id, mut val)) = self.built_recv.try_recv() {
world.reset_building_flag(val.position);
if let Some(render_buffer) = world.get_section_buffer(val.position.0, val.position.1, val.position.2) {
renderer.update_chunk_solid(render_buffer, &val.solid_buffer, val.solid_count);
if let Some(sec) = world.get_section_mut(val.position.0, val.position.1, val.position.2) {
sec.cull_info = val.cull_info;
renderer.update_chunk_solid(&mut sec.render_buffer, &val.solid_buffer, val.solid_count);
}
val.solid_buffer.clear();
@ -75,30 +70,11 @@ impl ChunkBuilder {
if self.free_builders.is_empty() {
return;
}
self.next_collection -= delta;
if self.next_collection <= 0.0 {
let mut sections = world.get_dirty_chunk_sections();
sections.sort_by(|a, b| {
let xx = ((a.0<<4)+8) as f64 - renderer.camera.pos.x;
let yy = ((a.1<<4)+8) as f64 - renderer.camera.pos.y;
let zz = ((a.2<<4)+8) as f64 - renderer.camera.pos.z;
let a_dist = xx*xx + yy*yy + zz*zz;
let xx = ((b.0<<4)+8) as f64 - renderer.camera.pos.x;
let yy = ((b.1<<4)+8) as f64 - renderer.camera.pos.y;
let zz = ((b.2<<4)+8) as f64 - renderer.camera.pos.z;
let b_dist = xx*xx + yy*yy + zz*zz;
if a_dist == b_dist {
Ordering::Equal
} else if a_dist > b_dist {
Ordering::Less
} else {
Ordering::Greater
}
});
self.sections = sections;
self.next_collection = 60.0;
}
while let Some((x, y, z)) = self.sections.pop() {
let dirty_sections = world.get_render_list().iter()
.map(|v| v.0)
.filter(|v| world.is_section_dirty(*v))
.collect::<Vec<_>>();
for (x,y, z) in dirty_sections {
let t_id = self.free_builders.pop().unwrap();
world.set_building_flag((x, y, z));
let (cx, cy, cz) = (x << 4, y << 4, z << 4);
@ -126,6 +102,7 @@ struct BuildReply {
position: (i32, i32, i32),
solid_buffer: Vec<u8>,
solid_count: usize,
cull_info: CullInfo,
}
fn build_func(id: usize, models: Arc<RwLock<model::Factory>>, work_recv: mpsc::Receiver<BuildReq>, built_send: mpsc::Sender<(usize, BuildReply)>) {
@ -173,14 +150,106 @@ fn build_func(id: usize, models: Arc<RwLock<model::Factory>>, work_recv: mpsc::R
}
}
let cull_info = build_cull_info(&snapshot);
built_send.send((id, BuildReply {
position: position,
solid_buffer: solid_buffer,
solid_count: solid_count,
cull_info: cull_info,
})).unwrap();
}
}
fn build_cull_info(snapshot: &world::Snapshot) -> CullInfo {
let mut visited = Set::new(16 * 16 * 16);
let mut info = CullInfo::new();
for y in 0 .. 16 {
for z in 0 .. 16 {
for x in 0 .. 16 {
if visited.get(x | (z << 4) | (y << 8)) {
continue;
}
let touched = flood_fill(snapshot, &mut visited, x as i32, y as i32, z as i32);
if touched == 0 {
continue;
}
for d1 in Direction::all() {
if (touched & (1 << d1.index())) != 0 {
for d2 in Direction::all() {
if (touched & (1 << d2.index())) != 0 {
info.set_visible(d1, d2);
}
}
}
}
}
}
}
info
}
fn flood_fill(snapshot: &world::Snapshot, visited: &mut Set, x: i32, y: i32, z: i32) -> u8 {
let idx = (x | (z << 4) | (y << 8)) as usize;
if x < 0 || x > 15 || y < 0 || y > 15 || z < 0 || z > 15 || visited.get(idx) {
return 0;
}
visited.set(idx, true);
if snapshot.get_block(x, y, z).get_material().should_cull_against {
return 0;
}
let mut touched = 0;
if x == 0 {
touched |= 1 << Direction::West.index();
} else if x == 15 {
touched |= 1 << Direction::East.index();
}
if y == 0 {
touched |= 1 << Direction::Down.index();
} else if y == 15 {
touched |= 1 << Direction::Up.index();
}
if z == 0 {
touched |= 1 << Direction::North.index();
} else if z == 15 {
touched |= 1 << Direction::South.index();
}
for d in Direction::all() {
let (ox, oy, oz) = d.get_offset();
touched |= flood_fill(snapshot, visited, x+ox, y+oy, z+oz);
}
touched
}
#[derive(Clone, Copy)]
pub struct CullInfo(u64);
impl CullInfo {
pub fn new() -> CullInfo {
CullInfo(0)
}
pub fn all_vis() -> CullInfo {
CullInfo(0xFFFFFFFFFFFFFFFF)
}
pub fn is_visible(&self, from: Direction, to: Direction) -> bool {
(self.0 & (1 << (from.index() * 6 + to.index()))) != 0
}
pub fn set_visible(&mut self, from: Direction, to: Direction) {
self.0 |= 1 << (from.index() * 6 + to.index());
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Invalid,
@ -213,6 +282,18 @@ impl Direction {
}
}
pub fn opposite(&self) -> Direction {
match *self {
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
Direction::North => Direction::South,
Direction::South => Direction::North,
Direction::West => Direction::East,
Direction::East => Direction::West,
_ => unreachable!(),
}
}
pub fn get_verts(&self) -> &'static [BlockVertex; 4] {
match *self {
Direction::Up => PRECOMPUTED_VERTS[0],

View File

@ -70,6 +70,8 @@ pub struct Renderer {
pub frustum: collision::Frustum<f32>,
pub view_vector: cgmath::Vector3<f32>,
pub frame_id: u32,
trans: Option<TransInfo>,
last_width: u32,
@ -214,6 +216,8 @@ impl Renderer {
frustum: collision::Frustum::from_matrix4(cgmath::Matrix4::identity()).unwrap(),
view_vector: cgmath::Vector3::zero(),
frame_id: 1,
trans: None,
}
}
@ -287,9 +291,11 @@ impl Renderer {
for (pos, info) in world.get_render_list() {
if let Some(solid) = info.solid.as_ref() {
self.chunk_shader.offset.set_int3(pos.0, pos.1 * 4096, pos.2);
solid.array.bind();
gl::draw_elements(gl::TRIANGLES, solid.count, self.element_buffer_type, 0);
if solid.count > 0 {
self.chunk_shader.offset.set_int3(pos.0, pos.1 * 4096, pos.2);
solid.array.bind();
gl::draw_elements(gl::TRIANGLES, solid.count, self.element_buffer_type, 0);
}
}
}
@ -337,6 +343,8 @@ impl Renderer {
gl::disable(gl::MULTISAMPLE);
self.ui.tick(width, height);
self.frame_id = self.frame_id.wrapping_add(1);
}
fn ensure_element_buffer(&mut self, size: usize) {

View File

@ -23,6 +23,7 @@ use protocol;
use render;
use collision;
use cgmath;
use chunk_builder;
pub mod biome;
@ -61,15 +62,57 @@ impl World {
}
pub fn compute_render_list(&mut self, renderer: &mut render::Renderer) {
use chunk_builder::{self, Direction};
use cgmath::Vector;
use std::collections::VecDeque;
self.render_list.clear();
for (pos, chunk) in &self.chunks {
for (y, sec) in chunk.sections.iter().enumerate() {
if let Some(sec) = sec.as_ref() {
let pos = (pos.0, y as i32, pos.1);
let min = cgmath::Point3::new(pos.0 as f32 * 16.0, -pos.1 as f32 * 16.0, pos.2 as f32 * 16.0);
let bounds = collision::Aabb3::new(min, min + cgmath::Vector3::new(16.0, -16.0, 16.0));
if renderer.frustum.contains(bounds) != collision::Relation::Out {
self.render_list.push(pos);
let mut valid_dirs = [false; 6];
for dir in Direction::all() {
let (ox, oy, oz) = dir.get_offset();
let dir_vec = cgmath::Vector3::new(ox as f32, oy as f32, oz as f32);
valid_dirs[dir.index()] = renderer.view_vector.dot(dir_vec) > -0.8;
}
let start = (
((renderer.camera.pos.x as i32) >> 4),
((renderer.camera.pos.y as i32) >> 4),
((renderer.camera.pos.z as i32) >> 4)
);
let mut process_queue = VecDeque::with_capacity(self.chunks.len() * 16);
process_queue.push_front((Direction::Invalid, start));
while let Some((from, pos)) = process_queue.pop_front() {
let (exists, cull) = if let Some((sec, rendered_on)) = self.get_render_section_mut(pos.0, pos.1, pos.2) {
if *rendered_on == renderer.frame_id {
continue;
}
*rendered_on = renderer.frame_id;
let min = cgmath::Point3::new(pos.0 as f32 * 16.0, -pos.1 as f32 * 16.0, pos.2 as f32 * 16.0);
let bounds = collision::Aabb3::new(min, min + cgmath::Vector3::new(16.0, -16.0, 16.0));
if renderer.frustum.contains(bounds) == collision::Relation::Out {
continue;
}
(sec.is_some(), sec.map(|v| v.cull_info).unwrap_or(chunk_builder::CullInfo::all_vis()))
} else {
continue;
};
if exists {
self.render_list.push(pos);
}
for dir in Direction::all() {
let (ox, oy, oz) = dir.get_offset();
let opos = (pos.0 + ox, pos.1 + oy, pos.2 + oz);
if let Some((_, rendered_on)) = self.get_render_section_mut(opos.0, opos.1, opos.2) {
if *rendered_on == renderer.frame_id {
continue;
}
if from == Direction::Invalid || (valid_dirs[dir.index()] && cull.is_visible(from, dir)) {
process_queue.push_back((dir.opposite(), opos));
}
}
}
@ -84,15 +127,29 @@ impl World {
}).collect()
}
pub fn get_section_buffer(&mut self, x: i32, y: i32, z: i32) -> Option<&mut render::ChunkBuffer> {
pub fn get_section_mut(&mut self, x: i32, y: i32, z: i32) -> Option<&mut Section> {
if let Some(chunk) = self.chunks.get_mut(&CPos(x, z)) {
if let Some(sec) = chunk.sections[y as usize].as_mut() {
return Some(&mut sec.render_buffer);
return Some(sec);
}
}
None
}
fn get_render_section_mut(&mut self, x: i32, y: i32, z: i32) -> Option<(Option<&mut Section>, &mut u32)> {
if y < 0 || y > 15 {
return None;
}
if let Some(chunk) = self.chunks.get_mut(&CPos(x, z)) {
let rendered = &mut chunk.sections_rendered_on[y as usize];
if let Some(sec) = chunk.sections[y as usize].as_mut() {
return Some((Some(sec), rendered));
}
return Some((None, rendered));
}
None
}
pub fn get_dirty_chunk_sections(&mut self) -> Vec<(i32, i32, i32)> {
let mut out = vec![];
for (_, chunk) in &mut self.chunks {
@ -107,6 +164,15 @@ impl World {
out
}
pub fn is_section_dirty(&self, pos: (i32, i32, i32)) -> bool {
if let Some(chunk) = self.chunks.get(&CPos(pos.0, pos.2)) {
if let Some(sec) = chunk.sections[pos.1 as usize].as_ref() {
return sec.dirty && !sec.building;
}
}
false
}
pub fn set_building_flag(&mut self, pos: (i32, i32, i32)) {
if let Some(chunk) = self.chunks.get_mut(&CPos(pos.0, pos.2)) {
if let Some(sec) = chunk.sections[pos.1 as usize].as_mut() {
@ -375,6 +441,7 @@ pub struct Chunk {
position: CPos,
sections: [Option<Section>; 16],
sections_rendered_on: [u32; 16],
biomes: [u8; 16 * 16],
}
@ -388,6 +455,7 @@ impl Chunk {
None,None,None,None,
None,None,None,None,
],
sections_rendered_on: [0; 16],
biomes: [0; 16 * 16],
}
}
@ -428,8 +496,10 @@ pub struct SectionKey {
pos: (i32, u8, i32),
}
struct Section {
pub struct Section {
pub cull_info: chunk_builder::CullInfo,
pub render_buffer: render::ChunkBuffer,
y: u8,
blocks: bit::Map,
@ -446,6 +516,7 @@ struct Section {
impl Section {
fn new(x: i32, y: u8, z: i32) -> Section {
let mut section = Section {
cull_info: chunk_builder::CullInfo::all_vis(),
render_buffer: render::ChunkBuffer::new(),
y: y,