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
+206
-184
No files matched your search
@@ -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
@@ -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
|
||||||
|
|||||||
+114
-81
@@ -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
@@ -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(
|
||||||
|
|||||||
@@ -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(", "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user