Make a texture a registered primitive like any other

`write_texture` differed from `write` by one argument, which is what the
generic parameter was already for, so textures register as a primitive
with a `TexturePrimitive` holding the slot. `write_texture`,
`InstanceKind` and the layer's separate texture list are gone;
`DrawLayers` is back to `write` and `free`, with the kind carried in
`PrimitiveInst` as it carries everything else.

What differs between a texture and a rect is only what it samples, so
that is what registration says: `PrimitiveTexture::Atlas` binds the
shared atlas once for the layer, `PerInstance` binds the texture its own
data names and draws one instance at a time. One loop over a layer's
lists, one match on that.

`Pod` is back to being a supertrait of `Primitive` rather than the bound
itself. The guarantee is that a `PrimitiveKind<P>` is only minted by
`register::<P>` and `write` takes the kind and the value together, so a
primitive always has a list of its own to go in and the write does not
check anything: a list takes its stride from the type it was made for
instead of inferring it from the first write and asserting on the rest.

Also from reviewing this: a layer's lists and their buffers are created
only when that layer draws that primitive, so a primitive nobody uses no
longer costs two buffers in every layer -- which matters more now the set
is open-ended. `ListBuffers::update` takes the two things it uses rather
than the whole pipeline.

Verified again over all five cases: an image alone in a layer, three
images added and one deleted, the masked text-edit tab, the text-layout
tab and the default tab.
This commit is contained in:
iris committed 2026-09-13 14:46:08 -04:00
1 parent 89491a5949
commit 7b318e3271
5 files changed
+207 -185

No files matched your search

+4 -20
View File
@@ -1,8 +1,7 @@
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use crate::{ use crate::{
UiRegion, WidgetId, render::{LayerDraws, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
render::{LayerDraws, MaskIdx, PrimitiveHandle, PrimitiveKind},
util::to_mut, util::to_mut,
}; };
@@ -121,32 +120,17 @@ impl<T: Default> Layers<T> {
} }
impl DrawLayers { impl DrawLayers {
pub fn write<P: bytemuck::Pod>( pub fn write<P: Primitive>(
&mut self, &mut self,
layer: LayerId, layer: LayerId,
kind: PrimitiveKind<P>, info: PrimitiveInst<P>,
id: WidgetId,
primitive: P,
region: UiRegion,
mask_idx: MaskIdx,
) -> PrimitiveHandle { ) -> PrimitiveHandle {
self[layer].write(layer, kind, id, primitive, region, mask_idx) self[layer].write(layer, info)
} }
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx { pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
self[h.layer].free(h) self[h.layer].free(h)
} }
pub fn write_texture(
&mut self,
layer: LayerId,
id: WidgetId,
texture: u32,
region: UiRegion,
mask_idx: MaskIdx,
) -> PrimitiveHandle {
self[layer].write_texture(layer, id, texture, region, mask_idx)
}
} }
impl<T: Default> Default for Layers<T> { impl<T: Default> Default for Layers<T> {
+53 -65
View File
@@ -26,19 +26,15 @@ pub use data::{Mask, MaskIdx};
pub use primitive::*; pub use primitive::*;
const PRELUDE: &str = include_str!("./shader/prelude.wgsl"); const PRELUDE: &str = include_str!("./shader/prelude.wgsl");
const TEXTURE_SHADER: &str = include_str!("./shader/texture.wgsl");
pub struct UiRenderNode { pub struct UiRenderNode {
shared_layout: BindGroupLayout, shared_layout: BindGroupLayout,
shared_group: BindGroup, shared_group: BindGroup,
texture_data_layout: BindGroupLayout,
texture_layout: BindGroupLayout, texture_layout: BindGroupLayout,
format: TextureFormat, format: TextureFormat,
/// One per registered primitive, in id order. The texture pipeline is /// One per registered primitive, in id order.
/// apart because a texture binds group 2 per instance, not per draw.
primitives: Vec<PrimitivePipeline>, primitives: Vec<PrimitivePipeline>,
texture_pipeline: RenderPipeline,
layers: HashMap<usize, RenderLayer>, layers: HashMap<usize, RenderLayer>,
active: Vec<usize>, active: Vec<usize>,
@@ -50,11 +46,10 @@ pub struct UiRenderNode {
} }
struct RenderLayer { struct RenderLayer {
/// One per registered primitive, in id order, matching `LayerDraws`. /// One per registered primitive, in id order, matching `LayerDraws` --
primitives: Vec<ListBuffers>, /// `None` where this layer draws none, so a primitive nobody uses costs no
textures: ListBuffers, /// buffers per layer.
/// Which texture each entry of `textures` samples, in the same order. primitives: Vec<Option<ListBuffers>>,
texture_slots: Vec<u32>,
} }
/// What draws one registered primitive. The group 1 layout is its own rather /// What draws one registered primitive. The group 1 layout is its own rather
@@ -62,6 +57,7 @@ struct RenderLayer {
struct PrimitivePipeline { struct PrimitivePipeline {
data_layout: BindGroupLayout, data_layout: BindGroupLayout,
pipeline: RenderPipeline, pipeline: RenderPipeline,
texture: PrimitiveTexture,
} }
/// One list's vertex buffer and the data its shader reads at group 1. /// One list's vertex buffer and the data its shader reads at group 1.
@@ -69,6 +65,8 @@ struct ListBuffers {
instance: ArrBuf<PrimitiveInstance>, instance: ArrBuf<PrimitiveInstance>,
data: ArrBuf<u8>, data: ArrBuf<u8>,
group: Option<BindGroup>, group: Option<BindGroup>,
/// For a `PerInstance` primitive, the texture each instance binds.
slots: Vec<u32>,
} }
impl UiRenderNode { impl UiRenderNode {
@@ -80,36 +78,37 @@ impl UiRenderNode {
// under a texture. Ordering beyond that is what `Layers` is for -- // under a texture. Ordering beyond that is what `Layers` is for --
// freeing an instance swaps another into its place. // freeing an instance swaps another into its place.
for (id, list) in layer.primitives.iter().enumerate() { for (id, list) in layer.primitives.iter().enumerate() {
let Some(list) = list else {
continue;
};
let Some(group) = &list.group else { let Some(group) = &list.group else {
continue; continue;
}; };
pass.set_pipeline(&self.primitives[id].pipeline); let primitive = &self.primitives[id];
// Both after the pipeline: each primitive has its own pipeline pass.set_pipeline(&primitive.pipeline);
// layout, and a change drops the groups from where they differ. // Both groups after the pipeline: each primitive has its own
// pipeline layout, and a change drops the groups from where
// the two layouts differ.
pass.set_bind_group(1, group, &[]); pass.set_bind_group(1, group, &[]);
pass.set_bind_group(2, self.pages.group(), &[]);
pass.set_vertex_buffer(0, list.instance.buffer.slice(..)); pass.set_vertex_buffer(0, list.instance.buffer.slice(..));
match primitive.texture {
PrimitiveTexture::Atlas => {
pass.set_bind_group(2, self.pages.group(), &[]);
pass.draw(0..4, 0..list.instance.len() as u32); pass.draw(0..4, 0..list.instance.len() as u32);
} }
if !layer.texture_slots.is_empty() { PrimitiveTexture::PerInstance => {
// Group 1 too, unread as it is: a layer holding only an image for (i, &slot) in list.slots.iter().enumerate() {
// never ran the loop above, so nothing is bound there. let Some(texture) = self.textures.group(slot) else {
let Some(data) = &layer.textures.group else {
continue; continue;
}; };
pass.set_pipeline(&self.texture_pipeline); pass.set_bind_group(2, texture, &[]);
pass.set_bind_group(1, data, &[]);
pass.set_vertex_buffer(0, layer.textures.instance.buffer.slice(..));
for (i, &slot) in layer.texture_slots.iter().enumerate() {
let Some(group) = self.textures.group(slot) else {
continue;
};
pass.set_bind_group(2, group, &[]);
pass.draw(0..4, i as u32..i as u32 + 1); pass.draw(0..4, i as u32..i as u32 + 1);
} }
} }
} }
} }
}
}
pub fn update( pub fn update(
&mut self, &mut self,
@@ -135,35 +134,33 @@ impl UiRenderNode {
} }
} }
} }
let rlayer = self let rlayer = self.layers.entry(i).or_insert_with(RenderLayer::new);
.layers
.entry(i)
.or_insert_with(|| RenderLayer::new(device));
if draws.updated { if draws.updated {
rlayer rlayer
.primitives .primitives
.resize_with(draws.primitives().len(), || ListBuffers::new(device)); .resize_with(draws.primitives().len(), || None);
for (id, (list, draws)) in rlayer for (id, (buffers, list)) in rlayer
.primitives .primitives
.iter_mut() .iter_mut()
.zip(draws.primitives()) .zip(draws.primitives())
.enumerate() .enumerate()
{ {
let Some(list) = list else {
continue;
};
// Indexed, not zipped: a missing pipeline should say so // Indexed, not zipped: a missing pipeline should say so
// rather than quietly leave the list unbuilt. // rather than quietly leave the list unbuilt.
let layout = &self.primitives[id].data_layout; let primitive = &self.primitives[id];
list.update(device, queue, layout, draws); buffers
.get_or_insert_with(|| ListBuffers::new(device))
.update(
device,
queue,
primitive.texture,
&primitive.data_layout,
list,
);
} }
rlayer
.textures
.update(device, queue, &self.texture_data_layout, &draws.textures);
rlayer.texture_slots.clear();
// Read rather than cast: the payload is a byte vec, so it
// carries no alignment a `u32` slice could borrow.
let slots = draws.textures.data().as_chunks::<4>().0;
rlayer
.texture_slots
.extend(slots.iter().copied().map(u32::from_ne_bytes));
draws.updated = false; draws.updated = false;
} }
} }
@@ -218,30 +215,12 @@ impl UiRenderNode {
let pages = GpuPages::new(device, queue, &texture_layout, &sampler); let pages = GpuPages::new(device, queue, &texture_layout, &sampler);
let textures = GpuTextures::new(device, queue); let textures = GpuTextures::new(device, queue);
// A texture instance's data is the slot it binds, which its shader
// never reads -- but group 1 is in the layout, so it is bound anyway.
let texture_data_layout = Self::data_layout(device, size_of::<u32>() as u64);
let texture_pipeline = Self::pipeline(
device,
&Self::pipeline_layout(
device,
&shared_layout,
&texture_data_layout,
&texture_layout,
),
config.format,
TEXTURE_SHADER,
"texture",
);
Self { Self {
shared_layout, shared_layout,
shared_group, shared_group,
texture_data_layout,
texture_layout, texture_layout,
format: config.format, format: config.format,
primitives: Vec::new(), primitives: Vec::new(),
texture_pipeline,
window_buffer, window_buffer,
layers: HashMap::default(), layers: HashMap::default(),
active: Vec::new(), active: Vec::new(),
@@ -267,6 +246,7 @@ impl UiRenderNode {
self.primitives.push(PrimitivePipeline { self.primitives.push(PrimitivePipeline {
data_layout, data_layout,
pipeline, pipeline,
texture: source.texture,
}); });
} }
} }
@@ -438,11 +418,9 @@ impl UiRenderNode {
} }
impl RenderLayer { impl RenderLayer {
fn new(device: &Device) -> Self { fn new() -> Self {
Self { Self {
primitives: Vec::new(), primitives: Vec::new(),
textures: ListBuffers::new(device),
texture_slots: Vec::new(),
} }
} }
} }
@@ -461,6 +439,7 @@ impl ListBuffers {
"primitive data", "primitive data",
), ),
group: None, group: None,
slots: Vec::new(),
} }
} }
@@ -468,9 +447,18 @@ impl ListBuffers {
&mut self, &mut self,
device: &Device, device: &Device,
queue: &Queue, queue: &Queue,
texture: PrimitiveTexture,
layout: &BindGroupLayout, layout: &BindGroupLayout,
list: &InstanceList, list: &InstanceList,
) { ) {
if texture == PrimitiveTexture::PerInstance {
self.slots.clear();
// Read rather than cast: the payload is a byte vec, so it carries
// no alignment a `u32` slice could borrow.
let slots = list.data().as_chunks::<4>().0;
self.slots
.extend(slots.iter().copied().map(u32::from_ne_bytes));
}
self.instance.update(device, queue, list.instances()); self.instance.update(device, queue, list.instances());
let resized = self.data.update(device, queue, list.data()); let resized = self.data.update(device, queue, list.data());
// An empty list has no buffer big enough for one entry, and nothing // An empty list has no buffer big enough for one entry, and nothing
+115 -82
View File
@@ -7,6 +7,14 @@ use crate::{
}; };
use bytemuck::Pod; use bytemuck::Pod;
/// One instance of a registered primitive, laid out as the struct that
/// primitive's shader reads at `@group(1) @binding(0)`.
///
/// A `PrimitiveKind<P>` is only minted by `register::<P>`, and `write` takes
/// the kind and the value together, so holding one is the proof that `P` has a
/// list of its own to go in and a write needs no check.
pub trait Primitive: Pod {}
/// Which registered primitive an instance is, and so which list it lives in /// Which registered primitive an instance is, and so which list it lives in
/// and which pipeline draws it. The type ties a `write` to what its shader reads. /// and which pipeline draws it. The type ties a `write` to what its shader reads.
pub struct PrimitiveKind<P> { pub struct PrimitiveKind<P> {
@@ -37,6 +45,7 @@ impl<P> Copy for PrimitiveKind<P> {}
pub const RECT: PrimitiveKind<RectPrimitive> = PrimitiveKind::new(0); pub const RECT: PrimitiveKind<RectPrimitive> = PrimitiveKind::new(0);
pub const GLYPH: PrimitiveKind<GlyphPrimitive> = PrimitiveKind::new(1); pub const GLYPH: PrimitiveKind<GlyphPrimitive> = PrimitiveKind::new(1);
pub const TEXTURE: PrimitiveKind<TexturePrimitive> = PrimitiveKind::new(2);
/// Every primitive a ui can draw, in id order. Registering one is all the /// Every primitive a ui can draw, in id order. Registering one is all the
/// wiring it needs: its list, buffers, free list and pipeline follow, and /// wiring it needs: its list, buffers, free list and pipeline follow, and
@@ -44,6 +53,9 @@ pub const GLYPH: PrimitiveKind<GlyphPrimitive> = PrimitiveKind::new(1);
/// ///
/// A source is compiled after `prelude.wgsl` and supplies its data at /// A source is compiled after `prelude.wgsl` and supplies its data at
/// `@group(1) @binding(0)` and an `fs_main` shading one instance. /// `@group(1) @binding(0)` and an `fs_main` shading one instance.
///
/// Order is draw order within a layer, so a primitive registered later is
/// drawn over one registered earlier.
pub struct PrimitiveRegistry { pub struct PrimitiveRegistry {
kinds: Vec<PrimitiveSource>, kinds: Vec<PrimitiveSource>,
} }
@@ -54,30 +66,55 @@ pub struct PrimitiveSource {
/// Size of one instance's entry, which the renderer states as the group 1 /// Size of one instance's entry, which the renderer states as the group 1
/// binding's minimum rather than leaving it to be inferred. /// binding's minimum rather than leaving it to be inferred.
pub stride: u64, pub stride: u64,
pub texture: PrimitiveTexture,
}
/// What a primitive samples at group 2, which is the whole of why some of them
/// cannot share one instanced draw.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrimitiveTexture {
/// The shared glyph atlas, bound once for the layer.
Atlas,
/// The `Textures` slot in the first four bytes of its own data, bound for
/// that instance alone -- so one draw call each.
PerInstance,
} }
impl Default for PrimitiveRegistry { impl Default for PrimitiveRegistry {
fn default() -> Self { fn default() -> Self {
use PrimitiveTexture::*;
let mut registry = Self { kinds: Vec::new() }; let mut registry = Self { kinds: Vec::new() };
let rect = registry.register::<RectPrimitive>(include_str!("shader/rect.wgsl"), "rect"); let rect =
let glyph = registry.register::<GlyphPrimitive>(include_str!("shader/glyph.wgsl"), "glyph"); registry.register::<RectPrimitive>(include_str!("shader/rect.wgsl"), "rect", Atlas);
let glyph =
registry.register::<GlyphPrimitive>(include_str!("shader/glyph.wgsl"), "glyph", Atlas);
let texture = registry.register::<TexturePrimitive>(
include_str!("shader/texture.wgsl"),
"texture",
PerInstance,
);
// The built-ins have constant ids so a widget can name one without the // The built-ins have constant ids so a widget can name one without the
// registry; registering them first is what makes those constants true. // registry; registering them first is what makes those constants true.
assert_eq!((rect.id(), glyph.id()), (RECT.id(), GLYPH.id())); assert_eq!(
(rect.id(), glyph.id(), texture.id()),
(RECT.id(), GLYPH.id(), TEXTURE.id())
);
registry registry
} }
} }
impl PrimitiveRegistry { impl PrimitiveRegistry {
pub fn register<P: Pod>( pub fn register<P: Primitive>(
&mut self, &mut self,
wgsl: &'static str, wgsl: &'static str,
label: &'static str, label: &'static str,
texture: PrimitiveTexture,
) -> PrimitiveKind<P> { ) -> PrimitiveKind<P> {
self.kinds.push(PrimitiveSource { self.kinds.push(PrimitiveSource {
wgsl, wgsl,
label, label,
stride: size_of::<P>() as u64, stride: size_of::<P>() as u64,
texture,
}); });
PrimitiveKind::new(self.kinds.len() as u32 - 1) PrimitiveKind::new(self.kinds.len() as u32 - 1)
} }
@@ -87,21 +124,13 @@ impl PrimitiveRegistry {
} }
} }
/// Which of a layer's lists an instance is in. They index independently, so a /// One registered primitive's instances in one layer: the instances, the
/// handle or a renumbering naming only a position would be ambiguous. /// widget each belongs to, the slots waiting to be reused, and `stride` bytes
#[derive(Debug, Clone, Copy, PartialEq, Eq)] /// of that primitive's data per instance at the same index.
pub enum InstanceKind {
Primitive(u32),
Texture,
}
/// Instances, the widget each belongs to, the slots waiting to be reused, and
/// `stride` bytes of data per instance at the same index.
/// ///
/// That data is the struct a primitive's shader reads, or the slot a texture /// `stride` comes from the type the list was made for, so a write is never
/// binds. Keeping both here is what holds them in step through a /// checked against it. The data rides here rather than beside the list so the
/// `swap_remove`. /// two stay in step through a `swap_remove`.
#[derive(Default)]
pub struct InstanceList { pub struct InstanceList {
instances: Vec<PrimitiveInstance>, instances: Vec<PrimitiveInstance>,
assoc: Vec<WidgetId>, assoc: Vec<WidgetId>,
@@ -111,6 +140,16 @@ pub struct InstanceList {
} }
impl InstanceList { impl InstanceList {
fn new<P: Primitive>() -> Self {
Self {
instances: Vec::new(),
assoc: Vec::new(),
free: Vec::new(),
data: Vec::new(),
stride: size_of::<P>(),
}
}
pub fn instances(&self) -> &[PrimitiveInstance] { pub fn instances(&self) -> &[PrimitiveInstance] {
&self.instances &self.instances
} }
@@ -120,14 +159,6 @@ impl InstanceList {
} }
fn push(&mut self, id: WidgetId, inst: PrimitiveInstance, data: &[u8]) -> usize { fn push(&mut self, id: WidgetId, inst: PrimitiveInstance, data: &[u8]) -> usize {
if self.instances.is_empty() {
self.stride = data.len();
}
debug_assert_eq!(
data.len(),
self.stride,
"primitive written to the wrong list"
);
if let Some(i) = self.free.pop() { if let Some(i) = self.free.pop() {
self.instances[i] = inst; self.instances[i] = inst;
self.assoc[i] = id; self.assoc[i] = id;
@@ -147,7 +178,7 @@ impl InstanceList {
self.instances[i].mask_idx self.instances[i].mask_idx
} }
fn apply_free(&mut self, kind: InstanceKind) -> impl Iterator<Item = PrimitiveChange> { fn apply_free(&mut self, kind: u32) -> impl Iterator<Item = PrimitiveChange> {
self.free.sort_by(|a, b| b.cmp(a)); self.free.sort_by(|a, b| b.cmp(a));
let instances = &mut self.instances; let instances = &mut self.instances;
let assoc = &mut self.assoc; let assoc = &mut self.assoc;
@@ -173,11 +204,13 @@ impl InstanceList {
} }
} }
/// Everything one layer draws. A texture binds its own group 2, so it draws on /// Everything one layer draws, one list per registered primitive. They index
/// its own and cannot join the instanced draw a primitive gets. /// independently, so a handle or a renumbering naming only a position would be
/// ambiguous between them.
pub struct LayerDraws { pub struct LayerDraws {
primitives: Vec<InstanceList>, /// `None` until this layer draws that primitive, because only the write
pub textures: InstanceList, /// knows the type the list is for.
primitives: Vec<Option<InstanceList>>,
pub updated: bool, pub updated: bool,
} }
@@ -185,99 +218,85 @@ impl Default for LayerDraws {
fn default() -> Self { fn default() -> Self {
Self { Self {
primitives: Vec::new(), primitives: Vec::new(),
textures: InstanceList::default(),
updated: true, updated: true,
} }
} }
} }
impl LayerDraws { impl LayerDraws {
pub fn write<P: Pod>( pub fn write<P: Primitive>(
&mut self, &mut self,
layer: usize, layer: usize,
kind: PrimitiveKind<P>, PrimitiveInst {
id: WidgetId, kind,
primitive: P, id,
region: UiRegion, primitive,
mask_idx: MaskIdx, region,
mask_idx,
}: PrimitiveInst<P>,
) -> PrimitiveHandle { ) -> PrimitiveHandle {
self.updated = true; self.updated = true;
// Grown on first use rather than sized from the registry, which a
// layer cannot see.
if self.primitives.len() <= kind.id as usize { if self.primitives.len() <= kind.id as usize {
self.primitives self.primitives.resize_with(kind.id as usize + 1, || None);
.resize_with(kind.id as usize + 1, Default::default);
} }
let inst_idx = self.primitives[kind.id as usize].push( let inst_idx = self.primitives[kind.id as usize]
.get_or_insert_with(InstanceList::new::<P>)
.push(
id, id,
PrimitiveInstance { region, mask_idx }, PrimitiveInstance { region, mask_idx },
bytemuck::bytes_of(&primitive), bytemuck::bytes_of(&primitive),
); );
PrimitiveHandle { PrimitiveHandle {
layer, layer,
kind: InstanceKind::Primitive(kind.id), kind: kind.id,
inst_idx, inst_idx,
} }
} }
/// Writes an instance that samples the texture in slot `texture`, which is pub fn primitives(&self) -> &[Option<InstanceList>] {
/// bound for it alone rather than read from a buffer.
pub fn write_texture(
&mut self,
layer: usize,
id: WidgetId,
texture: u32,
region: UiRegion,
mask_idx: MaskIdx,
) -> PrimitiveHandle {
self.updated = true;
PrimitiveHandle {
layer,
kind: InstanceKind::Texture,
inst_idx: self.textures.push(
id,
PrimitiveInstance { region, mask_idx },
bytemuck::bytes_of(&texture),
),
}
}
pub fn primitives(&self) -> &[InstanceList] {
&self.primitives &self.primitives
} }
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> { pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
let Self { self.primitives
primitives,
textures,
..
} = self;
primitives
.iter_mut() .iter_mut()
.enumerate() .enumerate()
.flat_map(|(i, list)| list.apply_free(InstanceKind::Primitive(i as u32))) .filter_map(|(kind, list)| Some((kind as u32, list.as_mut()?)))
.chain(textures.apply_free(InstanceKind::Texture)) .flat_map(|(kind, list)| list.apply_free(kind))
} }
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx { pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
self.updated = true; self.updated = true;
self.list_mut(h.kind).free(h.inst_idx) self.list(h).free(h.inst_idx)
} }
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;
&mut self.list_mut(h.kind).instances[h.inst_idx].region &mut self.list(h).instances[h.inst_idx].region
} }
fn list_mut(&mut self, kind: InstanceKind) -> &mut InstanceList { /// A handle is only ever made by `write`, which is what created the list.
match kind { fn list(&mut self, h: &PrimitiveHandle) -> &mut InstanceList {
InstanceKind::Primitive(i) => &mut self.primitives[i as usize], self.primitives[h.kind as usize]
InstanceKind::Texture => &mut self.textures, .as_mut()
} .expect("handle names a primitive this layer never drew")
} }
} }
pub struct PrimitiveInst<P> {
pub kind: PrimitiveKind<P>,
pub id: WidgetId,
pub primitive: P,
pub region: UiRegion,
pub mask_idx: MaskIdx,
}
pub struct PrimitiveChange { pub struct PrimitiveChange {
pub id: WidgetId, pub id: WidgetId,
pub kind: InstanceKind, /// Which registered primitive's list moved, since they index separately.
pub kind: u32,
pub old: usize, pub old: usize,
pub new: usize, pub new: usize,
} }
@@ -285,7 +304,7 @@ pub struct PrimitiveChange {
#[derive(Debug)] #[derive(Debug)]
pub struct PrimitiveHandle { pub struct PrimitiveHandle {
pub layer: usize, pub layer: usize,
pub kind: InstanceKind, pub kind: u32,
pub inst_idx: usize, pub inst_idx: usize,
} }
@@ -300,6 +319,7 @@ pub struct RectPrimitive {
unsafe impl bytemuck::Pod for RectPrimitive {} unsafe impl bytemuck::Pod for RectPrimitive {}
unsafe impl bytemuck::Zeroable for RectPrimitive {} unsafe impl bytemuck::Zeroable for RectPrimitive {}
impl Primitive for RectPrimitive {}
impl RectPrimitive { impl RectPrimitive {
pub fn color(color: Color<u8>) -> Self { pub fn color(color: Color<u8>) -> Self {
@@ -327,3 +347,16 @@ pub struct GlyphPrimitive {
unsafe impl bytemuck::Pod for GlyphPrimitive {} unsafe impl bytemuck::Pod for GlyphPrimitive {}
unsafe impl bytemuck::Zeroable for GlyphPrimitive {} unsafe impl bytemuck::Zeroable for GlyphPrimitive {}
impl Primitive for GlyphPrimitive {}
/// One drawn image. Its shader reads nothing: the slot names the texture bound
/// for this instance alone, which is what `PrimitiveTexture::PerInstance` does.
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct TexturePrimitive {
pub slot: u32,
}
unsafe impl bytemuck::Pod for TexturePrimitive {}
unsafe impl bytemuck::Zeroable for TexturePrimitive {}
impl Primitive for TexturePrimitive {}
+29 -15
View File
@@ -1,9 +1,10 @@
use bytemuck::Pod;
use crate::{ use crate::{
Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData, Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId, TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
render::{GLYPH, GlyphPrimitive, Mask, MaskIdx, PrimitiveHandle, PrimitiveKind}, render::{
GLYPH, GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst,
PrimitiveKind, TEXTURE, TexturePrimitive,
},
util::Vec2, util::Vec2,
}; };
@@ -22,11 +23,22 @@ pub struct Painter<'a> {
} }
impl<'a> Painter<'a> { impl<'a> Painter<'a> {
fn primitive_at<P: Pod>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: UiRegion) { fn primitive_at<P: Primitive>(
let h = self &mut self,
.state kind: PrimitiveKind<P>,
.layers primitive: P,
.write(self.layer, kind, self.id, primitive, region, self.mask); region: UiRegion,
) {
let h = self.state.layers.write(
self.layer,
PrimitiveInst {
kind,
id: self.id,
primitive,
region,
mask_idx: self.mask,
},
);
self.push_primitive(h); self.push_primitive(h);
} }
@@ -39,11 +51,11 @@ impl<'a> Painter<'a> {
} }
/// Writes a primitive to be rendered /// Writes a primitive to be rendered
pub fn primitive<P: Pod>(&mut self, kind: PrimitiveKind<P>, primitive: P) { pub fn primitive<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P) {
self.primitive_at(kind, primitive, self.region) self.primitive_at(kind, primitive, self.region)
} }
pub fn primitive_within<P: Pod>( pub fn primitive_within<P: Primitive>(
&mut self, &mut self,
kind: PrimitiveKind<P>, kind: PrimitiveKind<P>,
primitive: P, primitive: P,
@@ -91,11 +103,13 @@ impl<'a> Painter<'a> {
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) { pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
self.textures.push(handle.clone()); self.textures.push(handle.clone());
let h = self.primitive_at(
self.state TEXTURE,
.layers TexturePrimitive {
.write_texture(self.layer, self.id, handle.slot(), region, self.mask); slot: handle.slot(),
self.push_primitive(h); },
region,
);
} }
pub fn render_text( pub fn render_text(
+6 -3
View File
@@ -245,9 +245,12 @@ impl UiRenderState {
pub fn debug_layers(&self) { pub fn debug_layers(&self) {
for ((idx, depth), draws) in self.layers.iter_depth() { for ((idx, depth), draws) in self.layers.iter_depth() {
let indent = " ".repeat(depth * 2); let indent = " ".repeat(depth * 2);
let primitives: usize = draws.primitives().iter().map(|l| l.instances().len()).sum(); let counts: Vec<String> = draws
let textures = draws.textures.instances().len(); .primitives()
println!("{indent}{idx}: {primitives} primitives, {textures} textures"); .iter()
.map(|l| l.as_ref().map_or(0, |l| l.instances().len()).to_string())
.collect();
println!("{indent}{idx}: [{}]", counts.join(", "));
} }
} }