Compare commits

...
Author SHA1 Message Date
Ludovic MarieandClaude Opus 5.5 458df373cc Renderer: a section's instance once a frame, without lookups
Each frame computed every visible section's instance (origin, fade code, chunk fade-in) for
the instance buffer and again for flat draws, each time looking its key up in
section_uploaded and lod_retiring and doing the fade-in's time arithmetic, though almost
every section finished fading long ago.

- A section keeps its section_uploaded entry (copied at upload, dropped with the entry),
  so the frame reads it instead of looking it up.
- A fade-in that began before now - its length is over: found by comparing instants.
- Flat draws take the instances made for the camera's sections; only the shadow camera's
  are made again (the same way as before).
- Flat draws' origins by section id are no longer cleared and refilled up to the highest id
  each frame: the ids no draw uses keep old values, which nothing reads.

The values written are the same as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-01 19:59:19 +01:00
3 changed files with 71 additions and 36 deletions

No files matched your search

+62 -30
View File
@@ -355,6 +355,9 @@ struct SectionGpu {
/// When it began to fade out, its column gone over far terrain that
/// stays behind it (`Renderer::leave_section`); released once it has.
leaving: Option<std::time::Instant>,
/// Its entry in `Renderer::section_uploaded` (the start of the vanilla chunk fade-in),
/// kept here so a frame finds it without a lookup; none once that entry is gone.
uploaded: Option<std::time::Instant>,
/// Animated sprite layers present in this section.
animated_layers: Box<[u16]>,
}
@@ -374,17 +377,39 @@ pub(crate) fn collect_animated_layers(
impl SectionGpu {
/// Its fade code this frame (`lod::fade_code`), 0 once whole. `retiring`:
/// its column has left the render distance, far terrain is coming in
/// under it (`lod::HELD`).
fn fade(&self, now: std::time::Instant, retiring: bool) -> u32 {
/// under it (`lod::HELD`). `faded` is `now - lod::FADE`: a fade-in that
/// began by then has ended, found without the time arithmetic.
fn fade(
&self,
now: std::time::Instant,
faded: Option<std::time::Instant>,
retiring: bool,
) -> u32 {
match self.leaving {
Some(since) => lod::fade_out_code(since, 0, now),
None if retiring => lod::HELD,
None => self
.fading
.filter(|&since| faded.is_none_or(|faded| since > faded))
.and_then(|since| lod::fade_code(Some(since), 0, now))
.unwrap_or(0),
}
}
/// `SectionRenderDispatcher.RenderSection.getVisibility` this frame, `fade_ms` the fade-in's
/// length; `whole` is `now - fade_ms`: a section uploaded by then is fully visible.
fn visibility(
&self,
now: std::time::Instant,
fade_ms: u64,
whole: Option<std::time::Instant>,
) -> f32 {
match self.uploaded {
Some(t) if fade_ms != 0 && whole.is_none_or(|whole| t > whole) => {
chunk_visibility(now.saturating_duration_since(t).as_millis() as u64, fade_ms)
}
_ => 1.0,
}
}
/// Its vertices in `arena`, and in `translucent_arena` when `split` (there
/// is one): (page, first vertex, vertices) of each.
fn vertex_ranges(&self, split: bool) -> (Option<(u32, u64, u64)>, Option<(u32, u64, u64)>) {
@@ -3912,9 +3937,12 @@ impl Renderer {
.or_insert_with(std::time::Instant::now);
}
self.release_section(key);
let section = self
.upload_mesh(mesh)
.map(|section| SectionGpu { fading, ..section });
let uploaded = self.section_uploaded.get(&key).copied();
let section = self.upload_mesh(mesh).map(|section| SectionGpu {
fading,
uploaded,
..section
});
self.set_detail(
key,
section.is_some() || (mesh.solid.is_empty() && mesh.translucent.is_empty()),
@@ -4047,6 +4075,7 @@ impl Renderer {
flat_id,
fading: None,
leaving: None,
uploaded: None,
animated_layers,
})
}
@@ -4351,7 +4380,11 @@ impl Renderer {
if self.light_blocks.remove(&key).is_some() {
self.light_revision += 1;
}
self.section_uploaded.remove(&key);
if self.section_uploaded.remove(&key).is_some()
&& let Some(section) = self.sections.find_mut(key)
{
section.uploaded = None;
}
if retiring {
// Distant tiles still leave its column out: drawn until they don't.
self.lod_retiring.insert(key);
@@ -5601,31 +5634,23 @@ impl Renderer {
let (sections, retiring) = (&self.sections, &self.lod_retiring);
// `LevelRenderer.extractSectionDrawGroups`: each section's `getVisibility`.
let fade_ms = fade_millis(frame.chunk_fade);
let uploaded = &self.section_uploaded;
let visibility = |key: SectionKey| {
if fade_ms == 0 {
return 1.0;
}
uploaded.get(&key).map_or(1.0, |t| {
chunk_visibility(
now.saturating_duration_since(*t).as_millis() as u64,
fade_ms,
)
})
};
// Fade-ins that began by these instants have ended: most sections, found by comparing.
let whole = now.checked_sub(std::time::Duration::from_millis(fade_ms));
let faded = now.checked_sub(lod::FADE);
let is_retiring = |key: &SectionKey| !retiring.is_empty() && retiring.contains(key);
self.origins.extend(
visible
.iter()
.map(|v| {
let code = sections.get(v.slot).fade(now, retiring.contains(&v.key));
section_instance(v.origin, code, visibility(v.key))
let s = sections.get(v.slot);
let code = s.fade(now, faded, is_retiring(&v.key));
section_instance(v.origin, code, s.visibility(now, fade_ms, whole))
})
.chain(visible_lods.iter().map(|v| lod::instance(v.origin, v.fade)))
.chain(
shadow_visible
.iter()
.map(|v| section_instance(v.origin, 0, visibility(v.key))),
),
.chain(shadow_visible.iter().map(|v| {
let s = sections.get(v.slot);
section_instance(v.origin, 0, s.visibility(now, fade_ms, whole))
})),
);
if !self.origins.is_empty() {
self.uploads.buffer(
@@ -6257,6 +6282,8 @@ impl Renderer {
let lods = &self.lods;
let sorted = &self.sorted_indices;
let phase2 = self.cull.as_ref().filter(|_| culling).map(|c| &c.phase2);
// The camera's sections take the instances made for them above.
let instances = &self.origins[..visible.len()];
crate::spans::time("flatten", || {
flat.prepare(
&self.device,
@@ -6270,15 +6297,20 @@ impl Renderer {
phase2,
visible
.iter()
.chain(&shadow_visible)
.filter_map(|v| {
.zip(instances)
.filter_map(|(v, &instance)| {
Some((sections.get(v.slot).flat_id?, instance))
})
.chain(shadow_visible.iter().filter_map(|v| {
let section = sections.get(v.slot);
let code = section.fade(now, retiring.contains(&v.key));
let retiring = !retiring.is_empty() && retiring.contains(&v.key);
let code = section.fade(now, faded, retiring);
let visibility = section.visibility(now, fade_ms, whole);
Some((
section.flat_id?,
section_instance(v.origin, code, visibility(v.key)),
section_instance(v.origin, code, visibility),
))
})
}))
.chain(visible_lods.iter().filter_map(|v| {
let id = lods[&v.key].drawn().flat_id?;
Some((id, lod::instance(v.origin, v.fade)))
@@ -696,6 +696,7 @@ mod tests {
flat_id: None,
fading: None,
leaving: None,
uploaded: None,
animated_layers: Box::new([]),
};
for split in [true, false] {
+8 -6
View File
@@ -530,17 +530,19 @@ impl Flat {
} else {
solid_draws
};
// Origins by section id.
self.origin_data.clear();
// Origins by section id, up to the highest drawn. The ids of sections not drawn keep
// what an earlier frame wrote: no draw reads them, so they are not cleared each frame.
let mut end = 0;
for (id, o) in origins {
let id = id as usize;
if self.origin_data.len() <= id {
self.origin_data.resize(id + 1, [0.0; 4]);
}
self.origin_data[id] = o;
end = end.max(id + 1);
}
if self.origin_data.len() as u64 > self.origin_capacity {
self.origin_capacity = (self.origin_data.len() as u64).next_power_of_two();
if end as u64 > self.origin_capacity {
self.origin_capacity = (end as u64).next_power_of_two();
self.origins = buffer(
device,
"Section origins by id",
@@ -551,12 +553,12 @@ impl Flat {
page.group = None;
}
}
if !self.origin_data.is_empty() {
if end > 0 {
uploads.buffer(
encoder,
&self.origins,
0,
bytemuck::cast_slice(&self.origin_data),
bytemuck::cast_slice(&self.origin_data[..end]),
);
}
let Plan {