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:
1 parent
b38e797db3
commit
203f53470c
8 files changed
+488
-339
No files matched your search
@@ -1,10 +1,6 @@
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
use crate::{
|
||||
UiRegion, WidgetId,
|
||||
render::{MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives},
|
||||
util::to_mut,
|
||||
};
|
||||
use crate::{render::LayerOrder, util::to_mut};
|
||||
|
||||
pub type LayerId = usize;
|
||||
|
||||
@@ -40,7 +36,10 @@ struct Child {
|
||||
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> {
|
||||
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> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
|
||||
@@ -8,6 +8,15 @@ pub struct WindowUniform {
|
||||
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)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct PrimitiveInstance {
|
||||
@@ -18,24 +27,17 @@ pub struct PrimitiveInstance {
|
||||
pub move_idx: MoveIdx,
|
||||
}
|
||||
|
||||
impl PrimitiveInstance {
|
||||
const ATTRIBS: [VertexAttribute; 8] = vertex_attr_array![
|
||||
0 => Float32x2,
|
||||
1 => Float32x2,
|
||||
2 => Float32x2,
|
||||
3 => Float32x2,
|
||||
4 => Uint32,
|
||||
5 => Uint32,
|
||||
6 => Uint32,
|
||||
7 => Uint32,
|
||||
];
|
||||
|
||||
pub fn desc() -> VertexBufferLayout<'static> {
|
||||
/// The vertex layout of a layer's draw order: one `u32` slot into the
|
||||
/// global instance arena per instance, stepped per instance. Everything a
|
||||
/// primitive is made of used to be here as eight vertex attributes; it
|
||||
/// moved into the storage buffer above so the fragment stage can read it
|
||||
/// too.
|
||||
pub fn instance_slot_layout() -> VertexBufferLayout<'static> {
|
||||
const ATTRIBS: [VertexAttribute; 1] = vertex_attr_array![0 => Uint32];
|
||||
VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Self>() as BufferAddress,
|
||||
array_stride: std::mem::size_of::<u32>() as BufferAddress,
|
||||
step_mode: VertexStepMode::Instance,
|
||||
attributes: &Self::ATTRIBS,
|
||||
}
|
||||
attributes: &ATTRIBS,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+93
-70
@@ -1,6 +1,10 @@
|
||||
use crate::{
|
||||
UiData, UiRenderState,
|
||||
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
|
||||
render::{
|
||||
data::{PrimitiveInstance, instance_slot_layout},
|
||||
texture::GpuTextures,
|
||||
util::ArrBuf,
|
||||
},
|
||||
util::{HashMap, Vec2},
|
||||
};
|
||||
use data::WindowUniform;
|
||||
@@ -120,6 +124,11 @@ impl WgpuErrorLog {
|
||||
pub struct UiRenderNode {
|
||||
uniform_group: BindGroup,
|
||||
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_group: BindGroup,
|
||||
|
||||
@@ -129,6 +138,9 @@ pub struct UiRenderNode {
|
||||
active: Vec<usize>,
|
||||
window_buffer: Buffer,
|
||||
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>,
|
||||
move_offsets: ArrBuf<MoveOffset>,
|
||||
/// Group 3: the masks and move-offsets storage buffers, on their own --
|
||||
@@ -146,16 +158,16 @@ pub struct UiRenderNode {
|
||||
masks_group: BindGroup,
|
||||
}
|
||||
|
||||
/// One layer's vertex buffers: the slots it draws, in order. The
|
||||
/// primitives themselves are in `UiRenderNode::instances`.
|
||||
struct RenderLayer {
|
||||
instance: ArrBuf<PrimitiveInstance>,
|
||||
primitives: PrimitiveBuffers,
|
||||
primitive_group: BindGroup,
|
||||
/// A standalone image's instances, kept apart from `instance` because
|
||||
/// each one draws with its own bind group -- see `UiRenderNode::draw`.
|
||||
image_instance: ArrBuf<PrimitiveInstance>,
|
||||
/// The texture slot each entry of `image_instance` draws with, in the
|
||||
/// same order, refreshed alongside it. Not stored in the vertex buffer
|
||||
/// itself because it names a bind group, not shader data.
|
||||
order: ArrBuf<u32>,
|
||||
/// A standalone image's slots, kept apart from `order` because each
|
||||
/// one draws with its own bind group -- see `UiRenderNode::draw`.
|
||||
images: ArrBuf<u32>,
|
||||
/// The texture slot each entry of `images` draws with, in the same
|
||||
/// order, refreshed alongside it. Not in the vertex buffer itself
|
||||
/// because it names a bind group, not shader data.
|
||||
image_tex_indices: Vec<u32>,
|
||||
}
|
||||
|
||||
@@ -163,6 +175,8 @@ impl UiRenderNode {
|
||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
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
|
||||
// by every primitive and every standalone image alike, and living
|
||||
// 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, &[]);
|
||||
for i in &self.active {
|
||||
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;
|
||||
}
|
||||
pass.set_bind_group(1, &layer.primitive_group, &[]);
|
||||
if layer.instance.len() > 0 {
|
||||
if layer.order.len() > 0 {
|
||||
pass.set_bind_group(2, &self.rsc_group, &[]);
|
||||
pass.set_vertex_buffer(0, layer.instance.buffer.slice(..));
|
||||
pass.draw(0..4, 0..layer.instance.len() as u32);
|
||||
pass.set_vertex_buffer(0, layer.order.buffer.slice(..));
|
||||
pass.draw(0..4, 0..layer.order.len() as u32);
|
||||
}
|
||||
// Images draw after this layer's rects and glyphs, one draw call
|
||||
// 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
|
||||
// list -- nothing before this relied on interleaving a rect
|
||||
// between two images at a particular position.
|
||||
if layer.image_instance.len() > 0 {
|
||||
pass.set_vertex_buffer(0, layer.image_instance.buffer.slice(..));
|
||||
if layer.images.len() > 0 {
|
||||
pass.set_vertex_buffer(0, layer.images.buffer.slice(..));
|
||||
for (k, &tex_idx) in layer.image_tex_indices.iter().enumerate() {
|
||||
pass.set_bind_group(2, self.textures.image_bind_group(tex_idx), &[]);
|
||||
pass.draw(0..4, k as u32..k as u32 + 1);
|
||||
@@ -206,67 +219,45 @@ impl UiRenderNode {
|
||||
ui_render: &mut UiRenderState,
|
||||
) -> FrameUpdateStats {
|
||||
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);
|
||||
for change in primitives.apply_free() {
|
||||
if let Some(inst) = ui_render.active.get_mut(&change.id) {
|
||||
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(
|
||||
let rlayer = self.layers.entry(i).or_insert_with(|| RenderLayer {
|
||||
order: ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
||||
"instance",
|
||||
"layer order",
|
||||
),
|
||||
primitives,
|
||||
primitive_group,
|
||||
image_instance: ArrBuf::new(
|
||||
images: ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
||||
"image instance",
|
||||
"layer image order",
|
||||
),
|
||||
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,
|
||||
&self.primitive_layout,
|
||||
rlayer.primitives.buffers(),
|
||||
);
|
||||
rlayer
|
||||
.image_instance
|
||||
.update(device, queue, primitives.image_instances());
|
||||
rlayer.image_tex_indices = primitives
|
||||
.image_instances()
|
||||
if order.updated {
|
||||
rlayer.order.update(device, queue, order.order());
|
||||
rlayer.images.update(device, queue, order.images());
|
||||
rlayer.image_tex_indices = order
|
||||
.images()
|
||||
.iter()
|
||||
.map(|inst| inst.idx)
|
||||
.map(|&slot| ui_render.primitives.instance(slot).idx)
|
||||
.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 {
|
||||
ui.masks.changed = false;
|
||||
self.masks.update(device, queue, &ui.masks[..])
|
||||
@@ -280,9 +271,14 @@ impl UiRenderNode {
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if masks_resized || moves_resized {
|
||||
self.masks_group =
|
||||
Self::masks_group(device, &self.masks_layout, &self.masks, &self.move_offsets);
|
||||
if masks_resized || moves_resized || instances_resized {
|
||||
self.masks_group = Self::masks_group(
|
||||
device,
|
||||
&self.masks_layout,
|
||||
&self.masks,
|
||||
&self.move_offsets,
|
||||
&self.instances,
|
||||
);
|
||||
}
|
||||
let rebuild_main = self.textures.update(&mut ui.textures, &self.rsc_layout);
|
||||
if rebuild_main {
|
||||
@@ -408,6 +404,14 @@ impl UiRenderNode {
|
||||
});
|
||||
|
||||
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(
|
||||
device,
|
||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||
@@ -422,7 +426,8 @@ impl UiRenderNode {
|
||||
let rsc_layout = Self::rsc_layout(device);
|
||||
let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager);
|
||||
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 {
|
||||
label: Some("UI Shape Pipeline Layout"),
|
||||
@@ -440,7 +445,7 @@ impl UiRenderNode {
|
||||
vertex: VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[PrimitiveInstance::desc()],
|
||||
buffers: &[instance_slot_layout()],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
@@ -486,6 +491,8 @@ impl UiRenderNode {
|
||||
Ok(Self {
|
||||
uniform_group,
|
||||
primitive_layout,
|
||||
primitives,
|
||||
primitive_group,
|
||||
rsc_layout,
|
||||
rsc_group,
|
||||
pipeline,
|
||||
@@ -493,6 +500,7 @@ impl UiRenderNode {
|
||||
layers: HashMap::default(),
|
||||
active: Vec::new(),
|
||||
textures: tex_manager,
|
||||
instances,
|
||||
masks,
|
||||
move_offsets,
|
||||
masks_layout,
|
||||
@@ -627,6 +635,16 @@ impl UiRenderNode {
|
||||
},
|
||||
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"),
|
||||
})
|
||||
@@ -637,6 +655,7 @@ impl UiRenderNode {
|
||||
layout: &BindGroupLayout,
|
||||
masks: &ArrBuf<Mask>,
|
||||
move_offsets: &ArrBuf<MoveOffset>,
|
||||
instances: &ArrBuf<PrimitiveInstance>,
|
||||
) -> BindGroup {
|
||||
device.create_bind_group(&BindGroupDescriptor {
|
||||
layout,
|
||||
@@ -649,6 +668,10 @@ impl UiRenderNode {
|
||||
binding: 1,
|
||||
resource: move_offsets.buffer.as_entire_binding(),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: instances.buffer.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
label: Some("ui masks"),
|
||||
})
|
||||
|
||||
+246
-179
@@ -11,46 +11,11 @@ use crate::{
|
||||
use bytemuck::Pod;
|
||||
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
|
||||
/// `Primitive::BINDING` because images have no `PrimitiveData` entry to key
|
||||
/// 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
|
||||
/// in `Primitives::images`, not `Primitives::instances`" to the code below.
|
||||
/// has to match the shader's `TEXTURE` constant and flag "this instance is
|
||||
/// drawn with its own bind group" to the code below.
|
||||
pub const IMAGE_BINDING: u32 = 1;
|
||||
|
||||
pub trait Primitive: Pod {
|
||||
@@ -134,18 +99,61 @@ macro_rules! primitives {
|
||||
(@count $t:tt) => { 1 };
|
||||
}
|
||||
|
||||
pub struct PrimitiveInst<P> {
|
||||
pub id: WidgetId,
|
||||
pub primitive: P,
|
||||
pub region: UiRegion,
|
||||
pub mask_idx: MaskIdx,
|
||||
pub move_idx: MoveIdx,
|
||||
/// Every primitive instance in the tree, in one arena that all layers
|
||||
/// share, plus the per-primitive data (`rects`, `glyphs`) they index.
|
||||
///
|
||||
/// **Why one arena rather than one per layer**, which is what this was:
|
||||
/// the fragment stage evaluates a *mask's* primitive at the masked pixel
|
||||
/// (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 {
|
||||
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,
|
||||
layer: usize,
|
||||
PrimitiveInst {
|
||||
id,
|
||||
primitive,
|
||||
@@ -153,154 +161,118 @@ impl Primitives {
|
||||
mask_idx,
|
||||
move_idx,
|
||||
}: PrimitiveInst<P>,
|
||||
) -> PrimitiveHandle {
|
||||
self.updated = true;
|
||||
let vec = P::vec(&mut self.data);
|
||||
let i = vec.add(primitive);
|
||||
let inst = PrimitiveInstance {
|
||||
) -> (u32, usize) {
|
||||
let data_idx = P::vec(&mut self.data).add(primitive);
|
||||
let slot = self.push(
|
||||
PrimitiveInstance {
|
||||
region,
|
||||
idx: i as u32,
|
||||
idx: data_idx as u32,
|
||||
mask_idx,
|
||||
move_idx,
|
||||
binding: P::BINDING,
|
||||
};
|
||||
let inst_i = if let Some(i) = self.free.pop() {
|
||||
self.instances[i] = inst;
|
||||
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)
|
||||
},
|
||||
id,
|
||||
);
|
||||
(slot, data_idx)
|
||||
}
|
||||
|
||||
/// Writes an image instance directly -- there is no `Primitive` impl for
|
||||
/// it to go through `write`, since it has nowhere in `PrimitiveData` to
|
||||
/// put a per-instance entry. `texture_idx` is the slot the bind group at
|
||||
/// draw time is chosen from, carried in the otherwise-unused `idx` field.
|
||||
pub fn write_image(
|
||||
/// A standalone image, which has no `PrimitiveData` entry to allocate
|
||||
/// -- its bind group already picks the texture, so `texture_idx` rides
|
||||
/// in the otherwise-unused `idx` field and names the bind group the
|
||||
/// draw call selects.
|
||||
pub fn alloc_image(
|
||||
&mut self,
|
||||
layer: usize,
|
||||
id: WidgetId,
|
||||
texture_idx: u32,
|
||||
region: UiRegion,
|
||||
mask_idx: MaskIdx,
|
||||
move_idx: MoveIdx,
|
||||
) -> PrimitiveHandle {
|
||||
self.updated = true;
|
||||
let inst = PrimitiveInstance {
|
||||
) -> u32 {
|
||||
self.push(
|
||||
PrimitiveInstance {
|
||||
region,
|
||||
idx: texture_idx,
|
||||
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;
|
||||
},
|
||||
id,
|
||||
)
|
||||
}
|
||||
|
||||
fn push(&mut self, inst: PrimitiveInstance, id: WidgetId) -> u32 {
|
||||
self.updated = true;
|
||||
let slot = if let Some(i) = self.reusable.pop() {
|
||||
self.instances[i] = inst;
|
||||
self.assoc[i] = id;
|
||||
i
|
||||
} else {
|
||||
let i = self.images.len();
|
||||
self.images.push(inst);
|
||||
self.image_assoc.push(id);
|
||||
i
|
||||
self.instances.push(inst);
|
||||
self.assoc.push(id);
|
||||
self.instances.len() - 1
|
||||
};
|
||||
PrimitiveHandle {
|
||||
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()
|
||||
slot as u32
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
self.updated = true;
|
||||
if h.binding == IMAGE_BINDING {
|
||||
self.image_free.push(h.inst_idx);
|
||||
self.images[h.inst_idx].mask_idx
|
||||
} else {
|
||||
let slot = h.slot as usize;
|
||||
if h.binding != IMAGE_BINDING {
|
||||
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
|
||||
/// the orphan check, so the O(primitives) walk below only runs on a
|
||||
/// frame that already looks wrong. See
|
||||
/// Hands this frame's freed slots back for reuse. Called once per
|
||||
/// frame from `UiRenderState::update`, **after** every layer has
|
||||
/// 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`].
|
||||
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,
|
||||
/// owner, is_image)` -- everything except the slots already handed to
|
||||
/// [`Self::free`] and waiting for [`Self::apply_free`] to compact them
|
||||
/// away. Only [`crate::UiRenderState::orphaned_primitives`] uses this,
|
||||
/// to check that every drawn primitive still belongs to a live widget.
|
||||
pub fn live_instances(&self) -> impl Iterator<Item = (usize, WidgetId, bool)> + '_ {
|
||||
let free: HashSet<usize> = self.free.iter().copied().collect();
|
||||
let image_free: HashSet<usize> = self.image_free.iter().copied().collect();
|
||||
let rects = (0..self.instances.len())
|
||||
.filter(move |i| !free.contains(i))
|
||||
.map(|i| (i, self.assoc[i], false));
|
||||
let images = (0..self.images.len())
|
||||
.filter(move |i| !image_free.contains(i))
|
||||
.map(|i| (i, self.image_assoc[i], true));
|
||||
rects.chain(images)
|
||||
/// Every live instance as `(slot, owner, is_image)` -- everything
|
||||
/// except the freed and the reusable. Only
|
||||
/// [`crate::UiRenderState::orphaned_primitives`] uses this, to check
|
||||
/// that every live primitive still belongs to a live widget.
|
||||
pub fn live_instances(&self) -> impl Iterator<Item = (u32, WidgetId, bool)> + '_ {
|
||||
let dead: HashSet<usize> = self.freed.iter().chain(&self.reusable).copied().collect();
|
||||
(0..self.instances.len())
|
||||
.filter(move |i| !dead.contains(i))
|
||||
.map(|i| {
|
||||
(
|
||||
i as u32,
|
||||
self.assoc[i],
|
||||
self.instances[i].binding == IMAGE_BINDING,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &PrimitiveData {
|
||||
@@ -311,44 +283,139 @@ impl Primitives {
|
||||
&self.instances
|
||||
}
|
||||
|
||||
pub fn instance(&self, slot: u32) -> &PrimitiveInstance {
|
||||
&self.instances[slot as usize]
|
||||
}
|
||||
|
||||
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
|
||||
self.updated = true;
|
||||
if h.binding == IMAGE_BINDING {
|
||||
&mut self.images[h.inst_idx].region
|
||||
&mut self.instances[h.slot as usize].region
|
||||
}
|
||||
}
|
||||
|
||||
/// One layer's draw order: the slots of the global arena it draws, in the
|
||||
/// order they were written. The vertex buffer of a layer is exactly this.
|
||||
///
|
||||
/// Both lists free with `swap_remove`, so a layer's draw order was already
|
||||
/// undefined before this split: nothing here may assume one primitive
|
||||
/// stays adjacent to another once anything in the layer has been freed.
|
||||
#[derive(Default)]
|
||||
pub struct LayerOrder {
|
||||
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.instances[h.inst_idx].region
|
||||
&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);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PrimitiveChange {
|
||||
pub id: WidgetId,
|
||||
/// Which of `Primitives::instances`/`Primitives::images` this change
|
||||
/// belongs to -- their `old`/`new` indices are independent, so a
|
||||
/// consumer matching only on `(layer, inst_idx)` could apply an image's
|
||||
/// renumbering to a rect's handle that happens to share the same index.
|
||||
/// 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 old: usize,
|
||||
pub new: usize,
|
||||
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)]
|
||||
pub struct PrimitiveHandle {
|
||||
pub layer: usize,
|
||||
pub inst_idx: usize,
|
||||
pub pos: usize,
|
||||
pub slot: u32,
|
||||
pub data_idx: usize,
|
||||
pub binding: u32,
|
||||
}
|
||||
|
||||
impl PrimitiveHandle {
|
||||
fn new<P: Primitive>(layer: usize, inst_idx: usize, data_idx: usize) -> Self {
|
||||
Self {
|
||||
layer,
|
||||
inst_idx,
|
||||
data_idx,
|
||||
binding: P::BINDING,
|
||||
pub fn is_image(&self) -> bool {
|
||||
self.binding == IMAGE_BINDING
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PrimitiveInst<P> {
|
||||
pub id: WidgetId,
|
||||
pub primitive: P,
|
||||
pub region: UiRegion,
|
||||
pub mask_idx: MaskIdx,
|
||||
pub move_idx: MoveIdx,
|
||||
}
|
||||
|
||||
primitives!(
|
||||
|
||||
@@ -83,6 +83,13 @@ var samp: sampler;
|
||||
var<storage> masks: array<Mask>;
|
||||
@group(3) @binding(1)
|
||||
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
|
||||
// render_state.rs, which walks the identical chain on the CPU side for
|
||||
@@ -117,15 +124,20 @@ struct WindowUniform {
|
||||
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 {
|
||||
@location(0) x_start: vec2<f32>,
|
||||
@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,
|
||||
@location(0) slot: u32,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@@ -151,13 +163,14 @@ fn vs_main(
|
||||
in: InstanceInput,
|
||||
) -> 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_abs = vec2(in.x_start.y, in.y_start.y);
|
||||
let bot_right_rel = vec2(in.x_end.x, in.y_end.x);
|
||||
let bot_right_abs = vec2(in.x_end.y, in.y_end.y);
|
||||
let top_left_rel = vec2(inst.x.start.rel, inst.y.start.rel);
|
||||
let top_left_abs = vec2(inst.x.start.abs, inst.y.start.abs);
|
||||
let bot_right_rel = vec2(inst.x.end.rel, inst.y.end.rel);
|
||||
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 bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs) + move_delta;
|
||||
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;
|
||||
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
||||
out.uv = uv;
|
||||
out.binding = in.binding;
|
||||
out.idx = in.idx;
|
||||
out.binding = inst.binding;
|
||||
out.idx = inst.idx;
|
||||
out.top_left = top_left;
|
||||
out.bot_right = bot_right;
|
||||
out.mask_idx = in.mask_idx;
|
||||
out.mask_idx = inst.mask_idx;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ pub struct Painter<'a> {
|
||||
|
||||
impl<'a> Painter<'a> {
|
||||
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,
|
||||
PrimitiveInst {
|
||||
id: self.id,
|
||||
@@ -223,7 +223,7 @@ impl<'a> Painter<'a> {
|
||||
/// the layer's one instanced draw, so it goes through
|
||||
/// `Primitives::write_image` instead of `primitive_at`/`Primitive::vec`.
|
||||
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.id,
|
||||
texture_idx,
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::time::{Duration, Instant};
|
||||
use crate::{
|
||||
ActiveData, IdLike, MaskIdx, MoveIdx, Painter, PixelRegion, PrimitiveLayers, RegionAlign,
|
||||
StrongWidget, UiRegion, UiRsc, UiVec2, WidgetId, Widgets,
|
||||
render::{IMAGE_BINDING, MoveOffset},
|
||||
render::{MoveOffset, Primitive, PrimitiveHandle, PrimitiveInst, Primitives},
|
||||
util::{HashMap, HashSet, Id, Vec2},
|
||||
};
|
||||
|
||||
@@ -25,6 +25,10 @@ pub enum RedrawKind {
|
||||
|
||||
pub struct UiRenderState {
|
||||
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(super) output_size: Vec2,
|
||||
/// Physical pixels per `dp` -- see `Len::dp`'s field doc. `1.0` (an
|
||||
@@ -124,6 +128,7 @@ impl UiRenderState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
active: Default::default(),
|
||||
primitives: Default::default(),
|
||||
layers: Default::default(),
|
||||
output_size: Vec2::ZERO,
|
||||
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>) {
|
||||
self.output_size = size.into();
|
||||
self.resized = true;
|
||||
@@ -232,6 +301,10 @@ impl UiRenderState {
|
||||
self.last_layout = layout_start.elapsed();
|
||||
self.last_redraw_kind = kind;
|
||||
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)]
|
||||
debug_assert!(self.primitive_counts_agree(), "{}", self.orphan_report(rsc),);
|
||||
}
|
||||
@@ -379,7 +452,7 @@ impl UiRenderState {
|
||||
// instead of redrawing. See LAYOUT.md section 3.
|
||||
let from = active.region;
|
||||
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(®ion);
|
||||
self.region_mut_count += 1;
|
||||
}
|
||||
@@ -616,7 +689,8 @@ impl UiRenderState {
|
||||
let mut active = self.active.remove(&id);
|
||||
if let Some(active) = &mut active {
|
||||
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 {
|
||||
rsc.ui_mut().masks.remove(mask);
|
||||
}
|
||||
@@ -676,6 +750,7 @@ impl UiRenderState {
|
||||
rsc.on_undraw(&active);
|
||||
}
|
||||
self.layers.clear();
|
||||
self.primitives.clear();
|
||||
rsc.widgets_mut().needs_redraw.clear();
|
||||
rsc.free();
|
||||
}
|
||||
@@ -718,8 +793,9 @@ impl UiRenderState {
|
||||
/// Primitive instances still bound for the GPU whose owner is no
|
||||
/// longer in `active`, or whose owner's `ActiveData` no longer names
|
||||
/// them: a copy nothing can move, clip, resize or free, redrawn every
|
||||
/// frame at whatever position it last had. `(layer, inst_idx, owner)`
|
||||
/// each.
|
||||
/// frame at whatever position it last had. `(slot, owner)` 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
|
||||
/// 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
|
||||
/// stranded when the widget was drawn a second time without the first
|
||||
/// 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();
|
||||
for (layer, primitives) in self.layers.iter() {
|
||||
for (inst_idx, owner, is_image) in primitives.live_instances() {
|
||||
let owned = self.active.get(&owner).is_some_and(|a| {
|
||||
a.primitives.iter().any(|h| {
|
||||
h.layer == layer
|
||||
&& h.inst_idx == inst_idx
|
||||
&& (h.binding == IMAGE_BINDING) == is_image
|
||||
})
|
||||
});
|
||||
for (slot, owner, _) in self.primitives.live_instances() {
|
||||
let owned = self
|
||||
.active
|
||||
.get(&owner)
|
||||
.is_some_and(|a| a.primitives.iter().any(|h| h.slot == slot));
|
||||
if !owned {
|
||||
orphans.push((layer, inst_idx, owner));
|
||||
}
|
||||
orphans.push((slot, owner));
|
||||
}
|
||||
}
|
||||
orphans
|
||||
@@ -755,7 +826,7 @@ impl UiRenderState {
|
||||
/// transcript is tens of thousands and made a debug build on a phone
|
||||
/// too slow to finish a benchmark run.
|
||||
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();
|
||||
live == owned
|
||||
}
|
||||
@@ -769,10 +840,10 @@ impl UiRenderState {
|
||||
let mut lines: Vec<String> = orphans
|
||||
.iter()
|
||||
.take(8)
|
||||
.map(|(layer, idx, owner)| {
|
||||
.map(|(slot, owner)| {
|
||||
let alive = self.active.contains_key(owner);
|
||||
format!(
|
||||
" layer {layer} instance {idx}: owner '{}' ({owner:?}), owner still active: {alive}",
|
||||
" instance {slot}: owner '{}' ({owner:?}), owner still active: {alive}",
|
||||
rsc.widgets().label(*owner),
|
||||
)
|
||||
})
|
||||
@@ -817,12 +888,12 @@ impl UiRenderState {
|
||||
}
|
||||
|
||||
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 len = primitives.instances().len();
|
||||
let len = order.order().len();
|
||||
print!("{indent}{idx}: {len} primitives");
|
||||
if len >= 1 {
|
||||
print!(" ({})", primitives.instances()[0].binding);
|
||||
print!(" ({})", self.primitives.instance(order.order()[0]).binding);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ fn primitives_under(h: &Harness, id: WidgetId) -> Vec<MaskIdx> {
|
||||
.primitives
|
||||
.iter()
|
||||
.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();
|
||||
for child in &active.children {
|
||||
out.extend(primitives_under(h, *child));
|
||||
|
||||
Reference in new issue
Block a user