fix: stabilize web loading and paper rendering

This commit is contained in:
Hakan Seven 2026-07-25 21:34:29 +03:00
commit bca65600cd
25 changed files with 109 additions and 62 deletions

View file

@ -3219,22 +3219,40 @@ impl MultiPipeline {
self.slot_clock = self.slot_clock.wrapping_add(1).max(1);
let now = self.slot_clock;
let reserved: rustc_hash::FxHashSet<u64> = instance_ids.iter().copied().collect();
// `inner.slot_id` is updated later in `prepare`, after this whole
// method returns. Without a per-call claim set, two new viewports in
// the same primitive both see the freshly-grown slot as `MAX` and get
// assigned to it. Paper then blits the final occupant's resolve texture
// once as the full sheet and again as the floating viewport.
let mut claimed: rustc_hash::FxHashSet<usize> =
rustc_hash::FxHashSet::default();
let mut slots = Vec::with_capacity(instance_ids.len());
for &instance_id in instance_ids {
let slot = if let Some(&slot) = self.slot_by_instance.get(&instance_id) {
let existing = self
.slot_by_instance
.get(&instance_id)
.copied()
.filter(|slot| !claimed.contains(slot));
let slot = if let Some(slot) = existing {
slot
} else {
self.slot_by_instance.remove(&instance_id);
let vacant = self
.inners
.iter()
.position(|inner| inner.slot_id == u64::MAX);
.enumerate()
.find(|(slot, inner)| {
!claimed.contains(slot) && inner.slot_id == u64::MAX
})
.map(|(slot, _)| slot);
let recyclable = vacant.or_else(|| {
(self.inners.len() >= SOFT_LIMIT)
.then(|| {
self.inners
.iter()
.enumerate()
.filter(|(slot, _)| !claimed.contains(slot))
.filter(|(_, inner)| !reserved.contains(&inner.slot_id))
.filter(|(slot, _)| {
now.saturating_sub(self.slot_last_used[*slot]) > HOT_WINDOW
@ -3256,6 +3274,7 @@ impl MultiPipeline {
self.slot_by_instance.insert(instance_id, slot);
slot
};
claimed.insert(slot);
self.slot_last_used[slot] = now;
slots.push(slot);
}

View file

@ -310,7 +310,7 @@ impl WireArena {
) -> Option<Self> {
let ranges = handle_ranges(wires)?;
let perf = crate::perf::enabled();
let total_started = std::time::Instant::now();
let total_started = iced::time::Instant::now();
// Reject an oversized batch before parallel emission allocates hundreds
// of megabytes. `points.len() - 1` is an upper bound because NaN-break
@ -363,7 +363,7 @@ impl WireArena {
})
.collect();
let pack_started = std::time::Instant::now();
let pack_started = iced::time::Instant::now();
use crate::par::prelude::*;
let packed: Vec<PackedSlab> = plans
.par_iter()
@ -441,7 +441,7 @@ impl WireArena {
let const_cap = ((const_tail as u64 * HEADROOM_NUM / HEADROOM_DEN)
.max(MIN_CONST_CAP)
.min(MAX_CONSTS)) as u32;
let upload_started = std::time::Instant::now();
let upload_started = iced::time::Instant::now();
let inst_buf = alloc_inst_initialized(device, inst_cap as u64, &instances);
let const_buf = alloc_const_initialized(device, const_cap as u64, &consts_cpu);
let upload_ms = upload_started.elapsed().as_secs_f64() * 1000.0;

View file

@ -224,9 +224,6 @@ impl PackedWireInstance {
}
}
#[cfg(target_arch = "wasm32")]
pub type WireInstance = PackedWireInstance;
/// Wire and hatch pipelines switch together: the fast path uses storage
/// buffers; the compatibility path carries wire constants in packed vertex
/// attributes and hatch data in a texture.