Draw each primitive with its own pipeline, registered rather than declared
The `primitives!` macro, `PrimitiveData`, `PrimitiveVec`, `PrimitiveBuffers`, the `Primitive` trait and the shader's dispatch switch are gone. A primitive is now a registration: its WGSL and, implicitly, the size of the entry that WGSL reads. Everything else -- its instance list, its free list, its buffers, its bind group and its pipeline -- follows from that, so adding one is a `register` call and a shader file, with nothing per-type to remember and no cross-type dispatch to extend. Nothing dispatches dynamically. Push, free, renumber, upload and draw are identical for every primitive; what differs is the entry size and the pipeline, which are data. So `InstanceList` carries a runtime stride and its instances' data as bytes, and one concrete type serves every primitive and the textures. Measured against a typed list it costs 0.2ns per write, where a trait object costs 1.6ns. Because each type has its own list, an instance's index is also its data index: `@builtin(instance_index)` replaces the `idx` field, the `binding` field goes with the switch, and `PrimitiveInstance` drops from 28 bytes to 20. `PrimitiveVec`'s free list merges into the instance list's, so an instance and its data are freed by one `swap_remove` rather than two arenas kept in step. `shader.wgsl` becomes `shader/prelude.wgsl` plus one file per primitive. The prelude carries the window, masks, sampled texture, vertex shader and `masked()`, and is compiled ahead of each primitive's own source -- which is also what a caller's own primitive would be. Masks move back into group 0 beside the window uniform, since every pipeline shares one layout. Within a layer, types now draw in registration order: a rect under a glyph under a texture. Order within a layer was never meaningful -- freeing an instance swaps another into its place -- so this replaces an accident with a defined order, and backgrounds land under their content. Verified with a headless run per case: text over its own rect and an image over its own rect in one layer, a masked stack clipping, the text-layout tab, and adding three images and deleting one.
This commit is contained in:
1 parent
b3d3da5dab
commit
4d9839f380
15 files changed
+608
-614
No files matched your search
@@ -2,7 +2,7 @@ use std::ops::{Index, IndexMut};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
UiRegion, WidgetId,
|
UiRegion, WidgetId,
|
||||||
render::{LayerDraws, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
render::{LayerDraws, MaskIdx, PrimitiveHandle, PrimitiveKind},
|
||||||
util::to_mut,
|
util::to_mut,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -121,12 +121,16 @@ impl<T: Default> Layers<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DrawLayers {
|
impl DrawLayers {
|
||||||
pub fn write<P: Primitive>(
|
pub fn write<P: bytemuck::Pod>(
|
||||||
&mut self,
|
&mut self,
|
||||||
layer: LayerId,
|
layer: LayerId,
|
||||||
info: PrimitiveInst<P>,
|
kind: PrimitiveKind<P>,
|
||||||
|
id: WidgetId,
|
||||||
|
primitive: P,
|
||||||
|
region: UiRegion,
|
||||||
|
mask_idx: MaskIdx,
|
||||||
) -> PrimitiveHandle {
|
) -> PrimitiveHandle {
|
||||||
self[layer].write(layer, info)
|
self[layer].write(layer, kind, id, primitive, region, mask_idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||||
|
|||||||
@@ -12,20 +12,16 @@ pub struct WindowUniform {
|
|||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct PrimitiveInstance {
|
pub struct PrimitiveInstance {
|
||||||
pub region: UiRegion,
|
pub region: UiRegion,
|
||||||
pub binding: u32,
|
|
||||||
pub idx: u32,
|
|
||||||
pub mask_idx: MaskIdx,
|
pub mask_idx: MaskIdx,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrimitiveInstance {
|
impl PrimitiveInstance {
|
||||||
const ATTRIBS: [VertexAttribute; 7] = vertex_attr_array![
|
const ATTRIBS: [VertexAttribute; 5] = vertex_attr_array![
|
||||||
0 => Float32x2,
|
0 => Float32x2,
|
||||||
1 => Float32x2,
|
1 => Float32x2,
|
||||||
2 => Float32x2,
|
2 => Float32x2,
|
||||||
3 => Float32x2,
|
3 => Float32x2,
|
||||||
4 => Uint32,
|
4 => Uint32,
|
||||||
5 => Uint32,
|
|
||||||
6 => Uint32,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn desc() -> VertexBufferLayout<'static> {
|
pub fn desc() -> VertexBufferLayout<'static> {
|
||||||
|
|||||||
+233
-171
@@ -25,16 +25,22 @@ pub use atlas::*;
|
|||||||
pub use data::{Mask, MaskIdx};
|
pub use data::{Mask, MaskIdx};
|
||||||
pub use primitive::*;
|
pub use primitive::*;
|
||||||
|
|
||||||
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
const PRELUDE: &str = include_str!("./shader/prelude.wgsl");
|
||||||
|
const TEXTURE_SHADER: &str = include_str!("./shader/texture.wgsl");
|
||||||
|
|
||||||
pub struct UiRenderNode {
|
pub struct UiRenderNode {
|
||||||
uniform_group: BindGroup,
|
device: Device,
|
||||||
primitive_layout: BindGroupLayout,
|
shared_layout: BindGroupLayout,
|
||||||
|
shared_group: BindGroup,
|
||||||
|
data_layout: BindGroupLayout,
|
||||||
texture_layout: BindGroupLayout,
|
texture_layout: BindGroupLayout,
|
||||||
mask_layout: BindGroupLayout,
|
pipeline_layout: PipelineLayout,
|
||||||
mask_group: BindGroup,
|
format: TextureFormat,
|
||||||
|
|
||||||
pipeline: RenderPipeline,
|
/// One per registered primitive, in id order. The texture pipeline is
|
||||||
|
/// apart because a texture binds group 2 per instance, not per draw.
|
||||||
|
pipelines: Vec<RenderPipeline>,
|
||||||
|
texture_pipeline: RenderPipeline,
|
||||||
|
|
||||||
layers: HashMap<usize, RenderLayer>,
|
layers: HashMap<usize, RenderLayer>,
|
||||||
active: Vec<usize>,
|
active: Vec<usize>,
|
||||||
@@ -46,30 +52,41 @@ pub struct UiRenderNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct RenderLayer {
|
struct RenderLayer {
|
||||||
instance: ArrBuf<PrimitiveInstance>,
|
/// One per registered primitive, in id order, matching `LayerDraws`.
|
||||||
primitives: PrimitiveBuffers,
|
primitives: Vec<ListBuffers>,
|
||||||
primitive_group: BindGroup,
|
textures: ListBuffers,
|
||||||
texture_instance: ArrBuf<PrimitiveInstance>,
|
/// Which texture each entry of `textures` samples, in the same order.
|
||||||
/// Which texture each entry of `texture_instance` samples, in the same
|
|
||||||
/// order. Not in the vertex buffer because it names a bind group.
|
|
||||||
texture_slots: Vec<u32>,
|
texture_slots: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One list's vertex buffer and the data its shader reads at group 1.
|
||||||
|
struct ListBuffers {
|
||||||
|
instance: ArrBuf<PrimitiveInstance>,
|
||||||
|
data: ArrBuf<u8>,
|
||||||
|
group: Option<BindGroup>,
|
||||||
|
}
|
||||||
|
|
||||||
impl UiRenderNode {
|
impl UiRenderNode {
|
||||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||||
pass.set_pipeline(&self.pipeline);
|
pass.set_bind_group(0, &self.shared_group, &[]);
|
||||||
pass.set_bind_group(0, &self.uniform_group, &[]);
|
|
||||||
pass.set_bind_group(3, &self.mask_group, &[]);
|
|
||||||
for i in &self.active {
|
for i in &self.active {
|
||||||
let layer = &self.layers[i];
|
let layer = &self.layers[i];
|
||||||
pass.set_bind_group(1, &layer.primitive_group, &[]);
|
// Types run in registration order, so a rect is under a glyph is
|
||||||
if layer.instance.len() > 0 {
|
// under a texture. Ordering beyond that is what `Layers` is for --
|
||||||
|
// freeing an instance swaps another into its place.
|
||||||
pass.set_bind_group(2, self.pages.group(), &[]);
|
pass.set_bind_group(2, self.pages.group(), &[]);
|
||||||
pass.set_vertex_buffer(0, layer.instance.buffer.slice(..));
|
for (id, list) in layer.primitives.iter().enumerate() {
|
||||||
pass.draw(0..4, 0..layer.instance.len() as u32);
|
let (Some(group), true) = (&list.group, list.instance.len() > 0) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
pass.set_pipeline(&self.pipelines[id]);
|
||||||
|
pass.set_bind_group(1, 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() {
|
if !layer.texture_slots.is_empty() {
|
||||||
pass.set_vertex_buffer(0, layer.texture_instance.buffer.slice(..));
|
pass.set_pipeline(&self.texture_pipeline);
|
||||||
|
pass.set_vertex_buffer(0, layer.textures.instance.buffer.slice(..));
|
||||||
for (i, &slot) in layer.texture_slots.iter().enumerate() {
|
for (i, &slot) in layer.texture_slots.iter().enumerate() {
|
||||||
let Some(group) = self.textures.group(slot) else {
|
let Some(group) = self.textures.group(slot) else {
|
||||||
continue;
|
continue;
|
||||||
@@ -101,52 +118,40 @@ impl UiRenderNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let rlayer = self.layers.entry(i).or_insert_with(|| {
|
let rlayer = self
|
||||||
let primitives = PrimitiveBuffers::new(device);
|
.layers
|
||||||
let primitive_group =
|
.entry(i)
|
||||||
Self::primitive_group(device, &self.primitive_layout, primitives.buffers());
|
.or_insert_with(|| RenderLayer::new(device));
|
||||||
RenderLayer {
|
|
||||||
instance: ArrBuf::new(
|
|
||||||
device,
|
|
||||||
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
|
||||||
"instance",
|
|
||||||
),
|
|
||||||
primitives,
|
|
||||||
primitive_group,
|
|
||||||
texture_instance: ArrBuf::new(
|
|
||||||
device,
|
|
||||||
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
|
||||||
"texture instance",
|
|
||||||
),
|
|
||||||
texture_slots: Vec::new(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if draws.updated {
|
if draws.updated {
|
||||||
rlayer
|
|
||||||
.instance
|
|
||||||
.update(device, queue, draws.primitives.instances());
|
|
||||||
rlayer
|
rlayer
|
||||||
.primitives
|
.primitives
|
||||||
.update(device, queue, draws.primitives.data());
|
.resize_with(draws.primitives().len(), || ListBuffers::new(device));
|
||||||
rlayer.primitive_group = Self::primitive_group(
|
for (list, draws) in rlayer.primitives.iter_mut().zip(draws.primitives()) {
|
||||||
device,
|
list.update(device, queue, &self.data_layout, draws);
|
||||||
&self.primitive_layout,
|
}
|
||||||
rlayer.primitives.buffers(),
|
|
||||||
);
|
|
||||||
rlayer
|
rlayer
|
||||||
.texture_instance
|
.textures
|
||||||
.update(device, queue, draws.textures.instances());
|
.update(device, queue, &self.data_layout, &draws.textures);
|
||||||
rlayer.texture_slots.clear();
|
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
|
rlayer
|
||||||
.texture_slots
|
.texture_slots
|
||||||
.extend(draws.textures.instances().iter().map(|inst| inst.idx));
|
.extend(slots.iter().copied().map(u32::from_ne_bytes));
|
||||||
draws.updated = false;
|
draws.updated = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.build_pipelines(&ui.primitives);
|
||||||
if ui.masks.changed {
|
if ui.masks.changed {
|
||||||
ui.masks.changed = false;
|
ui.masks.changed = false;
|
||||||
if self.masks.update(device, queue, &ui.masks[..]) {
|
if self.masks.update(device, queue, &ui.masks[..]) {
|
||||||
self.mask_group = Self::mask_group(device, &self.mask_layout, &self.masks);
|
self.shared_group = Self::shared_group(
|
||||||
|
device,
|
||||||
|
&self.shared_layout,
|
||||||
|
&self.window_buffer,
|
||||||
|
&self.masks,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.pages
|
self.pages
|
||||||
@@ -165,11 +170,6 @@ impl UiRenderNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(device: &Device, queue: &Queue, config: &SurfaceConfiguration) -> Self {
|
pub fn new(device: &Device, queue: &Queue, config: &SurfaceConfiguration) -> Self {
|
||||||
let shader = device.create_shader_module(ShaderModuleDescriptor {
|
|
||||||
label: Some("UI Shape Shader"),
|
|
||||||
source: ShaderSource::Wgsl(SHAPE_SHADER.into()),
|
|
||||||
});
|
|
||||||
|
|
||||||
let window_uniform = WindowUniform {
|
let window_uniform = WindowUniform {
|
||||||
width: config.width as f32,
|
width: config.width as f32,
|
||||||
height: config.height as f32,
|
height: config.height as f32,
|
||||||
@@ -180,74 +180,95 @@ impl UiRenderNode {
|
|||||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||||
});
|
});
|
||||||
|
|
||||||
let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
let shared_layout = Self::shared_layout(device);
|
||||||
entries: &[BindGroupLayoutEntry {
|
let data_layout = Self::data_layout(device);
|
||||||
binding: 0,
|
let texture_layout = Self::texture_layout(device);
|
||||||
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Buffer {
|
|
||||||
ty: BufferBindingType::Uniform,
|
|
||||||
has_dynamic_offset: false,
|
|
||||||
min_binding_size: None,
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
}],
|
|
||||||
label: Some("window"),
|
|
||||||
});
|
|
||||||
|
|
||||||
let uniform_group = Self::bind_group_0(device, &uniform_layout, &window_buffer);
|
|
||||||
|
|
||||||
let primitive_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
|
||||||
entries: &PrimitiveBuffers::BINDINGS.map(|binding| BindGroupLayoutEntry {
|
|
||||||
binding,
|
|
||||||
visibility: ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Buffer {
|
|
||||||
ty: BufferBindingType::Storage { read_only: true },
|
|
||||||
has_dynamic_offset: false,
|
|
||||||
min_binding_size: None,
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
}),
|
|
||||||
label: Some("primitive"),
|
|
||||||
});
|
|
||||||
|
|
||||||
let masks = ArrBuf::new(
|
let masks = ArrBuf::new(
|
||||||
device,
|
device,
|
||||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||||
"ui masks",
|
"ui masks",
|
||||||
);
|
);
|
||||||
|
let shared_group = Self::shared_group(device, &shared_layout, &window_buffer, &masks);
|
||||||
let texture_layout = Self::texture_layout(device);
|
|
||||||
let mask_layout = Self::mask_layout(device);
|
|
||||||
let mask_group = Self::mask_group(device, &mask_layout, &masks);
|
|
||||||
|
|
||||||
let sampler = default_sampler(device);
|
let sampler = default_sampler(device);
|
||||||
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);
|
||||||
|
|
||||||
|
// One layout for every pipeline, so a primitive that does not sample
|
||||||
|
// or read a buffer binds what is there instead of needing its own.
|
||||||
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
||||||
label: Some("UI Shape Pipeline Layout"),
|
label: Some("ui"),
|
||||||
bind_group_layouts: &[
|
bind_group_layouts: &[&shared_layout, &data_layout, &texture_layout],
|
||||||
&uniform_layout,
|
|
||||||
&primitive_layout,
|
|
||||||
&texture_layout,
|
|
||||||
&mask_layout,
|
|
||||||
],
|
|
||||||
immediate_size: 0,
|
immediate_size: 0,
|
||||||
});
|
});
|
||||||
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
|
let texture_pipeline = Self::pipeline(
|
||||||
label: Some("UI Shape Pipeline"),
|
device,
|
||||||
layout: Some(&pipeline_layout),
|
&pipeline_layout,
|
||||||
|
config.format,
|
||||||
|
TEXTURE_SHADER,
|
||||||
|
"texture",
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
device: device.clone(),
|
||||||
|
shared_layout,
|
||||||
|
shared_group,
|
||||||
|
data_layout,
|
||||||
|
texture_layout,
|
||||||
|
pipeline_layout,
|
||||||
|
format: config.format,
|
||||||
|
pipelines: Vec::new(),
|
||||||
|
texture_pipeline,
|
||||||
|
window_buffer,
|
||||||
|
layers: HashMap::default(),
|
||||||
|
active: Vec::new(),
|
||||||
|
sampler,
|
||||||
|
pages,
|
||||||
|
textures,
|
||||||
|
masks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compiles a pipeline for every primitive registered since the last call.
|
||||||
|
/// Sources only ever arrive at the end, so an id keeps its pipeline.
|
||||||
|
fn build_pipelines(&mut self, registry: &PrimitiveRegistry) {
|
||||||
|
for source in ®istry.sources()[self.pipelines.len()..] {
|
||||||
|
self.pipelines.push(Self::pipeline(
|
||||||
|
&self.device,
|
||||||
|
&self.pipeline_layout,
|
||||||
|
self.format,
|
||||||
|
source.wgsl,
|
||||||
|
source.label,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pipeline(
|
||||||
|
device: &Device,
|
||||||
|
layout: &PipelineLayout,
|
||||||
|
format: TextureFormat,
|
||||||
|
wgsl: &str,
|
||||||
|
label: &str,
|
||||||
|
) -> RenderPipeline {
|
||||||
|
let module = device.create_shader_module(ShaderModuleDescriptor {
|
||||||
|
label: Some(label),
|
||||||
|
source: ShaderSource::Wgsl(format!("{PRELUDE}\n{wgsl}").into()),
|
||||||
|
});
|
||||||
|
device.create_render_pipeline(&RenderPipelineDescriptor {
|
||||||
|
label: Some(label),
|
||||||
|
layout: Some(layout),
|
||||||
vertex: VertexState {
|
vertex: VertexState {
|
||||||
module: &shader,
|
module: &module,
|
||||||
entry_point: Some("vs_main"),
|
entry_point: Some("vs_main"),
|
||||||
buffers: &[PrimitiveInstance::desc()],
|
buffers: &[PrimitiveInstance::desc()],
|
||||||
compilation_options: Default::default(),
|
compilation_options: Default::default(),
|
||||||
},
|
},
|
||||||
fragment: Some(FragmentState {
|
fragment: Some(FragmentState {
|
||||||
module: &shader,
|
module: &module,
|
||||||
entry_point: Some("fs_main"),
|
entry_point: Some("fs_main"),
|
||||||
targets: &[Some(ColorTargetState {
|
targets: &[Some(ColorTargetState {
|
||||||
format: config.format,
|
format,
|
||||||
blend: Some(BlendState::ALPHA_BLENDING),
|
blend: Some(BlendState::ALPHA_BLENDING),
|
||||||
write_mask: ColorWrites::ALL,
|
write_mask: ColorWrites::ALL,
|
||||||
})],
|
})],
|
||||||
@@ -270,52 +291,74 @@ impl UiRenderNode {
|
|||||||
},
|
},
|
||||||
multiview_mask: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
uniform_group,
|
|
||||||
primitive_layout,
|
|
||||||
texture_layout,
|
|
||||||
mask_layout,
|
|
||||||
mask_group,
|
|
||||||
pipeline,
|
|
||||||
window_buffer,
|
|
||||||
layers: HashMap::default(),
|
|
||||||
active: Vec::new(),
|
|
||||||
sampler,
|
|
||||||
pages,
|
|
||||||
textures,
|
|
||||||
masks,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bind_group_0(
|
|
||||||
device: &Device,
|
|
||||||
layout: &BindGroupLayout,
|
|
||||||
window_buffer: &Buffer,
|
|
||||||
) -> BindGroup {
|
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
|
||||||
layout,
|
|
||||||
entries: &[BindGroupEntry {
|
|
||||||
binding: 0,
|
|
||||||
resource: window_buffer.as_entire_binding(),
|
|
||||||
}],
|
|
||||||
label: Some("ui window"),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn primitive_group(
|
/// Group 0: what every draw in the ui shares.
|
||||||
|
fn shared_layout(device: &Device) -> BindGroupLayout {
|
||||||
|
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
|
entries: &[
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Storage { read_only: true },
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: Some("ui shared"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shared_group(
|
||||||
device: &Device,
|
device: &Device,
|
||||||
layout: &BindGroupLayout,
|
layout: &BindGroupLayout,
|
||||||
buffers: [(u32, &Buffer); PrimitiveBuffers::LEN],
|
window: &Buffer,
|
||||||
|
masks: &ArrBuf<Mask>,
|
||||||
) -> BindGroup {
|
) -> BindGroup {
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
device.create_bind_group(&BindGroupDescriptor {
|
||||||
layout,
|
layout,
|
||||||
entries: &buffers.map(|(binding, buf)| BindGroupEntry {
|
entries: &[
|
||||||
binding,
|
BindGroupEntry {
|
||||||
resource: buf.as_entire_binding(),
|
binding: 0,
|
||||||
}),
|
resource: window.as_entire_binding(),
|
||||||
label: Some("ui primitives"),
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: masks.buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: Some("ui shared"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Group 1: one list's per-instance data, whatever its shader reads it as.
|
||||||
|
fn data_layout(device: &Device) -> BindGroupLayout {
|
||||||
|
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
|
entries: &[BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Storage { read_only: true },
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
}],
|
||||||
|
label: Some("ui primitive data"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,36 +388,55 @@ impl UiRenderNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Group 3, apart from the textures so that resizing the masks buffer
|
|
||||||
/// leaves every texture group intact.
|
|
||||||
fn mask_layout(device: &Device) -> BindGroupLayout {
|
|
||||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
|
||||||
entries: &[BindGroupLayoutEntry {
|
|
||||||
binding: 0,
|
|
||||||
visibility: ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Buffer {
|
|
||||||
ty: BufferBindingType::Storage { read_only: true },
|
|
||||||
has_dynamic_offset: false,
|
|
||||||
min_binding_size: None,
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
}],
|
|
||||||
label: Some("ui masks"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mask_group(device: &Device, layout: &BindGroupLayout, masks: &ArrBuf<Mask>) -> BindGroup {
|
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
|
||||||
layout,
|
|
||||||
entries: &[BindGroupEntry {
|
|
||||||
binding: 0,
|
|
||||||
resource: masks.buffer.as_entire_binding(),
|
|
||||||
}],
|
|
||||||
label: Some("ui masks"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn texture_count(&self) -> usize {
|
pub fn texture_count(&self) -> usize {
|
||||||
self.textures.count()
|
self.textures.count()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RenderLayer {
|
||||||
|
fn new(device: &Device) -> Self {
|
||||||
|
Self {
|
||||||
|
primitives: Vec::new(),
|
||||||
|
textures: ListBuffers::new(device),
|
||||||
|
texture_slots: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListBuffers {
|
||||||
|
fn new(device: &Device) -> Self {
|
||||||
|
Self {
|
||||||
|
instance: ArrBuf::new(
|
||||||
|
device,
|
||||||
|
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
||||||
|
"instance",
|
||||||
|
),
|
||||||
|
data: ArrBuf::new(
|
||||||
|
device,
|
||||||
|
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||||
|
"primitive data",
|
||||||
|
),
|
||||||
|
group: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
device: &Device,
|
||||||
|
queue: &Queue,
|
||||||
|
layout: &BindGroupLayout,
|
||||||
|
list: &InstanceList,
|
||||||
|
) {
|
||||||
|
self.instance.update(device, queue, list.instances());
|
||||||
|
if self.data.update(device, queue, list.data()) || self.group.is_none() {
|
||||||
|
self.group = Some(device.create_bind_group(&BindGroupDescriptor {
|
||||||
|
layout,
|
||||||
|
entries: &[BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: self.data.buffer.as_entire_binding(),
|
||||||
|
}],
|
||||||
|
label: Some("ui primitive data"),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+158
-216
@@ -1,68 +1,143 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Color, UiRegion, WidgetId,
|
Color, UiRegion, WidgetId,
|
||||||
render::{
|
render::data::{MaskIdx, PrimitiveInstance},
|
||||||
ArrBuf,
|
|
||||||
data::{MaskIdx, PrimitiveInstance},
|
|
||||||
},
|
|
||||||
util::Vec2,
|
util::Vec2,
|
||||||
};
|
};
|
||||||
use bytemuck::Pod;
|
use bytemuck::Pod;
|
||||||
use wgpu::*;
|
|
||||||
|
|
||||||
/// Everything one layer draws. A texture binds its own group 2, so it draws on
|
/// Which registered primitive an instance is, and so which list it lives in
|
||||||
/// its own and cannot join the instanced draw the primitives share.
|
/// and which pipeline draws it. The type ties a `write` to what its shader reads.
|
||||||
pub struct LayerDraws {
|
pub struct PrimitiveKind<P> {
|
||||||
pub primitives: Primitives,
|
id: u32,
|
||||||
pub textures: InstanceList,
|
_p: PhantomData<fn(P)>,
|
||||||
pub updated: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LayerDraws {
|
impl<P> PrimitiveKind<P> {
|
||||||
fn default() -> Self {
|
const fn new(id: u32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
primitives: Default::default(),
|
id,
|
||||||
textures: Default::default(),
|
_p: PhantomData,
|
||||||
updated: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> u32 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Which of a layer's two lists an instance is in. They index independently,
|
impl<P> Clone for PrimitiveKind<P> {
|
||||||
/// so a handle or a renumbering naming only a position would be ambiguous.
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> Copy for PrimitiveKind<P> {}
|
||||||
|
|
||||||
|
pub const RECT: PrimitiveKind<RectPrimitive> = PrimitiveKind::new(0);
|
||||||
|
pub const GLYPH: PrimitiveKind<GlyphPrimitive> = PrimitiveKind::new(1);
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// nothing here knows its type.
|
||||||
|
///
|
||||||
|
/// A source is compiled after `prelude.wgsl` and supplies its data at
|
||||||
|
/// `@group(1) @binding(0)` and an `fs_main` shading one instance.
|
||||||
|
pub struct PrimitiveRegistry {
|
||||||
|
kinds: Vec<PrimitiveSource>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PrimitiveSource {
|
||||||
|
pub wgsl: &'static str,
|
||||||
|
pub label: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PrimitiveRegistry {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut kinds = Self { kinds: Vec::new() };
|
||||||
|
assert_eq!(
|
||||||
|
kinds
|
||||||
|
.register::<RectPrimitive>(include_str!("shader/rect.wgsl"), "rect")
|
||||||
|
.id(),
|
||||||
|
RECT.id()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
kinds
|
||||||
|
.register::<GlyphPrimitive>(include_str!("shader/glyph.wgsl"), "glyph")
|
||||||
|
.id(),
|
||||||
|
GLYPH.id()
|
||||||
|
);
|
||||||
|
kinds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimitiveRegistry {
|
||||||
|
pub fn register<P: Pod>(
|
||||||
|
&mut self,
|
||||||
|
wgsl: &'static str,
|
||||||
|
label: &'static str,
|
||||||
|
) -> PrimitiveKind<P> {
|
||||||
|
self.kinds.push(PrimitiveSource { wgsl, label });
|
||||||
|
PrimitiveKind::new(self.kinds.len() as u32 - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sources(&self) -> &[PrimitiveSource] {
|
||||||
|
&self.kinds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum InstanceKind {
|
pub enum InstanceKind {
|
||||||
Primitive,
|
Primitive(u32),
|
||||||
Texture,
|
Texture,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A texture instance's `idx` names the texture to bind rather than a group-1
|
/// Instances, the widget each belongs to, the slots waiting to be reused, and
|
||||||
/// entry, so it is not in `primitives!` and has no buffer of its own.
|
/// `stride` bytes of data per instance at the same index.
|
||||||
pub const TEXTURE_BINDING: u32 = 2;
|
///
|
||||||
|
/// That data is the struct a primitive's shader reads, or the slot a texture
|
||||||
/// Instances, the widget each belongs to, and the slots waiting to be reused.
|
/// binds. Keeping both here is what holds them in step through a
|
||||||
|
/// `swap_remove`.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct InstanceList {
|
pub struct InstanceList {
|
||||||
instances: Vec<PrimitiveInstance>,
|
instances: Vec<PrimitiveInstance>,
|
||||||
assoc: Vec<WidgetId>,
|
assoc: Vec<WidgetId>,
|
||||||
free: Vec<usize>,
|
free: Vec<usize>,
|
||||||
|
data: Vec<u8>,
|
||||||
|
stride: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstanceList {
|
impl InstanceList {
|
||||||
pub fn instances(&self) -> &Vec<PrimitiveInstance> {
|
pub fn instances(&self) -> &[PrimitiveInstance] {
|
||||||
&self.instances
|
&self.instances
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(&mut self, id: WidgetId, inst: PrimitiveInstance) -> usize {
|
pub fn data(&self) -> &[u8] {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
self.data[i * self.stride..][..self.stride].copy_from_slice(data);
|
||||||
i
|
i
|
||||||
} else {
|
} else {
|
||||||
let i = self.instances.len();
|
let i = self.instances.len();
|
||||||
self.instances.push(inst);
|
self.instances.push(inst);
|
||||||
self.assoc.push(id);
|
self.assoc.push(id);
|
||||||
|
self.data.extend_from_slice(data);
|
||||||
i
|
i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,156 +151,75 @@ impl InstanceList {
|
|||||||
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;
|
||||||
|
let data = &mut self.data;
|
||||||
|
let stride = self.stride;
|
||||||
self.free.drain(..).filter_map(move |i| {
|
self.free.drain(..).filter_map(move |i| {
|
||||||
instances.swap_remove(i);
|
instances.swap_remove(i);
|
||||||
assoc.swap_remove(i);
|
assoc.swap_remove(i);
|
||||||
if i == instances.len() {
|
let last = instances.len();
|
||||||
|
data.copy_within(last * stride..(last + 1) * stride, i * stride);
|
||||||
|
data.truncate(last * stride);
|
||||||
|
if i == last {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let id = assoc[i];
|
let id = assoc[i];
|
||||||
let old = instances.len();
|
|
||||||
Some(PrimitiveChange {
|
Some(PrimitiveChange {
|
||||||
id,
|
id,
|
||||||
kind,
|
kind,
|
||||||
old,
|
old: last,
|
||||||
new: i,
|
new: i,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Primitive: Pod {
|
/// Everything one layer draws. A texture binds its own group 2, so it draws on
|
||||||
const BINDING: u32;
|
/// its own and cannot join the instanced draw a primitive gets.
|
||||||
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self>;
|
pub struct LayerDraws {
|
||||||
|
primitives: Vec<InstanceList>,
|
||||||
|
pub textures: InstanceList,
|
||||||
|
pub updated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! primitives {
|
impl Default for LayerDraws {
|
||||||
($($name:ident: $ty:ty => $binding:expr,)*) => {
|
fn default() -> Self {
|
||||||
#[derive(Default)]
|
|
||||||
pub struct PrimitiveData {
|
|
||||||
$(pub(crate) $name: PrimitiveVec<$ty>,)*
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PrimitiveBuffers {
|
|
||||||
$($name: ArrBuf<$ty>,)*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveBuffers {
|
|
||||||
pub fn update(&mut self, device: &Device, queue: &Queue, data: &PrimitiveData) {
|
|
||||||
$(self.$name.update(device, queue, &data.$name);)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveBuffers {
|
|
||||||
pub const LEN: usize = primitives!(@count $($name)*);
|
|
||||||
pub const BINDINGS: [u32; Self::LEN] = [$(<$ty>::BINDING,)*];
|
|
||||||
pub fn buffers(&self) -> [(u32, &Buffer); Self::LEN] {
|
|
||||||
[
|
|
||||||
$((<$ty>::BINDING, &self.$name.buffer),)*
|
|
||||||
]
|
|
||||||
}
|
|
||||||
pub fn new(device: &Device) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
$($name: ArrBuf::new(
|
primitives: Vec::new(),
|
||||||
device,
|
textures: InstanceList::default(),
|
||||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
updated: true,
|
||||||
stringify!($name),
|
|
||||||
),)*
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveData {
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
$(self.$name.clear();)*
|
|
||||||
}
|
|
||||||
pub fn free(&mut self, binding: u32, idx: usize) {
|
|
||||||
match binding {
|
|
||||||
$(<$ty>::BINDING => self.$name.free(idx),)*
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(
|
|
||||||
unsafe impl bytemuck::Pod for $ty {}
|
|
||||||
unsafe impl bytemuck::Zeroable for $ty {}
|
|
||||||
impl Primitive for $ty {
|
|
||||||
const BINDING: u32 = $binding;
|
|
||||||
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self> {
|
|
||||||
&mut data.$name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
(@count $t1:tt $($t:tt)+) => { 1 + primitives!(@count $($t)+) };
|
|
||||||
(@count $t:tt) => { 1 };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PrimitiveInst<P> {
|
|
||||||
pub id: WidgetId,
|
|
||||||
pub primitive: P,
|
|
||||||
pub region: UiRegion,
|
|
||||||
pub mask_idx: MaskIdx,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The instanced half of a layer: a list, plus the group-1 data it reads.
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Primitives {
|
|
||||||
list: InstanceList,
|
|
||||||
data: PrimitiveData,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Primitives {
|
|
||||||
fn write<P: Primitive>(
|
|
||||||
&mut self,
|
|
||||||
PrimitiveInst {
|
|
||||||
id,
|
|
||||||
primitive,
|
|
||||||
region,
|
|
||||||
mask_idx,
|
|
||||||
}: PrimitiveInst<P>,
|
|
||||||
) -> usize {
|
|
||||||
let idx = P::vec(&mut self.data).add(primitive) as u32;
|
|
||||||
self.list.push(
|
|
||||||
id,
|
|
||||||
PrimitiveInstance {
|
|
||||||
region,
|
|
||||||
idx,
|
|
||||||
mask_idx,
|
|
||||||
binding: P::BINDING,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn free(&mut self, i: usize) -> MaskIdx {
|
|
||||||
// The instance says where its group-1 entry is, so the handle need not.
|
|
||||||
let inst = self.list.instances[i];
|
|
||||||
self.data.free(inst.binding, inst.idx as usize);
|
|
||||||
self.list.free(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn data(&self) -> &PrimitiveData {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instances(&self) -> &Vec<PrimitiveInstance> {
|
|
||||||
&self.list.instances
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerDraws {
|
impl LayerDraws {
|
||||||
pub fn write<P: Primitive>(&mut self, layer: usize, inst: PrimitiveInst<P>) -> PrimitiveHandle {
|
pub fn write<P: Pod>(
|
||||||
|
&mut self,
|
||||||
|
layer: usize,
|
||||||
|
kind: PrimitiveKind<P>,
|
||||||
|
id: WidgetId,
|
||||||
|
primitive: P,
|
||||||
|
region: UiRegion,
|
||||||
|
mask_idx: MaskIdx,
|
||||||
|
) -> PrimitiveHandle {
|
||||||
self.updated = true;
|
self.updated = true;
|
||||||
|
if self.primitives.len() <= kind.id as usize {
|
||||||
|
self.primitives
|
||||||
|
.resize_with(kind.id as usize + 1, Default::default);
|
||||||
|
}
|
||||||
|
let inst_idx = self.primitives[kind.id as usize].push(
|
||||||
|
id,
|
||||||
|
PrimitiveInstance { region, mask_idx },
|
||||||
|
bytemuck::bytes_of(&primitive),
|
||||||
|
);
|
||||||
PrimitiveHandle {
|
PrimitiveHandle {
|
||||||
layer,
|
layer,
|
||||||
kind: InstanceKind::Primitive,
|
kind: InstanceKind::Primitive(kind.id),
|
||||||
inst_idx: self.primitives.write(inst),
|
inst_idx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes an instance that samples the texture in slot `texture` instead of
|
/// Writes an instance that samples the texture in slot `texture`, which is
|
||||||
/// reading a group-1 buffer.
|
/// bound for it alone rather than read from a buffer.
|
||||||
pub fn write_texture(
|
pub fn write_texture(
|
||||||
&mut self,
|
&mut self,
|
||||||
layer: usize,
|
layer: usize,
|
||||||
@@ -240,16 +234,16 @@ impl LayerDraws {
|
|||||||
kind: InstanceKind::Texture,
|
kind: InstanceKind::Texture,
|
||||||
inst_idx: self.textures.push(
|
inst_idx: self.textures.push(
|
||||||
id,
|
id,
|
||||||
PrimitiveInstance {
|
PrimitiveInstance { region, mask_idx },
|
||||||
region,
|
bytemuck::bytes_of(&texture),
|
||||||
idx: texture,
|
|
||||||
mask_idx,
|
|
||||||
binding: TEXTURE_BINDING,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn primitives(&self) -> &[InstanceList] {
|
||||||
|
&self.primitives
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
||||||
let Self {
|
let Self {
|
||||||
primitives,
|
primitives,
|
||||||
@@ -257,17 +251,15 @@ impl LayerDraws {
|
|||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
primitives
|
primitives
|
||||||
.list
|
.iter_mut()
|
||||||
.apply_free(InstanceKind::Primitive)
|
.enumerate()
|
||||||
|
.flat_map(|(i, list)| list.apply_free(InstanceKind::Primitive(i as u32)))
|
||||||
.chain(textures.apply_free(InstanceKind::Texture))
|
.chain(textures.apply_free(InstanceKind::Texture))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||||
self.updated = true;
|
self.updated = true;
|
||||||
match h.kind {
|
self.list_mut(h.kind).free(h.inst_idx)
|
||||||
InstanceKind::Primitive => self.primitives.free(h.inst_idx),
|
|
||||||
InstanceKind::Texture => self.textures.free(h.inst_idx),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
|
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
|
||||||
@@ -277,7 +269,7 @@ impl LayerDraws {
|
|||||||
|
|
||||||
fn list_mut(&mut self, kind: InstanceKind) -> &mut InstanceList {
|
fn list_mut(&mut self, kind: InstanceKind) -> &mut InstanceList {
|
||||||
match kind {
|
match kind {
|
||||||
InstanceKind::Primitive => &mut self.primitives.list,
|
InstanceKind::Primitive(i) => &mut self.primitives[i as usize],
|
||||||
InstanceKind::Texture => &mut self.textures,
|
InstanceKind::Texture => &mut self.textures,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,11 +289,6 @@ pub struct PrimitiveHandle {
|
|||||||
pub inst_idx: usize,
|
pub inst_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
primitives!(
|
|
||||||
rects: RectPrimitive => 0,
|
|
||||||
glyphs: GlyphPrimitive => 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct RectPrimitive {
|
pub struct RectPrimitive {
|
||||||
@@ -311,6 +298,9 @@ pub struct RectPrimitive {
|
|||||||
pub inner_radius: f32,
|
pub inner_radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl bytemuck::Pod for RectPrimitive {}
|
||||||
|
unsafe impl bytemuck::Zeroable for RectPrimitive {}
|
||||||
|
|
||||||
impl RectPrimitive {
|
impl RectPrimitive {
|
||||||
pub fn color(color: Color<u8>) -> Self {
|
pub fn color(color: Color<u8>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -335,53 +325,5 @@ pub struct GlyphPrimitive {
|
|||||||
pub flags: u32,
|
pub flags: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PrimitiveVec<T> {
|
unsafe impl bytemuck::Pod for GlyphPrimitive {}
|
||||||
vec: Vec<T>,
|
unsafe impl bytemuck::Zeroable for GlyphPrimitive {}
|
||||||
free: Vec<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> PrimitiveVec<T> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
vec: Vec::new(),
|
|
||||||
free: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn add(&mut self, t: T) -> usize {
|
|
||||||
if let Some(i) = self.free.pop() {
|
|
||||||
self.vec[i] = t;
|
|
||||||
i
|
|
||||||
} else {
|
|
||||||
let i = self.vec.len();
|
|
||||||
self.vec.push(t);
|
|
||||||
i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn free(&mut self, i: usize) {
|
|
||||||
self.free.push(i);
|
|
||||||
}
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.free.clear();
|
|
||||||
self.vec.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Default for PrimitiveVec<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for PrimitiveVec<T> {
|
|
||||||
type Target = Vec<T>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.vec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> DerefMut for PrimitiveVec<T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.vec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
const RECT: u32 = 0u;
|
|
||||||
const GLYPH: u32 = 1u;
|
|
||||||
// No group 1 entry: a texture instance's idx names the texture bound in group 2.
|
|
||||||
const TEXTURE: u32 = 2u;
|
|
||||||
|
|
||||||
@group(0) @binding(0)
|
|
||||||
var<uniform> window: WindowUniform;
|
|
||||||
@group(1) @binding(RECT)
|
|
||||||
var<storage> rects: array<Rect>;
|
|
||||||
@group(1) @binding(GLYPH)
|
|
||||||
var<storage> glyphs: array<GlyphInfo>;
|
|
||||||
|
|
||||||
struct Rect {
|
|
||||||
color: u32,
|
|
||||||
radius: f32,
|
|
||||||
thickness: f32,
|
|
||||||
inner_radius: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GlyphInfo {
|
|
||||||
uv_min: vec2<f32>,
|
|
||||||
uv_max: vec2<f32>,
|
|
||||||
layer: u32,
|
|
||||||
color: u32,
|
|
||||||
flags: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Mask {
|
|
||||||
x: UiSpan,
|
|
||||||
y: UiSpan,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UiSpan {
|
|
||||||
start: UiScalar,
|
|
||||||
end: UiScalar,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UiScalar {
|
|
||||||
rel: f32,
|
|
||||||
abs: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UiVec2 {
|
|
||||||
rel: vec2<f32>,
|
|
||||||
abs: vec2<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// What this draw samples: the glyph atlas, whose layers are its pages, or one
|
|
||||||
// standalone image as an array of one.
|
|
||||||
@group(2) @binding(0)
|
|
||||||
var tex: texture_2d_array<f32>;
|
|
||||||
@group(2) @binding(1)
|
|
||||||
var samp: sampler;
|
|
||||||
@group(3) @binding(0)
|
|
||||||
var<storage> masks: array<Mask>;
|
|
||||||
|
|
||||||
struct WindowUniform {
|
|
||||||
dim: vec2<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InstanceInput {
|
|
||||||
@location(0) x_start: vec2<f32>,
|
|
||||||
@location(1) x_end: vec2<f32>,
|
|
||||||
@location(2) y_start: vec2<f32>,
|
|
||||||
@location(3) y_end: vec2<f32>,
|
|
||||||
@location(4) binding: u32,
|
|
||||||
@location(5) idx: u32,
|
|
||||||
@location(6) mask_idx: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@location(0) top_left: vec2<f32>,
|
|
||||||
@location(1) bot_right: vec2<f32>,
|
|
||||||
@location(2) uv: vec2<f32>,
|
|
||||||
@location(3) binding: u32,
|
|
||||||
@location(4) idx: u32,
|
|
||||||
@location(5) mask_idx: u32,
|
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Region {
|
|
||||||
pos: vec2<f32>,
|
|
||||||
uv: vec2<f32>,
|
|
||||||
top_left: vec2<f32>,
|
|
||||||
bot_right: vec2<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vs_main(
|
|
||||||
@builtin(vertex_index) vi: u32,
|
|
||||||
in: InstanceInput,
|
|
||||||
) -> VertexOutput {
|
|
||||||
var out: VertexOutput;
|
|
||||||
|
|
||||||
let top_left_rel = vec2(in.x_start.x, in.y_start.x);
|
|
||||||
let top_left_abs = vec2(in.x_start.y, in.y_start.y);
|
|
||||||
let bot_right_rel = vec2(in.x_end.x, in.y_end.x);
|
|
||||||
let bot_right_abs = vec2(in.x_end.y, in.y_end.y);
|
|
||||||
|
|
||||||
let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs);
|
|
||||||
let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs);
|
|
||||||
let size = bot_right - top_left;
|
|
||||||
|
|
||||||
let uv = vec2<f32>(
|
|
||||||
f32(vi % 2u),
|
|
||||||
f32(vi / 2u)
|
|
||||||
);
|
|
||||||
let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0;
|
|
||||||
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
|
||||||
out.uv = uv;
|
|
||||||
out.binding = in.binding;
|
|
||||||
out.idx = in.idx;
|
|
||||||
out.top_left = top_left;
|
|
||||||
out.bot_right = bot_right;
|
|
||||||
out.mask_idx = in.mask_idx;
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_main(
|
|
||||||
in: VertexOutput
|
|
||||||
) -> @location(0) vec4<f32> {
|
|
||||||
let pos = in.clip_position.xy;
|
|
||||||
let region = Region(pos, in.uv, in.top_left, in.bot_right);
|
|
||||||
let i = in.idx;
|
|
||||||
var color: vec4<f32>;
|
|
||||||
switch in.binding {
|
|
||||||
case RECT: {
|
|
||||||
color = draw_rounded_rect(region, rects[i]);
|
|
||||||
}
|
|
||||||
case TEXTURE: {
|
|
||||||
color = draw_texture(region);
|
|
||||||
}
|
|
||||||
case GLYPH: {
|
|
||||||
color = draw_glyph(region, glyphs[i]);
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
color = vec4(1.0, 0.0, 1.0, 1.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if in.mask_idx != 4294967295u {
|
|
||||||
let mask = masks[in.mask_idx];
|
|
||||||
let tl = UiVec2(vec2(mask.x.start.rel, mask.y.start.rel), vec2(mask.x.start.abs, mask.y.start.abs));
|
|
||||||
let br = UiVec2(vec2(mask.x.end.rel, mask.y.end.rel), vec2(mask.x.end.abs, mask.y.end.abs));
|
|
||||||
|
|
||||||
let top_left = floor(tl.rel * window.dim) + floor(tl.abs);
|
|
||||||
let bot_right = floor(br.rel * window.dim) + floor(br.abs);
|
|
||||||
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
|
|
||||||
color *= 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_texture(region: Region) -> vec4<f32> {
|
|
||||||
return textureSample(tex, samp, region.uv, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_glyph(region: Region, g: GlyphInfo) -> vec4<f32> {
|
|
||||||
let uv = mix(g.uv_min, g.uv_max, region.uv);
|
|
||||||
let texel = textureSample(tex, samp, uv, i32(g.layer));
|
|
||||||
if (g.flags & 1u) != 0u {
|
|
||||||
return texel;
|
|
||||||
}
|
|
||||||
var color = unpack4x8unorm(g.color);
|
|
||||||
color.a *= texel.a;
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_rounded_rect(region: Region, rect: Rect) -> vec4<f32> {
|
|
||||||
var color = unpack4x8unorm(rect.color);
|
|
||||||
|
|
||||||
let edge = 0.5;
|
|
||||||
|
|
||||||
let size = region.bot_right - region.top_left;
|
|
||||||
let corner = size / 2.0;
|
|
||||||
let center = region.top_left + corner;
|
|
||||||
|
|
||||||
let dist = distance_from_rect(region.pos, center, corner, rect.radius);
|
|
||||||
color.a *= 1.0 - smoothstep(-min(edge, rect.radius), edge, dist);
|
|
||||||
|
|
||||||
if rect.thickness > 0.0 {
|
|
||||||
let dist2 = distance_from_rect(region.pos, center, corner - rect.thickness, rect.inner_radius);
|
|
||||||
color.a *= smoothstep(-min(edge, rect.inner_radius), edge, dist2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner: vec2<f32>, radius: f32) -> f32 {
|
|
||||||
// vec from center to pixel
|
|
||||||
let p = pixel_pos - rect_center;
|
|
||||||
// vec from inner rect corner to pixel
|
|
||||||
let q = abs(p) - (rect_corner - radius);
|
|
||||||
return length(max(q, vec2(0.0))) - radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
struct GlyphInfo {
|
||||||
|
uv_min: vec2<f32>,
|
||||||
|
uv_max: vec2<f32>,
|
||||||
|
// Which layer of the atlas array this glyph's page is.
|
||||||
|
layer: u32,
|
||||||
|
color: u32,
|
||||||
|
flags: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(1) @binding(0)
|
||||||
|
var<storage> glyphs: array<GlyphInfo>;
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let g = glyphs[in.idx];
|
||||||
|
let uv = mix(g.uv_min, g.uv_max, in.uv);
|
||||||
|
let texel = textureSample(tex, samp, uv, i32(g.layer));
|
||||||
|
if (g.flags & 1u) != 0u {
|
||||||
|
return masked(in, texel);
|
||||||
|
}
|
||||||
|
var color = unpack4x8unorm(g.color);
|
||||||
|
color.a *= texel.a;
|
||||||
|
return masked(in, color);
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
// Prepended to every primitive's shader, which supplies only its own data
|
||||||
|
// struct at group 1 and an `fs_main` that shades one instance.
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var<uniform> window: WindowUniform;
|
||||||
|
@group(0) @binding(1)
|
||||||
|
var<storage> masks: array<Mask>;
|
||||||
|
|
||||||
|
// The texture this draw samples: the glyph atlas, whose layers are its pages,
|
||||||
|
// or one standalone image as an array of one.
|
||||||
|
@group(2) @binding(0)
|
||||||
|
var tex: texture_2d_array<f32>;
|
||||||
|
@group(2) @binding(1)
|
||||||
|
var samp: sampler;
|
||||||
|
|
||||||
|
struct WindowUniform {
|
||||||
|
dim: vec2<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Mask {
|
||||||
|
x: UiSpan,
|
||||||
|
y: UiSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UiSpan {
|
||||||
|
start: UiScalar,
|
||||||
|
end: UiScalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UiScalar {
|
||||||
|
rel: f32,
|
||||||
|
abs: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InstanceInput {
|
||||||
|
@location(0) x_start: vec2<f32>,
|
||||||
|
@location(1) x_end: vec2<f32>,
|
||||||
|
@location(2) y_start: vec2<f32>,
|
||||||
|
@location(3) y_end: vec2<f32>,
|
||||||
|
@location(4) mask_idx: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@location(0) top_left: vec2<f32>,
|
||||||
|
@location(1) bot_right: vec2<f32>,
|
||||||
|
@location(2) uv: vec2<f32>,
|
||||||
|
@location(3) mask_idx: u32,
|
||||||
|
// The instance's own index, which is also where its data sits in group 1.
|
||||||
|
@location(4) @interpolate(flat) idx: u32,
|
||||||
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(
|
||||||
|
@builtin(vertex_index) vi: u32,
|
||||||
|
@builtin(instance_index) ii: u32,
|
||||||
|
in: InstanceInput,
|
||||||
|
) -> VertexOutput {
|
||||||
|
var out: VertexOutput;
|
||||||
|
|
||||||
|
let top_left_rel = vec2(in.x_start.x, in.y_start.x);
|
||||||
|
let top_left_abs = vec2(in.x_start.y, in.y_start.y);
|
||||||
|
let bot_right_rel = vec2(in.x_end.x, in.y_end.x);
|
||||||
|
let bot_right_abs = vec2(in.x_end.y, in.y_end.y);
|
||||||
|
|
||||||
|
let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs);
|
||||||
|
let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs);
|
||||||
|
let size = bot_right - top_left;
|
||||||
|
|
||||||
|
let uv = vec2<f32>(
|
||||||
|
f32(vi % 2u),
|
||||||
|
f32(vi / 2u)
|
||||||
|
);
|
||||||
|
let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0;
|
||||||
|
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
||||||
|
out.uv = uv;
|
||||||
|
out.top_left = top_left;
|
||||||
|
out.bot_right = bot_right;
|
||||||
|
out.mask_idx = in.mask_idx;
|
||||||
|
out.idx = ii;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn masked(in: VertexOutput, color: vec4<f32>) -> vec4<f32> {
|
||||||
|
if in.mask_idx == 4294967295u {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
let mask = masks[in.mask_idx];
|
||||||
|
let tl = vec2(mask.x.start.rel, mask.y.start.rel);
|
||||||
|
let tl_abs = vec2(mask.x.start.abs, mask.y.start.abs);
|
||||||
|
let br = vec2(mask.x.end.rel, mask.y.end.rel);
|
||||||
|
let br_abs = vec2(mask.x.end.abs, mask.y.end.abs);
|
||||||
|
|
||||||
|
let top_left = floor(tl * window.dim) + floor(tl_abs);
|
||||||
|
let bot_right = floor(br * window.dim) + floor(br_abs);
|
||||||
|
let pos = in.clip_position.xy;
|
||||||
|
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
|
||||||
|
return color * 0.0;
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
struct Rect {
|
||||||
|
color: u32,
|
||||||
|
radius: f32,
|
||||||
|
thickness: f32,
|
||||||
|
inner_radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(1) @binding(0)
|
||||||
|
var<storage> rects: array<Rect>;
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let rect = rects[in.idx];
|
||||||
|
var color = unpack4x8unorm(rect.color);
|
||||||
|
|
||||||
|
let edge = 0.5;
|
||||||
|
let size = in.bot_right - in.top_left;
|
||||||
|
let corner = size / 2.0;
|
||||||
|
let center = in.top_left + corner;
|
||||||
|
let pos = in.clip_position.xy;
|
||||||
|
|
||||||
|
let dist = distance_from_rect(pos, center, corner, rect.radius);
|
||||||
|
color.a *= 1.0 - smoothstep(-min(edge, rect.radius), edge, dist);
|
||||||
|
|
||||||
|
if rect.thickness > 0.0 {
|
||||||
|
let dist2 = distance_from_rect(pos, center, corner - rect.thickness, rect.inner_radius);
|
||||||
|
color.a *= smoothstep(-min(edge, rect.inner_radius), edge, dist2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return masked(in, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner: vec2<f32>, radius: f32) -> f32 {
|
||||||
|
// vec from center to pixel
|
||||||
|
let p = pixel_pos - rect_center;
|
||||||
|
// vec from inner rect corner to pixel
|
||||||
|
let q = abs(p) - (rect_corner - radius);
|
||||||
|
return length(max(q, vec2(0.0))) - radius;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// No group 1: the texture is bound at group 2 for this instance alone, so
|
||||||
|
// there is nothing per-instance left to look up.
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
return masked(in, textureSample(tex, samp, in.uv, 0));
|
||||||
|
}
|
||||||
@@ -39,7 +39,11 @@ impl<T: Pod> ArrBuf<T> {
|
|||||||
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
|
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
|
||||||
let mut size = size as u64;
|
let mut size = size as u64;
|
||||||
if usage.contains(BufferUsages::STORAGE) {
|
if usage.contains(BufferUsages::STORAGE) {
|
||||||
size = size.max(std::mem::size_of::<T>() as u64);
|
// An empty storage buffer is still bound, and a binding has to be
|
||||||
|
// non-empty and a multiple of four however small `T` is.
|
||||||
|
size = size
|
||||||
|
.max(std::mem::size_of::<T>() as u64)
|
||||||
|
.next_multiple_of(4);
|
||||||
}
|
}
|
||||||
device.create_buffer(&BufferDescriptor {
|
device.create_buffer(&BufferDescriptor {
|
||||||
label: Some(label),
|
label: Some(label),
|
||||||
|
|||||||
+5
-1
@@ -1,4 +1,6 @@
|
|||||||
use crate::{Mask, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena};
|
use crate::{
|
||||||
|
Mask, PrimitiveRegistry, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena,
|
||||||
|
};
|
||||||
|
|
||||||
mod active;
|
mod active;
|
||||||
mod cache;
|
mod cache;
|
||||||
@@ -14,6 +16,8 @@ pub use size::*;
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct UiData {
|
pub struct UiData {
|
||||||
pub widgets: Widgets,
|
pub widgets: Widgets,
|
||||||
|
/// Every primitive this ui can draw.
|
||||||
|
pub primitives: PrimitiveRegistry,
|
||||||
pub textures: Textures,
|
pub textures: Textures,
|
||||||
pub text: TextData,
|
pub text: TextData,
|
||||||
pub masks: TrackedArena<Mask, u32>,
|
pub masks: TrackedArena<Mask, u32>,
|
||||||
|
|||||||
+18
-15
@@ -1,7 +1,9 @@
|
|||||||
|
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::{GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
render::{GLYPH, GlyphPrimitive, Mask, MaskIdx, PrimitiveHandle, PrimitiveKind},
|
||||||
util::Vec2,
|
util::Vec2,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,16 +22,11 @@ pub struct Painter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Painter<'a> {
|
impl<'a> Painter<'a> {
|
||||||
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
fn primitive_at<P: Pod>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: UiRegion) {
|
||||||
let h = self.state.layers.write(
|
let h = self
|
||||||
self.layer,
|
.state
|
||||||
PrimitiveInst {
|
.layers
|
||||||
id: self.id,
|
.write(self.layer, kind, self.id, primitive, region, self.mask);
|
||||||
primitive,
|
|
||||||
region,
|
|
||||||
mask_idx: self.mask,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.push_primitive(h);
|
self.push_primitive(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,12 +39,17 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a primitive to be rendered
|
/// Writes a primitive to be rendered
|
||||||
pub fn primitive<P: Primitive>(&mut self, primitive: P) {
|
pub fn primitive<P: Pod>(&mut self, kind: PrimitiveKind<P>, primitive: P) {
|
||||||
self.primitive_at(primitive, self.region)
|
self.primitive_at(kind, primitive, self.region)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
pub fn primitive_within<P: Pod>(
|
||||||
self.primitive_at(primitive, region.within(&self.region));
|
&mut self,
|
||||||
|
kind: PrimitiveKind<P>,
|
||||||
|
primitive: P,
|
||||||
|
region: UiRegion,
|
||||||
|
) {
|
||||||
|
self.primitive_at(kind, primitive, region.within(&self.region));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_mask(&mut self, region: UiRegion) {
|
pub fn set_mask(&mut self, region: UiRegion) {
|
||||||
@@ -115,6 +117,7 @@ impl<'a> Painter<'a> {
|
|||||||
region.x.end = region.x.start + UiScalar::abs(glyph.entry.width as f32);
|
region.x.end = region.x.start + UiScalar::abs(glyph.entry.width as f32);
|
||||||
region.y.end = region.y.start + UiScalar::abs(glyph.entry.height as f32);
|
region.y.end = region.y.start + UiScalar::abs(glyph.entry.height as f32);
|
||||||
self.primitive_at(
|
self.primitive_at(
|
||||||
|
GLYPH,
|
||||||
GlyphPrimitive {
|
GlyphPrimitive {
|
||||||
uv_min: glyph.entry.uv_min,
|
uv_min: glyph.entry.uv_min,
|
||||||
uv_max: glyph.entry.uv_max,
|
uv_max: glyph.entry.uv_max,
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ 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 = draws.primitives.instances().len();
|
let primitives: usize = draws.primitives().iter().map(|l| l.instances().len()).sum();
|
||||||
let textures = draws.textures.instances().len();
|
let textures = draws.textures.instances().len();
|
||||||
println!("{indent}{idx}: {primitives} primitives, {textures} textures");
|
println!("{indent}{idx}: {primitives} primitives, {textures} textures");
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -29,12 +29,15 @@ impl Rect {
|
|||||||
|
|
||||||
impl Widget for Rect {
|
impl Widget for Rect {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.primitive(RectPrimitive {
|
painter.primitive(
|
||||||
|
RECT,
|
||||||
|
RectPrimitive {
|
||||||
color: self.color,
|
color: self.color,
|
||||||
radius: self.radius,
|
radius: self.radius,
|
||||||
thickness: self.thickness,
|
thickness: self.thickness,
|
||||||
inner_radius: self.inner_radius,
|
inner_radius: self.inner_radius,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ impl Widget for TextEdit {
|
|||||||
let size = vec2(rect.width() as f32, rect.height() as f32);
|
let size = vec2(rect.width() as f32, rect.height() as f32);
|
||||||
let top_left = vec2(rect.x0 as f32, rect.y0 as f32);
|
let top_left = vec2(rect.x0 as f32, rect.y0 as f32);
|
||||||
painter.primitive_within(
|
painter.primitive_within(
|
||||||
|
RECT,
|
||||||
RectPrimitive::color(Color::SKY),
|
RectPrimitive::color(Color::SKY),
|
||||||
size.align(Align::TOP_LEFT).offset(top_left).within(®ion),
|
size.align(Align::TOP_LEFT).offset(top_left).within(®ion),
|
||||||
);
|
);
|
||||||
@@ -82,6 +83,7 @@ impl Widget for TextEdit {
|
|||||||
let size = vec2(caret.width() as f32, caret.height() as f32);
|
let size = vec2(caret.width() as f32, caret.height() as f32);
|
||||||
let top_left = vec2(caret.x0 as f32, caret.y0 as f32);
|
let top_left = vec2(caret.x0 as f32, caret.y0 as f32);
|
||||||
painter.primitive_within(
|
painter.primitive_within(
|
||||||
|
RECT,
|
||||||
RectPrimitive::color(Color::WHITE),
|
RectPrimitive::color(Color::WHITE),
|
||||||
size.align(Align::TOP_LEFT).offset(top_left).within(®ion),
|
size.align(Align::TOP_LEFT).offset(top_left).within(®ion),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in new issue
Block a user