diff --git a/build.gradle b/build.gradle index 0e6e6cb8b3..0d1dd073ac 100644 --- a/build.gradle +++ b/build.gradle @@ -63,7 +63,7 @@ tasks.register('remapApiJar', RemapJarTask) { build.dependsOn apiJar build.dependsOn remapApiJar -build.dependsOn buildNatives +//build.dependsOn buildNatives jar { from sourceSets.api.output.classesDirs diff --git a/native/core/Cargo.toml b/native/core/Cargo.toml index 9b7c308816..c7afef3399 100644 --- a/native/core/Cargo.toml +++ b/native/core/Cargo.toml @@ -12,10 +12,22 @@ debug = true [profile.release] panic = "abort" +lto = "thin" + +[profile.asm] +inherits = "release" +panic = "abort" +debug = true +lto = "off" [profile.production] inherits = "release" -lto = true +lto = "fat" + +[profile.profiling] +inherits = "release" +lto = "fat" +debug = true [dependencies] rustc-hash = "1.1.0" diff --git a/native/core/src/ffi.rs b/native/core/src/ffi.rs index efb42e97e2..fe76d1a567 100644 --- a/native/core/src/ffi.rs +++ b/native/core/src/ffi.rs @@ -4,7 +4,7 @@ use std::boxed::Box; use std::mem::MaybeUninit; use std::ptr; -use crate::frustum::Frustum; +use crate::graph::frustum::LocalFrustum; use crate::graph::*; use crate::jni::types::*; use crate::math::*; @@ -67,12 +67,6 @@ impl CInlineVec { } } -#[repr(C)] -pub struct CGraphNode { - connections: u64, - flags: u32, -} - #[allow(non_snake_case)] mod java { use std::boxed::Box; @@ -82,7 +76,7 @@ mod java { use core_simd::simd::f32x4; use crate::ffi::*; - use crate::frustum::Frustum; + use crate::graph::frustum::LocalFrustum; use crate::graph::*; use crate::jni::types::*; use crate::math::*; @@ -124,7 +118,7 @@ mod java { } #[no_mangle] - pub unsafe extern "C" fn Java_me_jellysquid_mods_sodium_core_CoreLibFFI_graphAddChunk( + pub unsafe extern "C" fn Java_me_jellysquid_mods_sodium_core_CoreLibFFI_graphAddSection( _: *mut JEnv, _: *mut JClass, graph: JPtrMut, @@ -133,30 +127,30 @@ mod java { z: Jint, ) { let graph = graph.into_mut_ref(); - graph.add_chunk(i32x3::from_xyz(x, y, z)); + graph.add_chunk(LocalSectionCoord::from_xyz(x, y, z)); } #[no_mangle] - pub unsafe extern "C" fn Java_me_jellysquid_mods_sodium_core_CoreLibFFI_graphUpdateChunk( + pub unsafe extern "C" fn Java_me_jellysquid_mods_sodium_core_CoreLibFFI_graphUpdateSection( _: *mut JEnv, _: *mut JClass, graph: JPtrMut, x: Jint, y: Jint, z: Jint, - node: JPtr, + node: JPtr, ) { let node = node.as_ref(); let graph = graph.into_mut_ref(); graph.update_chunk( - i32x3::from_xyz(x, y, z), - Node::new(VisibilityData::from_u64(node.connections), node.flags as u8), + LocalSectionCoord::from_xyz(x, y, z), + Node::new(VisibilityData::pack(node.connections), node.flags as u8), ); } #[no_mangle] - pub unsafe extern "C" fn Java_me_jellysquid_mods_sodium_core_CoreLibFFI_graphRemoveChunk( + pub unsafe extern "C" fn Java_me_jellysquid_mods_sodium_core_CoreLibFFI_graphRemoveSection( _: *mut JEnv, _: *mut JClass, graph: JPtrMut, @@ -165,7 +159,7 @@ mod java { z: Jint, ) { let graph = graph.into_mut_ref(); - graph.remove_chunk(i32x3::from_xyz(x, y, z)); + graph.remove_chunk(LocalSectionCoord::from_xyz(x, y, z)); } #[no_mangle] @@ -173,7 +167,7 @@ mod java { _: *mut JEnv, _: *mut JClass, graph: JPtrMut, - frustum: JPtr, + frustum: JPtr, view_distance: Jint, out_results: JPtrMut>, ) { @@ -197,15 +191,15 @@ mod java { pub unsafe extern "C" fn Java_me_jellysquid_mods_sodium_core_CoreLibFFI_frustumCreate( _: *mut JEnv, _: *mut JClass, - out_frustum: JPtrMut<*const Frustum>, + out_frustum: JPtrMut<*const LocalFrustum>, planes: JPtr<[[f32; 4]; 6]>, offset: JPtr<[f32; 3]>, ) { - let planes = planes.as_ref().map(f32x4::from_array); + let planes = planes.as_ref().map(f32x3::from_array); let offset = f32x3::from_array(*offset.as_ref()); - let frustum = Box::new(Frustum::new(planes, offset)); + let frustum = Box::new(LocalFrustum::new(planes, offset)); let out_frustum = out_frustum.into_mut_ref(); *out_frustum = Box::into_raw(frustum); @@ -215,7 +209,7 @@ mod java { pub unsafe extern "C" fn Java_me_jellysquid_mods_sodium_core_CoreLibFFI_frustumDelete( _: *mut JEnv, _: *mut JClass, - frustum: JPtrMut, + frustum: JPtrMut, ) { std::mem::drop(Box::from_raw(frustum.into_mut_ref())); } diff --git a/native/core/src/frustum.rs b/native/core/src/frustum.rs deleted file mode 100644 index cf208cf95a..0000000000 --- a/native/core/src/frustum.rs +++ /dev/null @@ -1,83 +0,0 @@ -use core_simd::simd::*; - -use crate::math::*; - -pub struct Frustum { - planes: [f32x4; 6], - plane_xs: f32x8, - plane_ys: f32x8, - plane_zs: f32x8, - plane_ws: f32x8, - position: f32x3, -} - -impl Frustum { - pub fn new(planes: [f32x4; 6], position: f32x3) -> Self { - let mut plane_xs = Simd::splat(f32::NAN); - let mut plane_ys = Simd::splat(f32::NAN); - let mut plane_zs = Simd::splat(f32::NAN); - let mut plane_ws = Simd::splat(f32::NAN); - - for (i, p) in planes.iter().enumerate() { - plane_xs[i] = p.x(); - plane_ys[i] = p.y(); - plane_zs[i] = p.z(); - plane_ws[i] = p.w(); - } - - Frustum { - planes, - plane_xs, - plane_ys, - plane_zs, - plane_ws, - position, - } - } - - pub fn test_bounding_box(&self, bb: &BoundingBox) -> bool { - let min_neg = self.position - bb.min; - let max_neg = self.position - bb.max; - - unsafe { - let points_x = Self::mask_plane_elements( - Mask::from_int_unchecked(self.plane_xs.to_bits().cast::() >> Simd::splat(31)) - .select(Simd::splat(min_neg.x()), Simd::splat(max_neg.x())), - ); - let points_y = Self::mask_plane_elements( - Mask::from_int_unchecked(self.plane_ys.to_bits().cast::() >> Simd::splat(31)) - .select(Simd::splat(min_neg.y()), Simd::splat(max_neg.y())), - ); - let points_z = Self::mask_plane_elements( - Mask::from_int_unchecked(self.plane_zs.to_bits().cast::() >> Simd::splat(31)) - .select(Simd::splat(min_neg.z()), Simd::splat(max_neg.z())), - ); - - let points_dot = self.plane_xs.fast_fma( - points_x, - self.plane_ys.fast_fma(points_y, self.plane_zs * points_z), - ); - - points_dot.simd_le(self.plane_ws).to_bitmask() == 0b00111111 - } - } - - fn mask_plane_elements(value: f32x8) -> f32x8 { - f32x8::from_bits(value.to_bits() & u32x8::from_array([!0, !0, !0, !0, !0, !0, 0, 0])) - } - - pub fn position(&self) -> &f32x3 { - &self.position - } -} - -pub struct BoundingBox { - min: f32x3, - max: f32x3, -} - -impl BoundingBox { - pub fn new(min: f32x3, max: f32x3) -> Self { - BoundingBox { min, max } - } -} diff --git a/native/core/src/graph.rs b/native/core/src/graph.rs deleted file mode 100644 index 0805f93f5f..0000000000 --- a/native/core/src/graph.rs +++ /dev/null @@ -1,675 +0,0 @@ -use std::cell::{RefCell, RefMut}; -use std::collections::VecDeque; -use std::fmt::Debug; -use std::marker::PhantomData; -use std::mem::MaybeUninit; -use std::ops::*; -use std::ptr::NonNull; -use std::vec::Vec; -use std::{ops, ptr}; - -use core_simd::simd::*; -use rustc_hash::FxHashMap as HashMap; -use std_float::StdFloat; - -use crate::collections::ArrayDeque; -use crate::ffi::{CInlineVec, CVec}; -use crate::frustum::{BoundingBox, Frustum}; -use crate::math::*; - -const SECTIONS_IN_REGION: usize = 8 * 4 * 8; - -#[derive(Clone, Copy)] -#[repr(transparent)] -pub struct LocalNodeIndex(u8); - -impl LocalNodeIndex { - const X_MASK_SINGLE: u8 = 0b00000111; - const Y_MASK_SINGLE: u8 = 0b00000011; - const Z_MASK_SINGLE: u8 = 0b00000111; - - const X_MASK_LINEAR_SHIFT: usize = 0; - const Y_MASK_LINEAR_SHIFT: usize = 3; - const Z_MASK_LINEAR_SHIFT: usize = 5; - const X_MASK_LINEAR: u8 = 0b00000111; - const Y_MASK_LINEAR: u8 = 0b00011000; - const Z_MASK_LINEAR: u8 = 0b11100000; - - // XYZXYZXZ - const X_MASK_MORTON: u8 = 0b10010010; - const Y_MASK_MORTON: u8 = 0b01001000; - const Z_MASK_MORTON: u8 = 0b00100101; - - #[inline(always)] - pub fn from_global(position: i32x3) -> Self { - // shrink each element into byte, trim bits - let pos_trimmed = position.cast::() - & u8x3::from_array([ - Self::X_MASK_SINGLE, - Self::Y_MASK_SINGLE, - Self::Z_MASK_SINGLE, - ]); - - // allocate one byte per bit for each element. - // each element is still has its individual bits in standard ordering, but the bytes in the - // vector are in morten ordering. - let expanded_linear_bits = simd_swizzle!(pos_trimmed, [2, 0, 2, 1, 0, 2, 1, 0]); - - // shifting each bit into the sign bit for morten ordering - let expanded_morton_bits = - expanded_linear_bits << u8x8::from_array([7, 7, 6, 7, 6, 5, 6, 5]); - - // arithmetic shift to set each whole lane to its sign bit, then shrinking all lanes to bitmask - let morton_packed = unsafe { - Mask::from_int_unchecked(expanded_morton_bits.cast::() >> i8x8::splat(7)) - } - .to_bitmask(); - - Self(morton_packed) - } - - #[inline(always)] - pub fn inc_x(self) -> (Self, bool) { - self.inc::<{ Self::X_MASK_MORTON }>() - } - - #[inline(always)] - pub fn inc_y(self) -> (Self, bool) { - self.inc::<{ Self::Y_MASK_MORTON }>() - } - - #[inline(always)] - pub fn inc_z(self) -> (Self, bool) { - self.inc::<{ Self::Z_MASK_MORTON }>() - } - - #[inline(always)] - pub fn dec_x(self) -> (Self, bool) { - self.dec::<{ Self::X_MASK_MORTON }>() - } - - #[inline(always)] - pub fn dec_y(self) -> (Self, bool) { - self.dec::<{ Self::Y_MASK_MORTON }>() - } - - #[inline(always)] - pub fn dec_z(self) -> (Self, bool) { - self.dec::<{ Self::Z_MASK_MORTON }>() - } - - #[inline(always)] - pub fn inc(self) -> (Self, bool) { - // make the other bits in the number 1 - let mut masked = self.0 | !MASK; - let overflow = masked == u8::MAX; - - // increment - masked = masked.wrapping_add(1); - - // modify only the masked bits in the original number - (Self((self.0 & !MASK) | (masked & MASK)), overflow) - } - - #[inline(always)] - pub fn dec(self) -> (Self, bool) { - // make the other bits in the number 0 - let mut masked = self.0 & MASK; - let underflow = masked == 0; - - // decrement - masked = masked.wrapping_sub(1); - - // modify only the masked bits in the original number - (Self((self.0 & !MASK) | (masked & MASK)), underflow) - } - - #[inline(always)] - pub fn as_array_offset(&self) -> usize { - self.0 as usize - } - - #[inline(always)] - pub fn as_global_coord(&self, region_coord: i32x3) -> i32x3 { - // allocate one byte per bit for each element - let morton_bytes = u8x8::splat(self.0); - - // shifting each bit into the sign bit for linear ordering - let expanded_linear_bits = morton_bytes << u8x8::from_array([6, 3, 0, 4, 1, 7, 5, 2]); - - // arithmetic shift to set each whole lane to its sign bit, then shrinking all lanes to bitmask - let linear_packed = unsafe { - Mask::from_int_unchecked(expanded_linear_bits.cast::() >> i8x8::splat(7)) - } - .to_bitmask(); - - // unpacking linear pack to individual axis - let mut pos_split_axis = i32x3::splat(linear_packed as i32); - - pos_split_axis &= i32x3::from_xyz( - Self::X_MASK_LINEAR as i32, - Self::Y_MASK_LINEAR as i32, - Self::Z_MASK_LINEAR as i32, - ); - - pos_split_axis >>= i32x3::from_xyz( - Self::X_MASK_LINEAR_SHIFT as i32, - Self::Y_MASK_LINEAR_SHIFT as i32, - Self::Z_MASK_LINEAR_SHIFT as i32, - ); - - (region_coord << i32x3::from_xyz(3, 2, 3)) + pos_split_axis - } -} - -#[derive(Clone, Copy)] -#[repr(transparent)] -pub struct PackedChunkCoord(u64); - -impl PackedChunkCoord { - const TOTAL_SIZE: usize = u64::BITS as usize; - - const X_MASK: u64 = 0x3FFFFF; - const Y_MASK: u64 = 0x0FFFFF; - const Z_MASK: u64 = 0x3FFFFF; - - const X_SIZE: usize = 22; - const Y_SIZE: usize = 20; - const Z_SIZE: usize = 22; - - const X_OFFSET: usize = 42; - const Y_OFFSET: usize = 0; - const Z_OFFSET: usize = 20; - - pub fn from(coord: i32x3) -> Self { - let mut packed: u64 = 0; - packed |= (coord.x() as u64 & Self::X_MASK) << Self::X_OFFSET; - packed |= (coord.y() as u64 & Self::Y_MASK) << Self::Y_OFFSET; - packed |= (coord.z() as u64 & Self::Z_MASK) << Self::Z_OFFSET; - - PackedChunkCoord(packed) - } - - pub fn x(&self) -> i32 { - ((self.0 << (Self::TOTAL_SIZE - Self::X_SIZE - Self::X_OFFSET)) - >> (Self::TOTAL_SIZE - Self::X_SIZE)) as i32 - } - - pub fn y(&self) -> i32 { - ((self.0 << (Self::TOTAL_SIZE - Self::Y_SIZE - Self::Y_OFFSET)) - >> (Self::TOTAL_SIZE - Self::Y_SIZE)) as i32 - } - - pub fn z(&self) -> i32 { - ((self.0 << (Self::TOTAL_SIZE - Self::Z_SIZE - Self::Z_OFFSET)) - >> (Self::TOTAL_SIZE - Self::Z_SIZE)) as i32 - } -} - -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum GraphDirection { - NegX, - NegY, - NegZ, - PosX, - PosY, - PosZ, -} - -impl GraphDirection { - pub const fn as_vector(&self) -> i32x3 { - match *self { - GraphDirection::NegX => from_xyz(-1, 0, 0), - GraphDirection::NegY => from_xyz(0, -1, 0), - GraphDirection::NegZ => from_xyz(0, 0, -1), - GraphDirection::PosX => from_xyz(1, 0, 0), - GraphDirection::PosY => from_xyz(0, 1, 0), - GraphDirection::PosZ => from_xyz(0, 0, 1), - } - } - - pub const fn ordered() -> &'static [GraphDirection; 6] { - const ORDERED: [GraphDirection; 6] = [ - GraphDirection::NegX, - GraphDirection::NegY, - GraphDirection::NegZ, - GraphDirection::PosX, - GraphDirection::PosY, - GraphDirection::PosZ, - ]; - &ORDERED - } - - pub const fn opposite(&self) -> GraphDirection { - match *self { - GraphDirection::NegX => GraphDirection::PosX, - GraphDirection::NegY => GraphDirection::PosY, - GraphDirection::NegZ => GraphDirection::PosZ, - GraphDirection::PosX => GraphDirection::NegX, - GraphDirection::PosY => GraphDirection::NegY, - GraphDirection::PosZ => GraphDirection::NegZ, - } - } -} - -#[derive(Clone, Copy)] -pub struct GraphDirectionSet(u8); - -impl GraphDirectionSet { - pub fn from(packed: u8) -> Self { - GraphDirectionSet(packed) - } - - pub fn none() -> GraphDirectionSet { - GraphDirectionSet(0) - } - - pub fn all() -> GraphDirectionSet { - let mut set = GraphDirectionSet::none(); - - for dir in GraphDirection::ordered() { - set.add(*dir); - } - - set - } - - pub fn single(direction: GraphDirection) -> GraphDirectionSet { - let mut set = GraphDirectionSet::none(); - set.add(direction); - set - } - - pub fn add(&mut self, dir: GraphDirection) { - self.0 |= 1 << dir as usize; - } - - pub fn add_all(&mut self, set: GraphDirectionSet) { - self.0 |= set.0; - } - - pub fn contains(&self, dir: GraphDirection) -> bool { - (self.0 & (1 << dir as usize)) != 0 - } - - fn is_empty(&self) -> bool { - self.0 == 0 - } -} - -impl Default for GraphDirectionSet { - fn default() -> Self { - GraphDirectionSet::none() - } -} - -impl ops::BitAnd for GraphDirectionSet { - type Output = GraphDirectionSet; - - fn bitand(self, rhs: Self) -> Self::Output { - GraphDirectionSet(self.0 & rhs.0) - } -} - -#[derive(Default, Clone, Copy)] -pub struct VisibilityData { - data: [u8; 6], -} - -impl VisibilityData { - pub fn from_u64(packed: u64) -> Self { - VisibilityData { - data: VisibilityData::unpack(packed), - } - } - - fn get_outgoing_directions(&self, incoming: GraphDirectionSet) -> GraphDirectionSet { - let packed = VisibilityData::pack(&self.data); - - let mut outgoing = (((0b_0000001_0000001_0000001_0000001_0000001_0000001u64 * incoming.0 as u64) & 0x010101010101_u64) * 0xFF) // turn bitmask into lane wise mask - & packed; // apply visibility to incoming - outgoing |= outgoing >> 32; // fold top 32 bits onto bottom 32 bits - outgoing |= outgoing >> 16; // fold top 16 bits onto bottom 16 bits - outgoing |= outgoing >> 8; // fold top 8 bits onto bottom 8 bits - - GraphDirectionSet(outgoing as u8) - } - - fn unpack(packed: u64) -> [u8; 6] { - let mut data = [0u8; 6]; - - unsafe { - data.copy_from_slice(&packed.to_ne_bytes()[0..6]); - } - - data - } - - fn pack(data: &[u8; 6]) -> u64 { - let mut packed = [0u8; 8]; - packed[0..6].copy_from_slice(data); - - unsafe { u64::from_ne_bytes(packed) } - } -} - -#[repr(C)] -pub struct RegionDrawBatch { - region_coord: (i32, i32, i32), - sections: CInlineVec, -} - -impl RegionDrawBatch { - pub fn new(region_coord: i32x3) -> Self { - RegionDrawBatch { - region_coord: region_coord.into_tuple(), - sections: CInlineVec::new(), - } - } - - fn is_empty(&self) -> bool { - self.sections.is_empty() - } -} - -#[derive(Default, Clone, Copy)] -pub struct Node { - pub connections: VisibilityData, - pub flags: u8, -} - -impl Node { - pub fn new(connections: VisibilityData, flags: u8) -> Self { - Node { connections, flags } - } -} - -struct RegionSearchState { - incoming: [GraphDirectionSet; SECTIONS_IN_REGION], - queue: ArrayDeque, - - enqueued: bool, -} - -impl RegionSearchState { - pub fn enqueue(&mut self, index: LocalNodeIndex, directions: GraphDirectionSet) { - let incoming = &mut self.incoming[index.as_array_offset()]; - let should_enqueue = incoming.is_empty(); - - incoming.add_all(directions); - - unsafe { - self.queue - .push_conditionally_unchecked(index, should_enqueue); - } - } - - fn reset(&mut self) { - self.queue.reset(); - self.incoming.fill(GraphDirectionSet::none()); - - self.enqueued = false; - } -} - -impl Default for RegionSearchState { - fn default() -> Self { - Self { - queue: Default::default(), - incoming: [GraphDirectionSet::default(); SECTIONS_IN_REGION], - enqueued: false, - } - } -} - -struct Region { - nodes: [Node; SECTIONS_IN_REGION], - search_state: RegionSearchState, - - loaded_count: usize, -} - -impl Region { - fn new() -> Region { - Region { - nodes: [Node::default(); SECTIONS_IN_REGION], - search_state: RegionSearchState::default(), - - loaded_count: 0, - } - } - - fn set_chunk(&mut self, coord: i32x3, node: Node) { - let local_index = LocalNodeIndex::from_global(coord); - self.nodes[local_index.as_array_offset()] = node; - } - - fn remove_chunk(&mut self, coord: i32x3) { - let local_index = LocalNodeIndex::from_global(coord); - self.nodes[local_index.as_array_offset()] = Node::default(); - } - - fn get_chunk(&self, coord: i32x3) -> &Node { - let local_index = LocalNodeIndex::from_global(coord); - &self.nodes[local_index.as_array_offset()] - } -} - -pub struct Graph { - regions: HashMap, -} - -impl Graph { - pub fn new() -> Self { - Graph { - regions: HashMap::default(), - } - } - - pub fn search(&mut self, frustum: &Frustum, view_distance: i32) -> CVec { - let mut region_iteration_queue: VecDeque = VecDeque::new(); - - let origin_node_coord = position_to_chunk_coord(*frustum.position()); - - if let Some(node) = self.get_node(origin_node_coord) { - let region_coord = chunk_coord_to_region_coord(origin_node_coord); - - let mut region = self.regions.get_mut(®ion_coord).unwrap(); - region.search_state.enqueue( - LocalNodeIndex::from_global(origin_node_coord), - GraphDirectionSet::all(), - ); - region.search_state.enqueued = true; - - region_iteration_queue.push_back(region_coord); - } - - let mut sorted_batches: Vec = Vec::new(); - - while let Some(region_coord) = region_iteration_queue.pop_front() { - let mut search_ctx = SearchContext::create(&mut self.regions, region_coord); - let mut batch: RegionDrawBatch = RegionDrawBatch::new(region_coord); - - while let Some(node_idx) = search_ctx.origin().search_state.queue.pop() { - let node_coord = node_idx.as_global_coord(region_coord); - - let node = search_ctx.origin().nodes[node_idx.as_array_offset()]; - let node_incoming = - search_ctx.origin().search_state.incoming[node_idx.as_array_offset()]; - - if !chunk_inside_view_distance(node_coord, origin_node_coord, view_distance) - || !chunk_inside_frustum(node_coord, frustum) - { - continue; - } - - if (node.flags & (1 << 1)) != 0 { - batch.sections.push(node_idx); - } - - let valid_directions = get_valid_directions(origin_node_coord, node_coord); - let allowed_directions = - node.connections.get_outgoing_directions(node_incoming) & valid_directions; - - Self::enqueue_all_neighbors(&mut search_ctx, allowed_directions, node_idx); - } - - if !batch.is_empty() { - sorted_batches.push(batch); - } - - for direction in GraphDirection::ordered() { - let adjacent_region_coord: i32x3 = region_coord + direction.as_vector(); - - if let Some(region) = &mut search_ctx.adjacent(*direction, true) { - if region.search_state.queue.is_empty() || region.search_state.enqueued { - continue; - } - - region.search_state.enqueued = true; - region_iteration_queue.push_back(adjacent_region_coord); - } - } - - search_ctx.origin().search_state.reset(); - } - - CVec::from_boxed_slice(sorted_batches.into_boxed_slice()) - } - - fn enqueue_all_neighbors( - context: &mut SearchContext, - directions: GraphDirectionSet, - origin: LocalNodeIndex, - ) { - for direction in GraphDirection::ordered() { - if directions.contains(*direction) { - let (neighbor, wrapped) = match direction { - GraphDirection::NegX => origin.dec_x(), - GraphDirection::NegY => origin.dec_y(), - GraphDirection::NegZ => origin.dec_z(), - GraphDirection::PosX => origin.inc_x(), - GraphDirection::PosY => origin.inc_y(), - GraphDirection::PosZ => origin.inc_z(), - }; - - if let Some(neighbor_region) = context.adjacent(*direction, wrapped) { - neighbor_region - .search_state - .enqueue(neighbor, GraphDirectionSet::single(direction.opposite())); - } - } - } - } - - pub fn add_chunk(&mut self, chunk_coord: i32x3) { - let mut region = self - .regions - .entry(chunk_coord_to_region_coord(chunk_coord)) - .or_insert_with(|| Region::new()); - - region.set_chunk(chunk_coord, Node::default()); - } - - pub fn update_chunk(&mut self, chunk_coord: i32x3, node: Node) { - if let Some(region) = self - .regions - .get_mut(&chunk_coord_to_region_coord(chunk_coord)) - { - region.set_chunk(chunk_coord, node); - } - } - - pub fn remove_chunk(&mut self, chunk_coord: i32x3) { - if let Some(region) = self - .regions - .get_mut(&chunk_coord_to_region_coord(chunk_coord)) - { - region.remove_chunk(chunk_coord); - } - } - - fn get_node(&self, chunk_coord: i32x3) -> Option { - self.regions - .get(&chunk_coord_to_region_coord(chunk_coord)) - .map(|region| *region.get_chunk(chunk_coord)) - } -} - -#[repr(C)] // SAFETY: This ensures the layout of our struct will not change -pub struct SearchContext<'a> { - adjacent: [*mut Region; 6], - origin: NonNull, - - reference: PhantomData<&'a mut HashMap>, -} - -impl<'a> SearchContext<'a> { - fn adjacent(&mut self, direction: GraphDirection, wrapped: bool) -> Option<&'a mut Region> { - unsafe { - // SAFETY: The C layout ensures the field SearchContext.origin will always be the N+6 element - let offset = if wrapped { direction as usize } else { 6 }; - let ptr = self.adjacent.as_ptr().add(offset); - - // SAFETY: Pointer is always valid, if non-null - NonNull::new(*ptr).map(|mut ptr| ptr.as_mut()) - } - } - - fn origin(&mut self) -> &'a mut Region { - unsafe { - // SAFETY: Not possible to take more than one reference at a time - self.origin.as_mut() - } - } - - fn create(regions: &'a mut HashMap, origin_coord: i32x3) -> SearchContext<'a> { - SearchContext { - adjacent: GraphDirection::ordered() - .map(|direction| origin_coord + direction.as_vector()) - .map(|position| Self::get_ptr(regions, &position)), - - origin: NonNull::new(Self::get_ptr(regions, &origin_coord)) - .expect("Origin region does not exist"), - - reference: PhantomData, - } - } - - fn get_ptr(regions: &mut HashMap, position: &i32x3) -> *mut Region { - regions - .get_mut(position) - .map_or(ptr::null_mut(), |cell| cell as *mut Region) - } -} - -fn chunk_inside_view_distance(position: i32x3, center: i32x3, view_distance: i32) -> bool { - let distance: i32x3 = (position - center).abs(); - distance.simd_lt(i32x3::splat(view_distance)).all() -} - -fn get_valid_directions(center: i32x3, position: i32x3) -> GraphDirectionSet { - let negative = position.simd_le(center); - let positive = position.simd_ge(center); - - GraphDirectionSet::from(negative.to_bitmask() | (positive.to_bitmask() << 3)) -} - -fn chunk_inside_frustum(position: i32x3, frustum: &Frustum) -> bool { - frustum.test_bounding_box(&get_chunk_bounding_box(position)) -} - -fn get_chunk_bounding_box(chunk_coord: i32x3) -> BoundingBox { - let min = (chunk_coord << i32x3::splat(4)).cast::(); - let max = min + f32x3::splat(16.0); - - BoundingBox::new(min, max) -} - -fn chunk_coord_to_region_coord(node_position: i32x3) -> i32x3 { - node_position >> i32x3::from_xyz(3, 2, 3) -} - -fn position_to_chunk_coord(position: f32x3) -> i32x3 { - position.floor().cast::().shr(i32x3::splat(4)) -} diff --git a/native/core/src/graph/coords.rs b/native/core/src/graph/coords.rs new file mode 100644 index 0000000000..99f54598b5 --- /dev/null +++ b/native/core/src/graph/coords.rs @@ -0,0 +1,161 @@ +use std::mem::transmute; +use std::ops::Shr; + +use core_simd::simd::*; +use std_float::StdFloat; + +use crate::graph::frustum::*; +use crate::graph::*; +use crate::math::*; + +pub struct LocalCoordinateContext { + frustum: LocalFrustum, + + // the camera coords relative to the local origin, which is the (0, 0, 0) point of the + // 256x256x256 cube we hold the section data in. + camera_coords: f32x3, + camera_section_coords: u8x3, + + // this is the section that encompasses the corner of the view distance bounding box where the + // coordinate for each axis is closest to negative infinity, and truncated to the origin of the + // level 3 node it's contained in. + iter_start_section_idx: LocalSectionIndex, + iter_start_section_coords: u8x3, + + // similar to the previous, but rounded to the closest region coord and relative to the world + // origin, rather than the data structure origin. + iter_start_region_coord: i32x3, + + fog_distance_squared: f32, + + world_bottom_section_y: i32, + world_top_section_y: i32, +} + +impl LocalCoordinateContext { + pub fn new( + world_pos: f64x3, + planes: [f32x6; 4], + fog_distance: f32, + section_view_distance: i32, + world_bottom_section_y: i32, + world_top_section_y: i32, + ) -> Self { + let frustum = LocalFrustum::new(planes); + + let camera_section_world_coords = world_pos.floor().cast::() >> i64x3::splat(4); + + // the cast to u8 puts it in the local coordinate space + let camera_section_coords = camera_section_world_coords.cast::(); + + let camera_coords = (camera_section_coords.cast::() << i32x3::splat(4)).cast::(); + + let fog_distance_capped = fog_distance.min(((section_view_distance + 1) << 4) as f32); + let fog_distance_squared = fog_distance_capped * fog_distance_capped; + + LocalCoordinateContext { + frustum, + camera_coords, + camera_section_coords, + iter_start_section_idx: LocalSectionIndex(), + iter_start_region_coord: Default::default(), + fog_distance_squared, + world_bottom_section_y, + world_top_section_y, + } + } + + #[inline(always)] + pub fn bounds_inside_world(&self, local_bounds: LocalBoundingBox) -> BoundsCheckResult { + // on the x and z axis, check if the bounding box is inside the fog. + let camera_coords_xz = simd_swizzle!(self.camera_coords, [X, Z, X, Z]); + + // the first pair of lanes is the closest xz to the player in the chunk, + // the second pair of lanes is the furthest xz from the player in the chunk + let extrema_in_chunk = camera_coords_xz + .simd_min(simd_swizzle!( + local_bounds.min, + local_bounds.max, + [Second(X), Second(Z), First(X), First(Z)] + )) + .simd_max(simd_swizzle!( + local_bounds.min, + local_bounds.max, + [First(X), First(Z), Second(X), Second(Z)] + )); + + let differences = camera_coords_xz - extrema_in_chunk; + let differences_squared = differences * differences; + + // lane 1 is closest dist, lane 2 is furthest dist + let distances_squared = + simd_swizzle!(differences_squared, [0, 2]) + simd_swizzle!(differences_squared, [1, 3]); + + let fog_result = unsafe { + BoundsCheckResult::from_int_unchecked( + distances_squared + .simd_lt(f32x2::splat(self.fog_distance_squared)) + .select(u32x2::splat(1), u32x2::splat(0)) + .reduce_sum() as u8, + ) + }; + + // on the y axis, check if the bounding box is inside the world height. + let height_result = BoundsCheckResult::Outside; + + BoundsCheckResult::combine(fog_result, height_result) + } + + #[inline(always)] + pub fn get_valid_directions(&self, position: u8x3) -> GraphDirectionSet { + let negative = position.simd_le(self.camera_section_coords); + let positive = position.simd_ge(self.camera_section_coords); + + GraphDirectionSet::from(negative.to_bitmask() | (positive.to_bitmask() << 3)) + } + + #[inline(always)] + pub fn node_get_local_bounds( + &self, + local_origin: u8x3, + ) -> LocalBoundingBox { + let min_pos = local_origin.cast::() + + local_origin + .simd_lt(self.iter_start_section_coords) + .cast() + .select(Simd::splat(256.0_f32), Simd::splat(0.0_f32)); + + let max_pos = min_pos + Simd::splat((16 << LEVEL) as f32); + + LocalBoundingBox { + min: min_pos, + max: max_pos, + } + } +} + +#[repr(u8)] +pub enum BoundsCheckResult { + Outside = 0, + Partial = 1, + Inside = 2, +} + +impl BoundsCheckResult { + /// SAFETY: if out of bounds, this will fail to assert in debug mode + pub unsafe fn from_int_unchecked(val: u8) -> Self { + debug_assert!(val <= 2); + + transmute(val) + } + + pub fn combine(self, rhs: Self) -> Self { + // SAFETY: given 2 valid inputs, the result will always be valid + unsafe { Self::from_int_unchecked((self as u8).min(rhs as u8)) } + } +} + +pub struct LocalBoundingBox { + pub min: f32x3, + pub max: f32x3, +} diff --git a/native/core/src/graph/frustum.rs b/native/core/src/graph/frustum.rs new file mode 100644 index 0000000000..357237af9a --- /dev/null +++ b/native/core/src/graph/frustum.rs @@ -0,0 +1,60 @@ +use core_simd::simd::*; + +use crate::graph::coords::*; +use crate::graph::LocalSectionIndex; +use crate::math::*; + +/// This is called the "local" frustum because it doesn't have a position associated with it. +/// When using this, it is expected that coordinates are relative to the camera rather than the +/// world origin. +pub struct LocalFrustum { + plane_xs: f32x6, + plane_ys: f32x6, + plane_zs: f32x6, + plane_ws: f32x6, +} + +impl LocalFrustum { + pub fn new(planes: [f32x6; 4]) -> Self { + LocalFrustum { + plane_xs: planes[0], + plane_ys: planes[1], + plane_zs: planes[2], + plane_ws: planes[3], + } + } + + #[inline(always)] + pub fn test_local_bounding_box(&self, bb: &LocalBoundingBox) -> BoundsCheckResult { + unsafe { + // The unsafe mask shenanigans here just checks if the sign bit is set for each lane. + // This is faster than doing a manual comparison with something like simd_gt, and compiles + // down to 1 instruction each (vmaskmovps) on x86 machines with AVX. + + let points_x = + Mask::from_int_unchecked(self.plane_xs.to_bits().cast::() >> Simd::splat(31)) + .select(Simd::splat(bb.min.x()), Simd::splat(bb.max.x())); + + let points_y = + Mask::from_int_unchecked(self.plane_ys.to_bits().cast::() >> Simd::splat(31)) + .select(Simd::splat(bb.min.y()), Simd::splat(bb.max.y())); + + let points_z = + Mask::from_int_unchecked(self.plane_zs.to_bits().cast::() >> Simd::splat(31)) + .select(Simd::splat(bb.min.z()), Simd::splat(bb.max.z())); + + let points_dot = self.plane_xs.fast_fma( + points_x, + self.plane_ys.fast_fma(points_y, self.plane_zs * points_z), + ); + + let int_result = points_dot.simd_ge(-self.plane_ws).to_bitmask(); + + // janky way to create the enum result branchlessly + let enum_int: u8 = if int_result > 0b000000 { 1 } else { 0 } + + if int_result == 0b111111 { 1 } else { 0 }; + + BoundsCheckResult::from_int_unchecked(enum_int) + } + } +} diff --git a/native/core/src/graph/mod.rs b/native/core/src/graph/mod.rs new file mode 100644 index 0000000000..83383312f8 --- /dev/null +++ b/native/core/src/graph/mod.rs @@ -0,0 +1,601 @@ +use std::collections::VecDeque; +use std::fmt::Debug; +use std::ops::*; +use std::vec::Vec; + +use coords::LocalCoordinateContext; +use core_simd::simd::Which::*; +use core_simd::simd::*; +use frustum::LocalFrustum; +use std_float::StdFloat; + +use crate::collections::ArrayDeque; +use crate::ffi::{CInlineVec, CVec}; +use crate::graph::octree::LinearBitOctree; +use crate::math::*; + +pub mod coords; +pub mod frustum; +mod octree; + +const SECTIONS_IN_REGION: usize = 8 * 4 * 8; +const SECTIONS_IN_GRAPH: usize = 256 * 256 * 256; + +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct LocalSectionIndex(u32); + +impl LocalSectionIndex { + // XYZXYZXYZXYZXYZXYZXYZXYZ + const X_MASK: u32 = 0b10010010_01001001_00100100; + const Y_MASK: u32 = 0b01001001_00100100_10010010; + const Z_MASK: u32 = 0b00100100_10010010_01001001; + + #[inline(always)] + pub fn pack(unpacked: u8x3) -> Self { + // allocate one byte per bit for each element. + // each element is still has its individual bits in linear ordering, but the bytes in the + // vector are in morton ordering. + #[rustfmt::skip] + let expanded_linear_bits = simd_swizzle!( + unpacked, + [ + // X, Y, Z + 2, 1, 0, + 2, 1, 0, + 2, 1, 0, + 2, 1, 0, + 2, 1, 0, + 2, 1, 0, + 2, 1, 0, + 2, 1, 0, // LSB + ] + ); + + // shift each bit into the sign bit for morton ordering + #[rustfmt::skip] + let expanded_morton_bits = expanded_linear_bits << Simd::::from_array( + [ + 7, 7, 7, + 6, 6, 6, + 5, 5, 5, + 4, 4, 4, + 3, 3, 3, + 2, 2, 2, + 1, 1, 1, + 0, 0, 0, // LSB + ], + ); + + // arithmetic shift to set each whole lane to its sign bit, then shrinking all lanes to bitmask + let morton_packed = unsafe { + Mask::::from_int_unchecked(expanded_morton_bits.cast::() >> Simd::splat(7)) + } + .to_bitmask(); + + Self(morton_packed) + } + + #[inline(always)] + pub fn inc_x(self) -> Self { + self.inc::<{ Self::X_MASK }>() + } + + #[inline(always)] + pub fn inc_y(self) -> Self { + self.inc::<{ Self::Y_MASK }>() + } + + #[inline(always)] + pub fn inc_z(self) -> Self { + self.inc::<{ Self::Z_MASK }>() + } + + #[inline(always)] + pub fn dec_x(self) -> Self { + self.dec::<{ Self::X_MASK }>() + } + + #[inline(always)] + pub fn dec_y(self) -> Self { + self.dec::<{ Self::Y_MASK }>() + } + + #[inline(always)] + pub fn dec_z(self) -> Self { + self.dec::<{ Self::Z_MASK }>() + } + + #[inline(always)] + pub fn inc(self) -> Self { + // make the other bits in the number 1 + let mut masked = self.0 | !MASK; + + // increment + masked = masked.wrapping_add(1); + + // modify only the masked bits in the original number + Self((self.0 & !MASK) | (masked & MASK)) + } + + #[inline(always)] + pub fn dec(self) -> Self { + // make the other bits in the number 0 + let mut masked = self.0 & MASK; + + // decrement + masked = masked.wrapping_sub(1); + + // modify only the masked bits in the original number + Self((self.0 & !MASK) | (masked & MASK)) + } + + #[inline(always)] + pub fn as_array_offset(&self) -> usize { + self.0 as usize + } + + #[inline(always)] + pub fn unpack(&self) -> u8x3 { + // allocate one byte per bit for each element. + // each element is still has its individual bits in morton ordering, but the bytes in the + // vector are in linear ordering. + #[rustfmt::skip] + let expanded_linear_bits = simd_swizzle!( + u8x4::from_array(self.0.to_le_bytes()), + [ + // X + 2, 2, 2, 1, 1, 1, 0, 0, + // Y + 2, 2, 2, 1, 1, 0, 0, 0, + // Z + 2, 2, 1, 1, 1, 0, 0, 0, // LSB + ] + ); + + // shift each bit into the sign bit for morton ordering + #[rustfmt::skip] + let expanded_morton_bits = expanded_linear_bits << Simd::::from_array( + [ + // X + 0, 3, 6, + 1, 4, 7, + 2, 5, + // Y + 1, 4, 7, + 2, 5, 0, + 3, 6, + // Z + 2, 5, 0, + 3, 6, 1, + 4, 7, // LSB + ], + ); + + // arithmetic shift to set each whole lane to its sign bit, then shrinking all lanes to bitmask + let linear_packed = unsafe { + Mask::::from_int_unchecked(expanded_morton_bits.cast::() >> Simd::splat(7)) + } + .to_bitmask(); + + u8x3::from_slice(&linear_packed.to_le_bytes()[0..=2]) + } +} + +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct RegionSectionIndex(u8); + +impl RegionSectionIndex { + const X_MASK_SINGLE: u8 = 0b00000111; + const Y_MASK_SINGLE: u8 = 0b00000011; + const Z_MASK_SINGLE: u8 = 0b00000111; + + const X_MASK_SHIFT: u8 = 5; + const Y_MASK_SHIFT: u8 = 3; + const Z_MASK_SHIFT: u8 = 0; + + #[inline(always)] + pub fn from_local(local_section_coord: u8x3) -> Self { + Self( + (local_section_coord + & u8x3::from_array([ + Self::X_MASK_SINGLE, + Self::Y_MASK_SINGLE, + Self::Z_MASK_SINGLE, + ]) << u8x3::from_array([ + Self::X_MASK_SHIFT, + Self::Y_MASK_SHIFT, + Self::Z_MASK_SHIFT, + ])) + .reduce_or(), + ) + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum GraphDirection { + NegX, + NegY, + NegZ, + PosX, + PosY, + PosZ, +} + +impl GraphDirection { + pub const ORDERED: [GraphDirection; 6] = [ + GraphDirection::NegX, + GraphDirection::NegY, + GraphDirection::NegZ, + GraphDirection::PosX, + GraphDirection::PosY, + GraphDirection::PosZ, + ]; + + #[inline(always)] + pub const fn opposite(&self) -> GraphDirection { + match self { + GraphDirection::NegX => GraphDirection::PosX, + GraphDirection::NegY => GraphDirection::PosY, + GraphDirection::NegZ => GraphDirection::PosZ, + GraphDirection::PosX => GraphDirection::NegX, + GraphDirection::PosY => GraphDirection::NegY, + GraphDirection::PosZ => GraphDirection::NegZ, + } + } +} + +#[derive(Clone, Copy)] +pub struct GraphDirectionSet(u8); + +impl GraphDirectionSet { + #[inline(always)] + pub fn from(packed: u8) -> Self { + GraphDirectionSet(packed) + } + + #[inline(always)] + pub fn none() -> GraphDirectionSet { + GraphDirectionSet(0) + } + + #[inline(always)] + pub fn all() -> GraphDirectionSet { + let mut set = GraphDirectionSet::none(); + + for dir in GraphDirection::ORDERED { + set.add(dir); + } + + set + } + + #[inline(always)] + pub fn single(direction: GraphDirection) -> GraphDirectionSet { + let mut set = GraphDirectionSet::none(); + set.add(direction); + set + } + + #[inline(always)] + pub fn add(&mut self, dir: GraphDirection) { + self.0 |= 1 << dir as usize; + } + + #[inline(always)] + pub fn add_all(&mut self, set: GraphDirectionSet) { + self.0 |= set.0; + } + + #[inline(always)] + pub fn contains(&self, dir: GraphDirection) -> bool { + (self.0 & (1 << dir as usize)) != 0 + } + + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.0 == 0 + } +} + +impl Default for GraphDirectionSet { + fn default() -> Self { + GraphDirectionSet::none() + } +} + +impl BitAnd for GraphDirectionSet { + type Output = GraphDirectionSet; + + fn bitand(self, rhs: Self) -> Self::Output { + GraphDirectionSet(self.0 & rhs.0) + } +} + +#[derive(Default, Clone, Copy)] +pub struct VisibilityData(u16); + +impl VisibilityData { + #[inline(always)] + pub fn pack(mut raw: u64) -> Self { + raw >>= 6; + let mut packed = (raw & 0b1) as u16; + raw >>= 5; + packed |= (raw & 0b110) as u16; + raw >>= 4; + packed |= (raw & 0b111000) as u16; + raw >>= 3; + packed |= (raw & 0b1111000000) as u16; + raw >>= 2; + packed |= (raw & 0b111110000000000) as u16; + + VisibilityData(packed) + } + + #[inline(always)] + pub fn get_outgoing_directions(&self, incoming: GraphDirectionSet) -> GraphDirectionSet { + // extend everything to u32s because we can shift them faster on x86 without avx512 + let vis_bits = Simd::::splat(self.0 as u32); + let in_bits = Simd::::splat(incoming.0 as u32); + + let rows_cols = (vis_bits >> Simd::from_array([0_u32, 1_u32, 3_u32, 6_u32, 10_u32])).cast() + & Simd::from_array([0b1_u32, 0b11_u32, 0b111_u32, 0b1111_u32, 0b11111_u32]); + + let rows = (rows_cols & in_bits) + .cast::() + .simd_ne(Simd::splat(0)) + .select( + Simd::from_array([0b10_u32, 0b100_u32, 0b1000_u32, 0b10000_u32, 0b100000_u32]), + Simd::splat(0_u32), + ); + + let cols = ((in_bits + << Simd::from_array([ + u32::BITS - 2, + u32::BITS - 3, + u32::BITS - 4, + u32::BITS - 5, + u32::BITS - 6, + ])) + .cast::() + >> Simd::splat((u32::BITS - 1) as i32)) + .cast::() + & rows_cols; + + // extend to po2 vectors to make the reduction happy + let outgoing_bits = simd_swizzle!( + rows, + cols, + [ + First(0), + First(1), + First(2), + First(3), + First(4), + First(4), + First(4), + First(4), + Second(0), + Second(1), + Second(2), + Second(3), + Second(4), + Second(4), + Second(4), + Second(4), + ] + ) + .reduce_or() as u8; // & !incoming + + GraphDirectionSet(outgoing_bits) + } +} + +struct GraphSearchState { + incoming: [GraphDirectionSet; SECTIONS_IN_REGION], + queue: ArrayDeque, + + enqueued: bool, +} + +impl GraphSearchState { + pub fn enqueue(&mut self, index: LocalNodeIndex, directions: GraphDirectionSet) { + let incoming = &mut self.incoming[index.as_array_offset()]; + let should_enqueue = incoming.is_empty(); + + incoming.add_all(directions); + + unsafe { + self.queue + .push_conditionally_unchecked(index, should_enqueue); + } + } + + fn reset(&mut self) { + self.queue.reset(); + self.incoming.fill(GraphDirectionSet::none()); + + self.enqueued = false; + } +} + +impl Default for GraphSearchState { + fn default() -> Self { + Self { + queue: Default::default(), + incoming: [GraphDirectionSet::default(); SECTIONS_IN_REGION], + enqueued: false, + } + } +} + +pub struct Graph { + section_populated_bits: LinearBitOctree, + section_visibility_bits: LinearBitOctree, +} + +impl Graph { + pub fn new() -> Self { + Graph { + section_populated_bits: Default::default(), + section_visibility_bits: Default::default(), + } + } + + pub fn cull(&mut self, context: LocalCoordinateContext, no_occlusion_cull: bool) {} + + fn frustum_and_fog(&mut self, context: LocalCoordinateContext) {} + + fn bfs_and_occlusion_cull( + &mut self, + context: LocalCoordinateContext, + no_occlusion_cull: bool, + ) -> CVec { + let mut region_iteration_queue: VecDeque = VecDeque::new(); + + let origin_node_coord = position_to_chunk_coord(*frustum.position()); + + if let Some(node) = self.get_node(origin_node_coord) { + let region_coord = chunk_coord_to_region_coord(origin_node_coord); + + let mut region = self.regions.get_mut(®ion_coord).unwrap(); + region.search_state.enqueue( + LocalNodeIndex::from_global(origin_node_coord), + GraphDirectionSet::all(), + ); + region.search_state.enqueued = true; + + region_iteration_queue.push_back(region_coord); + } + + let mut sorted_batches: Vec = Vec::new(); + + while let Some(region_coord) = region_iteration_queue.pop_front() { + let mut search_ctx = SearchContext::create(&mut self.regions, region_coord); + let mut batch: RegionDrawBatch = RegionDrawBatch::new(region_coord); + + while let Some(node_idx) = search_ctx.origin().search_state.queue.pop() { + let node_coord = node_idx.as_global_coord(region_coord); + + let node = search_ctx.origin().nodes[node_idx.as_array_offset()]; + let node_incoming = + search_ctx.origin().search_state.incoming[node_idx.as_array_offset()]; + + if !chunk_inside_fog(node_coord, origin_node_coord, view_distance) + || !chunk_inside_frustum(node_coord, frustum) + { + continue; + } + + if (node.flags & (1 << 1)) != 0 { + batch.sections.push(node_idx); + } + + let valid_directions = get_valid_directions(origin_node_coord, node_coord); + let allowed_directions = + node.connections.get_outgoing_directions(node_incoming) & valid_directions; + + Self::enqueue_all_neighbors(&mut search_ctx, allowed_directions, node_idx); + } + + if !batch.is_empty() { + sorted_batches.push(batch); + } + + for direction in GraphDirection::ORDERED { + let adjacent_region_coord: LocalSectionCoord = region_coord + direction.as_vector(); + + if let Some(region) = &mut search_ctx.adjacent(*direction, true) { + if region.search_state.queue.is_empty() || region.search_state.enqueued { + continue; + } + + region.search_state.enqueued = true; + region_iteration_queue.push_back(adjacent_region_coord); + } + } + + search_ctx.origin().search_state.reset(); + } + + CVec::from_boxed_slice(sorted_batches.into_boxed_slice()) + } + + fn enqueue_all_neighbors( + context: &mut SearchContext, + directions: GraphDirectionSet, + origin: LocalNodeIndex, + ) { + for direction in GraphDirection::ORDERED { + if directions.contains(direction) { + let (neighbor, wrapped) = match direction { + GraphDirection::NegX => origin.dec_x(), + GraphDirection::NegY => origin.dec_y(), + GraphDirection::NegZ => origin.dec_z(), + GraphDirection::PosX => origin.inc_x(), + GraphDirection::PosY => origin.inc_y(), + GraphDirection::PosZ => origin.inc_z(), + }; + + if let Some(neighbor_region) = context.adjacent(direction, wrapped) { + neighbor_region + .search_state + .enqueue(neighbor, GraphDirectionSet::single(direction.opposite())); + } + } + } + } + + pub fn add_section(&mut self, chunk_coord: LocalSectionCoord) { + let mut region = self + .regions + .entry(chunk_coord_to_region_coord(chunk_coord)) + .or_insert_with(Region::new); + + region.set_chunk(chunk_coord, Node::default()); + } + + pub fn update_section(&mut self, chunk_coord: LocalSectionCoord, node: Node) { + if let Some(region) = self + .regions + .get_mut(&chunk_coord_to_region_coord(chunk_coord)) + { + region.set_chunk(chunk_coord, node); + } + } + + pub fn remove_section(&mut self, chunk_coord: LocalSectionCoord) { + if let Some(region) = self + .regions + .get_mut(&chunk_coord_to_region_coord(chunk_coord)) + { + region.remove_chunk(chunk_coord); + } + } + + fn get_node(&self, chunk_coord: LocalSectionCoord) -> Option { + self.regions + .get(&chunk_coord_to_region_coord(chunk_coord)) + .map(|region| *region.get_chunk(chunk_coord)) + } +} + +#[repr(C)] +pub struct RegionDrawBatch { + region_coord: (i32, i32, i32), + sections: CInlineVec, +} + +impl RegionDrawBatch { + pub fn new(region_coord: i32x3) -> Self { + RegionDrawBatch { + region_coord: region_coord.into_tuple(), + sections: CInlineVec::new(), + } + } + + fn is_empty(&self) -> bool { + self.sections.is_empty() + } +} diff --git a/native/core/src/graph/octree.rs b/native/core/src/graph/octree.rs new file mode 100644 index 0000000000..55fa4cf97b --- /dev/null +++ b/native/core/src/graph/octree.rs @@ -0,0 +1,121 @@ +use std::mem::size_of; + +use core_simd::simd::*; + +use crate::graph::*; + +pub type Level3Node = Simd; +pub type Level2Node = u64; +pub type Level1Node = u8; +pub type Level0Node = bool; + +pub const LEVEL_3_IDX_SHIFT: usize = 9; +pub const LEVEL_2_IDX_SHIFT: usize = 6; +pub const LEVEL_1_IDX_SHIFT: usize = 3; +pub const LEVEL_0_IDX_SHIFT: usize = 0; + +pub union LinearBitOctree { + level_3: [Level3Node; SECTIONS_IN_GRAPH / size_of::() / 8], + level_2: [Level2Node; SECTIONS_IN_GRAPH / size_of::() / 8], + level_1: [Level1Node; SECTIONS_IN_GRAPH / size_of::() / 8], +} + +impl Default for LinearBitOctree { + fn default() -> Self { + LinearBitOctree { + level_3: [Level3Node::splat(0); SECTIONS_IN_GRAPH / size_of::() / 8], + } + } +} + +impl LinearBitOctree { + pub fn get(&self, section: LocalSectionIndex) -> bool { + let array_offset = section.as_array_offset(); + + match LEVEL { + 0 => { + let level_1_idx = array_offset >> LEVEL_1_IDX_SHIFT; + let bit_idx = array_offset & 0b111; + + // SAFETY: The value returned by as_array_offset will never have the top 8 bits set, + // and our arrays are exactly 2^24 bytes long. + let level_1_node = unsafe { *self.level_1.get_unchecked(level_1_idx) }; + + let bit = (level_1_node >> bit_idx) & 0b1; + + bit == 0b1 + } + 1 => { + let level_1_idx = array_offset >> LEVEL_1_IDX_SHIFT; + + // SAFETY: The value returned by as_array_offset will never have the top 8 bits set, + // and our arrays are exactly 2^24 bytes long. + let level_1_node = unsafe { *self.level_1.get_unchecked(level_1_idx) }; + + level_1_node == u8::MAX + } + 2 => { + let level_2_idx = array_offset >> LEVEL_2_IDX_SHIFT; + + let level_2_node = unsafe { *self.level_2.get_unchecked(level_2_idx) }; + + level_2_node == u64::MAX + } + 3 => { + let level_3_idx = array_offset >> LEVEL_3_IDX_SHIFT; + + // SAFETY: The value returned by as_array_offset will never have the top 8 bits set, + // and our arrays are exactly 2^24 bytes long. + let level_3_node = unsafe { *self.level_3.get_unchecked(level_3_idx) }; + + level_3_node == u8x64::splat(u8::MAX) + } + _ => unreachable!(), + } + } + + pub fn set(&self, section: LocalSectionIndex) { + let array_offset = section.as_array_offset(); + + match LEVEL { + 0 => { + let level_1_idx = array_offset >> LEVEL_1_IDX_SHIFT; + let bit_idx = array_offset & 0b111; + + // SAFETY: The value returned by as_array_offset will never have the top 8 bits set, + // and our arrays are exactly 2^24 bytes long. + let level_1_node = unsafe { self.level_1.get_unchecked_mut(level_1_idx) }; + + let bit = 0b1 << bit_idx; + + *level_1_node |= bit; + } + 1 => { + let level_1_idx = array_offset >> LEVEL_1_IDX_SHIFT; + + // SAFETY: The value returned by as_array_offset will never have the top 8 bits set, + // and our arrays are exactly 2^24 bytes long. + let level_1_node = unsafe { self.level_1.get_unchecked_mut(level_1_idx) }; + + *level_1_node = u8::MAX; + } + 2 => { + let level_2_idx = array_offset >> LEVEL_2_IDX_SHIFT; + + let level_2_node = unsafe { self.level_2.get_unchecked_mut(level_2_idx) }; + + *level_2_node = u64::MAX; + } + 3 => { + let level_3_idx = array_offset >> LEVEL_3_IDX_SHIFT; + + // SAFETY: The value returned by as_array_offset will never have the top 8 bits set, + // and our arrays are exactly 2^24 bytes long. + let level_3_node = unsafe { self.level_3.get_unchecked_mut(level_3_idx) }; + + *level_3_node = u8x64::splat(u8::MAX); + } + _ => unreachable!(), + } + } +} diff --git a/native/core/src/lib.rs b/native/core/src/lib.rs index c0078c3139..e4bb9344df 100644 --- a/native/core/src/lib.rs +++ b/native/core/src/lib.rs @@ -4,7 +4,6 @@ mod collections; mod ffi; -mod frustum; mod graph; mod jni; mod math; diff --git a/native/core/src/math.rs b/native/core/src/math.rs index 3365daa5dd..d7208221f9 100644 --- a/native/core/src/math.rs +++ b/native/core/src/math.rs @@ -7,6 +7,12 @@ use std::ops::*; use core_simd::simd::*; use std_float::StdFloat; +pub const X: usize = 0; +pub const Y: usize = 1; +pub const Z: usize = 2; +pub const W: usize = 3; + +// the most common non-po2 length we use is 3, so we create shorthands for it pub type i8x3 = Simd; pub type i16x3 = Simd; pub type i32x3 = Simd; @@ -20,6 +26,9 @@ pub type u64x3 = Simd; pub type f32x3 = Simd; pub type f64x3 = Simd; +// additional useful shorthands +pub type f32x6 = Simd; + // additional declarations outside of traits for const usage pub const fn from_xyz(x: T, y: T, z: T) -> Simd { Simd::from_array([x, y, z]) @@ -36,7 +45,8 @@ pub trait Coords3 { fn y(&self) -> T; fn z(&self) -> T; } - +ays)] + fn x(&self) - impl Coords3 for Simd where T: SimdElement, @@ -53,17 +63,17 @@ where #[inline(always)] fn x(&self) -> T { - self[0] + self[X] } #[inline(always)] fn y(&self) -> T { - self[1] + self[Y] } #[inline(always)] fn z(&self) -> T { - self[2] + self[Z] } } @@ -92,22 +102,22 @@ where #[inline(always)] fn x(&self) -> T { - self[0] + self[X] } #[inline(always)] fn y(&self) -> T { - self[1] + self[Y] } #[inline(always)] fn z(&self) -> T { - self[2] + self[Z] } #[inline(always)] fn w(&self) -> T { - self[3] + self[W] } } @@ -150,26 +160,50 @@ pub trait ToBitMaskExtended { fn from_bitmask(bitmask: Self::BitMask) -> Self; } -// if we need more impls, we can create a macro to generate them -impl ToBitMaskExtended for Mask -where - T: MaskElement, -{ - type BitMask = u8; - - fn to_bitmask(self) -> Self::BitMask { - // This is safe because the alignment should match the next PO2 type, and we are masking off - // the last value once converted. - let larger_mask = unsafe { (&self as *const _ as *const Mask).read() }; - - larger_mask.to_bitmask() & 0b111 - } - - fn from_bitmask(bitmask: Self::BitMask) -> Self { - let larger_mask = Mask::::from_bitmask(bitmask); - - // This is safe because the alignment should be correct for the next PO2 type, and we are - // ignoring the last value. - unsafe { (&larger_mask as *const _ as *const Mask).read() } - } +macro_rules! bitmask_macro { + ($t:ty, [$($len:expr),+]) => { + $( + impl ToBitMaskExtended for Mask + where + T: MaskElement, + { + type BitMask = $t; + + fn to_bitmask(self) -> Self::BitMask { + const NEXT_PO2_LANES: usize = 1_usize << (u8::BITS - ($len - 1_u8).leading_zeros()); + + // This is safe because the alignment should match the next PO2 type, and we are masking off + // the last value once converted. + let larger_mask = + unsafe { (&self as *const _ as *const Mask).read() }; + + larger_mask.to_bitmask() & !(<$t>::MAX << $len) + } + + fn from_bitmask(bitmask: Self::BitMask) -> Self { + const NEXT_PO2_LANES: usize = 1_usize << (u8::BITS - ($len - 1_u8).leading_zeros()); + + let larger_mask = Mask::::from_bitmask(bitmask); + + // This is safe because the alignment should be correct for the next PO2 type, and we are + // ignoring the last value. + unsafe { (&larger_mask as *const _ as *const Mask).read() } + } + } + )+ + }; } + +bitmask_macro!(u8, [3, 5, 6, 7]); +bitmask_macro!(u16, [9, 10, 11, 12, 13, 14, 15]); +bitmask_macro!( + u32, + [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31] +); +bitmask_macro!( + u64, + [ + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63 + ] +); diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderer.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderer.java index 32bf60609c..a021eed6b9 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderer.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderer.java @@ -1,7 +1,7 @@ package me.jellysquid.mods.sodium.client.render.chunk; import me.jellysquid.mods.sodium.client.gl.device.CommandList; -import me.jellysquid.mods.sodium.client.render.chunk.lists.ChunkRenderList; +import me.jellysquid.mods.sodium.client.render.chunk.lists.SectionRenderList; import me.jellysquid.mods.sodium.client.render.chunk.region.RenderRegionManager; import me.jellysquid.mods.sodium.client.render.chunk.terrain.TerrainRenderPass; @@ -20,7 +20,7 @@ public interface ChunkRenderer { * @param camera The camera context containing chunk offsets for the current render * @param renderRegions */ - void render(ChunkRenderMatrices matrices, CommandList commandList, ChunkRenderList renderList, TerrainRenderPass pass, ChunkCameraContext camera, RenderRegionManager renderRegions); + void render(ChunkRenderMatrices matrices, CommandList commandList, SectionRenderList renderList, TerrainRenderPass pass, ChunkCameraContext camera, RenderRegionManager renderRegions); /** * Deletes this render backend and any resources attached to it. diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RegionChunkRenderer.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RegionChunkRenderer.java index ea0f4bf8f2..d8d44f87bd 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RegionChunkRenderer.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RegionChunkRenderer.java @@ -11,7 +11,7 @@ import me.jellysquid.mods.sodium.client.render.chunk.data.SectionRenderDataStorage; import me.jellysquid.mods.sodium.client.render.chunk.data.SectionRenderDataUnsafe; import me.jellysquid.mods.sodium.client.render.chunk.graph.LocalSectionIndex; -import me.jellysquid.mods.sodium.client.render.chunk.lists.ChunkRenderList; +import me.jellysquid.mods.sodium.client.render.chunk.lists.SectionRenderList; import me.jellysquid.mods.sodium.client.render.chunk.region.RenderRegionManager; import me.jellysquid.mods.sodium.client.render.chunk.terrain.TerrainRenderPass; import me.jellysquid.mods.sodium.client.model.quad.properties.ModelQuadFacing; @@ -39,7 +39,7 @@ public RegionChunkRenderer(RenderDevice device, ChunkVertexType vertexType) { @Override public void render(ChunkRenderMatrices matrices, CommandList commandList, - ChunkRenderList list, TerrainRenderPass renderPass, + SectionRenderList list, TerrainRenderPass renderPass, ChunkCameraContext camera, RenderRegionManager renderRegions) { super.begin(renderPass); diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RenderSection.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RenderSection.java index 1ad15f5dff..f5db7a49ee 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RenderSection.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RenderSection.java @@ -179,7 +179,6 @@ public void markForUpdate(ChunkUpdateType type) { public void onBuildSubmitted(CompletableFuture task) { if (this.rebuildTask != null) { this.rebuildTask.cancel(false); - this.rebuildTask = null; } this.rebuildTask = task; diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RenderSectionManager.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RenderSectionManager.java index 7324407f1c..d596c375c0 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -10,7 +10,7 @@ import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuilder; import me.jellysquid.mods.sodium.client.render.chunk.data.BuiltSectionInfo; import me.jellysquid.mods.sodium.client.render.chunk.graph.VisibilityEncoding; -import me.jellysquid.mods.sodium.client.render.chunk.lists.ChunkRenderList; +import me.jellysquid.mods.sodium.client.render.chunk.lists.SectionRenderList; import me.jellysquid.mods.sodium.client.render.chunk.region.RenderRegionManager; import me.jellysquid.mods.sodium.client.render.chunk.tasks.ChunkRenderBuildTask; import me.jellysquid.mods.sodium.client.render.chunk.tasks.ChunkRenderEmptyBuildTask; @@ -24,7 +24,6 @@ import me.jellysquid.mods.sodium.client.world.cloned.ClonedChunkSectionCache; import me.jellysquid.mods.sodium.common.util.collections.WorkStealingFutureDrain; import me.jellysquid.mods.sodium.core.CoreLib; -import me.jellysquid.mods.sodium.core.GraphNode; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.Camera; import net.minecraft.client.world.ClientWorld; @@ -65,7 +64,7 @@ public class RenderSectionManager { private boolean alwaysDeferChunkUpdates; // TODO: do not allow this to be null - private @Nullable ChunkRenderList renderList = null; + private @Nullable SectionRenderList renderList = null; private final SodiumWorldRenderer worldRenderer; private final CoreLib.Graph pGraph; @@ -386,8 +385,7 @@ public void onChunkRenderUpdates(int x, int y, int z, RenderSection section, Bui if (section.region != null) { // TODO: terrible hack, we shouldn't even get here if this was unloaded already section.region.updateNode(section, data); - CoreLib.graphUpdateChunk(this.pGraph, x, y, z, - new GraphNode(VisibilityEncoding.encode(data.getOcclusionData()), data.getFlags())); + CoreLib.graphUpdateChunk(this.pGraph, x, y, z, VisibilityEncoding.extract(data.getOcclusionData()), data.getFlags()); } } @@ -455,7 +453,7 @@ public boolean isSectionReady(int x, int y, int z) { return false; } - public ChunkRenderList getRenderList() { + public SectionRenderList getRenderList() { return this.renderList; } } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/graph/VisibilityEncoding.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/graph/VisibilityEncoding.java index 4fad2b915d..3fbcfd2260 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/graph/VisibilityEncoding.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/graph/VisibilityEncoding.java @@ -1,41 +1,13 @@ package me.jellysquid.mods.sodium.client.render.chunk.graph; -import me.jellysquid.mods.sodium.common.util.DirectionUtil; import net.minecraft.client.render.chunk.ChunkOcclusionData; -import net.minecraft.util.math.Direction; public class VisibilityEncoding { - public static long encode(ChunkOcclusionData occlusionData) { - long visibilityData = 0; - - for (Direction from : DirectionUtil.ALL_DIRECTIONS) { - for (Direction to : DirectionUtil.ALL_DIRECTIONS) { - if (from == to) { - continue; - } - - if (occlusionData == null || occlusionData.isVisibleThrough(from, to)) { - visibilityData |= 1L << bit(GraphDirection.from(from), GraphDirection.from(to)); - } - } - } - - return visibilityData; - } - - private static int bit(GraphDirection from, GraphDirection to) { - return (from.ordinal() * 8) + to.ordinal(); - } - - // Returns a merged bit-field of the outgoing directions for each incoming direction - public static int getOutgoingDirections(long data, int incoming) { - long outgoing = 0L; - - for (int i = 0; i < 6; i++){ - outgoing |= ((incoming & (1 << i)) != 0 ? data : 0); - data >>= 8; + public static long extract(ChunkOcclusionData occlusionData) { + if (occlusionData == null) { + return 0; + } else { + return occlusionData.visibility.toLongArray()[0]; } - - return (int) (outgoing & 0b111111); } } \ No newline at end of file diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/lists/ChunkRenderList.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/lists/SectionRenderList.java similarity index 96% rename from src/main/java/me/jellysquid/mods/sodium/client/render/chunk/lists/ChunkRenderList.java rename to src/main/java/me/jellysquid/mods/sodium/client/render/chunk/lists/SectionRenderList.java index 2702b1e918..0332bbf39f 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/lists/ChunkRenderList.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/lists/SectionRenderList.java @@ -9,11 +9,11 @@ import java.util.function.Consumer; -public class ChunkRenderList { +public class SectionRenderList { private final int count; private final long addr; - public ChunkRenderList(CVec vec) { + public SectionRenderList(CVec vec) { this.count = vec.len(); this.addr = vec.data(); } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/util/frustum/FrustumAccessor.java b/src/main/java/me/jellysquid/mods/sodium/client/util/frustum/FrustumAccessor.java index 4020e99c4f..1e8daf3354 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/util/frustum/FrustumAccessor.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/util/frustum/FrustumAccessor.java @@ -2,6 +2,7 @@ import net.minecraft.client.render.Frustum; import org.joml.FrustumIntersection; +import org.joml.Vector3d; import org.joml.Vector3f; public interface FrustumAccessor { @@ -9,6 +10,6 @@ static FrustumAccessor of(Frustum frustum) { return (FrustumAccessor) frustum; } - Vector3f getTranslation(); + Vector3d getTranslation(); FrustumIntersection getFrustumIntersection(); } diff --git a/src/main/java/me/jellysquid/mods/sodium/core/CoreLib.java b/src/main/java/me/jellysquid/mods/sodium/core/CoreLib.java index d0c0411983..5cb7de0bb2 100644 --- a/src/main/java/me/jellysquid/mods/sodium/core/CoreLib.java +++ b/src/main/java/me/jellysquid/mods/sodium/core/CoreLib.java @@ -1,15 +1,14 @@ package me.jellysquid.mods.sodium.core; -import me.jellysquid.mods.sodium.client.render.chunk.graph.LocalSectionIndex; -import me.jellysquid.mods.sodium.client.render.chunk.lists.ChunkRenderList; +import me.jellysquid.mods.sodium.client.render.chunk.lists.SectionRenderList; import me.jellysquid.mods.sodium.core.callback.PanicCallback; -import me.jellysquid.mods.sodium.core.types.CRegionDrawBatch; import me.jellysquid.mods.sodium.core.types.CVec; import org.joml.FrustumIntersection; -import org.joml.Vector3f; +import org.joml.Vector3d; import org.joml.Vector4f; import org.lwjgl.PointerBuffer; -import org.lwjgl.system.*; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; import java.nio.FloatBuffer; @@ -18,16 +17,16 @@ public class CoreLib { private static final PanicCallback CALLBACK = PanicCallback.defaultHandler(); - public static Frustum createFrustum(FrustumIntersection frustum, Vector3f offset) { + public static Frustum createFrustum(FrustumIntersection frustum, Vector3d offset) { long pFrustum; try (MemoryStack stack = MemoryStack.stackPush()) { var ppFrustum = stack.mallocPointer(1); - var ppPoints = stack.mallocFloat(4 * 6); + var ppPoints = stack.mallocFloat(6 * 4); copyFrustumPoints(ppPoints, frustum); - var pOffset = stack.mallocFloat(3); + var pOffset = stack.mallocDouble(3); offset.get(pOffset); CoreLibFFI.frustumCreate(memAddress(ppFrustum), memAddress(ppPoints), memAddress(pOffset)); @@ -39,7 +38,7 @@ public static Frustum createFrustum(FrustumIntersection frustum, Vector3f offset public static void deleteFrustum(Frustum frustum) { CoreLibFFI.frustumDelete(frustum.ptr()); - frustum.invalidate();; + frustum.invalidate(); } private static void copyFrustumPoints(FloatBuffer buf, FrustumIntersection frustum) { @@ -50,11 +49,15 @@ private static void copyFrustumPoints(FloatBuffer buf, FrustumIntersection frust var planes = (Vector4f[]) field.get(frustum); for (int i = 0; i < 6; i++) { - buf.put((i * 4) + 0, planes[i].x); - buf.put((i * 4) + 1, planes[i].y); - buf.put((i * 4) + 2, planes[i].z); - buf.put((i * 4) + 3, planes[i].w); + buf.put((i * )planes[i].x); } + + planes[0].get(0, buf); + planes[1].get(16, buf); + planes[2].get(32, buf); + planes[3].get(48, buf); + planes[4].get(64, buf); + planes[5].get(80, buf); } catch (ReflectiveOperationException e) { throw new RuntimeException("Failed to extract planes from frustum", e); } @@ -78,31 +81,32 @@ public static void deleteGraph(Graph graph) { graph.invalidate(); } - public static void graphAddChunk(Graph graph, int x, int y, int z) { - CoreLibFFI.graphAddChunk(graph.ptr(), x, y, z); + public static void graphAddSection(Graph graph, int x, int y, int z) { + CoreLibFFI.graphAddSection(graph.ptr(), x, y, z); } - public static void graphUpdateChunk(Graph graph, int x, int y, int z, GraphNode node) { + public static void graphUpdateSection(Graph graph, int x, int y, int z, long connections, int flags) { try (MemoryStack stack = MemoryStack.stackPush()) { var pNode = stack.mallocInt(16); - memPutLong(memAddress(pNode) + 0, node.connections); - memPutInt(memAddress(pNode) + 8, node.flags); + memPutLong(memAddress(pNode) + 0, connections); + memPutInt(memAddress(pNode) + 8, flags); - CoreLibFFI.graphUpdateChunk(graph.ptr(), x, y, z, memAddress(pNode)); + CoreLibFFI.graphUpdateSection(graph.ptr(), x, y, z, memAddress(pNode)); } } - public static void graphRemoveChunk(Graph graph, int x, int y, int z) { - CoreLibFFI.graphRemoveChunk(graph.ptr(), x, y, z); + public static void graphRemoveSection(Graph graph, int x, int y, int z) { + CoreLibFFI.graphRemoveSection(graph.ptr(), x, y, z); } - public static ChunkRenderList graphSearch(Graph graph, Frustum frustum, int viewDistance) { + public static SectionRenderList graphSearch(Graph graph, Frustum frustum, int viewDistance) { try (MemoryStack stack = MemoryStack.stackPush()) { var drawBatches = CVec.stackAlloc(stack); + // TODO: deallocate the cvec at some point, because right now we never do... CoreLibFFI.graphSearch(graph.ptr(), frustum.ptr(), viewDistance, drawBatches.address()); - return new ChunkRenderList(drawBatches); + return new SectionRenderList(drawBatches); } } diff --git a/src/main/java/me/jellysquid/mods/sodium/core/CoreLibFFI.java b/src/main/java/me/jellysquid/mods/sodium/core/CoreLibFFI.java index 8f5b9b9a4c..beaafd1596 100644 --- a/src/main/java/me/jellysquid/mods/sodium/core/CoreLibFFI.java +++ b/src/main/java/me/jellysquid/mods/sodium/core/CoreLibFFI.java @@ -10,13 +10,14 @@ public class CoreLibFFI { static native void graphAddChunk(long pGraph, int x, int y, int z); static native void graphUpdateChunk(long pGraph, int x, int y, int z, long pNode); static native void graphRemoveChunk(long pGraph, int x, int y, int z); - static native void graphSearch(long pGraph, long pFrustum, int viewDistance, long pResults); - - static native void graphDelete(long pGraph); - - static native void frustumCreate(long ppFrustum, long ppPoints, long pOffset); - - static native void frustumDelete(long pFrustum); + static native void graphSearch( + long pGraph, + long ppFrustumPoints, + long pCameraPos, + int viewDistance, + float fogDistance, + long pResults + ); static { System.loadLibrary("sodium_core"); diff --git a/src/main/java/me/jellysquid/mods/sodium/mixin/core/MixinFrustum.java b/src/main/java/me/jellysquid/mods/sodium/mixin/core/MixinFrustum.java index 27b0c90616..f55d243d12 100644 --- a/src/main/java/me/jellysquid/mods/sodium/mixin/core/MixinFrustum.java +++ b/src/main/java/me/jellysquid/mods/sodium/mixin/core/MixinFrustum.java @@ -3,6 +3,7 @@ import me.jellysquid.mods.sodium.client.util.frustum.FrustumAccessor; import net.minecraft.client.render.Frustum; import org.joml.FrustumIntersection; +import org.joml.Vector3d; import org.joml.Vector3f; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -24,8 +25,8 @@ public abstract class MixinFrustum implements FrustumAccessor { private double z; @Override - public Vector3f getTranslation() { - return new Vector3f((float) this.x, (float) this.y, (float) this.z); + public Vector3d getTranslation() { + return new Vector3d(this.x, this.y, this.z); } @Override diff --git a/src/main/resources/sodium.accesswidener b/src/main/resources/sodium.accesswidener index 83ce7c75ab..4a9d485fa9 100644 --- a/src/main/resources/sodium.accesswidener +++ b/src/main/resources/sodium.accesswidener @@ -24,3 +24,5 @@ accessible method net/minecraft/client/util/math/MatrixStack$Entry (Lorg/ accessible field net/minecraft/client/color/world/GrassColors colorMap [I accessible field net/minecraft/client/color/world/FoliageColors colorMap [I accessible field net/minecraft/world/biome/Biome$Weather downfall F + +accessible field net/minecraft/client/render/chunk/ChunkOcclusionData visibility Ljava/util/BitSet;