iris: one primitive arena all layers share, with placement in a storage buffer

A mask is about to reference a primitive already drawn and evaluate it at
the masked pixel (docs/LAYOUT.md's "Masks with a shape"), which the data
layout could not answer: a primitive's placement lived in its layer's
*vertex* buffer, invisible to the fragment stage, and `rects`/`glyphs`
were per layer too -- so a mask whose shape is a rounded container in one
layer, clipping content a `Stack` put in another, would have read the
wrong layer's rect with nothing on screen to say so.

So the instances and the per-primitive data become one arena
(`UiRenderState::primitives`), bound once per frame; a layer keeps only
its draw *order*, which is what its vertex buffer now is -- one `u32`
slot per instance instead of eight attributes. The vertex stage reads the
placement it is drawing from `instances[slot]`; the fragment stage can
read any other primitive's from the same buffer, which is what the mask
work needs and the reason there is no second copy for masks.

Arena slots are stable (nothing is compacted), so a `Mask` can hold one
across frames. A slot freed during a redraw is therefore not reusable
until every layer's order has been compacted around it -- otherwise the
reused slot would draw twice, once through the stale order entry -- which
is what `Primitives::freed` and `UiRenderState::apply_free` are. That
compaction moved out of `UiRenderNode::update` into `UiRenderState::
update`: it is bookkeeping over `active`, not GPU work, and the harness
(which has no renderer) needs it too.

Same 164 tests, the `--phone` screenshot unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Fable 5.1 committed 2026-09-07 21:21:55 -04:00
1 parent b38e797db3
commit 203f53470c
8 files changed
+510 -361

No files matched your search

+5 -32
View File
@@ -1,10 +1,6 @@
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use crate::{ use crate::{render::LayerOrder, util::to_mut};
UiRegion, WidgetId,
render::{MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives},
util::to_mut,
};
pub type LayerId = usize; pub type LayerId = usize;
@@ -40,7 +36,10 @@ struct Child {
tail: usize, tail: usize,
} }
pub type PrimitiveLayers = Layers<Primitives>; /// The draw order of every layer. The primitives themselves live in one
/// arena beside this (`UiRenderState::primitives`); a layer names the
/// slots it draws, which is what its vertex buffer is.
pub type PrimitiveLayers = Layers<LayerOrder>;
impl<T: Default> Layers<T> { impl<T: Default> Layers<T> {
pub fn new() -> Layers<T> { pub fn new() -> Layers<T> {
@@ -120,32 +119,6 @@ impl<T: Default> Layers<T> {
} }
} }
impl PrimitiveLayers {
pub fn write<P: Primitive>(
&mut self,
layer: LayerId,
info: PrimitiveInst<P>,
) -> PrimitiveHandle {
self[layer].write(layer, info)
}
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
self[h.layer].free(h)
}
pub fn write_image(
&mut self,
layer: LayerId,
id: WidgetId,
texture_idx: u32,
region: UiRegion,
mask_idx: MaskIdx,
move_idx: MoveIdx,
) -> PrimitiveHandle {
self[layer].write_image(layer, id, texture_idx, region, mask_idx, move_idx)
}
}
impl<T: Default> Default for Layers<T> { impl<T: Default> Default for Layers<T> {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
+20 -18
View File
@@ -8,6 +8,15 @@ pub struct WindowUniform {
pub height: f32, pub height: f32,
} }
/// One primitive's placement and what to draw there, in the one arena
/// every layer shares (`Primitives`). Read from a storage buffer by
/// **both** shader stages: the vertex stage for the corners of the
/// primitive it is drawing, the fragment stage for the corners of a
/// *mask's* primitive, which is generally a different one and often in
/// another layer. A layer's vertex buffer carries only the slot
/// ([`instance_slot_layout`]), so there is exactly one copy of a
/// placement and a mask cannot disagree with what was drawn. See
/// LAYOUT.md's "Masks with a shape".
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct PrimitiveInstance { pub struct PrimitiveInstance {
@@ -18,24 +27,17 @@ pub struct PrimitiveInstance {
pub move_idx: MoveIdx, pub move_idx: MoveIdx,
} }
impl PrimitiveInstance { /// The vertex layout of a layer's draw order: one `u32` slot into the
const ATTRIBS: [VertexAttribute; 8] = vertex_attr_array![ /// global instance arena per instance, stepped per instance. Everything a
0 => Float32x2, /// primitive is made of used to be here as eight vertex attributes; it
1 => Float32x2, /// moved into the storage buffer above so the fragment stage can read it
2 => Float32x2, /// too.
3 => Float32x2, pub fn instance_slot_layout() -> VertexBufferLayout<'static> {
4 => Uint32, const ATTRIBS: [VertexAttribute; 1] = vertex_attr_array![0 => Uint32];
5 => Uint32, VertexBufferLayout {
6 => Uint32, array_stride: std::mem::size_of::<u32>() as BufferAddress,
7 => Uint32, step_mode: VertexStepMode::Instance,
]; attributes: &ATTRIBS,
pub fn desc() -> VertexBufferLayout<'static> {
VertexBufferLayout {
array_stride: std::mem::size_of::<Self>() as BufferAddress,
step_mode: VertexStepMode::Instance,
attributes: &Self::ATTRIBS,
}
} }
} }
+100 -77
View File
@@ -1,6 +1,10 @@
use crate::{ use crate::{
UiData, UiRenderState, UiData, UiRenderState,
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf}, render::{
data::{PrimitiveInstance, instance_slot_layout},
texture::GpuTextures,
util::ArrBuf,
},
util::{HashMap, Vec2}, util::{HashMap, Vec2},
}; };
use data::WindowUniform; use data::WindowUniform;
@@ -120,6 +124,11 @@ impl WgpuErrorLog {
pub struct UiRenderNode { pub struct UiRenderNode {
uniform_group: BindGroup, uniform_group: BindGroup,
primitive_layout: BindGroupLayout, primitive_layout: BindGroupLayout,
/// Group 1: `rects` and `glyphs`. Global and bound once per frame,
/// not per layer -- a mask referencing a rect drawn in another layer
/// has to be able to read it (see `Primitives`).
primitives: PrimitiveBuffers,
primitive_group: BindGroup,
rsc_layout: BindGroupLayout, rsc_layout: BindGroupLayout,
rsc_group: BindGroup, rsc_group: BindGroup,
@@ -129,6 +138,9 @@ pub struct UiRenderNode {
active: Vec<usize>, active: Vec<usize>,
window_buffer: Buffer, window_buffer: Buffer,
textures: GpuTextures, textures: GpuTextures,
/// Every primitive's placement, read by the vertex stage for the
/// primitive being drawn and by the fragment stage for a mask's.
instances: ArrBuf<PrimitiveInstance>,
masks: ArrBuf<Mask>, masks: ArrBuf<Mask>,
move_offsets: ArrBuf<MoveOffset>, move_offsets: ArrBuf<MoveOffset>,
/// Group 3: the masks and move-offsets storage buffers, on their own -- /// Group 3: the masks and move-offsets storage buffers, on their own --
@@ -146,16 +158,16 @@ pub struct UiRenderNode {
masks_group: BindGroup, masks_group: BindGroup,
} }
/// One layer's vertex buffers: the slots it draws, in order. The
/// primitives themselves are in `UiRenderNode::instances`.
struct RenderLayer { struct RenderLayer {
instance: ArrBuf<PrimitiveInstance>, order: ArrBuf<u32>,
primitives: PrimitiveBuffers, /// A standalone image's slots, kept apart from `order` because each
primitive_group: BindGroup, /// one draws with its own bind group -- see `UiRenderNode::draw`.
/// A standalone image's instances, kept apart from `instance` because images: ArrBuf<u32>,
/// each one draws with its own bind group -- see `UiRenderNode::draw`. /// The texture slot each entry of `images` draws with, in the same
image_instance: ArrBuf<PrimitiveInstance>, /// order, refreshed alongside it. Not in the vertex buffer itself
/// The texture slot each entry of `image_instance` draws with, in the /// because it names a bind group, not shader data.
/// same order, refreshed alongside it. Not stored in the vertex buffer
/// itself because it names a bind group, not shader data.
image_tex_indices: Vec<u32>, image_tex_indices: Vec<u32>,
} }
@@ -163,6 +175,8 @@ impl UiRenderNode {
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) { pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
pass.set_pipeline(&self.pipeline); pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &self.uniform_group, &[]); pass.set_bind_group(0, &self.uniform_group, &[]);
// Group 1 is global now, so it is set here rather than per layer.
pass.set_bind_group(1, &self.primitive_group, &[]);
// Set once, not per layer or per image: masks/move_offsets are read // Set once, not per layer or per image: masks/move_offsets are read
// by every primitive and every standalone image alike, and living // by every primitive and every standalone image alike, and living
// in their own group (rather than folded into group 2 alongside the // in their own group (rather than folded into group 2 alongside the
@@ -172,14 +186,13 @@ impl UiRenderNode {
pass.set_bind_group(3, &self.masks_group, &[]); pass.set_bind_group(3, &self.masks_group, &[]);
for i in &self.active { for i in &self.active {
let layer = &self.layers[i]; let layer = &self.layers[i];
if layer.instance.len() == 0 && layer.image_instance.len() == 0 { if layer.order.len() == 0 && layer.images.len() == 0 {
continue; continue;
} }
pass.set_bind_group(1, &layer.primitive_group, &[]); if layer.order.len() > 0 {
if layer.instance.len() > 0 {
pass.set_bind_group(2, &self.rsc_group, &[]); pass.set_bind_group(2, &self.rsc_group, &[]);
pass.set_vertex_buffer(0, layer.instance.buffer.slice(..)); pass.set_vertex_buffer(0, layer.order.buffer.slice(..));
pass.draw(0..4, 0..layer.instance.len() as u32); pass.draw(0..4, 0..layer.order.len() as u32);
} }
// Images draw after this layer's rects and glyphs, one draw call // Images draw after this layer's rects and glyphs, one draw call
// each with its own bind group. That draws every image "on top" // each with its own bind group. That draws every image "on top"
@@ -188,8 +201,8 @@ impl UiRenderNode {
// draw order was already undefined before images had their own // draw order was already undefined before images had their own
// list -- nothing before this relied on interleaving a rect // list -- nothing before this relied on interleaving a rect
// between two images at a particular position. // between two images at a particular position.
if layer.image_instance.len() > 0 { if layer.images.len() > 0 {
pass.set_vertex_buffer(0, layer.image_instance.buffer.slice(..)); pass.set_vertex_buffer(0, layer.images.buffer.slice(..));
for (k, &tex_idx) in layer.image_tex_indices.iter().enumerate() { for (k, &tex_idx) in layer.image_tex_indices.iter().enumerate() {
pass.set_bind_group(2, self.textures.image_bind_group(tex_idx), &[]); pass.set_bind_group(2, self.textures.image_bind_group(tex_idx), &[]);
pass.draw(0..4, k as u32..k as u32 + 1); pass.draw(0..4, k as u32..k as u32 + 1);
@@ -206,67 +219,45 @@ impl UiRenderNode {
ui_render: &mut UiRenderState, ui_render: &mut UiRenderState,
) -> FrameUpdateStats { ) -> FrameUpdateStats {
self.active.clear(); self.active.clear();
for (i, primitives) in ui_render.layers.iter_mut() { for (i, order) in ui_render.layers.iter_mut() {
self.active.push(i); self.active.push(i);
for change in primitives.apply_free() { let rlayer = self.layers.entry(i).or_insert_with(|| RenderLayer {
if let Some(inst) = ui_render.active.get_mut(&change.id) { order: ArrBuf::new(
for h in &mut inst.primitives {
// `is_image` disambiguates: `instances` and `images`
// are separate lists with independent indices, so
// without it a rect's renumbering could be applied to
// an image handle that happened to share the same
// (layer, inst_idx).
if h.layer == i
&& h.inst_idx == change.old
&& (h.binding == IMAGE_BINDING) == change.is_image
{
h.inst_idx = change.new;
break;
}
}
}
}
let rlayer = self.layers.entry(i).or_insert_with(|| {
let primitives = PrimitiveBuffers::new(device);
let primitive_group =
Self::primitive_group(device, &self.primitive_layout, primitives.buffers());
RenderLayer {
instance: ArrBuf::new(
device,
BufferUsages::VERTEX | BufferUsages::COPY_DST,
"instance",
),
primitives,
primitive_group,
image_instance: ArrBuf::new(
device,
BufferUsages::VERTEX | BufferUsages::COPY_DST,
"image instance",
),
image_tex_indices: Vec::new(),
}
});
if primitives.updated {
rlayer
.instance
.update(device, queue, primitives.instances());
rlayer.primitives.update(device, queue, primitives.data());
rlayer.primitive_group = Self::primitive_group(
device, device,
&self.primitive_layout, BufferUsages::VERTEX | BufferUsages::COPY_DST,
rlayer.primitives.buffers(), "layer order",
); ),
rlayer images: ArrBuf::new(
.image_instance device,
.update(device, queue, primitives.image_instances()); BufferUsages::VERTEX | BufferUsages::COPY_DST,
rlayer.image_tex_indices = primitives "layer image order",
.image_instances() ),
image_tex_indices: Vec::new(),
});
if order.updated {
rlayer.order.update(device, queue, order.order());
rlayer.images.update(device, queue, order.images());
rlayer.image_tex_indices = order
.images()
.iter() .iter()
.map(|inst| inst.idx) .map(|&slot| ui_render.primitives.instance(slot).idx)
.collect(); .collect();
primitives.updated = false; order.updated = false;
} }
} }
let instances_resized = if ui_render.primitives.updated {
ui_render.primitives.updated = false;
let resized = self
.instances
.update(device, queue, ui_render.primitives.instances());
self.primitives
.update(device, queue, ui_render.primitives.data());
self.primitive_group =
Self::primitive_group(device, &self.primitive_layout, self.primitives.buffers());
resized
} else {
false
};
let masks_resized = if ui.masks.changed { let masks_resized = if ui.masks.changed {
ui.masks.changed = false; ui.masks.changed = false;
self.masks.update(device, queue, &ui.masks[..]) self.masks.update(device, queue, &ui.masks[..])
@@ -280,9 +271,14 @@ impl UiRenderNode {
} else { } else {
false false
}; };
if masks_resized || moves_resized { if masks_resized || moves_resized || instances_resized {
self.masks_group = self.masks_group = Self::masks_group(
Self::masks_group(device, &self.masks_layout, &self.masks, &self.move_offsets); device,
&self.masks_layout,
&self.masks,
&self.move_offsets,
&self.instances,
);
} }
let rebuild_main = self.textures.update(&mut ui.textures, &self.rsc_layout); let rebuild_main = self.textures.update(&mut ui.textures, &self.rsc_layout);
if rebuild_main { if rebuild_main {
@@ -408,6 +404,14 @@ impl UiRenderNode {
}); });
let tex_manager = GpuTextures::new(device, queue); let tex_manager = GpuTextures::new(device, queue);
let primitives = PrimitiveBuffers::new(device);
let primitive_group =
Self::primitive_group(device, &primitive_layout, primitives.buffers());
let instances = ArrBuf::new(
device,
BufferUsages::STORAGE | BufferUsages::COPY_DST,
"ui instances",
);
let masks = ArrBuf::new( let masks = ArrBuf::new(
device, device,
BufferUsages::STORAGE | BufferUsages::COPY_DST, BufferUsages::STORAGE | BufferUsages::COPY_DST,
@@ -422,7 +426,8 @@ impl UiRenderNode {
let rsc_layout = Self::rsc_layout(device); let rsc_layout = Self::rsc_layout(device);
let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager); let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager);
let masks_layout = Self::masks_layout(device); let masks_layout = Self::masks_layout(device);
let masks_group = Self::masks_group(device, &masks_layout, &masks, &move_offsets); let masks_group =
Self::masks_group(device, &masks_layout, &masks, &move_offsets, &instances);
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some("UI Shape Pipeline Layout"), label: Some("UI Shape Pipeline Layout"),
@@ -440,7 +445,7 @@ impl UiRenderNode {
vertex: VertexState { vertex: VertexState {
module: &shader, module: &shader,
entry_point: Some("vs_main"), entry_point: Some("vs_main"),
buffers: &[PrimitiveInstance::desc()], buffers: &[instance_slot_layout()],
compilation_options: Default::default(), compilation_options: Default::default(),
}, },
fragment: Some(FragmentState { fragment: Some(FragmentState {
@@ -486,6 +491,8 @@ impl UiRenderNode {
Ok(Self { Ok(Self {
uniform_group, uniform_group,
primitive_layout, primitive_layout,
primitives,
primitive_group,
rsc_layout, rsc_layout,
rsc_group, rsc_group,
pipeline, pipeline,
@@ -493,6 +500,7 @@ impl UiRenderNode {
layers: HashMap::default(), layers: HashMap::default(),
active: Vec::new(), active: Vec::new(),
textures: tex_manager, textures: tex_manager,
instances,
masks, masks,
move_offsets, move_offsets,
masks_layout, masks_layout,
@@ -627,6 +635,16 @@ impl UiRenderNode {
}, },
count: None, count: None,
}, },
BindGroupLayoutEntry {
binding: 2,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
], ],
label: Some("ui masks"), label: Some("ui masks"),
}) })
@@ -637,6 +655,7 @@ impl UiRenderNode {
layout: &BindGroupLayout, layout: &BindGroupLayout,
masks: &ArrBuf<Mask>, masks: &ArrBuf<Mask>,
move_offsets: &ArrBuf<MoveOffset>, move_offsets: &ArrBuf<MoveOffset>,
instances: &ArrBuf<PrimitiveInstance>,
) -> BindGroup { ) -> BindGroup {
device.create_bind_group(&BindGroupDescriptor { device.create_bind_group(&BindGroupDescriptor {
layout, layout,
@@ -649,6 +668,10 @@ impl UiRenderNode {
binding: 1, binding: 1,
resource: move_offsets.buffer.as_entire_binding(), resource: move_offsets.buffer.as_entire_binding(),
}, },
BindGroupEntry {
binding: 2,
resource: instances.buffer.as_entire_binding(),
},
], ],
label: Some("ui masks"), label: Some("ui masks"),
}) })
+258 -191
View File
@@ -11,46 +11,11 @@ use crate::{
use bytemuck::Pod; use bytemuck::Pod;
use wgpu::*; use wgpu::*;
pub struct Primitives {
instances: Vec<PrimitiveInstance>,
assoc: Vec<WidgetId>,
data: PrimitiveData,
free: Vec<usize>,
/// Standalone images, kept apart from `instances` because each one draws
/// with its own bind group rather than sharing the layer's one instanced
/// draw -- see TEXTURES.md's "Recommended shape". `idx` on each
/// `PrimitiveInstance` here is the texture's slot in `Textures`/
/// `GpuTextures`, not an index into `data`; there is no per-image entry
/// in `data` because a bind group already picks the texture; nothing
/// left to look up per-instance.
images: Vec<PrimitiveInstance>,
image_assoc: Vec<WidgetId>,
image_free: Vec<usize>,
pub updated: bool,
}
impl Default for Primitives {
fn default() -> Self {
Self {
instances: Default::default(),
assoc: Default::default(),
data: Default::default(),
free: Vec::new(),
images: Default::default(),
image_assoc: Default::default(),
image_free: Vec::new(),
updated: true,
}
}
}
/// The `binding` tag `Painter` writes on an image instance. Distinct from any /// The `binding` tag `Painter` writes on an image instance. Distinct from any
/// `Primitive::BINDING` because images have no `PrimitiveData` entry to key /// `Primitive::BINDING` because images have no `PrimitiveData` entry to key
/// one from -- a bind group already selects the texture -- so this only ever /// one from -- a bind group already selects the texture -- so this only ever
/// has to match the shader's `TEXTURE` constant and flag "this instance lives /// has to match the shader's `TEXTURE` constant and flag "this instance is
/// in `Primitives::images`, not `Primitives::instances`" to the code below. /// drawn with its own bind group" to the code below.
pub const IMAGE_BINDING: u32 = 1; pub const IMAGE_BINDING: u32 = 1;
pub trait Primitive: Pod { pub trait Primitive: Pod {
@@ -134,18 +99,61 @@ macro_rules! primitives {
(@count $t:tt) => { 1 }; (@count $t:tt) => { 1 };
} }
pub struct PrimitiveInst<P> { /// Every primitive instance in the tree, in one arena that all layers
pub id: WidgetId, /// share, plus the per-primitive data (`rects`, `glyphs`) they index.
pub primitive: P, ///
pub region: UiRegion, /// **Why one arena rather than one per layer**, which is what this was:
pub mask_idx: MaskIdx, /// the fragment stage evaluates a *mask's* primitive at the masked pixel
pub move_idx: MoveIdx, /// (LAYOUT.md's "Masks with a shape"), and the widget that owns a mask is
/// routinely in a different layer from the content it clips -- a rounded
/// container in one layer, a `Stack`'s child content in the layer below.
/// A per-layer buffer cannot answer that lookup at all: only one layer's
/// group is bound at a time, so the mask would silently read another
/// layer's rect. Both buffers are therefore global and bound once per
/// frame, and a layer keeps only its draw *order* ([`LayerOrder`]).
///
/// Slots are stable for a primitive's whole life: nothing here is
/// compacted, so a `Mask` can hold a slot across frames.
pub struct Primitives {
instances: Vec<PrimitiveInstance>,
assoc: Vec<WidgetId>,
/// Slots freed since the last [`Self::apply_free`]. Deliberately not
/// reusable yet: the layer that drew one still names it in its draw
/// order until that call compacts the order, so handing it out again
/// first would draw the new primitive twice -- once through the stale
/// order entry and once through the new one.
freed: Vec<usize>,
/// Slots [`Self::apply_free`] released, which is what [`Self::alloc`]
/// hands out.
reusable: Vec<usize>,
data: PrimitiveData,
/// Whether the instance arena or the per-primitive data changed since
/// the last upload -- one flag for both, since they are uploaded
/// together.
pub updated: bool,
}
impl Default for Primitives {
fn default() -> Self {
Self {
instances: Default::default(),
assoc: Default::default(),
freed: Vec::new(),
reusable: Vec::new(),
data: Default::default(),
updated: true,
}
}
} }
impl Primitives { impl Primitives {
pub fn write<P: Primitive>( /// Writes a primitive into the arena and hands back its slot and its
/// entry in the per-primitive data. The caller (`UiRenderState`) puts
/// the slot into a layer's draw order -- an instance that no layer
/// names is never rasterized, which is what a mask shape drawn only to
/// be *referenced* uses.
pub fn alloc<P: Primitive>(
&mut self, &mut self,
layer: usize,
PrimitiveInst { PrimitiveInst {
id, id,
primitive, primitive,
@@ -153,154 +161,118 @@ impl Primitives {
mask_idx, mask_idx,
move_idx, move_idx,
}: PrimitiveInst<P>, }: PrimitiveInst<P>,
) -> PrimitiveHandle { ) -> (u32, usize) {
self.updated = true; let data_idx = P::vec(&mut self.data).add(primitive);
let vec = P::vec(&mut self.data); let slot = self.push(
let i = vec.add(primitive); PrimitiveInstance {
let inst = PrimitiveInstance { region,
region, idx: data_idx as u32,
idx: i as u32, mask_idx,
mask_idx, move_idx,
move_idx, binding: P::BINDING,
binding: P::BINDING, },
}; id,
let inst_i = if let Some(i) = self.free.pop() { );
self.instances[i] = inst; (slot, data_idx)
self.assoc[i] = id;
i
} else {
let i = self.instances.len();
self.instances.push(inst);
self.assoc.push(id);
i
};
PrimitiveHandle::new::<P>(layer, inst_i, i)
} }
/// Writes an image instance directly -- there is no `Primitive` impl for /// A standalone image, which has no `PrimitiveData` entry to allocate
/// it to go through `write`, since it has nowhere in `PrimitiveData` to /// -- its bind group already picks the texture, so `texture_idx` rides
/// put a per-instance entry. `texture_idx` is the slot the bind group at /// in the otherwise-unused `idx` field and names the bind group the
/// draw time is chosen from, carried in the otherwise-unused `idx` field. /// draw call selects.
pub fn write_image( pub fn alloc_image(
&mut self, &mut self,
layer: usize,
id: WidgetId, id: WidgetId,
texture_idx: u32, texture_idx: u32,
region: UiRegion, region: UiRegion,
mask_idx: MaskIdx, mask_idx: MaskIdx,
move_idx: MoveIdx, move_idx: MoveIdx,
) -> PrimitiveHandle { ) -> u32 {
self.push(
PrimitiveInstance {
region,
idx: texture_idx,
mask_idx,
move_idx,
binding: IMAGE_BINDING,
},
id,
)
}
fn push(&mut self, inst: PrimitiveInstance, id: WidgetId) -> u32 {
self.updated = true; self.updated = true;
let inst = PrimitiveInstance { let slot = if let Some(i) = self.reusable.pop() {
region, self.instances[i] = inst;
idx: texture_idx, self.assoc[i] = id;
mask_idx,
move_idx,
binding: IMAGE_BINDING,
};
let inst_i = if let Some(i) = self.image_free.pop() {
self.images[i] = inst;
self.image_assoc[i] = id;
i i
} else { } else {
let i = self.images.len(); self.instances.push(inst);
self.images.push(inst); self.assoc.push(id);
self.image_assoc.push(id); self.instances.len() - 1
i
}; };
PrimitiveHandle { slot as u32
layer,
inst_idx: inst_i,
data_idx: 0,
binding: IMAGE_BINDING,
}
}
pub fn image_instances(&self) -> &Vec<PrimitiveInstance> {
&self.images
}
/// returns (old index, new index) for both lists this layer keeps --
/// `PrimitiveChange::is_image` says which, since the two have separate
/// index spaces and `old`/`new` alone would collide between them.
///
/// Both lists free with `swap_remove`, so a layer's draw order was
/// already undefined before images existed: nothing here may assume one
/// primitive stays adjacent to another once anything in the layer has
/// been freed.
pub fn apply_free(&mut self) -> Vec<PrimitiveChange> {
let mut changes =
Self::apply_free_list(&mut self.free, &mut self.instances, &mut self.assoc, false);
changes.extend(Self::apply_free_list(
&mut self.image_free,
&mut self.images,
&mut self.image_assoc,
true,
));
changes
}
fn apply_free_list(
free: &mut Vec<usize>,
instances: &mut Vec<PrimitiveInstance>,
assoc: &mut Vec<WidgetId>,
is_image: bool,
) -> Vec<PrimitiveChange> {
free.sort_by(|a, b| b.cmp(a));
free.drain(..)
.filter_map(|i| {
instances.swap_remove(i);
assoc.swap_remove(i);
if i == instances.len() {
return None;
}
let id = assoc[i];
let old = instances.len();
Some(PrimitiveChange {
id,
is_image,
old,
new: i,
})
})
.collect()
} }
/// Retires a slot, answering the mask it was drawn under so the caller
/// can drop that mask's ref. The slot itself only becomes reusable at
/// the next [`Self::apply_free`] -- see `freed`.
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx { pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
self.updated = true; self.updated = true;
if h.binding == IMAGE_BINDING { let slot = h.slot as usize;
self.image_free.push(h.inst_idx); if h.binding != IMAGE_BINDING {
self.images[h.inst_idx].mask_idx
} else {
self.data.free(h.binding, h.data_idx); self.data.free(h.binding, h.data_idx);
self.free.push(h.inst_idx);
self.instances[h.inst_idx].mask_idx
} }
self.freed.push(slot);
self.instances[slot].mask_idx
} }
/// How many instances are still bound for the GPU -- the O(1) half of /// Hands this frame's freed slots back for reuse. Called once per
/// the orphan check, so the O(primitives) walk below only runs on a /// frame from `UiRenderState::update`, **after** every layer has
/// frame that already looks wrong. See /// compacted its draw order, since that order is the only thing still
/// naming them.
pub fn release_freed(&mut self) {
self.reusable.append(&mut self.freed);
}
/// Which widget drew the primitive in `slot` -- how a draw-order
/// change finds the handle it has to renumber.
pub fn owner(&self, slot: u32) -> WidgetId {
self.assoc[slot as usize]
}
pub fn clear(&mut self) {
self.updated = true;
self.instances.clear();
self.assoc.clear();
self.freed.clear();
self.reusable.clear();
self.data.clear();
}
/// How many instances are still live -- the O(1) half of the orphan
/// check, so the O(primitives) walk below only runs on a frame that
/// already looks wrong. See
/// [`crate::UiRenderState::orphaned_primitives`]. /// [`crate::UiRenderState::orphaned_primitives`].
pub fn live_count(&self) -> usize { pub fn live_count(&self) -> usize {
(self.instances.len() - self.free.len()) + (self.images.len() - self.image_free.len()) self.instances.len() - self.freed.len() - self.reusable.len()
} }
/// Every instance that is still bound for the GPU, as `(inst_idx, /// Every live instance as `(slot, owner, is_image)` -- everything
/// owner, is_image)` -- everything except the slots already handed to /// except the freed and the reusable. Only
/// [`Self::free`] and waiting for [`Self::apply_free`] to compact them /// [`crate::UiRenderState::orphaned_primitives`] uses this, to check
/// away. Only [`crate::UiRenderState::orphaned_primitives`] uses this, /// that every live primitive still belongs to a live widget.
/// to check that every drawn primitive still belongs to a live widget. pub fn live_instances(&self) -> impl Iterator<Item = (u32, WidgetId, bool)> + '_ {
pub fn live_instances(&self) -> impl Iterator<Item = (usize, WidgetId, bool)> + '_ { let dead: HashSet<usize> = self.freed.iter().chain(&self.reusable).copied().collect();
let free: HashSet<usize> = self.free.iter().copied().collect(); (0..self.instances.len())
let image_free: HashSet<usize> = self.image_free.iter().copied().collect(); .filter(move |i| !dead.contains(i))
let rects = (0..self.instances.len()) .map(|i| {
.filter(move |i| !free.contains(i)) (
.map(|i| (i, self.assoc[i], false)); i as u32,
let images = (0..self.images.len()) self.assoc[i],
.filter(move |i| !image_free.contains(i)) self.instances[i].binding == IMAGE_BINDING,
.map(|i| (i, self.image_assoc[i], true)); )
rects.chain(images) })
} }
pub fn data(&self) -> &PrimitiveData { pub fn data(&self) -> &PrimitiveData {
@@ -311,46 +283,141 @@ impl Primitives {
&self.instances &self.instances
} }
pub fn instance(&self, slot: u32) -> &PrimitiveInstance {
&self.instances[slot as usize]
}
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion { pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
self.updated = true; self.updated = true;
if h.binding == IMAGE_BINDING { &mut self.instances[h.slot as usize].region
&mut self.images[h.inst_idx].region
} else {
&mut self.instances[h.inst_idx].region
}
} }
} }
pub struct PrimitiveChange { /// One layer's draw order: the slots of the global arena it draws, in the
pub id: WidgetId, /// order they were written. The vertex buffer of a layer is exactly this.
/// Which of `Primitives::instances`/`Primitives::images` this change ///
/// belongs to -- their `old`/`new` indices are independent, so a /// Both lists free with `swap_remove`, so a layer's draw order was already
/// consumer matching only on `(layer, inst_idx)` could apply an image's /// undefined before this split: nothing here may assume one primitive
/// renumbering to a rect's handle that happens to share the same index. /// stays adjacent to another once anything in the layer has been freed.
pub is_image: bool, #[derive(Default)]
pub old: usize, pub struct LayerOrder {
pub new: usize, order: Vec<u32>,
/// Standalone images, kept apart because each draws with its own bind
/// group rather than sharing the layer's one instanced draw -- see
/// `UiRenderNode::draw`.
images: Vec<u32>,
free: Vec<usize>,
image_free: Vec<usize>,
pub updated: bool,
} }
impl LayerOrder {
pub fn push(&mut self, slot: u32, is_image: bool) -> usize {
self.updated = true;
let list = if is_image {
&mut self.images
} else {
&mut self.order
};
list.push(slot);
list.len() - 1
}
/// Marks a position for removal. Deferred to [`Self::apply_free`] like
/// the arena's own, so that a position is only renumbered once per
/// frame however many were dropped.
pub fn free(&mut self, pos: usize, is_image: bool) {
self.updated = true;
if is_image {
self.image_free.push(pos);
} else {
self.free.push(pos);
}
}
/// Compacts both lists, answering every primitive whose position
/// moved so its handle can be corrected.
pub fn apply_free(&mut self) -> Vec<OrderChange> {
let mut changes = Self::apply_free_list(&mut self.free, &mut self.order, false);
changes.extend(Self::apply_free_list(
&mut self.image_free,
&mut self.images,
true,
));
changes
}
fn apply_free_list(
free: &mut Vec<usize>,
list: &mut Vec<u32>,
is_image: bool,
) -> Vec<OrderChange> {
// Descending, so removing a contiguous tail costs no renumbering
// at all -- which is what freeing one widget's primitives is.
free.sort_by(|a, b| b.cmp(a));
free.drain(..)
.filter_map(|pos| {
list.swap_remove(pos);
if pos == list.len() {
return None;
}
Some(OrderChange {
slot: list[pos],
is_image,
pos,
})
})
.collect()
}
pub fn order(&self) -> &Vec<u32> {
&self.order
}
pub fn images(&self) -> &Vec<u32> {
&self.images
}
}
/// A primitive whose position in a layer's draw order moved when
/// something before it was freed -- `slot` names which primitive, so its
/// owner's handle can be found and pointed at `pos`.
pub struct OrderChange {
pub slot: u32,
/// Which of the layer's two lists moved: their positions are
/// independent index spaces, so a handle matching on position alone
/// could take an image's renumbering for a rect's.
pub is_image: bool,
pub pos: usize,
}
/// Where one primitive lives: its stable slot in the global arena, and
/// where in a layer's draw order it currently sits. A handle with no
/// layer position (`pos == NOT_DRAWN`) is a primitive that exists to be
/// *referenced* -- a mask's shape -- and is never rasterized.
#[derive(Debug)] #[derive(Debug)]
pub struct PrimitiveHandle { pub struct PrimitiveHandle {
pub layer: usize, pub layer: usize,
pub inst_idx: usize, pub pos: usize,
pub slot: u32,
pub data_idx: usize, pub data_idx: usize,
pub binding: u32, pub binding: u32,
} }
impl PrimitiveHandle { impl PrimitiveHandle {
fn new<P: Primitive>(layer: usize, inst_idx: usize, data_idx: usize) -> Self { pub fn is_image(&self) -> bool {
Self { self.binding == IMAGE_BINDING
layer,
inst_idx,
data_idx,
binding: P::BINDING,
}
} }
} }
pub struct PrimitiveInst<P> {
pub id: WidgetId,
pub primitive: P,
pub region: UiRegion,
pub mask_idx: MaskIdx,
pub move_idx: MoveIdx,
}
primitives!( primitives!(
rects: RectPrimitive => 0, rects: RectPrimitive => 0,
glyphs: GlyphPrimitive => 2, glyphs: GlyphPrimitive => 2,
+29 -16
View File
@@ -83,6 +83,13 @@ var samp: sampler;
var<storage> masks: array<Mask>; var<storage> masks: array<Mask>;
@group(3) @binding(1) @group(3) @binding(1)
var<storage> move_offsets: array<MoveOffset>; var<storage> move_offsets: array<MoveOffset>;
// Every primitive's placement, in one arena all layers share. The vertex
// stage reads the primitive it is drawing (its slot arrives as the only
// vertex attribute); the fragment stage reads a *mask's* primitive, which
// is generally a different one in a different layer. See LAYOUT.md's
// "Masks with a shape" and `Primitives` in primitive.rs.
@group(3) @binding(2)
var<storage> instances: array<PrimitiveInstance>;
// The bound on the parent walk, kept in step with `PARENT_CHAIN_LIMIT` in // The bound on the parent walk, kept in step with `PARENT_CHAIN_LIMIT` in
// render_state.rs, which walks the identical chain on the CPU side for // render_state.rs, which walks the identical chain on the CPU side for
@@ -117,15 +124,20 @@ struct WindowUniform {
dim: vec2<f32>, dim: vec2<f32>,
}; };
/// Mirrors `PrimitiveInstance` in data.rs -- the placement and what to
/// draw there. `x`/`y` are the `UiRegion`'s two spans.
struct PrimitiveInstance {
x: UiSpan,
y: UiSpan,
binding: u32,
idx: u32,
mask_idx: u32,
move_idx: u32,
}
/// A layer's draw order: one slot into `instances` per instance drawn.
struct InstanceInput { struct InstanceInput {
@location(0) x_start: vec2<f32>, @location(0) slot: u32,
@location(1) x_end: vec2<f32>,
@location(2) y_start: vec2<f32>,
@location(3) y_end: vec2<f32>,
@location(4) binding: u32,
@location(5) idx: u32,
@location(6) mask_idx: u32,
@location(7) move_idx: u32,
} }
struct VertexOutput { struct VertexOutput {
@@ -151,13 +163,14 @@ fn vs_main(
in: InstanceInput, in: InstanceInput,
) -> VertexOutput { ) -> VertexOutput {
var out: VertexOutput; var out: VertexOutput;
let inst = instances[in.slot];
let top_left_rel = vec2(in.x_start.x, in.y_start.x); let top_left_rel = vec2(inst.x.start.rel, inst.y.start.rel);
let top_left_abs = vec2(in.x_start.y, in.y_start.y); let top_left_abs = vec2(inst.x.start.abs, inst.y.start.abs);
let bot_right_rel = vec2(in.x_end.x, in.y_end.x); let bot_right_rel = vec2(inst.x.end.rel, inst.y.end.rel);
let bot_right_abs = vec2(in.x_end.y, in.y_end.y); let bot_right_abs = vec2(inst.x.end.abs, inst.y.end.abs);
let move_delta = resolve_move(in.move_idx); let move_delta = resolve_move(inst.move_idx);
let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs) + move_delta; let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs) + move_delta;
let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs) + move_delta; let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs) + move_delta;
let size = bot_right - top_left; let size = bot_right - top_left;
@@ -169,11 +182,11 @@ fn vs_main(
let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0; let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0;
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0); out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
out.uv = uv; out.uv = uv;
out.binding = in.binding; out.binding = inst.binding;
out.idx = in.idx; out.idx = inst.idx;
out.top_left = top_left; out.top_left = top_left;
out.bot_right = bot_right; out.bot_right = bot_right;
out.mask_idx = in.mask_idx; out.mask_idx = inst.mask_idx;
return out; return out;
} }
+2 -2
View File
@@ -26,7 +26,7 @@ pub struct Painter<'a> {
impl<'a> Painter<'a> { impl<'a> Painter<'a> {
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) { fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
let h = self.state.layers.write( let h = self.state.write_primitive(
self.layer, self.layer,
PrimitiveInst { PrimitiveInst {
id: self.id, id: self.id,
@@ -223,7 +223,7 @@ impl<'a> Painter<'a> {
/// the layer's one instanced draw, so it goes through /// the layer's one instanced draw, so it goes through
/// `Primitives::write_image` instead of `primitive_at`/`Primitive::vec`. /// `Primitives::write_image` instead of `primitive_at`/`Primitive::vec`.
fn write_image(&mut self, texture_idx: u32, region: UiRegion) { fn write_image(&mut self, texture_idx: u32, region: UiRegion) {
let h = self.state.layers.write_image( let h = self.state.write_image(
self.layer, self.layer,
self.id, self.id,
texture_idx, texture_idx,
+95 -24
View File
@@ -4,7 +4,7 @@ use std::time::{Duration, Instant};
use crate::{ use crate::{
ActiveData, IdLike, MaskIdx, MoveIdx, Painter, PixelRegion, PrimitiveLayers, RegionAlign, ActiveData, IdLike, MaskIdx, MoveIdx, Painter, PixelRegion, PrimitiveLayers, RegionAlign,
StrongWidget, UiRegion, UiRsc, UiVec2, WidgetId, Widgets, StrongWidget, UiRegion, UiRsc, UiVec2, WidgetId, Widgets,
render::{IMAGE_BINDING, MoveOffset}, render::{MoveOffset, Primitive, PrimitiveHandle, PrimitiveInst, Primitives},
util::{HashMap, HashSet, Id, Vec2}, util::{HashMap, HashSet, Id, Vec2},
}; };
@@ -25,6 +25,10 @@ pub enum RedrawKind {
pub struct UiRenderState { pub struct UiRenderState {
pub active: HashMap<WidgetId, ActiveData>, pub active: HashMap<WidgetId, ActiveData>,
/// Every primitive in the tree, in one arena -- see [`Primitives`] for
/// why it is not per layer.
pub primitives: Primitives,
/// What each layer draws, in order: slots into `primitives`.
pub layers: PrimitiveLayers, pub layers: PrimitiveLayers,
pub(super) output_size: Vec2, pub(super) output_size: Vec2,
/// Physical pixels per `dp` -- see `Len::dp`'s field doc. `1.0` (an /// Physical pixels per `dp` -- see `Len::dp`'s field doc. `1.0` (an
@@ -124,6 +128,7 @@ impl UiRenderState {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
active: Default::default(), active: Default::default(),
primitives: Default::default(),
layers: Default::default(), layers: Default::default(),
output_size: Vec2::ZERO, output_size: Vec2::ZERO,
density: 1.0, density: 1.0,
@@ -161,6 +166,70 @@ impl UiRenderState {
) )
} }
/// Writes a primitive into the arena and into `layer`'s draw order.
pub(super) fn write_primitive<P: Primitive>(
&mut self,
layer: usize,
inst: PrimitiveInst<P>,
) -> PrimitiveHandle {
let (slot, data_idx) = self.primitives.alloc(inst);
let pos = self.layers[layer].push(slot, false);
PrimitiveHandle {
layer,
pos,
slot,
data_idx,
binding: P::BINDING,
}
}
/// A standalone image, which draws with its own bind group rather
/// than sharing the layer's one instanced draw.
pub(super) fn write_image(
&mut self,
layer: usize,
id: WidgetId,
texture_idx: u32,
region: UiRegion,
mask_idx: MaskIdx,
move_idx: MoveIdx,
) -> PrimitiveHandle {
let slot = self
.primitives
.alloc_image(id, texture_idx, region, mask_idx, move_idx);
let pos = self.layers[layer].push(slot, true);
PrimitiveHandle {
layer,
pos,
slot,
data_idx: 0,
binding: crate::render::IMAGE_BINDING,
}
}
/// Compacts every layer's draw order around the primitives freed
/// this frame, corrects the handles that moved, and only then hands
/// the arena slots back for reuse -- that order is the whole reason
/// `Primitives::freed` exists. Once per frame, at the end of
/// [`Self::update`], so the harness (which has no renderer) applies
/// it exactly as a real backend does.
fn apply_free(&mut self) {
for (layer, order) in self.layers.iter_mut() {
for change in order.apply_free() {
let owner = self.primitives.owner(change.slot);
if let Some(active) = self.active.get_mut(&owner) {
for h in &mut active.primitives {
if h.layer == layer && h.slot == change.slot {
h.pos = change.pos;
break;
}
}
}
}
}
self.primitives.release_freed();
}
pub fn resize(&mut self, size: impl Into<Vec2>) { pub fn resize(&mut self, size: impl Into<Vec2>) {
self.output_size = size.into(); self.output_size = size.into();
self.resized = true; self.resized = true;
@@ -232,6 +301,10 @@ impl UiRenderState {
self.last_layout = layout_start.elapsed(); self.last_layout = layout_start.elapsed();
self.last_redraw_kind = kind; self.last_redraw_kind = kind;
self.frame_no += 1; self.frame_no += 1;
// After the redraw and before anything reads the frame: every
// slot freed above is still named by its layer's draw order until
// this runs.
self.apply_free();
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
debug_assert!(self.primitive_counts_agree(), "{}", self.orphan_report(rsc),); debug_assert!(self.primitive_counts_agree(), "{}", self.orphan_report(rsc),);
} }
@@ -379,7 +452,7 @@ impl UiRenderState {
// instead of redrawing. See LAYOUT.md section 3. // instead of redrawing. See LAYOUT.md section 3.
let from = active.region; let from = active.region;
for h in &active.primitives { for h in &active.primitives {
let r = self.layers[h.layer].region_mut(h); let r = self.primitives.region_mut(h);
*r = r.outside(&from).within(&region); *r = r.outside(&from).within(&region);
self.region_mut_count += 1; self.region_mut_count += 1;
} }
@@ -616,7 +689,8 @@ impl UiRenderState {
let mut active = self.active.remove(&id); let mut active = self.active.remove(&id);
if let Some(active) = &mut active { if let Some(active) = &mut active {
for h in &active.primitives { for h in &active.primitives {
let mask = self.layers.free(h); let mask = self.primitives.free(h);
self.layers[h.layer].free(h.pos, h.is_image());
if mask != MaskIdx::NONE { if mask != MaskIdx::NONE {
rsc.ui_mut().masks.remove(mask); rsc.ui_mut().masks.remove(mask);
} }
@@ -676,6 +750,7 @@ impl UiRenderState {
rsc.on_undraw(&active); rsc.on_undraw(&active);
} }
self.layers.clear(); self.layers.clear();
self.primitives.clear();
rsc.widgets_mut().needs_redraw.clear(); rsc.widgets_mut().needs_redraw.clear();
rsc.free(); rsc.free();
} }
@@ -718,8 +793,9 @@ impl UiRenderState {
/// Primitive instances still bound for the GPU whose owner is no /// Primitive instances still bound for the GPU whose owner is no
/// longer in `active`, or whose owner's `ActiveData` no longer names /// longer in `active`, or whose owner's `ActiveData` no longer names
/// them: a copy nothing can move, clip, resize or free, redrawn every /// them: a copy nothing can move, clip, resize or free, redrawn every
/// frame at whatever position it last had. `(layer, inst_idx, owner)` /// frame at whatever position it last had. `(slot, owner)` each --
/// each. /// the arena knows which primitive, not which layer's draw order still
/// names it.
/// ///
/// Asserted empty at the end of every [`Self::update`], because this /// Asserted empty at the end of every [`Self::update`], because this
/// is exactly the shape of the duplicated transcript row on Iris's /// is exactly the shape of the duplicated transcript row on Iris's
@@ -728,20 +804,15 @@ impl UiRenderState {
/// much alive -- it is the *earlier* set of primitives that got /// much alive -- it is the *earlier* set of primitives that got
/// stranded when the widget was drawn a second time without the first /// stranded when the widget was drawn a second time without the first
/// draw being freed. O(primitives), debug builds only. /// draw being freed. O(primitives), debug builds only.
pub fn orphaned_primitives(&self) -> Vec<(usize, usize, WidgetId)> { pub fn orphaned_primitives(&self) -> Vec<(u32, WidgetId)> {
let mut orphans = Vec::new(); let mut orphans = Vec::new();
for (layer, primitives) in self.layers.iter() { for (slot, owner, _) in self.primitives.live_instances() {
for (inst_idx, owner, is_image) in primitives.live_instances() { let owned = self
let owned = self.active.get(&owner).is_some_and(|a| { .active
a.primitives.iter().any(|h| { .get(&owner)
h.layer == layer .is_some_and(|a| a.primitives.iter().any(|h| h.slot == slot));
&& h.inst_idx == inst_idx if !owned {
&& (h.binding == IMAGE_BINDING) == is_image orphans.push((slot, owner));
})
});
if !owned {
orphans.push((layer, inst_idx, owner));
}
} }
} }
orphans orphans
@@ -755,7 +826,7 @@ impl UiRenderState {
/// transcript is tens of thousands and made a debug build on a phone /// transcript is tens of thousands and made a debug build on a phone
/// too slow to finish a benchmark run. /// too slow to finish a benchmark run.
fn primitive_counts_agree(&self) -> bool { fn primitive_counts_agree(&self) -> bool {
let live: usize = self.layers.iter().map(|(_, p)| p.live_count()).sum(); let live: usize = self.primitives.live_count();
let owned: usize = self.active.values().map(|a| a.primitives.len()).sum(); let owned: usize = self.active.values().map(|a| a.primitives.len()).sum();
live == owned live == owned
} }
@@ -769,10 +840,10 @@ impl UiRenderState {
let mut lines: Vec<String> = orphans let mut lines: Vec<String> = orphans
.iter() .iter()
.take(8) .take(8)
.map(|(layer, idx, owner)| { .map(|(slot, owner)| {
let alive = self.active.contains_key(owner); let alive = self.active.contains_key(owner);
format!( format!(
" layer {layer} instance {idx}: owner '{}' ({owner:?}), owner still active: {alive}", " instance {slot}: owner '{}' ({owner:?}), owner still active: {alive}",
rsc.widgets().label(*owner), rsc.widgets().label(*owner),
) )
}) })
@@ -817,12 +888,12 @@ impl UiRenderState {
} }
pub fn debug_layers(&self) { pub fn debug_layers(&self) {
for ((idx, depth), primitives) in self.layers.iter_depth() { for ((idx, depth), order) in self.layers.iter_depth() {
let indent = " ".repeat(depth * 2); let indent = " ".repeat(depth * 2);
let len = primitives.instances().len(); let len = order.order().len();
print!("{indent}{idx}: {len} primitives"); print!("{indent}{idx}: {len} primitives");
if len >= 1 { if len >= 1 {
print!(" ({})", primitives.instances()[0].binding); print!(" ({})", self.primitives.instance(order.order()[0]).binding);
} }
println!(); println!();
} }
+1 -1
View File
@@ -166,7 +166,7 @@ fn primitives_under(h: &Harness, id: WidgetId) -> Vec<MaskIdx> {
.primitives .primitives
.iter() .iter()
.filter(|p| p.binding != IMAGE_BINDING) .filter(|p| p.binding != IMAGE_BINDING)
.map(|p| h.render.layers[p.layer].instances()[p.inst_idx].mask_idx) .map(|p| h.render.primitives.instance(p.slot).mask_idx)
.collect(); .collect();
for child in &active.children { for child in &active.children {
out.extend(primitives_under(h, *child)); out.extend(primitives_under(h, *child));