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:
1 parent
89491a5949
commit
7b318e3271
5 files changed
+207
-185
No files matched your search
@@ -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<T: Default> Layers<T> {
|
||||
}
|
||||
|
||||
impl DrawLayers {
|
||||
pub fn write<P: bytemuck::Pod>(
|
||||
pub fn write<P: Primitive>(
|
||||
&mut self,
|
||||
layer: LayerId,
|
||||
kind: PrimitiveKind<P>,
|
||||
id: WidgetId,
|
||||
primitive: P,
|
||||
region: UiRegion,
|
||||
mask_idx: MaskIdx,
|
||||
info: PrimitiveInst<P>,
|
||||
) -> 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<T: Default> Default for Layers<T> {
|
||||
|
||||
+53
-65
@@ -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<PrimitivePipeline>,
|
||||
texture_pipeline: RenderPipeline,
|
||||
|
||||
layers: HashMap<usize, RenderLayer>,
|
||||
active: Vec<usize>,
|
||||
@@ -50,11 +46,10 @@ pub struct UiRenderNode {
|
||||
}
|
||||
|
||||
struct RenderLayer {
|
||||
/// One per registered primitive, in id order, matching `LayerDraws`.
|
||||
primitives: Vec<ListBuffers>,
|
||||
textures: ListBuffers,
|
||||
/// Which texture each entry of `textures` samples, in the same order.
|
||||
texture_slots: Vec<u32>,
|
||||
/// 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<Option<ListBuffers>>,
|
||||
}
|
||||
|
||||
/// 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<PrimitiveInstance>,
|
||||
data: ArrBuf<u8>,
|
||||
group: Option<BindGroup>,
|
||||
/// For a `PerInstance` primitive, the texture each instance binds.
|
||||
slots: Vec<u32>,
|
||||
}
|
||||
|
||||
impl UiRenderNode {
|
||||
@@ -80,36 +78,37 @@ 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(..));
|
||||
match primitive.texture {
|
||||
PrimitiveTexture::Atlas => {
|
||||
pass.set_bind_group(2, self.pages.group(), &[]);
|
||||
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 {
|
||||
PrimitiveTexture::PerInstance => {
|
||||
for (i, &slot) in list.slots.iter().enumerate() {
|
||||
let Some(texture) = self.textures.group(slot) 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.set_bind_group(2, texture, &[]);
|
||||
pass.draw(0..4, i as u32..i as u32 + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
@@ -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::<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 {
|
||||
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
|
||||
|
||||
+115
-82
@@ -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<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
|
||||
/// and which pipeline draws it. The type ties a `write` to what its shader reads.
|
||||
pub struct PrimitiveKind<P> {
|
||||
@@ -37,6 +45,7 @@ impl<P> Copy for PrimitiveKind<P> {}
|
||||
|
||||
pub const RECT: PrimitiveKind<RectPrimitive> = PrimitiveKind::new(0);
|
||||
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
|
||||
/// 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
|
||||
/// `@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<PrimitiveSource>,
|
||||
}
|
||||
@@ -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::<RectPrimitive>(include_str!("shader/rect.wgsl"), "rect");
|
||||
let glyph = registry.register::<GlyphPrimitive>(include_str!("shader/glyph.wgsl"), "glyph");
|
||||
let rect =
|
||||
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
|
||||
// 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<P: Pod>(
|
||||
pub fn register<P: Primitive>(
|
||||
&mut self,
|
||||
wgsl: &'static str,
|
||||
label: &'static str,
|
||||
texture: PrimitiveTexture,
|
||||
) -> PrimitiveKind<P> {
|
||||
self.kinds.push(PrimitiveSource {
|
||||
wgsl,
|
||||
label,
|
||||
stride: size_of::<P>() 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<PrimitiveInstance>,
|
||||
assoc: Vec<WidgetId>,
|
||||
@@ -111,6 +140,16 @@ pub struct 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] {
|
||||
&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<Item = PrimitiveChange> {
|
||||
fn apply_free(&mut self, kind: u32) -> impl Iterator<Item = PrimitiveChange> {
|
||||
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<InstanceList>,
|
||||
pub textures: InstanceList,
|
||||
/// `None` until this layer draws that primitive, because only the write
|
||||
/// knows the type the list is for.
|
||||
primitives: Vec<Option<InstanceList>>,
|
||||
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<P: Pod>(
|
||||
pub fn write<P: Primitive>(
|
||||
&mut self,
|
||||
layer: usize,
|
||||
kind: PrimitiveKind<P>,
|
||||
id: WidgetId,
|
||||
primitive: P,
|
||||
region: UiRegion,
|
||||
mask_idx: MaskIdx,
|
||||
PrimitiveInst {
|
||||
kind,
|
||||
id,
|
||||
primitive,
|
||||
region,
|
||||
mask_idx,
|
||||
}: PrimitiveInst<P>,
|
||||
) -> 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(
|
||||
let inst_idx = self.primitives[kind.id as usize]
|
||||
.get_or_insert_with(InstanceList::new::<P>)
|
||||
.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<InstanceList>] {
|
||||
&self.primitives
|
||||
}
|
||||
|
||||
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
||||
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<P> {
|
||||
pub kind: PrimitiveKind<P>,
|
||||
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<u8>) -> 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 {}
|
||||
+29
-15
@@ -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<P: Pod>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: UiRegion) {
|
||||
let h = self
|
||||
.state
|
||||
.layers
|
||||
.write(self.layer, kind, self.id, primitive, region, self.mask);
|
||||
fn primitive_at<P: Primitive>(
|
||||
&mut self,
|
||||
kind: PrimitiveKind<P>,
|
||||
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<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)
|
||||
}
|
||||
|
||||
pub fn primitive_within<P: Pod>(
|
||||
pub fn primitive_within<P: Primitive>(
|
||||
&mut self,
|
||||
kind: PrimitiveKind<P>,
|
||||
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(
|
||||
|
||||
@@ -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<String> = draws
|
||||
.primitives()
|
||||
.iter()
|
||||
.map(|l| l.as_ref().map_or(0, |l| l.instances().len()).to_string())
|
||||
.collect();
|
||||
println!("{indent}{idx}: [{}]", counts.join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user