Compare commits

..

10 Commits

7 changed files with 494 additions and 319 deletions

View File

@ -78,6 +78,7 @@ pub fn tri_lerp_block(p: &[f32], dims: &[u32], vals: &mut [f32]) {
}
/// Maps a 3d index to a 1d index
// TODO: Handle out of range!!
pub fn to_1d_index(p: glam::UVec3, dim: glam::UVec3) -> usize {
(p.x + p.y * dim.x + p.z * dim.x * dim.y) as usize
}

View File

@ -0,0 +1,139 @@
use std::collections::HashSet;
use crate::{
gfx::{BulkBufferBuilder, Context},
math,
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BrickgridFlag {
Empty = 0,
Unloaded = 1,
Loading = 2,
Loaded = 4,
}
impl From<u32> for BrickgridFlag {
fn from(value: u32) -> Self {
match value {
x if x == Self::Unloaded as u32 => Self::Unloaded,
x if x == Self::Loading as u32 => Self::Loading,
x if x == Self::Loaded as u32 => Self::Loaded,
_ => Self::Empty,
}
}
}
#[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
pub struct BrickgridElement(pub u32);
impl BrickgridElement {
pub fn new(brickmap_cache_idx: usize, flag: BrickgridFlag) -> Self {
Self(((brickmap_cache_idx as u32) << 8) + flag as u32)
}
pub fn get_pointer(&self) -> usize {
(self.0 >> 8) as usize
}
pub fn get_flag(&self) -> BrickgridFlag {
BrickgridFlag::from(self.0 & 0xF)
}
}
#[derive(Debug)]
pub struct Brickgrid {
dimensions: glam::UVec3,
data: Vec<BrickgridElement>,
staged: HashSet<usize>,
max_upload_count: usize,
buffer: wgpu::Buffer,
upload_buffer: wgpu::Buffer,
}
impl Brickgrid {
pub fn new(context: &Context, dimensions: glam::UVec3, max_upload_count: usize) -> Self {
let element_count = (dimensions.x * dimensions.y * dimensions.z) as usize;
let data = vec![BrickgridElement::new(0, BrickgridFlag::Unloaded); element_count];
// TODO: change type of upload data. Will need some messyness with bytemucking probably
// but should lead to clearer data definitions
let mut upload_data = vec![0u32; 4 + 4 * max_upload_count];
upload_data[0] = max_upload_count as u32;
let mut buffers = BulkBufferBuilder::new()
.set_usage(wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST)
.with_init_buffer_bm("Brickgrid", &data)
.with_init_buffer_bm("Brickgrid Upload", &upload_data)
.build(context);
Self {
dimensions,
data,
staged: HashSet::new(),
max_upload_count,
buffer: buffers.remove(0),
upload_buffer: buffers.remove(0),
}
}
/// Panics if position maps to out of range index
// pub fn set(&mut self, pos: glam::UVec3, value: BrickgridElement) -> BrickgridElement {
// let index = math::to_1d_index(pos, self.dimensions);
pub fn set(&mut self, index: usize, value: BrickgridElement) -> BrickgridElement {
let current = self.data[index];
self.data[index] = value;
self.staged.insert(index);
current
}
/// Panics if position maps to out of range index
// pub fn get(&mut self, pos: glam::UVec3) -> BrickgridElement {
// let index = math::to_1d_index(pos, self.dimensions);
pub fn get(&mut self, index: usize) -> BrickgridElement {
self.data[index]
}
pub fn upload(&mut self, context: &Context) {
let mut upload_data = Vec::new();
let mut idx = 0;
self.staged.retain(|e| {
// We have a limit of how many elements to upload each frame. So we need
// to keep any excess
if idx >= self.max_upload_count {
return true;
}
// Index of the brickgrid element, and the value of it
upload_data.push(*e as u32);
upload_data.push(self.data[*e].0);
idx += 1;
false
});
// Upload buffer is {max_count, count, pad, pad, bricks[]}. So we need to add
// the count and pads, and upload at an offset to skip max_count
let data = [&[upload_data.len() as u32, 0, 0], &upload_data[..]].concat();
context
.queue
.write_buffer(&self.upload_buffer, 4, bytemuck::cast_slice(&data));
if idx != 0 {
log::info!(
"Uploading {} brickgrid entries. ({} remaining)",
idx,
self.staged.len()
);
}
}
pub fn get_buffer(&self) -> &wgpu::Buffer {
&self.buffer
}
pub fn get_upload_buffer(&self) -> &wgpu::Buffer {
&self.upload_buffer
}
}

View File

@ -1,20 +1,14 @@
use std::collections::HashSet;
use crate::{
gfx::{self, BufferExt},
math,
voxel::world::{Voxel, WorldManager},
voxel::world::WorldManager,
};
use super::shading_table::ShadingTableAllocator;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct Brickmap {
pub bitmask: [u32; 16],
pub shading_table_offset: u32,
pub lod_color: u32,
}
use super::{
brickgrid::{Brickgrid, BrickgridElement, BrickgridFlag},
brickmap_cache::{BrickmapCache, BrickmapCacheEntry},
shading_table::ShadingTableAllocator,
};
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
@ -23,46 +17,17 @@ struct WorldState {
_pad: u32,
}
#[derive(Debug, Default, Copy, Clone)]
struct BrickmapCacheEntry {
grid_idx: usize,
shading_table_offset: u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct BrickmapUnpackElement {
cache_idx: u32,
brickmap: Brickmap,
shading_element_count: u32,
shading_elements: [u32; 512],
}
enum BrickgridFlag {
_Empty = 0,
_Unloaded = 1,
_Loading = 2,
Loaded = 4,
}
#[derive(Debug)]
pub struct BrickmapManager {
state_uniform: WorldState,
state_buffer: wgpu::Buffer,
brickgrid: Vec<u32>,
brickgrid_buffer: wgpu::Buffer,
brickmap_cache_map: Vec<Option<BrickmapCacheEntry>>,
brickmap_cache_idx: usize,
brickmap_buffer: wgpu::Buffer,
brickgrid: Brickgrid,
brickmap_cache: BrickmapCache,
shading_table_buffer: wgpu::Buffer,
shading_table_allocator: ShadingTableAllocator,
feedback_buffer: wgpu::Buffer,
feedback_result_buffer: wgpu::Buffer,
unpack_max_count: usize,
brickgrid_staged: HashSet<usize>,
brickgrid_unpack_buffer: wgpu::Buffer,
brickmap_staged: Vec<BrickmapUnpackElement>,
brickmap_unpack_buffer: wgpu::Buffer,
}
// TODO:
@ -81,11 +46,12 @@ impl BrickmapManager {
..Default::default()
};
let brickgrid =
vec![1u32; (brickgrid_dims.x * brickgrid_dims.y * brickgrid_dims.z) as usize];
let brickmap_cache = vec![Brickmap::default(); brickmap_cache_size];
let brickmap_cache_map = vec![None; brickmap_cache.capacity()];
let brickgrid = Brickgrid::new(context, brickgrid_dims, max_uploaded_brickmaps as usize);
let brickmap_cache = BrickmapCache::new(
context,
brickmap_cache_size,
max_uploaded_brickmaps as usize,
);
let shading_table_allocator = ShadingTableAllocator::new(4, shading_table_bucket_size);
let shading_table = vec![0u32; shading_table_allocator.total_elements as usize];
@ -94,22 +60,13 @@ impl BrickmapManager {
feedback_data[0] = max_requested_brickmaps;
let feedback_data_u8 = bytemuck::cast_slice(&feedback_data);
let mut brickgrid_upload_data = vec![0u32; 4 + 4 * max_uploaded_brickmaps as usize];
brickgrid_upload_data[0] = max_uploaded_brickmaps;
let brickgrid_staged = HashSet::new();
let mut brickmap_upload_data = vec![0u32; 4 + 532 * max_uploaded_brickmaps as usize];
brickmap_upload_data[0] = max_uploaded_brickmaps;
let brickmap_staged = Vec::new();
let mut buffers = gfx::BulkBufferBuilder::new()
.with_init_buffer_bm("Brick World State", &[state_uniform])
.set_usage(wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST)
.with_init_buffer_bm("Brickgrid", &brickgrid)
.with_init_buffer_bm("Brickmap Cache", &brickmap_cache)
.with_init_buffer_bm("Shading Table", &shading_table)
.with_init_buffer_bm("Brickgrid Unpack", &brickgrid_upload_data)
.with_init_buffer_bm("Brickmap Unpack", &brickmap_upload_data)
.set_usage(
wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::COPY_DST
@ -123,26 +80,19 @@ impl BrickmapManager {
Self {
state_uniform,
brickgrid,
brickmap_cache_map,
brickmap_cache_idx: 0,
brickmap_cache,
shading_table_allocator,
unpack_max_count: max_uploaded_brickmaps as usize,
brickgrid_staged,
brickmap_staged,
state_buffer: buffers.remove(0),
brickgrid_buffer: buffers.remove(0),
brickmap_buffer: buffers.remove(0),
shading_table_buffer: buffers.remove(0),
brickgrid_unpack_buffer: buffers.remove(0),
brickmap_unpack_buffer: buffers.remove(0),
feedback_buffer: buffers.remove(0),
feedback_result_buffer: buffers.remove(0),
}
}
pub fn get_brickgrid_buffer(&self) -> &wgpu::Buffer {
&self.brickgrid_buffer
self.brickgrid.get_buffer()
}
pub fn get_worldstate_buffer(&self) -> &wgpu::Buffer {
@ -150,7 +100,7 @@ impl BrickmapManager {
}
pub fn get_brickmap_buffer(&self) -> &wgpu::Buffer {
&self.brickmap_buffer
self.brickmap_cache.get_buffer()
}
pub fn get_shading_buffer(&self) -> &wgpu::Buffer {
@ -166,11 +116,11 @@ impl BrickmapManager {
}
pub fn get_brickmap_unpack_buffer(&self) -> &wgpu::Buffer {
&self.brickmap_unpack_buffer
self.brickmap_cache.get_upload_buffer()
}
pub fn get_brickgrid_unpack_buffer(&self) -> &wgpu::Buffer {
&self.brickgrid_unpack_buffer
self.brickgrid.get_upload_buffer()
}
pub fn get_unpack_max_count(&self) -> usize {
@ -198,8 +148,7 @@ impl BrickmapManager {
// TODO: Why do we call this here rather than doing it outside of here?
self.upload_unpack_buffers(context);
// TODO: This is inaccurate if we've looped
log::info!("Num loaded brickmaps: {}", self.brickmap_cache_idx);
log::info!("Num loaded brickmaps: {}", self.brickmap_cache.num_loaded);
}
fn handle_request(&mut self, world: &mut WorldManager, data: &[u32]) {
@ -215,264 +164,73 @@ impl BrickmapManager {
// We only want to upload voxels that are on the surface, so we cull anything
// that is surrounded by solid voxels
let grid_pos = grid_pos.as_ivec3();
let (bitmask_data, albedo_data) = Self::cull_interior_voxels(world, grid_pos);
let (bitmask_data, albedo_data) = super::util::cull_interior_voxels(world, grid_pos);
// If there's no voxel colour data post-culling it means the brickmap is
// empty. We don't need to upload it, just mark the relevant brickgrid entry.
if albedo_data.is_empty() {
self.update_brickgrid_element(grid_idx, 0);
if let Some(entry) = self.update_brickgrid_element(grid_idx, 0) {
// TODO: We need to actually remove the cache entry lmao
// The brickgrid element had a brickmap entry so we need to unload it's
// shading data
if let Err(e) = self
.shading_table_allocator
.try_dealloc(entry.shading_table_offset)
{
log::warn!("{}", e)
}
}
return;
}
// Update the brickgrid index
self.update_brickgrid_element(
grid_idx,
Self::to_brickgrid_element(self.brickmap_cache_idx as u32, BrickgridFlag::Loaded),
);
// If there's already something in the cache spot we want to write to, we
// need to unload it.
if self.brickmap_cache_map[self.brickmap_cache_idx].is_some() {
let entry = self.brickmap_cache_map[self.brickmap_cache_idx].unwrap();
self.update_brickgrid_element(entry.grid_idx, 1);
}
// Update the shading table
let shading_idx = self
.shading_table_allocator
.try_alloc(albedo_data.len() as u32)
.unwrap() as usize;
// We're all good to overwrite the cache map entry now :)
self.brickmap_cache_map[self.brickmap_cache_idx] = Some(BrickmapCacheEntry {
if let Some(entry) =
self.brickmap_cache
.add_entry(grid_idx, shading_idx as u32, bitmask_data, albedo_data)
{
self.update_brickgrid_element(entry.grid_idx, 1);
}
// Update the brickgrid index
if let Some(old_entry) = self.update_brickgrid_element(
grid_idx,
shading_table_offset: shading_idx as u32,
});
// Update the brickmap
let brickmap = Brickmap {
bitmask: bitmask_data,
shading_table_offset: shading_idx as u32,
lod_color: 0,
};
let shading_element_count = albedo_data.len();
let mut shading_elements = [0u32; 512];
shading_elements[..shading_element_count].copy_from_slice(&albedo_data);
let staged_brickmap = BrickmapUnpackElement {
cache_idx: self.brickmap_cache_idx as u32,
brickmap,
shading_element_count: shading_element_count as u32,
shading_elements,
};
self.brickmap_staged.push(staged_brickmap);
self.brickmap_cache_idx = (self.brickmap_cache_idx + 1) % self.brickmap_cache_map.len();
super::util::to_brickgrid_element(
self.brickmap_cache.index as u32,
BrickgridFlag::Loaded,
),
) {
// TODO: We need to actually remove the cache entry lmao
// The brickgrid element had a brickmap entry so we need to unload it's
// shading data
if let Err(e) = self
.shading_table_allocator
.try_dealloc(old_entry.shading_table_offset)
{
log::warn!("{}", e)
}
}
}
fn update_brickgrid_element(&mut self, index: usize, data: u32) {
// If we're updating a brickgrid element, we need to make sure to deallocate anything
// that's already there. The shading table gets deallocated, and the brickmap cache entry
// is marked as None.
if (self.brickgrid[index] & 0xF) == 4 {
let brickmap_idx = (self.brickgrid[index] >> 8) as usize;
let cache_map_entry = self.brickmap_cache_map[brickmap_idx];
match cache_map_entry {
Some(entry) => {
match self
.shading_table_allocator
.try_dealloc(entry.shading_table_offset)
{
Ok(_) => (),
Err(e) => log::warn!("{}", e),
}
self.brickmap_cache_map[brickmap_idx] = None;
}
None => log::warn!("Expected brickmap cache entry, found None!"),
}
fn update_brickgrid_element(&mut self, index: usize, data: u32) -> Option<BrickmapCacheEntry> {
let mut brickmap_cache_entry = None;
let current = self.brickgrid.get(index);
if current.get_flag() == BrickgridFlag::Loaded {
let cache_index = current.get_pointer();
brickmap_cache_entry = self.brickmap_cache.get_entry(cache_index);
}
// We're safe to overwrite the CPU brickgrid and mark for GPU upload now
self.brickgrid[index] = data;
self.brickgrid_staged.insert(index);
self.brickgrid.set(index, BrickgridElement(data));
brickmap_cache_entry
}
fn upload_unpack_buffers(&mut self, context: &gfx::Context) {
// Brickgrid
let mut data = Vec::new();
let mut iter = self.brickgrid_staged.iter();
let mut to_remove = Vec::new();
for _ in 0..self.unpack_max_count {
let el = iter.next();
if el.is_none() {
break;
}
let val = el.unwrap();
to_remove.push(*val as u32);
data.push(*val as u32);
data.push(self.brickgrid[*val]);
}
for val in &to_remove {
self.brickgrid_staged.remove(&(*val as usize));
}
if !data.is_empty() {
log::info!(
"Uploading {} brickgrid entries. ({} remaining)",
to_remove.len(),
self.brickgrid_staged.len()
);
}
context.queue.write_buffer(
&self.brickgrid_unpack_buffer,
4,
bytemuck::cast_slice(&[data.len()]),
);
context.queue.write_buffer(
&self.brickgrid_unpack_buffer,
16,
bytemuck::cast_slice(&data),
);
// Brickmap
let end = self.unpack_max_count.min(self.brickmap_staged.len());
let iter = self.brickmap_staged.drain(0..end);
let data = iter.as_slice();
context.queue.write_buffer(
&self.brickmap_unpack_buffer,
4,
bytemuck::cast_slice(&[end]),
);
context
.queue
.write_buffer(&self.brickmap_unpack_buffer, 16, bytemuck::cast_slice(data));
drop(iter);
if end > 0 {
log::info!(
"Uploading {} brickmap entries. ({} remaining)",
end,
self.brickmap_staged.len()
);
}
}
fn cull_interior_voxels(
world: &mut WorldManager,
grid_pos: glam::IVec3,
) -> ([u32; 16], Vec<u32>) {
// This is the data we want to return
let mut bitmask_data = [0xFFFFFFFF_u32; 16];
let mut albedo_data = Vec::<u32>::new();
// Calculate world chunk and block positions for each that may be accessed
let center_pos = Self::grid_pos_to_world_pos(world, grid_pos);
let forward_pos = Self::grid_pos_to_world_pos(world, grid_pos + glam::ivec3(1, 0, 0));
let backward_pos = Self::grid_pos_to_world_pos(world, grid_pos + glam::ivec3(-1, 0, 0));
let left_pos = Self::grid_pos_to_world_pos(world, grid_pos + glam::ivec3(0, 0, -1));
let right_pos = Self::grid_pos_to_world_pos(world, grid_pos + glam::ivec3(0, 0, 1));
let up_pos = Self::grid_pos_to_world_pos(world, grid_pos + glam::ivec3(0, 1, 0));
let down_pos = Self::grid_pos_to_world_pos(world, grid_pos + glam::ivec3(0, -1, 0));
// Fetch those blocks
let center_block = world.get_block(center_pos.0, center_pos.1);
let forward_block = world.get_block(forward_pos.0, forward_pos.1);
let backward_block = world.get_block(backward_pos.0, backward_pos.1);
let left_block = world.get_block(left_pos.0, left_pos.1);
let right_block = world.get_block(right_pos.0, right_pos.1);
let up_block = world.get_block(up_pos.0, up_pos.1);
let down_block = world.get_block(down_pos.0, down_pos.1);
// Reusable array of whether cardinal neighbours are empty
let mut neighbours = [false; 6];
for z in 0..8 {
// Each z level contains two bitmask segments of voxels
let mut entry = 0u64;
for y in 0..8 {
for x in 0..8 {
// Ignore non-solids
let idx = x + y * 8 + z * 8 * 8;
let empty_voxel = Voxel::Empty;
match center_block[idx] {
Voxel::Empty => continue,
Voxel::Color(r, g, b) => {
// A voxel is on the surface if at least one of it's
// cardinal neighbours is non-solid.
neighbours[0] = if x == 7 {
forward_block[idx - 7] == empty_voxel
} else {
center_block[idx + 1] == empty_voxel
};
neighbours[1] = if x == 0 {
backward_block[idx + 7] == empty_voxel
} else {
center_block[idx - 1] == empty_voxel
};
neighbours[2] = if z == 7 {
right_block[idx - 448] == empty_voxel
} else {
center_block[idx + 64] == empty_voxel
};
neighbours[3] = if z == 0 {
left_block[idx + 448] == empty_voxel
} else {
center_block[idx - 64] == empty_voxel
};
neighbours[4] = if y == 7 {
up_block[idx - 56] == empty_voxel
} else {
center_block[idx + 8] == empty_voxel
};
neighbours[5] = if y == 0 {
down_block[idx + 56] == empty_voxel
} else {
center_block[idx - 8] == empty_voxel
};
// Set the appropriate bit in the z entry and add the
// shading data
let surface_voxel = neighbours.iter().any(|v| *v);
if surface_voxel {
entry += 1 << (x + y * 8);
let albedo = ((r as u32) << 24)
+ ((g as u32) << 16)
+ ((b as u32) << 8)
+ 255u32;
albedo_data.push(albedo);
}
}
}
}
}
let offset = 2 * z;
bitmask_data[offset] = (entry & 0xFFFFFFFF).try_into().unwrap();
bitmask_data[offset + 1] = ((entry >> 32) & 0xFFFFFFFF).try_into().unwrap();
}
(bitmask_data, albedo_data)
}
fn to_brickgrid_element(brickmap_cache_idx: u32, flags: BrickgridFlag) -> u32 {
(brickmap_cache_idx << 8) + flags as u32
}
fn grid_pos_to_world_pos(
world: &mut WorldManager,
grid_pos: glam::IVec3,
) -> (glam::IVec3, glam::UVec3) {
// We deal with dvecs here because we want a negative grid_pos to have floored
// chunk_pos
let chunk_dims = world.get_chunk_dims().as_dvec3();
let chunk_pos = (grid_pos.as_dvec3() / chunk_dims).floor();
let block_pos = grid_pos - (chunk_pos * chunk_dims).as_ivec3();
(chunk_pos.as_ivec3(), block_pos.as_uvec3())
self.brickgrid.upload(context);
self.brickmap_cache.upload(context);
}
}

View File

@ -0,0 +1,156 @@
use crate::gfx::{BulkBufferBuilder, Context};
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Brickmap {
pub bitmask: [u32; 16],
pub shading_table_offset: u32,
pub lod_color: u32,
}
#[derive(Debug, Default, Copy, Clone)]
pub struct BrickmapCacheEntry {
pub grid_idx: usize,
pub shading_table_offset: u32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
// TODO: Rename struct
struct BrickmapUnpackElement {
cache_idx: u32, // TODO: Change to usize?
brickmap: Brickmap,
shading_element_count: u32,
shading_elements: [u32; 512], // TODO: Replace u32 with custom type?
}
#[derive(Debug)]
pub struct BrickmapCache {
cache: Vec<Option<BrickmapCacheEntry>>,
pub index: usize,
pub num_loaded: u32,
staged: Vec<BrickmapUnpackElement>,
max_upload_count: usize,
buffer: wgpu::Buffer,
upload_buffer: wgpu::Buffer,
}
impl BrickmapCache {
pub fn new(context: &Context, size: usize, max_upload_count: usize) -> Self {
let data = vec![Brickmap::default(); size];
// TODO: change type of upload data. Will need some messyness with bytemucking probably
// but should lead to clearer data definitions
let mut upload_data = vec![0u32; 4 + 532 * max_upload_count];
upload_data[0] = max_upload_count as u32;
let mut buffers = BulkBufferBuilder::new()
.set_usage(wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST)
.with_init_buffer_bm("Brickmap Cache", &data)
.with_init_buffer_bm("Brickmap Unpack", &upload_data)
.build(context);
Self {
cache: vec![None; size],
index: 0,
num_loaded: 0,
staged: vec![],
max_upload_count,
buffer: buffers.remove(0),
upload_buffer: buffers.remove(0),
}
}
pub fn get_buffer(&self) -> &wgpu::Buffer {
&self.buffer
}
pub fn get_upload_buffer(&self) -> &wgpu::Buffer {
&self.upload_buffer
}
/// Adds a brickmap entry and returns the entry that was overwritten.
pub fn add_entry(
&mut self,
grid_idx: usize,
shading_table_offset: u32,
bitmask: [u32; 16],
albedo_data: Vec<u32>,
) -> Option<BrickmapCacheEntry> {
// We do this first because we want this to be the index of the most recently added entry
// This has the side effect of meaning that on the first loop through the cache the first
// entry is empty, but it's fine.
self.index = (self.index + 1) % self.cache.len();
let existing_entry = self.cache[self.index];
if existing_entry.is_none() {
self.num_loaded += 1;
}
self.cache[self.index] = Some(BrickmapCacheEntry {
grid_idx,
shading_table_offset,
});
// Need to stage this entry
let brickmap = Brickmap {
bitmask,
shading_table_offset,
lod_color: 0,
};
let shading_element_count = albedo_data.len();
let mut shading_elements = [0u32; 512];
shading_elements[..shading_element_count].copy_from_slice(&albedo_data);
let staged_brickmap = BrickmapUnpackElement {
cache_idx: self.index as u32,
brickmap,
shading_element_count: shading_element_count as u32,
shading_elements,
};
self.staged.push(staged_brickmap);
existing_entry
}
/// Remove an entry from the cache and return it
pub fn remove_entry(&mut self, index: usize) -> Option<BrickmapCacheEntry> {
let entry = self.cache[index];
if entry.is_some() {
self.cache[index] = None;
self.num_loaded -= 1;
}
entry
}
pub fn get_entry(&self, index: usize) -> Option<BrickmapCacheEntry> {
self.cache[index]
}
pub fn upload(&mut self, context: &Context) {
// Takes up to max_upload_count upload elements
let count = usize::min(self.max_upload_count, self.staged.len());
let iter = self.staged.drain(0..count);
let upload_data = iter.as_slice();
// Upload buffer is {max_count, count, pad, pad, maps[]}. So we need to add
// the count and pads, and upload at an offset to skip max_count
let data: Vec<u8> = [
bytemuck::cast_slice(&[count as u32, 0, 0]),
bytemuck::cast_slice(upload_data),
]
.concat();
context.queue.write_buffer(&self.upload_buffer, 4, &data);
drop(iter);
if count > 0 {
log::info!(
"Uploading {} brickmap entries. ({} remaining)",
count,
self.staged.len()
);
}
}
}

View File

@ -1,4 +1,7 @@
mod brickgrid;
mod brickmap;
mod brickmap_cache;
mod shading_table;
mod util;
pub use brickmap::BrickmapManager;

View File

@ -0,0 +1,120 @@
use crate::voxel::world::{Voxel, WorldManager};
use super::brickgrid::BrickgridFlag;
pub fn cull_interior_voxels(
world: &mut WorldManager,
grid_pos: glam::IVec3,
) -> ([u32; 16], Vec<u32>) {
// This is the data we want to return
let mut bitmask_data = [0xFFFFFFFF_u32; 16];
let mut albedo_data = Vec::<u32>::new();
// Calculate world chunk and block positions for each that may be accessed
let center_pos = grid_pos_to_world_pos(world, grid_pos);
let forward_pos = grid_pos_to_world_pos(world, grid_pos + glam::ivec3(1, 0, 0));
let backward_pos = grid_pos_to_world_pos(world, grid_pos + glam::ivec3(-1, 0, 0));
let left_pos = grid_pos_to_world_pos(world, grid_pos + glam::ivec3(0, 0, -1));
let right_pos = grid_pos_to_world_pos(world, grid_pos + glam::ivec3(0, 0, 1));
let up_pos = grid_pos_to_world_pos(world, grid_pos + glam::ivec3(0, 1, 0));
let down_pos = grid_pos_to_world_pos(world, grid_pos + glam::ivec3(0, -1, 0));
// Fetch those blocks
let center_block = world.get_block(center_pos.0, center_pos.1);
let forward_block = world.get_block(forward_pos.0, forward_pos.1);
let backward_block = world.get_block(backward_pos.0, backward_pos.1);
let left_block = world.get_block(left_pos.0, left_pos.1);
let right_block = world.get_block(right_pos.0, right_pos.1);
let up_block = world.get_block(up_pos.0, up_pos.1);
let down_block = world.get_block(down_pos.0, down_pos.1);
// Reusable array of whether cardinal neighbours are empty
let mut neighbours = [false; 6];
for z in 0..8 {
// Each z level contains two bitmask segments of voxels
let mut entry = 0u64;
for y in 0..8 {
for x in 0..8 {
// Ignore non-solids
let idx = x + y * 8 + z * 8 * 8;
let empty_voxel = Voxel::Empty;
match center_block[idx] {
Voxel::Empty => continue,
Voxel::Color(r, g, b) => {
// A voxel is on the surface if at least one of it's
// cardinal neighbours is non-solid.
neighbours[0] = if x == 7 {
forward_block[idx - 7] == empty_voxel
} else {
center_block[idx + 1] == empty_voxel
};
neighbours[1] = if x == 0 {
backward_block[idx + 7] == empty_voxel
} else {
center_block[idx - 1] == empty_voxel
};
neighbours[2] = if z == 7 {
right_block[idx - 448] == empty_voxel
} else {
center_block[idx + 64] == empty_voxel
};
neighbours[3] = if z == 0 {
left_block[idx + 448] == empty_voxel
} else {
center_block[idx - 64] == empty_voxel
};
neighbours[4] = if y == 7 {
up_block[idx - 56] == empty_voxel
} else {
center_block[idx + 8] == empty_voxel
};
neighbours[5] = if y == 0 {
down_block[idx + 56] == empty_voxel
} else {
center_block[idx - 8] == empty_voxel
};
// Set the appropriate bit in the z entry and add the
// shading data
let surface_voxel = neighbours.iter().any(|v| *v);
if surface_voxel {
entry += 1 << (x + y * 8);
let albedo = ((r as u32) << 24)
+ ((g as u32) << 16)
+ ((b as u32) << 8)
+ 255u32;
albedo_data.push(albedo);
}
}
}
}
}
let offset = 2 * z;
bitmask_data[offset] = (entry & 0xFFFFFFFF).try_into().unwrap();
bitmask_data[offset + 1] = ((entry >> 32) & 0xFFFFFFFF).try_into().unwrap();
}
(bitmask_data, albedo_data)
}
pub fn to_brickgrid_element(brickmap_cache_idx: u32, flags: BrickgridFlag) -> u32 {
(brickmap_cache_idx << 8) + flags as u32
}
pub fn grid_pos_to_world_pos(
world: &mut WorldManager,
grid_pos: glam::IVec3,
) -> (glam::IVec3, glam::UVec3) {
// We deal with dvecs here because we want a negative grid_pos to have floored
// chunk_pos
let chunk_dims = world.get_chunk_dims().as_dvec3();
let chunk_pos = (grid_pos.as_dvec3() / chunk_dims).floor();
let block_pos = grid_pos - (chunk_pos * chunk_dims).as_ivec3();
(chunk_pos.as_ivec3(), block_pos.as_uvec3())
}

View File

@ -177,6 +177,15 @@ impl VoxelRenderer {
unpack_bind_group,
})
}
pub fn update_brickmap(
&mut self,
context: &gfx::Context,
world: &mut super::world::WorldManager,
) {
self.brickmap_manager
.process_feedback_buffer(context, world);
}
}
impl gfx::Renderer for VoxelRenderer {
@ -240,14 +249,3 @@ impl gfx::Renderer for VoxelRenderer {
Ok(())
}
}
impl VoxelRenderer {
pub fn update_brickmap(
&mut self,
context: &gfx::Context,
world: &mut super::world::WorldManager,
) {
self.brickmap_manager
.process_feedback_buffer(context, world);
}
}