Take a primitive's kind from its type, and keep images out of the rest
A `Primitive` now carries its own WGSL, and `PrimitiveRegistry` keys ids by `TypeId`, so `Painter::primitive` takes only the value and the `RECT`, `GLYPH` and `TEXTURE` constants are gone. Registering is what a first draw does; the built-ins are seeded up front so first-draw order cannot decide anything about them. What a primitive samples is no longer something every registration states. The glyph atlas and the one sampler moved into the shared group, which is where a mask texture would go too, so a rect's pipeline has no texture in its layout at all. Only a primitive whose type sets `TEXTURE` gets an image group, and that is also what records the slot at write time -- so nothing reads a `u32` back out of the instance payload. Verified on the headless rig: the tabs example, two images added at runtime, an image alone in a layer, and text spanning a four-layer atlas after the array grew twice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7b318e3271
commit
29d390da52
11 files changed
+244
-309
No files matched your search
+84
-103
@@ -30,7 +30,7 @@ const PRELUDE: &str = include_str!("./shader/prelude.wgsl");
|
|||||||
pub struct UiRenderNode {
|
pub struct UiRenderNode {
|
||||||
shared_layout: BindGroupLayout,
|
shared_layout: BindGroupLayout,
|
||||||
shared_group: BindGroup,
|
shared_group: BindGroup,
|
||||||
texture_layout: BindGroupLayout,
|
image_layout: BindGroupLayout,
|
||||||
format: TextureFormat,
|
format: TextureFormat,
|
||||||
|
|
||||||
/// One per registered primitive, in id order.
|
/// One per registered primitive, in id order.
|
||||||
@@ -46,26 +46,22 @@ pub struct UiRenderNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct RenderLayer {
|
struct RenderLayer {
|
||||||
/// One per registered primitive, in id order, matching `LayerDraws` --
|
/// One per registered primitive, `None` where this layer draws none.
|
||||||
/// `None` where this layer draws none, so a primitive nobody uses costs no
|
|
||||||
/// buffers per layer.
|
|
||||||
primitives: Vec<Option<ListBuffers>>,
|
primitives: Vec<Option<ListBuffers>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What draws one registered primitive. The group 1 layout is its own rather
|
/// What draws one registered primitive.
|
||||||
/// than shared, so its entry size is the minimum only for it.
|
|
||||||
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.
|
||||||
struct ListBuffers {
|
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.
|
/// The image each instance binds. Empty unless the primitive is textured.
|
||||||
slots: Vec<u32>,
|
slots: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,41 +70,28 @@ impl UiRenderNode {
|
|||||||
pass.set_bind_group(0, &self.shared_group, &[]);
|
pass.set_bind_group(0, &self.shared_group, &[]);
|
||||||
for i in &self.active {
|
for i in &self.active {
|
||||||
let layer = &self.layers[i];
|
let layer = &self.layers[i];
|
||||||
// Types run in registration order, so a rect is under a glyph is
|
|
||||||
// under a texture. Ordering beyond that is what `Layers` is for --
|
|
||||||
// freeing an instance swaps another into its place.
|
|
||||||
for (id, list) in layer.primitives.iter().enumerate() {
|
for (id, list) in layer.primitives.iter().enumerate() {
|
||||||
let Some(list) = list else {
|
let Some(list) = list else { continue };
|
||||||
continue;
|
let Some(group) = &list.group else { continue };
|
||||||
};
|
pass.set_pipeline(&self.primitives[id].pipeline);
|
||||||
let Some(group) = &list.group else {
|
// After the pipeline: a change drops the groups from where two
|
||||||
continue;
|
// pipeline layouts differ, and each primitive has its own.
|
||||||
};
|
|
||||||
let primitive = &self.primitives[id];
|
|
||||||
pass.set_pipeline(&primitive.pipeline);
|
|
||||||
// Both groups after the pipeline: each primitive has its own
|
|
||||||
// pipeline layout, and a change drops the groups from where
|
|
||||||
// the two layouts differ.
|
|
||||||
pass.set_bind_group(1, group, &[]);
|
pass.set_bind_group(1, group, &[]);
|
||||||
pass.set_vertex_buffer(0, list.instance.buffer.slice(..));
|
pass.set_vertex_buffer(0, list.instance.buffer.slice(..));
|
||||||
match primitive.texture {
|
if list.slots.is_empty() {
|
||||||
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);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
PrimitiveTexture::PerInstance => {
|
|
||||||
for (i, &slot) in list.slots.iter().enumerate() {
|
for (i, &slot) in list.slots.iter().enumerate() {
|
||||||
let Some(texture) = self.textures.group(slot) else {
|
let Some(image) = self.textures.group(slot) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
pass.set_bind_group(2, texture, &[]);
|
pass.set_bind_group(2, image, &[]);
|
||||||
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,
|
||||||
@@ -117,9 +100,7 @@ impl UiRenderNode {
|
|||||||
ui: &mut UiData,
|
ui: &mut UiData,
|
||||||
ui_render: &mut UiRenderState,
|
ui_render: &mut UiRenderState,
|
||||||
) {
|
) {
|
||||||
// Before the layers: a list is given the bind group layout of the
|
// Before the layers: each list is given its pipeline's data layout.
|
||||||
// pipeline that will draw it, so every registered primitive needs one
|
|
||||||
// by the time a layer is reached.
|
|
||||||
self.build_pipelines(device, &ui.primitives);
|
self.build_pipelines(device, &ui.primitives);
|
||||||
self.active.clear();
|
self.active.clear();
|
||||||
for (i, draws) in ui_render.layers.iter_mut() {
|
for (i, draws) in ui_render.layers.iter_mut() {
|
||||||
@@ -150,35 +131,30 @@ impl UiRenderNode {
|
|||||||
};
|
};
|
||||||
// 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 primitive = &self.primitives[id];
|
let layout = &self.primitives[id].data_layout;
|
||||||
buffers
|
buffers
|
||||||
.get_or_insert_with(|| ListBuffers::new(device))
|
.get_or_insert_with(|| ListBuffers::new(device))
|
||||||
.update(
|
.update(device, queue, layout, list);
|
||||||
device,
|
|
||||||
queue,
|
|
||||||
primitive.texture,
|
|
||||||
&primitive.data_layout,
|
|
||||||
list,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
draws.updated = false;
|
draws.updated = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut shared_stale = self.pages.update(&mut ui.text.atlas);
|
||||||
if ui.masks.changed {
|
if ui.masks.changed {
|
||||||
ui.masks.changed = false;
|
ui.masks.changed = false;
|
||||||
if self.masks.update(device, queue, &ui.masks[..]) {
|
shared_stale |= self.masks.update(device, queue, &ui.masks[..]);
|
||||||
|
}
|
||||||
|
if shared_stale {
|
||||||
self.shared_group = Self::shared_group(
|
self.shared_group = Self::shared_group(
|
||||||
device,
|
device,
|
||||||
&self.shared_layout,
|
&self.shared_layout,
|
||||||
&self.window_buffer,
|
&self.window_buffer,
|
||||||
&self.masks,
|
&self.masks,
|
||||||
|
&self.pages,
|
||||||
|
&self.sampler,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
self.textures.update(&mut ui.textures, &self.image_layout);
|
||||||
self.pages
|
|
||||||
.update(&mut ui.text.atlas, &self.texture_layout, &self.sampler);
|
|
||||||
self.textures
|
|
||||||
.update(&mut ui.textures, &self.texture_layout, &self.sampler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, size: impl Into<Vec2>, queue: &Queue) {
|
pub fn resize(&mut self, size: impl Into<Vec2>, queue: &Queue) {
|
||||||
@@ -202,23 +178,30 @@ impl UiRenderNode {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let shared_layout = Self::shared_layout(device);
|
let shared_layout = Self::shared_layout(device);
|
||||||
let texture_layout = Self::texture_layout(device);
|
let image_layout = Self::image_layout(device);
|
||||||
|
|
||||||
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 sampler = default_sampler(device);
|
let sampler = default_sampler(device);
|
||||||
let pages = GpuPages::new(device, queue, &texture_layout, &sampler);
|
let pages = GpuPages::new(device, queue);
|
||||||
let textures = GpuTextures::new(device, queue);
|
let textures = GpuTextures::new(device, queue);
|
||||||
|
let shared_group = Self::shared_group(
|
||||||
|
device,
|
||||||
|
&shared_layout,
|
||||||
|
&window_buffer,
|
||||||
|
&masks,
|
||||||
|
&pages,
|
||||||
|
&sampler,
|
||||||
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
shared_layout,
|
shared_layout,
|
||||||
shared_group,
|
shared_group,
|
||||||
texture_layout,
|
image_layout,
|
||||||
format: config.format,
|
format: config.format,
|
||||||
primitives: Vec::new(),
|
primitives: Vec::new(),
|
||||||
window_buffer,
|
window_buffer,
|
||||||
@@ -236,34 +219,23 @@ impl UiRenderNode {
|
|||||||
fn build_pipelines(&mut self, device: &Device, registry: &PrimitiveRegistry) {
|
fn build_pipelines(&mut self, device: &Device, registry: &PrimitiveRegistry) {
|
||||||
for source in ®istry.sources()[self.primitives.len()..] {
|
for source in ®istry.sources()[self.primitives.len()..] {
|
||||||
let data_layout = Self::data_layout(device, source.stride);
|
let data_layout = Self::data_layout(device, source.stride);
|
||||||
let layout = Self::pipeline_layout(
|
let mut groups = vec![&self.shared_layout, &data_layout];
|
||||||
device,
|
if source.textured {
|
||||||
&self.shared_layout,
|
groups.push(&self.image_layout);
|
||||||
&data_layout,
|
}
|
||||||
&self.texture_layout,
|
let layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
||||||
);
|
label: Some(source.label),
|
||||||
|
bind_group_layouts: &groups,
|
||||||
|
immediate_size: 0,
|
||||||
|
});
|
||||||
let pipeline = Self::pipeline(device, &layout, self.format, source.wgsl, source.label);
|
let pipeline = Self::pipeline(device, &layout, self.format, source.wgsl, source.label);
|
||||||
self.primitives.push(PrimitivePipeline {
|
self.primitives.push(PrimitivePipeline {
|
||||||
data_layout,
|
data_layout,
|
||||||
pipeline,
|
pipeline,
|
||||||
texture: source.texture,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pipeline_layout(
|
|
||||||
device: &Device,
|
|
||||||
shared: &BindGroupLayout,
|
|
||||||
data: &BindGroupLayout,
|
|
||||||
texture: &BindGroupLayout,
|
|
||||||
) -> PipelineLayout {
|
|
||||||
device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
|
||||||
label: Some("ui"),
|
|
||||||
bind_group_layouts: &[shared, data, texture],
|
|
||||||
immediate_size: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pipeline(
|
fn pipeline(
|
||||||
device: &Device,
|
device: &Device,
|
||||||
layout: &PipelineLayout,
|
layout: &PipelineLayout,
|
||||||
@@ -314,7 +286,8 @@ impl UiRenderNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Group 0: what every draw in the ui shares.
|
/// What every draw in the ui is given, whether or not its shader reads it:
|
||||||
|
/// the window, the masks, the glyph atlas and the one sampler.
|
||||||
fn shared_layout(device: &Device) -> BindGroupLayout {
|
fn shared_layout(device: &Device) -> BindGroupLayout {
|
||||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
entries: &[
|
entries: &[
|
||||||
@@ -338,6 +311,22 @@ impl UiRenderNode {
|
|||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Texture {
|
||||||
|
sample_type: TextureSampleType::Float { filterable: false },
|
||||||
|
view_dimension: TextureViewDimension::D2Array,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 3,
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
label: Some("ui shared"),
|
label: Some("ui shared"),
|
||||||
})
|
})
|
||||||
@@ -348,6 +337,8 @@ impl UiRenderNode {
|
|||||||
layout: &BindGroupLayout,
|
layout: &BindGroupLayout,
|
||||||
window: &Buffer,
|
window: &Buffer,
|
||||||
masks: &ArrBuf<Mask>,
|
masks: &ArrBuf<Mask>,
|
||||||
|
pages: &GpuPages,
|
||||||
|
sampler: &Sampler,
|
||||||
) -> BindGroup {
|
) -> BindGroup {
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
device.create_bind_group(&BindGroupDescriptor {
|
||||||
layout,
|
layout,
|
||||||
@@ -360,16 +351,22 @@ impl UiRenderNode {
|
|||||||
binding: 1,
|
binding: 1,
|
||||||
resource: masks.buffer.as_entire_binding(),
|
resource: masks.buffer.as_entire_binding(),
|
||||||
},
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: BindingResource::TextureView(pages.view()),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 3,
|
||||||
|
resource: BindingResource::Sampler(sampler),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
label: Some("ui shared"),
|
label: Some("ui shared"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Group 1: one list's per-instance data, whatever its shader reads it as.
|
/// One list's per-instance data. Per primitive with `stride` stated: a
|
||||||
///
|
/// `None` minimum is filled in from the first pipeline built against the
|
||||||
/// One per primitive with `stride` stated, because a `None` minimum is
|
/// layout, so a shared one would hold every primitive to the largest.
|
||||||
/// filled in from the first pipeline built against the layout -- sharing
|
|
||||||
/// one would hold every primitive to the largest.
|
|
||||||
fn data_layout(device: &Device, stride: u64) -> BindGroupLayout {
|
fn data_layout(device: &Device, stride: u64) -> BindGroupLayout {
|
||||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
entries: &[BindGroupLayoutEntry {
|
entries: &[BindGroupLayoutEntry {
|
||||||
@@ -386,29 +383,21 @@ impl UiRenderNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Group 2: the texture this draw samples, and the sampler. No `count` on
|
/// The one image an instance samples. The sampler is shared, so there is
|
||||||
/// either entry -- plain Vulkan 1.0 / GLES sampling is all this needs.
|
/// nothing else in here.
|
||||||
fn texture_layout(device: &Device) -> BindGroupLayout {
|
fn image_layout(device: &Device) -> BindGroupLayout {
|
||||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
entries: &[
|
entries: &[BindGroupLayoutEntry {
|
||||||
BindGroupLayoutEntry {
|
|
||||||
binding: 0,
|
binding: 0,
|
||||||
visibility: ShaderStages::FRAGMENT,
|
visibility: ShaderStages::FRAGMENT,
|
||||||
ty: BindingType::Texture {
|
ty: BindingType::Texture {
|
||||||
sample_type: TextureSampleType::Float { filterable: false },
|
sample_type: TextureSampleType::Float { filterable: false },
|
||||||
view_dimension: TextureViewDimension::D2Array,
|
view_dimension: TextureViewDimension::D2,
|
||||||
multisampled: false,
|
multisampled: false,
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
},
|
}],
|
||||||
BindGroupLayoutEntry {
|
label: Some("ui image"),
|
||||||
binding: 1,
|
|
||||||
visibility: ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
|
|
||||||
count: None,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
label: Some("ui texture"),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,22 +436,14 @@ 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();
|
self.slots.clear();
|
||||||
// Read rather than cast: the payload is a byte vec, so it carries
|
self.slots.extend_from_slice(list.slots());
|
||||||
// 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 to bind, and nothing to draw.
|
||||||
// draws it, so it has no bind group either.
|
|
||||||
if list.instances().is_empty() {
|
if list.instances().is_empty() {
|
||||||
self.group = None;
|
self.group = None;
|
||||||
} else if resized || self.group.is_none() {
|
} else if resized || self.group.is_none() {
|
||||||
|
|||||||
+21
-21
@@ -2,10 +2,7 @@ use wgpu::*;
|
|||||||
|
|
||||||
use crate::GlyphAtlas;
|
use crate::GlyphAtlas;
|
||||||
|
|
||||||
use super::{
|
use super::{atlas::PAGE, texture::write_region};
|
||||||
atlas::PAGE,
|
|
||||||
texture::{array_view, texture_group, write_region},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The glyph atlas on the GPU: one array texture whose layers are the pages
|
/// The glyph atlas on the GPU: one array texture whose layers are the pages
|
||||||
/// `GlyphAtlas` packs.
|
/// `GlyphAtlas` packs.
|
||||||
@@ -17,29 +14,25 @@ pub struct GpuPages {
|
|||||||
device: Device,
|
device: Device,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
texture: Texture,
|
texture: Texture,
|
||||||
group: BindGroup,
|
view: TextureView,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpuPages {
|
impl GpuPages {
|
||||||
pub fn new(
|
pub fn new(device: &Device, queue: &Queue) -> Self {
|
||||||
device: &Device,
|
|
||||||
queue: &Queue,
|
|
||||||
layout: &BindGroupLayout,
|
|
||||||
sampler: &Sampler,
|
|
||||||
) -> Self {
|
|
||||||
let texture = create_array(device, 1);
|
let texture = create_array(device, 1);
|
||||||
let group = texture_group(device, layout, &array_view(&texture), sampler);
|
|
||||||
Self {
|
Self {
|
||||||
device: device.clone(),
|
device: device.clone(),
|
||||||
queue: queue.clone(),
|
queue: queue.clone(),
|
||||||
|
view: array_view(&texture),
|
||||||
texture,
|
texture,
|
||||||
group,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, atlas: &mut GlyphAtlas, layout: &BindGroupLayout, sampler: &Sampler) {
|
/// Returns whether the array was replaced, which stales the view.
|
||||||
if atlas.page_count() > self.texture.depth_or_array_layers() {
|
pub fn update(&mut self, atlas: &mut GlyphAtlas) -> bool {
|
||||||
self.grow(atlas.page_count(), layout, sampler);
|
let grew = atlas.page_count() > self.texture.depth_or_array_layers();
|
||||||
|
if grew {
|
||||||
|
self.grow(atlas.page_count());
|
||||||
}
|
}
|
||||||
for (upload, page) in atlas.uploads() {
|
for (upload, page) in atlas.uploads() {
|
||||||
let dst = TexelCopyTextureInfo {
|
let dst = TexelCopyTextureInfo {
|
||||||
@@ -54,15 +47,15 @@ impl GpuPages {
|
|||||||
};
|
};
|
||||||
write_region(&self.queue, dst, page, upload.rect);
|
write_region(&self.queue, dst, page, upload.rect);
|
||||||
}
|
}
|
||||||
|
grew
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn group(&self) -> &BindGroup {
|
pub fn view(&self) -> &TextureView {
|
||||||
&self.group
|
&self.view
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Doubles until `needed` fits and copies the old layers across GPU side.
|
/// Doubles until `needed` fits and copies the old layers across GPU side.
|
||||||
/// The new view invalidates the old group, so that is rebuilt here.
|
fn grow(&mut self, needed: u32) {
|
||||||
fn grow(&mut self, needed: u32, layout: &BindGroupLayout, sampler: &Sampler) {
|
|
||||||
let old = self.texture.depth_or_array_layers();
|
let old = self.texture.depth_or_array_layers();
|
||||||
let mut layers = old;
|
let mut layers = old;
|
||||||
while layers < needed {
|
while layers < needed {
|
||||||
@@ -84,11 +77,18 @@ impl GpuPages {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.queue.submit(std::iter::once(encoder.finish()));
|
self.queue.submit(std::iter::once(encoder.finish()));
|
||||||
self.group = texture_group(&self.device, layout, &array_view(&texture), sampler);
|
self.view = array_view(&texture);
|
||||||
self.texture = texture;
|
self.texture = texture;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn array_view(texture: &Texture) -> TextureView {
|
||||||
|
texture.create_view(&TextureViewDescriptor {
|
||||||
|
dimension: Some(TextureViewDimension::D2Array),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn create_array(device: &Device, layers: u32) -> Texture {
|
fn create_array(device: &Device, layers: u32) -> Texture {
|
||||||
device.create_texture(&TextureDescriptor {
|
device.create_texture(&TextureDescriptor {
|
||||||
label: Some("glyph atlas"),
|
label: Some("glyph atlas"),
|
||||||
|
|||||||
@@ -1,38 +1,39 @@
|
|||||||
use std::marker::PhantomData;
|
use std::{any::TypeId, marker::PhantomData};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Color, UiRegion, WidgetId,
|
Color, UiRegion, WidgetId,
|
||||||
render::data::{MaskIdx, PrimitiveInstance},
|
render::data::{MaskIdx, PrimitiveInstance},
|
||||||
util::Vec2,
|
util::{HashMap, Vec2},
|
||||||
};
|
};
|
||||||
use bytemuck::Pod;
|
use bytemuck::Pod;
|
||||||
|
|
||||||
/// One instance of a registered primitive, laid out as the struct that
|
/// One instance of a primitive, laid out as the struct its shader reads.
|
||||||
/// primitive's shader reads at `@group(1) @binding(0)`.
|
|
||||||
///
|
///
|
||||||
/// A `PrimitiveKind<P>` is only minted by `register::<P>`, and `write` takes
|
/// The type carries its own shader, so drawing one is all the wiring it needs:
|
||||||
/// the kind and the value together, so holding one is the proof that `P` has a
|
/// its list, free list, buffers and pipeline follow from being registered.
|
||||||
/// list of its own to go in and a write needs no check.
|
pub trait Primitive: Pod + 'static {
|
||||||
pub trait Primitive: Pod {}
|
/// Compiled after `prelude.wgsl`, which states what it declares and what
|
||||||
|
/// it is given.
|
||||||
|
const WGSL: &'static str;
|
||||||
|
/// Reads the image an instance samples, for a primitive that draws one.
|
||||||
|
/// Each instance is then a draw of its own, bound for it alone.
|
||||||
|
const TEXTURE: Option<fn(&Self) -> u32> = None;
|
||||||
|
}
|
||||||
|
|
||||||
/// Which registered primitive an instance is, and so which list it lives in
|
/// Which registered primitive an instance is. Only `PrimitiveRegistry::kind`
|
||||||
/// and which pipeline draws it. The type ties a `write` to what its shader reads.
|
/// mints one, so holding it is the proof that `P` has a list to go in.
|
||||||
pub struct PrimitiveKind<P> {
|
pub struct PrimitiveKind<P> {
|
||||||
id: u32,
|
id: u32,
|
||||||
_p: PhantomData<fn(P)>,
|
_p: PhantomData<fn(P)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> PrimitiveKind<P> {
|
impl<P> PrimitiveKind<P> {
|
||||||
const fn new(id: u32) -> Self {
|
fn new(id: u32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
_p: PhantomData,
|
_p: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> u32 {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> Clone for PrimitiveKind<P> {
|
impl<P> Clone for PrimitiveKind<P> {
|
||||||
@@ -43,80 +44,50 @@ impl<P> Clone for PrimitiveKind<P> {
|
|||||||
|
|
||||||
impl<P> Copy for PrimitiveKind<P> {}
|
impl<P> Copy for PrimitiveKind<P> {}
|
||||||
|
|
||||||
pub const RECT: PrimitiveKind<RectPrimitive> = PrimitiveKind::new(0);
|
/// Every primitive a ui can draw, in the order they were first drawn.
|
||||||
pub const GLYPH: PrimitiveKind<GlyphPrimitive> = PrimitiveKind::new(1);
|
|
||||||
pub const TEXTURE: PrimitiveKind<TexturePrimitive> = PrimitiveKind::new(2);
|
|
||||||
|
|
||||||
/// Every primitive a ui can draw, in id order. Registering one is all the
|
|
||||||
/// wiring it needs: its list, buffers, free list and pipeline follow, and
|
|
||||||
/// 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.
|
|
||||||
///
|
|
||||||
/// 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>,
|
||||||
|
ids: HashMap<TypeId, u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PrimitiveSource {
|
pub struct PrimitiveSource {
|
||||||
pub wgsl: &'static str,
|
pub wgsl: &'static str,
|
||||||
pub label: &'static str,
|
pub label: &'static str,
|
||||||
/// Size of one instance's entry, which the renderer states as the group 1
|
/// Size of one instance's entry, stated as the data binding's minimum.
|
||||||
/// binding's minimum rather than leaving it to be inferred.
|
|
||||||
pub stride: u64,
|
pub stride: u64,
|
||||||
pub texture: PrimitiveTexture,
|
/// Whether an instance binds a texture of its own, from `P::TEXTURE`.
|
||||||
}
|
pub textured: bool,
|
||||||
|
|
||||||
/// 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 {
|
||||||
|
/// The built-ins are registered up front rather than on first use, so that
|
||||||
|
/// what a ui happens to draw first cannot decide anything about them.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
use PrimitiveTexture::*;
|
let mut registry = Self {
|
||||||
let mut registry = Self { kinds: Vec::new() };
|
kinds: Vec::new(),
|
||||||
let rect =
|
ids: HashMap::default(),
|
||||||
registry.register::<RectPrimitive>(include_str!("shader/rect.wgsl"), "rect", Atlas);
|
};
|
||||||
let glyph =
|
registry.kind::<RectPrimitive>();
|
||||||
registry.register::<GlyphPrimitive>(include_str!("shader/glyph.wgsl"), "glyph", Atlas);
|
registry.kind::<GlyphPrimitive>();
|
||||||
let texture = registry.register::<TexturePrimitive>(
|
registry.kind::<TexturePrimitive>();
|
||||||
include_str!("shader/texture.wgsl"),
|
|
||||||
"texture",
|
|
||||||
PerInstance,
|
|
||||||
);
|
|
||||||
// The built-ins have constant ids so a widget can name one without the
|
|
||||||
// registry; registering them first is what makes those constants true.
|
|
||||||
assert_eq!(
|
|
||||||
(rect.id(), glyph.id(), texture.id()),
|
|
||||||
(RECT.id(), GLYPH.id(), TEXTURE.id())
|
|
||||||
);
|
|
||||||
registry
|
registry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrimitiveRegistry {
|
impl PrimitiveRegistry {
|
||||||
pub fn register<P: Primitive>(
|
/// Registers `P` if this is the first time it has been drawn.
|
||||||
&mut self,
|
pub fn kind<P: Primitive>(&mut self) -> PrimitiveKind<P> {
|
||||||
wgsl: &'static str,
|
let Self { kinds, ids } = self;
|
||||||
label: &'static str,
|
let id = *ids.entry(TypeId::of::<P>()).or_insert_with(|| {
|
||||||
texture: PrimitiveTexture,
|
kinds.push(PrimitiveSource {
|
||||||
) -> PrimitiveKind<P> {
|
wgsl: P::WGSL,
|
||||||
self.kinds.push(PrimitiveSource {
|
label: std::any::type_name::<P>(),
|
||||||
wgsl,
|
|
||||||
label,
|
|
||||||
stride: size_of::<P>() as u64,
|
stride: size_of::<P>() as u64,
|
||||||
texture,
|
textured: P::TEXTURE.is_some(),
|
||||||
});
|
});
|
||||||
PrimitiveKind::new(self.kinds.len() as u32 - 1)
|
kinds.len() as u32 - 1
|
||||||
|
});
|
||||||
|
PrimitiveKind::new(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sources(&self) -> &[PrimitiveSource] {
|
pub fn sources(&self) -> &[PrimitiveSource] {
|
||||||
@@ -124,18 +95,18 @@ impl PrimitiveRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One registered primitive's instances in one layer: the instances, the
|
/// One registered primitive's instances in one layer. Everything per-instance
|
||||||
/// widget each belongs to, the slots waiting to be reused, and `stride` bytes
|
/// rides here, so it stays in step through a `swap_remove`.
|
||||||
/// of that primitive's data per instance at the same index.
|
|
||||||
///
|
|
||||||
/// `stride` comes from the type the list was made for, so a write is never
|
|
||||||
/// checked against it. The data rides here rather than beside the list so the
|
|
||||||
/// two stay in step through a `swap_remove`.
|
|
||||||
pub struct InstanceList {
|
pub struct InstanceList {
|
||||||
instances: Vec<PrimitiveInstance>,
|
instances: Vec<PrimitiveInstance>,
|
||||||
|
/// The widget each instance belongs to, for renumbering its handles.
|
||||||
assoc: Vec<WidgetId>,
|
assoc: Vec<WidgetId>,
|
||||||
free: Vec<usize>,
|
free: Vec<usize>,
|
||||||
|
/// `stride` bytes of the primitive's own data per instance.
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
|
/// The image each instance samples, from `P::TEXTURE`. Empty without it.
|
||||||
|
slots: Vec<u32>,
|
||||||
|
/// From the type the list was made for, so a write is never checked.
|
||||||
stride: usize,
|
stride: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +117,7 @@ impl InstanceList {
|
|||||||
assoc: Vec::new(),
|
assoc: Vec::new(),
|
||||||
free: Vec::new(),
|
free: Vec::new(),
|
||||||
data: Vec::new(),
|
data: Vec::new(),
|
||||||
|
slots: Vec::new(),
|
||||||
stride: size_of::<P>(),
|
stride: size_of::<P>(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,17 +130,31 @@ impl InstanceList {
|
|||||||
&self.data
|
&self.data
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(&mut self, id: WidgetId, inst: PrimitiveInstance, data: &[u8]) -> usize {
|
pub fn slots(&self) -> &[u32] {
|
||||||
|
&self.slots
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(
|
||||||
|
&mut self,
|
||||||
|
id: WidgetId,
|
||||||
|
inst: PrimitiveInstance,
|
||||||
|
data: &[u8],
|
||||||
|
slot: Option<u32>,
|
||||||
|
) -> usize {
|
||||||
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);
|
self.data[i * self.stride..][..self.stride].copy_from_slice(data);
|
||||||
|
if let Some(slot) = slot {
|
||||||
|
self.slots[i] = slot;
|
||||||
|
}
|
||||||
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);
|
self.data.extend_from_slice(data);
|
||||||
|
self.slots.extend(slot);
|
||||||
i
|
i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,10 +169,14 @@ impl InstanceList {
|
|||||||
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 data = &mut self.data;
|
||||||
|
let slots = &mut self.slots;
|
||||||
let stride = self.stride;
|
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 !slots.is_empty() {
|
||||||
|
slots.swap_remove(i);
|
||||||
|
}
|
||||||
let last = instances.len();
|
let last = instances.len();
|
||||||
data.copy_within(last * stride..(last + 1) * stride, i * stride);
|
data.copy_within(last * stride..(last + 1) * stride, i * stride);
|
||||||
data.truncate(last * stride);
|
data.truncate(last * stride);
|
||||||
@@ -204,9 +194,7 @@ impl InstanceList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Everything one layer draws, one list per registered primitive. They index
|
/// Everything one layer draws, one list per registered primitive.
|
||||||
/// independently, so a handle or a renumbering naming only a position would be
|
|
||||||
/// ambiguous between them.
|
|
||||||
pub struct LayerDraws {
|
pub struct LayerDraws {
|
||||||
/// `None` until this layer draws that primitive, because only the write
|
/// `None` until this layer draws that primitive, because only the write
|
||||||
/// knows the type the list is for.
|
/// knows the type the list is for.
|
||||||
@@ -247,6 +235,7 @@ impl LayerDraws {
|
|||||||
id,
|
id,
|
||||||
PrimitiveInstance { region, mask_idx },
|
PrimitiveInstance { region, mask_idx },
|
||||||
bytemuck::bytes_of(&primitive),
|
bytemuck::bytes_of(&primitive),
|
||||||
|
P::TEXTURE.map(|slot| slot(&primitive)),
|
||||||
);
|
);
|
||||||
PrimitiveHandle {
|
PrimitiveHandle {
|
||||||
layer,
|
layer,
|
||||||
@@ -309,7 +298,7 @@ pub struct PrimitiveHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct RectPrimitive {
|
pub struct RectPrimitive {
|
||||||
pub color: Color<u8>,
|
pub color: Color<u8>,
|
||||||
pub radius: f32,
|
pub radius: f32,
|
||||||
@@ -317,9 +306,9 @@ pub struct RectPrimitive {
|
|||||||
pub inner_radius: f32,
|
pub inner_radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl bytemuck::Pod for RectPrimitive {}
|
impl Primitive for RectPrimitive {
|
||||||
unsafe impl bytemuck::Zeroable for RectPrimitive {}
|
const WGSL: &'static str = include_str!("shader/rect.wgsl");
|
||||||
impl Primitive for RectPrimitive {}
|
}
|
||||||
|
|
||||||
impl RectPrimitive {
|
impl RectPrimitive {
|
||||||
pub fn color(color: Color<u8>) -> Self {
|
pub fn color(color: Color<u8>) -> Self {
|
||||||
@@ -345,18 +334,23 @@ pub struct GlyphPrimitive {
|
|||||||
pub flags: u32,
|
pub flags: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manual rather than derived: the align(8) leaves four bytes of padding, which
|
||||||
|
// is how WGSL lays the struct out.
|
||||||
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 {}
|
impl Primitive for GlyphPrimitive {
|
||||||
|
const WGSL: &'static str = include_str!("shader/glyph.wgsl");
|
||||||
|
}
|
||||||
|
|
||||||
/// One drawn image. Its shader reads nothing: the slot names the texture bound
|
/// One drawn image. Its shader reads nothing per instance; the slot names the
|
||||||
/// for this instance alone, which is what `PrimitiveTexture::PerInstance` does.
|
/// texture to bind for it.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct TexturePrimitive {
|
pub struct TexturePrimitive {
|
||||||
pub slot: u32,
|
pub slot: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl bytemuck::Pod for TexturePrimitive {}
|
impl Primitive for TexturePrimitive {
|
||||||
unsafe impl bytemuck::Zeroable for TexturePrimitive {}
|
const WGSL: &'static str = include_str!("shader/texture.wgsl");
|
||||||
impl Primitive for TexturePrimitive {}
|
const TEXTURE: Option<fn(&Self) -> u32> = Some(|texture| texture.slot);
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Matches `GlyphEntry::IS_COLORED`.
|
||||||
|
const COLORED: u32 = 1u;
|
||||||
|
|
||||||
struct GlyphInfo {
|
struct GlyphInfo {
|
||||||
uv_min: vec2<f32>,
|
uv_min: vec2<f32>,
|
||||||
uv_max: vec2<f32>,
|
uv_max: vec2<f32>,
|
||||||
@@ -14,8 +17,8 @@ var<storage> glyphs: array<GlyphInfo>;
|
|||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
let g = glyphs[in.idx];
|
let g = glyphs[in.idx];
|
||||||
let uv = mix(g.uv_min, g.uv_max, in.uv);
|
let uv = mix(g.uv_min, g.uv_max, in.uv);
|
||||||
let texel = textureSample(tex, samp, uv, i32(g.layer));
|
let texel = textureSample(atlas, samp, uv, i32(g.layer));
|
||||||
if (g.flags & 1u) != 0u {
|
if (g.flags & COLORED) != 0u {
|
||||||
return masked(in, texel);
|
return masked(in, texel);
|
||||||
}
|
}
|
||||||
var color = unpack4x8unorm(g.color);
|
var color = unpack4x8unorm(g.color);
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
// Prepended to every primitive's shader, which supplies only its own data
|
// Prepended to every primitive's shader, which declares its own instance data
|
||||||
// struct at group 1 and an `fs_main` that shades one instance.
|
// as `var<storage> <name>: array<T>` at group 1 binding 0, and an `fs_main`
|
||||||
|
// shading one instance of it. A primitive that samples an image of its own
|
||||||
|
// takes it at group 2 binding 0; see texture.wgsl.
|
||||||
|
|
||||||
@group(0) @binding(0)
|
@group(0) @binding(0)
|
||||||
var<uniform> window: WindowUniform;
|
var<uniform> window: WindowUniform;
|
||||||
@group(0) @binding(1)
|
@group(0) @binding(1)
|
||||||
var<storage> masks: array<Mask>;
|
var<storage> masks: array<Mask>;
|
||||||
|
// The glyph atlas, whose array layers are its pages.
|
||||||
// The texture this draw samples: the glyph atlas, whose layers are its pages,
|
@group(0) @binding(2)
|
||||||
// or one standalone image as an array of one.
|
var atlas: texture_2d_array<f32>;
|
||||||
@group(2) @binding(0)
|
@group(0) @binding(3)
|
||||||
var tex: texture_2d_array<f32>;
|
|
||||||
@group(2) @binding(1)
|
|
||||||
var samp: sampler;
|
var samp: sampler;
|
||||||
|
|
||||||
struct WindowUniform {
|
struct WindowUniform {
|
||||||
@@ -45,7 +45,6 @@ struct VertexOutput {
|
|||||||
@location(1) bot_right: vec2<f32>,
|
@location(1) bot_right: vec2<f32>,
|
||||||
@location(2) uv: vec2<f32>,
|
@location(2) uv: vec2<f32>,
|
||||||
@location(3) @interpolate(flat) mask_idx: u32,
|
@location(3) @interpolate(flat) mask_idx: u32,
|
||||||
// The instance's own index, which is also where its data sits in group 1.
|
|
||||||
@location(4) @interpolate(flat) idx: u32,
|
@location(4) @interpolate(flat) idx: u32,
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// No group 1: the texture is bound at group 2 for this instance alone, so
|
// The image this instance draws, bound for it alone.
|
||||||
// there is nothing per-instance left to look up.
|
@group(2) @binding(0)
|
||||||
|
var image: texture_2d<f32>;
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
return masked(in, textureSample(tex, samp, in.uv, 0));
|
return masked(in, textureSample(image, samp, in.uv));
|
||||||
}
|
}
|
||||||
+15
-47
@@ -3,7 +3,7 @@ use wgpu::{util::DeviceExt, *};
|
|||||||
|
|
||||||
use crate::{PatchRect, TextureUpdate, Textures};
|
use crate::{PatchRect, TextureUpdate, Textures};
|
||||||
|
|
||||||
/// The standalone images a ui draws, each its own texture and group 2 --
|
/// The standalone images a ui draws, each its own texture and bind group --
|
||||||
/// unlike the glyph atlas in `super::page`, which is one array they share.
|
/// unlike the glyph atlas in `super::page`, which is one array they share.
|
||||||
pub struct GpuTextures {
|
pub struct GpuTextures {
|
||||||
device: Device,
|
device: Device,
|
||||||
@@ -26,15 +26,15 @@ impl GpuTextures {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, textures: &mut Textures, layout: &BindGroupLayout, sampler: &Sampler) {
|
pub fn update(&mut self, textures: &mut Textures, layout: &BindGroupLayout) {
|
||||||
for update in textures.updates() {
|
for update in textures.updates() {
|
||||||
match update {
|
match update {
|
||||||
TextureUpdate::Push(image) => {
|
TextureUpdate::Push(image) => {
|
||||||
let image = self.create(image, layout, sampler);
|
let image = self.create(image, layout);
|
||||||
self.slots.push(Some(image));
|
self.slots.push(Some(image));
|
||||||
}
|
}
|
||||||
TextureUpdate::Set(i, image) => {
|
TextureUpdate::Set(i, image) => {
|
||||||
let image = self.create(image, layout, sampler);
|
let image = self.create(image, layout);
|
||||||
self.slots[i as usize] = Some(image);
|
self.slots[i as usize] = Some(image);
|
||||||
}
|
}
|
||||||
TextureUpdate::Patch(i, rect, image) => self.patch(i, rect, image),
|
TextureUpdate::Patch(i, rect, image) => self.patch(i, rect, image),
|
||||||
@@ -45,8 +45,6 @@ impl GpuTextures {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `None` once freed. A drawn instance holds a `TextureHandle`, so its
|
|
||||||
/// slot outlives it.
|
|
||||||
pub fn group(&self, slot: u32) -> Option<&BindGroup> {
|
pub fn group(&self, slot: u32) -> Option<&BindGroup> {
|
||||||
self.slots.get(slot as usize)?.as_ref().map(|i| &i.group)
|
self.slots.get(slot as usize)?.as_ref().map(|i| &i.group)
|
||||||
}
|
}
|
||||||
@@ -55,12 +53,7 @@ impl GpuTextures {
|
|||||||
self.slots.iter().flatten().count()
|
self.slots.iter().flatten().count()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(
|
fn create(&self, image: &DynamicImage, layout: &BindGroupLayout) -> ImageGpu {
|
||||||
&self,
|
|
||||||
image: &DynamicImage,
|
|
||||||
layout: &BindGroupLayout,
|
|
||||||
sampler: &Sampler,
|
|
||||||
) -> ImageGpu {
|
|
||||||
let rgba = image.to_rgba8();
|
let rgba = image.to_rgba8();
|
||||||
let (width, height) = rgba.dimensions();
|
let (width, height) = rgba.dimensions();
|
||||||
let texture = self.device.create_texture_with_data(
|
let texture = self.device.create_texture_with_data(
|
||||||
@@ -82,7 +75,16 @@ impl GpuTextures {
|
|||||||
wgt::TextureDataOrder::MipMajor,
|
wgt::TextureDataOrder::MipMajor,
|
||||||
rgba.as_bytes(),
|
rgba.as_bytes(),
|
||||||
);
|
);
|
||||||
let group = texture_group(&self.device, layout, &array_view(&texture), sampler);
|
let group = self.device.create_bind_group(&BindGroupDescriptor {
|
||||||
|
layout,
|
||||||
|
entries: &[BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: BindingResource::TextureView(
|
||||||
|
&texture.create_view(&TextureViewDescriptor::default()),
|
||||||
|
),
|
||||||
|
}],
|
||||||
|
label: Some("ui image"),
|
||||||
|
});
|
||||||
ImageGpu { texture, group }
|
ImageGpu { texture, group }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,8 +116,6 @@ impl GpuTextures {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Uploads `rect` of `src` without copying it out first: `write_texture` takes
|
|
||||||
/// a row stride, so a region can be addressed where it already is.
|
|
||||||
pub fn write_region(queue: &Queue, dst: TexelCopyTextureInfo, src: &RgbaImage, rect: PatchRect) {
|
pub fn write_region(queue: &Queue, dst: TexelCopyTextureInfo, src: &RgbaImage, rect: PatchRect) {
|
||||||
if rect.width == 0 || rect.height == 0 {
|
if rect.width == 0 || rect.height == 0 {
|
||||||
return;
|
return;
|
||||||
@@ -137,38 +137,6 @@ pub fn write_region(queue: &Queue, dst: TexelCopyTextureInfo, src: &RgbaImage, r
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One array texture and a sampler, so the atlas and a standalone image share a
|
|
||||||
/// layout and the shader samples whichever is bound -- an image being a
|
|
||||||
/// single-layer texture viewed as an array of one.
|
|
||||||
pub fn texture_group(
|
|
||||||
device: &Device,
|
|
||||||
layout: &BindGroupLayout,
|
|
||||||
view: &TextureView,
|
|
||||||
sampler: &Sampler,
|
|
||||||
) -> BindGroup {
|
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
|
||||||
layout,
|
|
||||||
entries: &[
|
|
||||||
BindGroupEntry {
|
|
||||||
binding: 0,
|
|
||||||
resource: BindingResource::TextureView(view),
|
|
||||||
},
|
|
||||||
BindGroupEntry {
|
|
||||||
binding: 1,
|
|
||||||
resource: BindingResource::Sampler(sampler),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
label: Some("ui texture"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn array_view(texture: &Texture) -> TextureView {
|
|
||||||
texture.create_view(&TextureViewDescriptor {
|
|
||||||
dimension: Some(TextureViewDimension::D2Array),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn default_sampler(device: &Device) -> Sampler {
|
pub fn default_sampler(device: &Device) -> Sampler {
|
||||||
device.create_sampler(&SamplerDescriptor::default())
|
device.create_sampler(&SamplerDescriptor::default())
|
||||||
}
|
}
|
||||||
@@ -39,11 +39,8 @@ 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) {
|
||||||
// An empty storage buffer is still bound, and a binding has to be
|
// A binding cannot be empty or under the layout's minimum.
|
||||||
// non-empty and a multiple of four however small `T` is.
|
size = size.max(std::mem::size_of::<T>() as u64);
|
||||||
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),
|
||||||
|
|||||||
+17
-20
@@ -2,8 +2,8 @@ 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::{
|
render::{
|
||||||
GLYPH, GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst,
|
GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, PrimitiveKind,
|
||||||
PrimitiveKind, TEXTURE, TexturePrimitive,
|
TexturePrimitive,
|
||||||
},
|
},
|
||||||
util::Vec2,
|
util::Vec2,
|
||||||
};
|
};
|
||||||
@@ -23,12 +23,14 @@ pub struct Painter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Painter<'a> {
|
impl<'a> Painter<'a> {
|
||||||
fn primitive_at<P: Primitive>(
|
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
||||||
&mut self,
|
let kind = self.rsc.ui_mut().primitives.kind::<P>();
|
||||||
kind: PrimitiveKind<P>,
|
self.write(kind, primitive, region);
|
||||||
primitive: P,
|
}
|
||||||
region: UiRegion,
|
|
||||||
) {
|
/// For a caller with many of one primitive to write, since looking the kind
|
||||||
|
/// up is per type rather than per instance.
|
||||||
|
fn write<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: UiRegion) {
|
||||||
let h = self.state.layers.write(
|
let h = self.state.layers.write(
|
||||||
self.layer,
|
self.layer,
|
||||||
PrimitiveInst {
|
PrimitiveInst {
|
||||||
@@ -51,17 +53,12 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a primitive to be rendered
|
/// Writes a primitive to be rendered
|
||||||
pub fn primitive<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P) {
|
pub fn primitive<P: Primitive>(&mut self, primitive: P) {
|
||||||
self.primitive_at(kind, primitive, self.region)
|
self.primitive_at(primitive, self.region)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn primitive_within<P: Primitive>(
|
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
||||||
&mut self,
|
self.primitive_at(primitive, region.within(&self.region));
|
||||||
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) {
|
||||||
@@ -104,7 +101,6 @@ 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());
|
||||||
self.primitive_at(
|
self.primitive_at(
|
||||||
TEXTURE,
|
|
||||||
TexturePrimitive {
|
TexturePrimitive {
|
||||||
slot: handle.slot(),
|
slot: handle.slot(),
|
||||||
},
|
},
|
||||||
@@ -123,6 +119,7 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
||||||
|
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
|
||||||
for glyph in text.glyphs.iter() {
|
for glyph in text.glyphs.iter() {
|
||||||
let mut region = origin;
|
let mut region = origin;
|
||||||
region.x.end = region.x.start;
|
region.x.end = region.x.start;
|
||||||
@@ -130,8 +127,8 @@ impl<'a> Painter<'a> {
|
|||||||
let mut region = region.offset(UiVec2::abs(glyph.offset));
|
let mut region = region.offset(UiVec2::abs(glyph.offset));
|
||||||
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.write(
|
||||||
GLYPH,
|
kind,
|
||||||
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,
|
||||||
|
|||||||
+2
-5
@@ -29,15 +29,12 @@ 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(
|
painter.primitive(RectPrimitive {
|
||||||
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,7 +73,6 @@ 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),
|
||||||
);
|
);
|
||||||
@@ -83,7 +82,6 @@ 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