From 7b318e3271484dc4a9493ee8dabfab60ff7b1e46 Mon Sep 17 00:00:00 2001
From: iris <2+iris@noreply.localhost>
Date: Sun, 13 Sep 2026 14:46:08 -0400
Subject: [PATCH] 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
` is only minted by
`register::
` 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.
---
core/src/primitive/layer.rs | 24 +---
core/src/render/mod.rs | 128 ++++++++++------------
core/src/render/primitive.rs | 205 ++++++++++++++++++++---------------
core/src/ui/painter.rs | 44 +++++---
core/src/ui/render_state.rs | 9 +-
5 files changed, 216 insertions(+), 194 deletions(-)
diff --git a/core/src/primitive/layer.rs b/core/src/primitive/layer.rs
index 51c4048..3fb2411 100644
--- a/core/src/primitive/layer.rs
+++ b/core/src/primitive/layer.rs
@@ -1,8 +1,7 @@
use std::ops::{Index, IndexMut};
use crate::{
- UiRegion, WidgetId,
- render::{LayerDraws, MaskIdx, PrimitiveHandle, PrimitiveKind},
+ render::{LayerDraws, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
util::to_mut,
};
@@ -121,32 +120,17 @@ impl Layers {
}
impl DrawLayers {
- pub fn write(
+ pub fn write(
&mut self,
layer: LayerId,
- kind: PrimitiveKind,
- id: WidgetId,
- primitive: P,
- region: UiRegion,
- mask_idx: MaskIdx,
+ info: PrimitiveInst
,
) -> PrimitiveHandle {
- self[layer].write(layer, kind, id, primitive, region, mask_idx)
+ self[layer].write(layer, info)
}
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
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 Default for Layers {
diff --git a/core/src/render/mod.rs b/core/src/render/mod.rs
index 9fe0777..98270bc 100644
--- a/core/src/render/mod.rs
+++ b/core/src/render/mod.rs
@@ -26,19 +26,15 @@ pub use data::{Mask, MaskIdx};
pub use primitive::*;
const PRELUDE: &str = include_str!("./shader/prelude.wgsl");
-const TEXTURE_SHADER: &str = include_str!("./shader/texture.wgsl");
pub struct UiRenderNode {
shared_layout: BindGroupLayout,
shared_group: BindGroup,
- texture_data_layout: BindGroupLayout,
texture_layout: BindGroupLayout,
format: TextureFormat,
- /// One per registered primitive, in id order. The texture pipeline is
- /// apart because a texture binds group 2 per instance, not per draw.
+ /// One per registered primitive, in id order.
primitives: Vec,
- texture_pipeline: RenderPipeline,
layers: HashMap,
active: Vec,
@@ -50,11 +46,10 @@ pub struct UiRenderNode {
}
struct RenderLayer {
- /// One per registered primitive, in id order, matching `LayerDraws`.
- primitives: Vec,
- textures: ListBuffers,
- /// Which texture each entry of `textures` samples, in the same order.
- texture_slots: Vec,
+ /// One per registered primitive, in id order, matching `LayerDraws` --
+ /// `None` where this layer draws none, so a primitive nobody uses costs no
+ /// buffers per layer.
+ primitives: Vec>,
}
/// What draws one registered primitive. The group 1 layout is its own rather
@@ -62,6 +57,7 @@ struct RenderLayer {
struct PrimitivePipeline {
data_layout: BindGroupLayout,
pipeline: RenderPipeline,
+ texture: PrimitiveTexture,
}
/// One list's vertex buffer and the data its shader reads at group 1.
@@ -69,6 +65,8 @@ struct ListBuffers {
instance: ArrBuf,
data: ArrBuf,
group: Option,
+ /// For a `PerInstance` primitive, the texture each instance binds.
+ slots: Vec,
}
impl UiRenderNode {
@@ -80,32 +78,33 @@ impl UiRenderNode {
// under a texture. Ordering beyond that is what `Layers` is for --
// freeing an instance swaps another into its place.
for (id, list) in layer.primitives.iter().enumerate() {
+ let Some(list) = list else {
+ continue;
+ };
let Some(group) = &list.group else {
continue;
};
- pass.set_pipeline(&self.primitives[id].pipeline);
- // Both after the pipeline: each primitive has its own pipeline
- // layout, and a change drops the groups from where they differ.
+ let primitive = &self.primitives[id];
+ pass.set_pipeline(&primitive.pipeline);
+ // 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(2, self.pages.group(), &[]);
pass.set_vertex_buffer(0, list.instance.buffer.slice(..));
- pass.draw(0..4, 0..list.instance.len() as u32);
- }
- if !layer.texture_slots.is_empty() {
- // Group 1 too, unread as it is: a layer holding only an image
- // never ran the loop above, so nothing is bound there.
- let Some(data) = &layer.textures.group else {
- continue;
- };
- pass.set_pipeline(&self.texture_pipeline);
- 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);
+ match primitive.texture {
+ PrimitiveTexture::Atlas => {
+ pass.set_bind_group(2, self.pages.group(), &[]);
+ pass.draw(0..4, 0..list.instance.len() as u32);
+ }
+ PrimitiveTexture::PerInstance => {
+ for (i, &slot) in list.slots.iter().enumerate() {
+ let Some(texture) = self.textures.group(slot) else {
+ continue;
+ };
+ pass.set_bind_group(2, texture, &[]);
+ pass.draw(0..4, i as u32..i as u32 + 1);
+ }
+ }
}
}
}
@@ -135,35 +134,33 @@ impl UiRenderNode {
}
}
}
- let rlayer = self
- .layers
- .entry(i)
- .or_insert_with(|| RenderLayer::new(device));
+ let rlayer = self.layers.entry(i).or_insert_with(RenderLayer::new);
if draws.updated {
rlayer
.primitives
- .resize_with(draws.primitives().len(), || ListBuffers::new(device));
- for (id, (list, draws)) in rlayer
+ .resize_with(draws.primitives().len(), || None);
+ for (id, (buffers, list)) in rlayer
.primitives
.iter_mut()
.zip(draws.primitives())
.enumerate()
{
+ let Some(list) = list else {
+ continue;
+ };
// Indexed, not zipped: a missing pipeline should say so
// rather than quietly leave the list unbuilt.
- let layout = &self.primitives[id].data_layout;
- list.update(device, queue, layout, draws);
+ let primitive = &self.primitives[id];
+ 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;
}
}
@@ -218,30 +215,12 @@ impl UiRenderNode {
let pages = GpuPages::new(device, queue, &texture_layout, &sampler);
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::() 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 {
shared_layout,
shared_group,
- texture_data_layout,
texture_layout,
format: config.format,
primitives: Vec::new(),
- texture_pipeline,
window_buffer,
layers: HashMap::default(),
active: Vec::new(),
@@ -267,6 +246,7 @@ impl UiRenderNode {
self.primitives.push(PrimitivePipeline {
data_layout,
pipeline,
+ texture: source.texture,
});
}
}
@@ -438,11 +418,9 @@ impl UiRenderNode {
}
impl RenderLayer {
- fn new(device: &Device) -> Self {
+ fn new() -> Self {
Self {
primitives: Vec::new(),
- textures: ListBuffers::new(device),
- texture_slots: Vec::new(),
}
}
}
@@ -461,6 +439,7 @@ impl ListBuffers {
"primitive data",
),
group: None,
+ slots: Vec::new(),
}
}
@@ -468,9 +447,18 @@ impl ListBuffers {
&mut self,
device: &Device,
queue: &Queue,
+ texture: PrimitiveTexture,
layout: &BindGroupLayout,
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());
let resized = self.data.update(device, queue, list.data());
// An empty list has no buffer big enough for one entry, and nothing
diff --git a/core/src/render/primitive.rs b/core/src/render/primitive.rs
index 048ebea..0b42b1f 100644
--- a/core/src/render/primitive.rs
+++ b/core/src/render/primitive.rs
@@ -7,6 +7,14 @@ use crate::{
};
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` is only minted by `register::
`, 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
/// and which pipeline draws it. The type ties a `write` to what its shader reads.
pub struct PrimitiveKind
{
@@ -37,6 +45,7 @@ impl
Copy for PrimitiveKind
{}
pub const RECT: PrimitiveKind = PrimitiveKind::new(0);
pub const GLYPH: PrimitiveKind = PrimitiveKind::new(1);
+pub const TEXTURE: PrimitiveKind = PrimitiveKind::new(2);
/// 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
@@ -44,6 +53,9 @@ pub const GLYPH: PrimitiveKind = PrimitiveKind::new(1);
///
/// A source is compiled after `prelude.wgsl` and supplies its data at
/// `@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 {
kinds: Vec,
}
@@ -54,30 +66,55 @@ pub struct PrimitiveSource {
/// Size of one instance's entry, which the renderer states as the group 1
/// binding's minimum rather than leaving it to be inferred.
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 {
fn default() -> Self {
+ use PrimitiveTexture::*;
let mut registry = Self { kinds: Vec::new() };
- let rect = registry.register::(include_str!("shader/rect.wgsl"), "rect");
- let glyph = registry.register::(include_str!("shader/glyph.wgsl"), "glyph");
+ let rect =
+ registry.register::(include_str!("shader/rect.wgsl"), "rect", Atlas);
+ let glyph =
+ registry.register::(include_str!("shader/glyph.wgsl"), "glyph", Atlas);
+ let texture = registry.register::(
+ include_str!("shader/texture.wgsl"),
+ "texture",
+ PerInstance,
+ );
// The built-ins have constant ids so a widget can name one without the
// 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
}
}
impl PrimitiveRegistry {
- pub fn register(
+ pub fn register(
&mut self,
wgsl: &'static str,
label: &'static str,
+ texture: PrimitiveTexture,
) -> PrimitiveKind {
self.kinds.push(PrimitiveSource {
wgsl,
label,
stride: size_of::
() as u64,
+ texture,
});
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
-/// handle or a renumbering naming only a position would be ambiguous.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-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.
+/// One registered primitive's instances in one layer: the instances, the
+/// widget each belongs to, the slots waiting to be reused, and `stride` bytes
+/// of that primitive's data per instance at the same index.
///
-/// That data is the struct a primitive's shader reads, or the slot a texture
-/// binds. Keeping both here is what holds them in step through a
-/// `swap_remove`.
-#[derive(Default)]
+/// `stride` comes from the type the list was made for, so a write is never
+/// checked against it. The data rides here rather than beside the list so the
+/// two stay in step through a `swap_remove`.
pub struct InstanceList {
instances: Vec,
assoc: Vec,
@@ -111,6 +140,16 @@ pub struct InstanceList {
}
impl InstanceList {
+ fn new() -> Self {
+ Self {
+ instances: Vec::new(),
+ assoc: Vec::new(),
+ free: Vec::new(),
+ data: Vec::new(),
+ stride: size_of::(),
+ }
+ }
+
pub fn instances(&self) -> &[PrimitiveInstance] {
&self.instances
}
@@ -120,14 +159,6 @@ impl InstanceList {
}
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() {
self.instances[i] = inst;
self.assoc[i] = id;
@@ -147,7 +178,7 @@ impl InstanceList {
self.instances[i].mask_idx
}
- fn apply_free(&mut self, kind: InstanceKind) -> impl Iterator- {
+ fn apply_free(&mut self, kind: u32) -> impl Iterator
- {
self.free.sort_by(|a, b| b.cmp(a));
let instances = &mut self.instances;
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
-/// its own and cannot join the instanced draw a primitive gets.
+/// Everything one layer draws, one list per registered primitive. They index
+/// independently, so a handle or a renumbering naming only a position would be
+/// ambiguous between them.
pub struct LayerDraws {
- primitives: Vec
,
- pub textures: InstanceList,
+ /// `None` until this layer draws that primitive, because only the write
+ /// knows the type the list is for.
+ primitives: Vec>,
pub updated: bool,
}
@@ -185,99 +218,85 @@ impl Default for LayerDraws {
fn default() -> Self {
Self {
primitives: Vec::new(),
- textures: InstanceList::default(),
updated: true,
}
}
}
impl LayerDraws {
- pub fn write(
+ pub fn write(
&mut self,
layer: usize,
- kind: PrimitiveKind,
- id: WidgetId,
- primitive: P,
- region: UiRegion,
- mask_idx: MaskIdx,
+ PrimitiveInst {
+ kind,
+ id,
+ primitive,
+ region,
+ mask_idx,
+ }: PrimitiveInst
,
) -> PrimitiveHandle {
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 {
- self.primitives
- .resize_with(kind.id as usize + 1, Default::default);
+ self.primitives.resize_with(kind.id as usize + 1, || None);
}
- let inst_idx = self.primitives[kind.id as usize].push(
- id,
- PrimitiveInstance { region, mask_idx },
- bytemuck::bytes_of(&primitive),
- );
+ let inst_idx = self.primitives[kind.id as usize]
+ .get_or_insert_with(InstanceList::new::
)
+ .push(
+ id,
+ PrimitiveInstance { region, mask_idx },
+ bytemuck::bytes_of(&primitive),
+ );
PrimitiveHandle {
layer,
- kind: InstanceKind::Primitive(kind.id),
+ kind: kind.id,
inst_idx,
}
}
- /// Writes an instance that samples the texture in slot `texture`, which is
- /// 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] {
+ pub fn primitives(&self) -> &[Option] {
&self.primitives
}
pub fn apply_free(&mut self) -> impl Iterator- {
- let Self {
- primitives,
- textures,
- ..
- } = self;
- primitives
+ self.primitives
.iter_mut()
.enumerate()
- .flat_map(|(i, list)| list.apply_free(InstanceKind::Primitive(i as u32)))
- .chain(textures.apply_free(InstanceKind::Texture))
+ .filter_map(|(kind, list)| Some((kind as u32, list.as_mut()?)))
+ .flat_map(|(kind, list)| list.apply_free(kind))
}
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
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 {
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 {
- match kind {
- InstanceKind::Primitive(i) => &mut self.primitives[i as usize],
- InstanceKind::Texture => &mut self.textures,
- }
+ /// A handle is only ever made by `write`, which is what created the list.
+ fn list(&mut self, h: &PrimitiveHandle) -> &mut InstanceList {
+ self.primitives[h.kind as usize]
+ .as_mut()
+ .expect("handle names a primitive this layer never drew")
}
}
+pub struct PrimitiveInst
{
+ pub kind: PrimitiveKind
,
+ pub id: WidgetId,
+ pub primitive: P,
+ pub region: UiRegion,
+ pub mask_idx: MaskIdx,
+}
+
pub struct PrimitiveChange {
pub id: WidgetId,
- pub kind: InstanceKind,
+ /// Which registered primitive's list moved, since they index separately.
+ pub kind: u32,
pub old: usize,
pub new: usize,
}
@@ -285,7 +304,7 @@ pub struct PrimitiveChange {
#[derive(Debug)]
pub struct PrimitiveHandle {
pub layer: usize,
- pub kind: InstanceKind,
+ pub kind: u32,
pub inst_idx: usize,
}
@@ -300,6 +319,7 @@ pub struct RectPrimitive {
unsafe impl bytemuck::Pod for RectPrimitive {}
unsafe impl bytemuck::Zeroable for RectPrimitive {}
+impl Primitive for RectPrimitive {}
impl RectPrimitive {
pub fn color(color: Color) -> Self {
@@ -327,3 +347,16 @@ pub struct GlyphPrimitive {
unsafe impl bytemuck::Pod 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 {}
diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs
index 7471f72..c0135bd 100644
--- a/core/src/ui/painter.rs
+++ b/core/src/ui/painter.rs
@@ -1,9 +1,10 @@
-use bytemuck::Pod;
-
use crate::{
Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
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,
};
@@ -22,11 +23,22 @@ pub struct Painter<'a> {
}
impl<'a> Painter<'a> {
- fn primitive_at(&mut self, kind: PrimitiveKind, primitive: P, region: UiRegion) {
- let h = self
- .state
- .layers
- .write(self.layer, kind, self.id, primitive, region, self.mask);
+ fn primitive_at(
+ &mut self,
+ kind: PrimitiveKind,
+ primitive: P,
+ 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);
}
@@ -39,11 +51,11 @@ impl<'a> Painter<'a> {
}
/// Writes a primitive to be rendered
- pub fn primitive(&mut self, kind: PrimitiveKind, primitive: P) {
+ pub fn primitive(&mut self, kind: PrimitiveKind, primitive: P) {
self.primitive_at(kind, primitive, self.region)
}
- pub fn primitive_within(
+ pub fn primitive_within(
&mut self,
kind: PrimitiveKind,
primitive: P,
@@ -91,11 +103,13 @@ impl<'a> Painter<'a> {
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
self.textures.push(handle.clone());
- let h =
- self.state
- .layers
- .write_texture(self.layer, self.id, handle.slot(), region, self.mask);
- self.push_primitive(h);
+ self.primitive_at(
+ TEXTURE,
+ TexturePrimitive {
+ slot: handle.slot(),
+ },
+ region,
+ );
}
pub fn render_text(
diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs
index 140de06..4bd7a90 100644
--- a/core/src/ui/render_state.rs
+++ b/core/src/ui/render_state.rs
@@ -245,9 +245,12 @@ impl UiRenderState {
pub fn debug_layers(&self) {
for ((idx, depth), draws) in self.layers.iter_depth() {
let indent = " ".repeat(depth * 2);
- let primitives: usize = draws.primitives().iter().map(|l| l.instances().len()).sum();
- let textures = draws.textures.instances().len();
- println!("{indent}{idx}: {primitives} primitives, {textures} textures");
+ let counts: Vec = draws
+ .primitives()
+ .iter()
+ .map(|l| l.as_ref().map_or(0, |l| l.instances().len()).to_string())
+ .collect();
+ println!("{indent}{idx}: [{}]", counts.join(", "));
}
}