Cave performance improvements

This commit is contained in:
Syniis 2024-03-02 23:31:42 +01:00
parent 3c846d4d17
commit 767731ecb0
5 changed files with 332 additions and 181 deletions

15
Cargo.lock generated
View File

@ -3166,8 +3166,22 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6acddbefae08bfba73e27f55513f491f35c365d84bf3002bf85ba9b916c5e5f" checksum = "c6acddbefae08bfba73e27f55513f491f35c365d84bf3002bf85ba9b916c5e5f"
dependencies = [ dependencies = [
"inline_tweak_derive",
"lazy_static", "lazy_static",
"proc-macro2 1.0.78",
"rustc-hash", "rustc-hash",
"syn 2.0.48",
]
[[package]]
name = "inline_tweak_derive"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46d62a0a3b6af04d4eee8e7251cd758ce74b0ed86253d3e4ac8a1b297a75f4a0"
dependencies = [
"proc-macro2 1.0.78",
"quote 1.0.35",
"syn 2.0.48",
] ]
[[package]] [[package]]
@ -7442,6 +7456,7 @@ dependencies = [
"hashbrown 0.13.2", "hashbrown 0.13.2",
"image", "image",
"indicatif", "indicatif",
"inline_tweak",
"itertools 0.10.5", "itertools 0.10.5",
"kiddo", "kiddo",
"lazy_static", "lazy_static",

View File

@ -40,7 +40,7 @@ packed_simd = { version = "0.3.9", optional = true }
rayon = { workspace = true } rayon = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
ron = { workspace = true } ron = { workspace = true }
# inline_tweak = { workspace = true, features = ["derive"] } inline_tweak = { workspace = true, features = ["derive"] }
kiddo = "0.2" kiddo = "0.2"
strum = { workspace = true } strum = { workspace = true }
@ -69,6 +69,10 @@ svg_fmt = "0.4"
harness = false harness = false
name = "tree" name = "tree"
[[bench]]
harness = false
name = "cave"
[[example]] [[example]]
name = "chunk_compression_benchmarks" name = "chunk_compression_benchmarks"
required-features = ["bin_compression"] required-features = ["bin_compression"]

67
world/benches/cave.rs Normal file
View File

@ -0,0 +1,67 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rayon::ThreadPoolBuilder;
use vek::Vec2;
use veloren_world::{
layer,
sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP},
Land, World,
};
fn cave(c: &mut Criterion) {
let pool = ThreadPoolBuilder::new().build().unwrap();
let (world, index) = World::generate(
123,
WorldOpts {
seed_elements: true,
world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()),
..WorldOpts::default()
},
&pool,
&|_| {},
);
let land = Land::from_sim(world.sim());
c.bench_function("generate", |b| {
b.iter(|| {
let entrances = black_box(layer::cave::surface_entrances(&land))
.step_by(5)
.map(|e| e / 32);
for entrance in entrances {
_ = black_box(world.generate_chunk(
index.as_index_ref(),
entrance,
None,
|| false,
None,
));
}
});
});
c.bench_function("generate_specific", |b| {
b.iter(|| {
let base_positions = vec![
Vec2::new(600, 650),
Vec2::new(630, 300),
Vec2::new(809, 141),
];
for base_pos in base_positions {
for i in 0..=4 {
for j in 0..=4 {
let pos = base_pos + Vec2::new(i as i32, j as i32) - 2;
_ = black_box(world.generate_chunk(
index.as_index_ref(),
pos,
None,
|| false,
None,
));
}
}
}
});
});
}
criterion_group!(benches, cave);
criterion_main!(benches);

View File

@ -174,6 +174,7 @@ impl Tunnel {
} }
} }
#[inline_tweak::tweak_fn]
fn biome_at(&self, wpos: Vec3<i32>, info: &CanvasInfo) -> Biome { fn biome_at(&self, wpos: Vec3<i32>, info: &CanvasInfo) -> Biome {
let Some(col) = info.col_or_gen(wpos.xy()) else { let Some(col) = info.col_or_gen(wpos.xy()) else {
return Biome::default(); return Biome::default();
@ -202,7 +203,7 @@ impl Tunnel {
.mul(2.0) .mul(2.0)
.sub(1.0) .sub(1.0)
.add( .add(
((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32 * 0.75)) ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32 * 0.6))
.clamped(0.0, 2.5), .clamped(0.0, 2.5),
), ),
below, below,
@ -233,38 +234,38 @@ impl Tunnel {
// Mushrooms grow underground and thrive in a humid environment with moderate // Mushrooms grow underground and thrive in a humid environment with moderate
// temperatures // temperatures
let mushroom = underground let mushroom = underground
* close(humidity, 1.0, 0.7, 3) * close(humidity, 1.0, 0.7, 4)
* close(temp, 1.5, 0.9, 3) * close(temp, 1.5, 0.9, 4)
* close(depth, 1.0, 0.6, 3); * close(depth, 1.0, 0.6, 4);
// Extremely hot and dry areas deep underground // Extremely hot and dry areas deep underground
let fire = underground let fire = underground
* close(humidity, 0.0, 0.6, 3) * close(humidity, 0.0, 0.6, 4)
* close(temp, 2.0, 1.3, 3) * close(temp, 2.0, 1.3, 4)
* close(depth, 1.0, 0.55, 3); * close(depth, 1.0, 0.55, 4);
// Overgrown with plants that need a moderate climate to survive // Overgrown with plants that need a moderate climate to survive
let leafy = underground let leafy = underground
* close(humidity, 0.8, 0.8, 3) * close(humidity, 0.8, 0.8, 4)
* close(temp, 0.95, 0.85, 3) * close(temp, 0.75, 1.25, 4)
* close(depth, 0.0, 0.6, 3); * close(depth, 0.0, 0.75, 4);
// Cool temperature, dry and devoid of value // Cool temperature, dry and devoid of value
let dusty = close(humidity, 0.0, 0.5, 3) * close(temp, -0.1, 0.6, 3); let dusty = close(humidity, 0.0, 0.5, 4) * close(temp, -0.1, 0.6, 4);
// Deep underground and freezing cold // Deep underground and freezing cold
let icy = underground let icy = underground
* close(temp, -1.5, 1.3, 3) * close(temp, -1.5, 1.3, 4)
* close(depth, 1.0, 0.65, 3) * close(depth, 1.0, 0.6, 4)
* close(humidity, 1.0, 0.7, 3); * close(humidity, 1.0, 0.7, 4);
// Rocky cold cave that appear near the surface // Rocky cold cave that appear near the surface
let snowy = close(temp, -0.6, 0.5, 3) * close(depth, 0.0, 0.45, 3); let snowy = close(temp, -0.6, 0.5, 4) * close(depth, 0.0, 0.45, 4);
// Crystals grow deep underground in areas rich with minerals. They are present // Crystals grow deep underground in areas rich with minerals. They are present
// in areas with colder temperatures and low humidity // in areas with colder temperatures and low humidity
let crystal = underground let crystal = underground
* close(humidity, 0.0, 0.5, 3) * close(humidity, 0.0, 0.5, 4)
* close(temp, -0.6, 0.75, 3) * close(temp, -0.6, 0.75, 4)
* close(depth, 1.0, 0.55, 3) * close(depth, 1.0, 0.55, 4)
* close(mineral, 2.0, 1.25, 3); * close(mineral, 2.0, 1.25, 4);
// Hot, dry and shallow // Hot, dry and shallow
let sandy = let sandy =
close(humidity, 0.0, 0.3, 3) * close(temp, 0.7, 0.9, 3) * close(depth, 0.0, 0.6, 3); close(humidity, 0.0, 0.3, 4) * close(temp, 0.7, 0.9, 4) * close(depth, 0.0, 0.6, 4);
let biomes = [ let biomes = [
barren, mushroom, fire, leafy, dusty, icy, snowy, crystal, sandy, barren, mushroom, fire, leafy, dusty, icy, snowy, crystal, sandy,
@ -434,6 +435,7 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
}) })
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !tunnels.is_empty() { if !tunnels.is_empty() {
let giant_tree_dist = info let giant_tree_dist = info
.chunk .chunk
@ -497,6 +499,7 @@ struct Biome {
depth: f32, depth: f32,
} }
#[derive(Clone)]
enum CaveStructure { enum CaveStructure {
Mushroom(Mushroom), Mushroom(Mushroom),
Crystal(CrystalCluster), Crystal(CrystalCluster),
@ -508,24 +511,28 @@ enum CaveStructure {
}, },
} }
#[derive(Clone)]
struct Mushroom { struct Mushroom {
pos: Vec3<i32>, pos: Vec3<i32>,
stalk: f32, stalk: f32,
head_color: Rgb<u8>, head_color: Rgb<u8>,
} }
#[derive(Clone)]
struct Crystal { struct Crystal {
dir: Vec3<f32>, dir: Vec3<f32>,
length: f32, length: f32,
radius: f32, radius: f32,
} }
#[derive(Clone)]
struct CrystalCluster { struct CrystalCluster {
pos: Vec3<i32>, pos: Vec3<i32>,
crystals: Vec<Crystal>, crystals: Vec<Crystal>,
color: Rgb<u8>, color: Rgb<u8>,
} }
#[derive(Clone)]
struct Flower { struct Flower {
pos: Vec3<i32>, pos: Vec3<i32>,
stalk: f32, stalk: f32,
@ -535,6 +542,7 @@ struct Flower {
// rotation: Mat3<f32>, // rotation: Mat3<f32>,
} }
#[inline_tweak::tweak_fn]
fn write_column<R: Rng>( fn write_column<R: Rng>(
canvas: &mut Canvas, canvas: &mut Canvas,
col: &ColumnSample, col: &ColumnSample,
@ -543,7 +551,7 @@ fn write_column<R: Rng>(
z_range: Range<i32>, z_range: Range<i32>,
tunnel: Tunnel, tunnel: Tunnel,
dimensions: (f32, f32, f32), dimensions: (f32, f32, f32),
giant_tree_factor: f32, giant_tree_dist: f32,
structure_cache: &mut SmallCache<Vec3<i32>, Option<CaveStructure>>, structure_cache: &mut SmallCache<Vec3<i32>, Option<CaveStructure>>,
rng: &mut R, rng: &mut R,
) { ) {
@ -741,22 +749,21 @@ fn write_column<R: Rng>(
0 0
}; };
let mut get_structure = |wpos: Vec3<i32>, dynamic_rng: &mut R| { let structures = StructureGen2d::new(34537, 24, 8)
for (wpos2d, seed) in StructureGen2d::new(34537, 24, 8).get(wpos.xy()) { .get(wpos2d)
let structure = if let Some(structure) = .as_slice()
structure_cache.get(wpos2d.with_z(tunnel.a.depth), |_| { .iter()
let mut rng = RandomPerm::new(seed); .filter_map(|(wpos2d, seed)| {
let structure = structure_cache.get(wpos2d.with_z(tunnel.a.depth), |_| {
let mut rng = RandomPerm::new(*seed);
let (z_range, horizontal, vertical, _) = let (z_range, horizontal, vertical, _) =
tunnel.z_range_at(wpos2d.map(|e| e as f64 + 0.5), info)?; tunnel.z_range_at(wpos2d.map(|e| e as f64 + 0.5), info)?;
let pos = wpos2d.with_z(z_range.start); let pos = wpos2d.with_z(z_range.start);
let biome = tunnel.biome_at(pos, &info); let biome = tunnel.biome_at(pos, &info);
let ground_below = !tunnel_bounds_at(pos.xy(), &info, &info.land()) let tunnel_intersection = || {
.any(|(_, z_range, _, _, _, _)| z_range.contains(&(z_range.start - 1))); tunnel_bounds_at(pos.xy(), &info, &info.land())
if !ground_below { .any(|(_, z_range, _, _, _, _)| z_range.contains(&(z_range.start - 1)))
return None; };
}
if biome.mushroom > 0.7 if biome.mushroom > 0.7
&& vertical > 16.0 && vertical > 16.0
&& rng.gen_bool( && rng.gen_bool(
@ -764,6 +771,9 @@ fn write_column<R: Rng>(
* close(biome.mushroom, 1.0, 0.7, 1) as f64, * close(biome.mushroom, 1.0, 0.7, 1) as f64,
) )
{ {
if tunnel_intersection() {
return None;
}
let purp = rng.gen_range(0..50); let purp = rng.gen_range(0..50);
Some(CaveStructure::Mushroom(Mushroom { Some(CaveStructure::Mushroom(Mushroom {
pos, pos,
@ -780,6 +790,9 @@ fn write_column<R: Rng>(
} else if biome.crystal > 0.5 } else if biome.crystal > 0.5
&& rng.gen_bool(0.4 * close(biome.crystal, 1.0, 0.7, 2) as f64) && rng.gen_bool(0.4 * close(biome.crystal, 1.0, 0.7, 2) as f64)
{ {
if tunnel_intersection() {
return None;
}
let on_ground = rng.gen_bool(0.6); let on_ground = rng.gen_bool(0.6);
let pos = wpos2d.with_z(if on_ground { let pos = wpos2d.with_z(if on_ground {
z_range.start z_range.start
@ -788,8 +801,7 @@ fn write_column<R: Rng>(
}); });
let mut crystals: Vec<Crystal> = Vec::new(); let mut crystals: Vec<Crystal> = Vec::new();
let max_length = let max_length = (48.0 * close(vertical, MAX_RADIUS, MAX_RADIUS, 1)).max(12.0);
(48.0 * close(vertical, MAX_RADIUS, MAX_RADIUS, 1)).max(12.0);
let length = rng.gen_range(8.0..max_length); let length = rng.gen_range(8.0..max_length);
let radius = let radius =
Lerp::lerp(2.0, 4.5, length / max_length + rng.gen_range(-0.1..0.1)); Lerp::lerp(2.0, 4.5, length / max_length + rng.gen_range(-0.1..0.1));
@ -839,6 +851,9 @@ fn write_column<R: Rng>(
* biome.leafy) as f64, * biome.leafy) as f64,
) )
{ {
if tunnel_intersection() {
return None;
}
let petal_radius = rng.gen_range(8.0..16.0); let petal_radius = rng.gen_range(8.0..16.0);
Some(CaveStructure::Flower(Flower { Some(CaveStructure::Flower(Flower {
pos, pos,
@ -850,13 +865,15 @@ fn write_column<R: Rng>(
petal_height: 0.4 * petal_radius * (1.0 + rng.gen::<f32>().powf(2.0)), petal_height: 0.4 * petal_radius * (1.0 + rng.gen::<f32>().powf(2.0)),
petal_radius, petal_radius,
})) }))
} else if (biome.leafy > 0.7 || giant_tree_factor > 0.0) } else if (biome.leafy > 0.7 || giant_tree_dist > 0.0)
&& rng.gen_bool( && rng.gen_bool(
(0.5 * close(biome.leafy, 1.0, 0.5, 1).max(1.0 + giant_tree_factor) (0.5 * close(biome.leafy, 1.0, 0.5, 1).max(1.0 + giant_tree_dist) as f64)
as f64)
.clamped(0.0, 1.0), .clamped(0.0, 1.0),
) )
{ {
if tunnel_intersection() {
return None;
}
Some(CaveStructure::GiantRoot { Some(CaveStructure::GiantRoot {
pos, pos,
radius: rng.gen_range( radius: rng.gen_range(
@ -869,26 +886,36 @@ fn write_column<R: Rng>(
} else { } else {
None None
} }
}) { });
structure
} else {
continue;
};
structure
.as_ref()
.map(|structure| (*seed, structure.clone()))
})
.collect_vec();
let get_structure = |wpos: Vec3<i32>, dynamic_rng: &mut R| {
let warp = |wposf: Vec3<f64>, freq: f64, amp: Vec3<f32>, seed: u32| -> Option<Vec3<f32>> {
let xy = wposf.xy();
let xz = Vec2::new(wposf.x, wposf.z);
let yz = Vec2::new(wposf.y, wposf.z);
Some(
Vec3::new(
FastNoise2d::new(seed).get(yz * freq),
FastNoise2d::new(seed).get(xz * freq),
FastNoise2d::new(seed).get(xy * freq),
) * amp,
)
};
for (seed, structure) in structures.iter() {
let seed = *seed;
match structure { match structure {
CaveStructure::Mushroom(mushroom) => { CaveStructure::Mushroom(mushroom) => {
let wposf = wpos.map(|e| e as f64); let wposf = wpos.map(|e| e as f64);
let warp_freq = 1.0 / 32.0; let warp_freq = 1.0 / 32.0;
let warp_amp = Vec3::new(12.0, 12.0, 12.0); let warp_amp = Vec3::new(12.0, 12.0, 12.0);
let xy = wposf.xy(); let warp_offset = warp(wposf, warp_freq, warp_amp, seed)?;
let xz = Vec2::new(wposf.x, wposf.z);
let yz = Vec2::new(wposf.y, wposf.z);
let wposf_warped = wposf.map(|e| e as f32) let wposf_warped = wposf.map(|e| e as f32)
+ Vec3::new( + warp_offset
FastNoise2d::new(seed).get(yz * warp_freq),
FastNoise2d::new(seed).get(xz * warp_freq),
FastNoise2d::new(seed).get(xy * warp_freq),
) * warp_amp
* (wposf.z as f32 - mushroom.pos.z as f32) * (wposf.z as f32 - mushroom.pos.z as f32)
.mul(0.1) .mul(0.1)
.clamped(0.0, 1.0); .clamped(0.0, 1.0);
@ -1011,15 +1038,9 @@ fn write_column<R: Rng>(
let wposf = wpos.map(|e| e as f64); let wposf = wpos.map(|e| e as f64);
let warp_freq = 1.0 / 16.0; let warp_freq = 1.0 / 16.0;
let warp_amp = Vec3::new(8.0, 8.0, 8.0); let warp_amp = Vec3::new(8.0, 8.0, 8.0);
let xy = wposf.xy(); let warp_offset = warp(wposf, warp_freq, warp_amp, seed)?;
let xz = Vec2::new(wposf.x, wposf.z);
let yz = Vec2::new(wposf.y, wposf.z);
let wposf_warped = wposf.map(|e| e as f32) let wposf_warped = wposf.map(|e| e as f32)
+ Vec3::new( + warp_offset
FastNoise2d::new(seed).get(yz * warp_freq),
FastNoise2d::new(seed).get(xz * warp_freq),
FastNoise2d::new(seed).get(xy * warp_freq),
) * warp_amp
* (wposf.z as f32 - flower.pos.z as f32) * (wposf.z as f32 - flower.pos.z as f32)
.mul(1.0 / flower.stalk) .mul(1.0 / flower.stalk)
.sub(1.0) .sub(1.0)
@ -1044,8 +1065,7 @@ fn write_column<R: Rng>(
let dist_sq = rpos.xy().magnitude_squared(); let dist_sq = rpos.xy().magnitude_squared();
let petal_radius_sq = flower.petal_radius.powi(2); let petal_radius_sq = flower.petal_radius.powi(2);
if dist_sq < petal_radius_sq { if dist_sq < petal_radius_sq {
let petal_height_at = let petal_height_at = (dist_sq / petal_radius_sq) * flower.petal_height;
(dist_sq / petal_radius_sq).powf(1.0) * flower.petal_height;
if rpos.z > petal_height_at - 1.0 if rpos.z > petal_height_at - 1.0
&& rpos.z <= petal_height_at + petal_thickness && rpos.z <= petal_height_at + petal_thickness
{ {
@ -1108,16 +1128,9 @@ fn write_column<R: Rng>(
} => { } => {
let wposf = wpos.map(|e| e as f64); let wposf = wpos.map(|e| e as f64);
let warp_freq = 1.0 / 32.0; let warp_freq = 1.0 / 32.0;
let warp_amp = Vec3::new(20.0, 20.0, 20.0); let warp_amp = Vec3::new(12.0, 12.0, 12.0);
let xy = wposf.xy(); let warp_offset = warp(wposf, warp_freq, warp_amp, seed)?;
let xz = Vec2::new(wposf.x, wposf.z); let wposf_warped = wposf.map(|e| e as f32) + warp_offset;
let yz = Vec2::new(wposf.y, wposf.z);
let wposf_warped = wposf.map(|e| e as f32)
+ Vec3::new(
FastNoise2d::new(seed).get(yz * warp_freq),
FastNoise2d::new(seed).get(xz * warp_freq),
FastNoise2d::new(seed).get(xy * warp_freq),
) * warp_amp;
let rpos = wposf_warped - pos.map(|e| e as f32); let rpos = wposf_warped - pos.map(|e| e as f32);
let dist_sq = rpos.xy().magnitude_squared(); let dist_sq = rpos.xy().magnitude_squared();
if dist_sq < radius.powi(2) { if dist_sq < radius.powi(2) {
@ -1142,7 +1155,7 @@ fn write_column<R: Rng>(
for z in bedrock..z_range.end { for z in bedrock..z_range.end {
let wpos = wpos2d.with_z(z); let wpos = wpos2d.with_z(z);
let mut try_spawn_entity = false; let mut try_spawn_entity = false;
canvas.map_resource(wpos, |_block| { canvas.set(wpos, {
if z < z_range.start - 4 && !void_below { if z < z_range.start - 4 && !void_below {
Block::new(BlockKind::Lava, Rgb::new(255, 65, 0)) Block::new(BlockKind::Lava, Rgb::new(255, 65, 0))
} else if basalt > 0.0 } else if basalt > 0.0

View File

@ -111,6 +111,58 @@ impl StructureGen2d {
) )
}) })
} }
/// Note: Generates all possible closest samples for elements in the range
/// of min to max, *exclusive.*
pub fn iter_with_wpos(
&self,
min: Vec2<i32>,
max: Vec2<i32>,
) -> impl Iterator<Item = (Vec2<i32>, StructureField)> {
let freq = self.freq;
let spread = self.spread;
let spread_mul = Self::spread_mul(spread);
assert!(spread * 2 == spread_mul);
assert!(spread_mul <= freq);
let spread = spread as i32;
let freq = freq as i32;
let freq_offset = Self::freq_offset(freq);
assert!(freq_offset * 2 == freq);
let min_index = Self::sample_to_index_internal(freq, min) - 1;
let max_index = Self::sample_to_index_internal(freq, max) + 1;
assert!(min_index.x < max_index.x);
// NOTE: xlen > 0
let xlen = (max_index.x - min_index.x) as u32;
assert!(min_index.y < max_index.y);
// NOTE: ylen > 0
let ylen = (max_index.y - min_index.y) as u32;
// NOTE: Cannot fail, since every product of u32s fits in a u64.
let len = ylen as u64 * xlen as u64;
// NOTE: since iteration is *exclusive* for the initial range, it's fine that we
// don't go up to the maximum value.
// NOTE: we convert to usize first, and then iterate, because we want to make
// sure we get a properly indexed parallel iterator that can deal with
// the whole range at once.
let x_field = self.x_field;
let y_field = self.y_field;
let seed_field = self.seed_field;
(0..len).map(move |xy| {
let index = min_index + Vec2::new((xy % xlen as u64) as i32, (xy / xlen as u64) as i32);
let index_wpos = min + Vec2::new((xy % xlen as u64) as i32, (xy / len as u64) as i32);
let field = Self::index_to_sample_internal(
freq,
freq_offset,
spread,
spread_mul,
x_field,
y_field,
seed_field,
index,
);
(index_wpos, field)
})
}
} }
impl Sampler<'static> for StructureGen2d { impl Sampler<'static> for StructureGen2d {