mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #11281 from ilguerro/perf/mesh-row-masks
Reduce repeated work in section meshing
This commit is contained in:
5 files changed
+136
-30
No files matched your search
@@ -108,6 +108,16 @@ impl<T> CompiledChoices<T> {
|
||||
multipart: self.multipart.iter().map(|c| c.map(&mut f)).collect(),
|
||||
}
|
||||
}
|
||||
/// A variant that needs no random selection and produces exactly one model.
|
||||
pub fn single_variant(&self, selected: &SelectedModels) -> Option<&T> {
|
||||
let SelectedModels::Variant(index) = selected else {
|
||||
return None;
|
||||
};
|
||||
match self.variants.get(*index)? {
|
||||
Choice::Single(value) => Some(value),
|
||||
Choice::Weighted { .. } => None,
|
||||
}
|
||||
}
|
||||
/// The variant `particleMaterial` comes from: the first entry of a weighted
|
||||
/// list (`WeightedVariants`) and, for a multipart root, of its first
|
||||
/// selector whether or not that selector applies (`MultiPartModel`).
|
||||
|
||||
@@ -401,6 +401,7 @@ fn visibility(pad: &Padded, info: &[Info]) -> u64 {
|
||||
}
|
||||
/// The section's cells that are not `isSolidRender`, by row (`y << 4 | z`, bit
|
||||
/// x), from the padded rows of `MeshScratch::solid_rows`.
|
||||
#[cfg(test)]
|
||||
fn open_rows(solid: &[u32]) -> [u16; 256] {
|
||||
std::array::from_fn(|r| {
|
||||
let (y, z) = ((r >> 4) as i32, (r & 15) as i32);
|
||||
@@ -977,10 +978,11 @@ impl Padded {
|
||||
// The section itself is unpacked in one pass and copied row by row;
|
||||
// the border cells of the neighbouring sections are read in runs, from
|
||||
// sections found once per layer.
|
||||
own.fill(0);
|
||||
let center = region[1][1];
|
||||
if let Some(c) = center {
|
||||
c.sections[sy as usize].unpack(own);
|
||||
} else {
|
||||
own.fill(0);
|
||||
}
|
||||
for y in -B..16 + B {
|
||||
let wy = base_y + y;
|
||||
@@ -1065,6 +1067,18 @@ fn fill_run(
|
||||
},
|
||||
}
|
||||
}
|
||||
// Direction order is the same as Direction::ALL; all values are evaluated at compile time.
|
||||
const DIRECTIONS: [(Direction, [i32; 3], u8); 6] = [
|
||||
direction_info(Direction::Down),
|
||||
direction_info(Direction::Up),
|
||||
direction_info(Direction::North),
|
||||
direction_info(Direction::South),
|
||||
direction_info(Direction::West),
|
||||
direction_info(Direction::East),
|
||||
];
|
||||
const fn direction_info(d: Direction) -> (Direction, [i32; 3], u8) {
|
||||
(d, d.normal(), d.opposite() as u8)
|
||||
}
|
||||
/// Per-worker temporary arrays. They never hold resource-pack references or
|
||||
/// geometry still owned by the renderer, and total about 150 KiB per worker
|
||||
/// (the leaf mask adds 8,000 cells, depths and a queue).
|
||||
@@ -1080,21 +1094,63 @@ pub struct MeshScratch {
|
||||
/// Fills `rows` with the padded section's `isSolidRender` blocks, one bit each (`MeshScratch::solid_rows`).
|
||||
fn solid_rows(pad: &Padded, solid: &[bool], rows: &mut Vec<u32>) {
|
||||
rows.clear();
|
||||
rows.extend(pad.blocks.as_chunks::<P>().0.iter().map(|row| {
|
||||
row.iter().enumerate().fold(0u32, |bits, (x, &id)| {
|
||||
bits | u32::from(solid[id as usize]) << x
|
||||
})
|
||||
}));
|
||||
for row in pad.blocks.as_chunks::<P>().0 {
|
||||
let mut bits = 0;
|
||||
for (x, &id) in row.iter().enumerate() {
|
||||
bits |= u32::from(solid[id as usize]) << x;
|
||||
}
|
||||
rows.push(bits);
|
||||
}
|
||||
}
|
||||
/// Whether the block at `(x, y, z)` (section-local) and its six neighbours
|
||||
/// are all `isSolidRender`: none of its faces can show.
|
||||
#[inline]
|
||||
fn hidden(rows: &[u32], x: i32, y: i32, z: i32) -> bool {
|
||||
let row = |y: i32, z: i32| rows[((y + B) as usize) * P + (z + B) as usize];
|
||||
let r = row(y, z);
|
||||
let around =
|
||||
r & r << 1 & r >> 1 & row(y - 1, z) & row(y + 1, z) & row(y, z - 1) & row(y, z + 1);
|
||||
around >> (x + B) & 1 != 0
|
||||
/// Open and fully surrounded cells in each inner row.
|
||||
fn row_masks(rows: &[u32]) -> ([u16; 256], [u16; 256]) {
|
||||
let mut open = [0; 256];
|
||||
let mut hidden = [0; 256];
|
||||
for y in 0..16 {
|
||||
for z in 0..16 {
|
||||
let i = (y + B as usize) * P + z + B as usize;
|
||||
let r = rows[i];
|
||||
let around =
|
||||
r & r << 1 & r >> 1 & rows[i - P] & rows[i + P] & rows[i - 1] & rows[i + 1];
|
||||
open[y * 16 + z] = !(r >> B) as u16;
|
||||
hidden[y * 16 + z] = (around >> B) as u16;
|
||||
}
|
||||
}
|
||||
(open, hidden)
|
||||
}
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn row_masks_match_cell_definition() {
|
||||
let mut seed = 42u32;
|
||||
for _ in 0..100 {
|
||||
let rows: Vec<u32> = (0..P * P)
|
||||
.map(|_| {
|
||||
seed ^= seed << 13;
|
||||
seed ^= seed >> 17;
|
||||
seed ^= seed << 5;
|
||||
seed & ((1 << P) - 1)
|
||||
})
|
||||
.collect();
|
||||
let (open, hidden) = row_masks(&rows);
|
||||
for y in 0..16 {
|
||||
for z in 0..16 {
|
||||
let cell = |x: usize, y: usize, z: usize| (rows[y * P + z] >> x) & 1 != 0;
|
||||
for x in 0..16 {
|
||||
let (x, py, pz) = (x + B as usize, y + B as usize, z + B as usize);
|
||||
let occupied = cell(x, py, pz);
|
||||
let enclosed = occupied
|
||||
&& cell(x - 1, py, pz)
|
||||
&& cell(x + 1, py, pz)
|
||||
&& cell(x, py - 1, pz)
|
||||
&& cell(x, py + 1, pz)
|
||||
&& cell(x, py, pz - 1)
|
||||
&& cell(x, py, pz + 1);
|
||||
assert_eq!((open[y * 16 + z] >> (x - B as usize)) & 1 != 0, !occupied);
|
||||
assert_eq!((hidden[y * 16 + z] >> (x - B as usize)) & 1 != 0, enclosed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod leaf_culling_tests;
|
||||
@@ -1463,7 +1519,8 @@ impl Mesher<'_> {
|
||||
let pad = &scratch.padded;
|
||||
solid_rows(pad, &self.tables.solid, &mut scratch.solid_rows);
|
||||
let opaque = &scratch.solid_rows;
|
||||
out.visibility = visibility_rows(&open_rows(opaque));
|
||||
let (open, hidden) = row_masks(opaque);
|
||||
out.visibility = visibility_rows(&open);
|
||||
let base_y = center.min_y() + sy * 16;
|
||||
if self.leaf_culling.is_interior() {
|
||||
scratch
|
||||
@@ -1497,7 +1554,7 @@ impl Mesher<'_> {
|
||||
out.lights
|
||||
.push(([center.x * 16 + x, base_y + y, center.z * 16 + z], id));
|
||||
}
|
||||
if hidden(opaque, x, y, z) {
|
||||
if hidden[(y * 16 + z) as usize] >> x & 1 != 0 {
|
||||
continue;
|
||||
}
|
||||
if state.fluid.kind() != FluidKind::Empty {
|
||||
@@ -1538,12 +1595,11 @@ impl Mesher<'_> {
|
||||
let interior_leaf = state.leaves
|
||||
&& self.leaf_culling.is_interior()
|
||||
&& scratch.leaf.interior(self.leaf_culling, x, y, z);
|
||||
for d in Direction::ALL {
|
||||
let [dx, dy, dz] = d.normal();
|
||||
for (d, [dx, dy, dz], opposite) in DIRECTIONS {
|
||||
let neighbor = pad.blocks[pi(x + dx, y + dy, z + dz)];
|
||||
// `should_render_face`'s first test, without its lookups: a
|
||||
// full occlusion face toward this block hides the face.
|
||||
if full_faces[neighbor as usize] >> d.opposite() as u8 & 1 != 0 {
|
||||
if full_faces[neighbor as usize] >> opposite & 1 != 0 {
|
||||
continue;
|
||||
}
|
||||
let other = &info[neighbor as usize];
|
||||
|
||||
@@ -229,7 +229,7 @@ pub(super) fn tesselate(
|
||||
let kind = me.fluid.kind();
|
||||
let same = |s: &Info| s.fluid.kind().same_family(kind);
|
||||
let neighbor = |d: Direction| {
|
||||
let [dx, dy, dz] = d.normal();
|
||||
let [dx, dy, dz] = super::DIRECTIONS[d as usize].1;
|
||||
at([x + dx, y + dy, z + dz])
|
||||
};
|
||||
let (down, up) = (neighbor(Direction::Down), neighbor(Direction::Up));
|
||||
@@ -238,12 +238,11 @@ pub(super) fn tesselate(
|
||||
let render_down =
|
||||
should_render(Direction::Down) && !occluded_by(Direction::Down, MAX_HEIGHT, down);
|
||||
let sides = [
|
||||
Direction::North,
|
||||
Direction::South,
|
||||
Direction::West,
|
||||
Direction::East,
|
||||
]
|
||||
.map(should_render);
|
||||
should_render(Direction::North),
|
||||
should_render(Direction::South),
|
||||
should_render(Direction::West),
|
||||
should_render(Direction::East),
|
||||
];
|
||||
if !render_up && !render_down && !sides.iter().any(|&s| s) {
|
||||
return;
|
||||
}
|
||||
@@ -356,9 +355,9 @@ pub(super) fn tesselate(
|
||||
fluid_face(color, face)
|
||||
};
|
||||
let vertex = |(pos, uv)| TerrainVertex::new(pos, uv, layer, color, light);
|
||||
target.extend(v.map(vertex));
|
||||
target.extend([vertex(v[0]), vertex(v[1]), vertex(v[2]), vertex(v[3])]);
|
||||
if back {
|
||||
target.extend([v[0], v[3], v[2], v[1]].map(vertex));
|
||||
target.extend([vertex(v[0]), vertex(v[3]), vertex(v[2]), vertex(v[1])]);
|
||||
}
|
||||
};
|
||||
let (fx, fy, fz) = (x as f32, y as f32, z as f32);
|
||||
|
||||
@@ -56,6 +56,7 @@ pub struct StateModels {
|
||||
bounds: OnceLock<Vec<Option<[[f32; 3]; 2]>>>,
|
||||
/// By state id: its definition in `choices` and the models it selects.
|
||||
states: Vec<Option<(usize, SelectedModels)>>,
|
||||
single_models: Vec<Option<u32>>,
|
||||
/// Every transformed model once, with the variant it was baked from.
|
||||
baked: Vec<(Variant, BakedModel)>,
|
||||
/// Standalone models by id: their index in `baked`.
|
||||
@@ -357,11 +358,19 @@ impl StateModels {
|
||||
baked.push((variant, model));
|
||||
}
|
||||
// Every variant of every definition was baked above.
|
||||
let choices = choices.iter().map(|c| c.map(|v| index[&key(v)])).collect();
|
||||
let choices: Vec<_> = choices.iter().map(|c| c.map(|v| index[&key(v)])).collect();
|
||||
let single_models = states
|
||||
.iter()
|
||||
.map(|state| {
|
||||
let (choice, selected) = state.as_ref()?;
|
||||
choices[*choice].single_variant(selected).copied()
|
||||
})
|
||||
.collect();
|
||||
Ok(Self {
|
||||
choices,
|
||||
bounds: OnceLock::new(),
|
||||
states,
|
||||
single_models,
|
||||
baked,
|
||||
standalone: BTreeMap::new(),
|
||||
})
|
||||
@@ -488,6 +497,10 @@ impl StateModels {
|
||||
out: &mut Vec<&'a BakedModel>,
|
||||
) -> Result<()> {
|
||||
out.clear();
|
||||
if let Some(Some(model)) = self.single_models.get(state.0 as usize) {
|
||||
out.push(&self.baked[*model as usize].1);
|
||||
return Ok(());
|
||||
}
|
||||
self.each_seeded(state, position_seed(position), |(_, model)| out.push(model))
|
||||
}
|
||||
fn each_seeded<'a>(
|
||||
|
||||
@@ -277,6 +277,29 @@ impl PalettedContainer {
|
||||
/// `get` of the entries `start..start + out.len()`, the palette matched once.
|
||||
#[inline]
|
||||
pub fn get_run(&self, start: usize, out: &mut [u32]) {
|
||||
if out.len() == 2 && !matches!(self.palette, Palette::Single(_)) {
|
||||
let bits = usize::from(self.bits);
|
||||
let per = 64 / bits;
|
||||
let slot = start % per;
|
||||
if slot + 1 < per {
|
||||
let word = self.words[start / per] >> (slot * bits);
|
||||
let mask = mask(self.bits);
|
||||
let a = (word & mask) as usize;
|
||||
let b = ((word >> bits) & mask) as usize;
|
||||
match &self.palette {
|
||||
Palette::List(list) => {
|
||||
out[0] = list[a];
|
||||
out[1] = list[b];
|
||||
}
|
||||
Palette::Global => {
|
||||
out[0] = a as u32;
|
||||
out[1] = b as u32;
|
||||
}
|
||||
Palette::Single(_) => unreachable!(),
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
match &self.palette {
|
||||
Palette::Single(value) => out.fill(*value),
|
||||
Palette::List(list) => {
|
||||
@@ -776,6 +799,11 @@ mod tests {
|
||||
.map(|_| (rng(&mut state) % distinct) as u32)
|
||||
.collect();
|
||||
let container = PalettedContainer::from_values(BLOCKS, &values);
|
||||
for start in 0..4095 {
|
||||
let mut pair = [u32::MAX; 2];
|
||||
container.get_run(start, &mut pair);
|
||||
assert_eq!(pair, values[start..start + 2]);
|
||||
}
|
||||
for _ in 0..200 {
|
||||
let start = (rng(&mut state) % 4096) as usize;
|
||||
let len = (rng(&mut state) % 20) as usize;
|
||||
|
||||
Reference in new issue
Block a user