Draw textures separately, and keep them out of Primitives
Interleaving textures with primitives was solving a problem that does not exist: within a layer, order is already undefined because freeing an instance swap-removes it, and layering is what layers are for. So the run batching is gone. A layer is now `LayerDraws`: a `Primitives` and a texture `InstanceList` side by side, with `updated` covering both. `Primitives` holds only primitives again -- its instance list plus the group-1 data those instances read -- and `InstanceList` is the shared push/free/apply_free the two lists have in common rather than a second copy of it. `PrimitiveHandle` names which list with `InstanceKind`, and `PrimitiveChange` carries the same, since the two index independently. The handle no longer carries a group-1 index at all: a primitive instance already records where its entry is. The renderer gives each layer a second instance buffer and draws its textures one at a time after the instanced draw, each binding its own group 2. Review fixes alongside: `GlyphAtlas::allocate` returns the `PageUpload` it reserved instead of a bare tuple; a page or image region uploads through a new `write_region`, which passes the row stride to `write_texture` rather than copying the rectangle out first. Verified by replaying taps into the `tabs` example: three images added and one deleted leaves two drawn with two live texture slots, and the masked text-edit tab still clips, with the images freed on tab switch.
This commit is contained in:
1 parent
f5864da3c4
commit
b3d3da5dab
10 files changed
+282
-241
No files matched your search
@@ -2,7 +2,7 @@ use std::ops::{Index, IndexMut};
|
||||
|
||||
use crate::{
|
||||
UiRegion, WidgetId,
|
||||
render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives},
|
||||
render::{LayerDraws, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
||||
util::to_mut,
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ struct Child {
|
||||
tail: usize,
|
||||
}
|
||||
|
||||
pub type PrimitiveLayers = Layers<Primitives>;
|
||||
pub type DrawLayers = Layers<LayerDraws>;
|
||||
|
||||
impl<T: Default> Layers<T> {
|
||||
pub fn new() -> Layers<T> {
|
||||
@@ -120,7 +120,7 @@ impl<T: Default> Layers<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimitiveLayers {
|
||||
impl DrawLayers {
|
||||
pub fn write<P: Primitive>(
|
||||
&mut self,
|
||||
layer: LayerId,
|
||||
|
||||
+23
-17
@@ -98,17 +98,10 @@ impl GlyphAtlas {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (layer, x, y) = self.allocate(w, h);
|
||||
write_glyph(&mut self.pages[layer as usize].image, image, x, y);
|
||||
self.uploads.push(PageUpload {
|
||||
layer,
|
||||
rect: PatchRect {
|
||||
x,
|
||||
y,
|
||||
width: w,
|
||||
height: h,
|
||||
},
|
||||
});
|
||||
let upload = self.allocate(w, h);
|
||||
let PatchRect { x, y, .. } = upload.rect;
|
||||
write_glyph(&mut self.pages[upload.layer as usize].image, image, x, y);
|
||||
self.uploads.push(upload);
|
||||
|
||||
let scale = 1.0 / PAGE as f32;
|
||||
let entry = GlyphEntry {
|
||||
@@ -119,20 +112,30 @@ impl GlyphAtlas {
|
||||
width: w,
|
||||
height: h,
|
||||
is_colored: matches!(image.content, Content::Color),
|
||||
layer,
|
||||
layer: upload.layer,
|
||||
};
|
||||
self.entries.insert(key, Some(entry));
|
||||
Some(entry)
|
||||
}
|
||||
|
||||
fn allocate(&mut self, w: u32, h: u32) -> (u32, u32, u32) {
|
||||
/// Reserves room for a `w` by `h` glyph, adding a page if none has it.
|
||||
fn allocate(&mut self, w: u32, h: u32) -> PageUpload {
|
||||
let rect = |x, y| PatchRect {
|
||||
x,
|
||||
y,
|
||||
width: w,
|
||||
height: h,
|
||||
};
|
||||
if let Some((i, (x, y))) = self
|
||||
.pages
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.find_map(|(i, page)| page.allocate(w, h).map(|position| (i, position)))
|
||||
{
|
||||
return (i as u32, x, y);
|
||||
return PageUpload {
|
||||
layer: i as u32,
|
||||
rect: rect(x, y),
|
||||
};
|
||||
}
|
||||
|
||||
self.pages.push(Page {
|
||||
@@ -141,12 +144,15 @@ impl GlyphAtlas {
|
||||
y: PAD,
|
||||
shelf_height: h + PAD,
|
||||
});
|
||||
(self.pages.len() as u32 - 1, PAD, PAD)
|
||||
PageUpload {
|
||||
layer: self.pages.len() as u32 - 1,
|
||||
rect: rect(PAD, PAD),
|
||||
}
|
||||
}
|
||||
|
||||
/// Drains what has been written since the last call, for the renderer to
|
||||
/// upload. Nothing else is needed for a new page: wgpu leaves the rest of
|
||||
/// a fresh layer transparent, which is what an atlas wants.
|
||||
/// upload. A new page needs nothing more: wgpu leaves the rest of a fresh
|
||||
/// layer transparent, which is what an atlas wants.
|
||||
pub fn uploads(&mut self) -> impl Iterator<Item = (PageUpload, &RgbaImage)> {
|
||||
let pages = &self.pages;
|
||||
self.uploads
|
||||
|
||||
+42
-57
@@ -1,5 +1,3 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{
|
||||
UiData, UiRenderState,
|
||||
render::{
|
||||
@@ -51,14 +49,10 @@ struct RenderLayer {
|
||||
instance: ArrBuf<PrimitiveInstance>,
|
||||
primitives: PrimitiveBuffers,
|
||||
primitive_group: BindGroup,
|
||||
draws: Vec<Draw>,
|
||||
}
|
||||
|
||||
/// A run of consecutive instances sharing one group 2: `texture` names a
|
||||
/// standalone image, or `None` is the glyph atlas rects and glyphs sample.
|
||||
struct Draw {
|
||||
texture: Option<u32>,
|
||||
instances: Range<u32>,
|
||||
texture_instance: ArrBuf<PrimitiveInstance>,
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
impl UiRenderNode {
|
||||
@@ -68,21 +62,21 @@ impl UiRenderNode {
|
||||
pass.set_bind_group(3, &self.mask_group, &[]);
|
||||
for i in &self.active {
|
||||
let layer = &self.layers[i];
|
||||
if layer.draws.is_empty() {
|
||||
continue;
|
||||
}
|
||||
pass.set_bind_group(1, &layer.primitive_group, &[]);
|
||||
pass.set_vertex_buffer(0, layer.instance.buffer.slice(..));
|
||||
for draw in &layer.draws {
|
||||
let group = match draw.texture {
|
||||
Some(slot) => match self.textures.group(slot) {
|
||||
Some(group) => group,
|
||||
None => continue,
|
||||
},
|
||||
None => self.pages.group(),
|
||||
};
|
||||
pass.set_bind_group(2, group, &[]);
|
||||
pass.draw(0..4, draw.instances.clone());
|
||||
if layer.instance.len() > 0 {
|
||||
pass.set_bind_group(2, self.pages.group(), &[]);
|
||||
pass.set_vertex_buffer(0, layer.instance.buffer.slice(..));
|
||||
pass.draw(0..4, 0..layer.instance.len() as u32);
|
||||
}
|
||||
if !layer.texture_slots.is_empty() {
|
||||
pass.set_vertex_buffer(0, layer.texture_instance.buffer.slice(..));
|
||||
for (i, &slot) in layer.texture_slots.iter().enumerate() {
|
||||
let Some(group) = self.textures.group(slot) else {
|
||||
continue;
|
||||
};
|
||||
pass.set_bind_group(2, group, &[]);
|
||||
pass.draw(0..4, i as u32..i as u32 + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,12 +89,12 @@ impl UiRenderNode {
|
||||
ui_render: &mut UiRenderState,
|
||||
) {
|
||||
self.active.clear();
|
||||
for (i, primitives) in ui_render.layers.iter_mut() {
|
||||
for (i, draws) in ui_render.layers.iter_mut() {
|
||||
self.active.push(i);
|
||||
for change in primitives.apply_free() {
|
||||
for change in draws.apply_free() {
|
||||
if let Some(inst) = ui_render.active.get_mut(&change.id) {
|
||||
for h in &mut inst.primitives {
|
||||
if h.layer == i && h.inst_idx == change.old {
|
||||
if h.layer == i && h.kind == change.kind && h.inst_idx == change.old {
|
||||
h.inst_idx = change.new;
|
||||
break;
|
||||
}
|
||||
@@ -119,21 +113,34 @@ impl UiRenderNode {
|
||||
),
|
||||
primitives,
|
||||
primitive_group,
|
||||
draws: Vec::new(),
|
||||
texture_instance: ArrBuf::new(
|
||||
device,
|
||||
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
||||
"texture instance",
|
||||
),
|
||||
texture_slots: Vec::new(),
|
||||
}
|
||||
});
|
||||
if primitives.updated {
|
||||
if draws.updated {
|
||||
rlayer
|
||||
.instance
|
||||
.update(device, queue, primitives.instances());
|
||||
rlayer.primitives.update(device, queue, primitives.data());
|
||||
.update(device, queue, draws.primitives.instances());
|
||||
rlayer
|
||||
.primitives
|
||||
.update(device, queue, draws.primitives.data());
|
||||
rlayer.primitive_group = Self::primitive_group(
|
||||
device,
|
||||
&self.primitive_layout,
|
||||
rlayer.primitives.buffers(),
|
||||
);
|
||||
rlayer.plan_draws(primitives.instances());
|
||||
primitives.updated = false;
|
||||
rlayer
|
||||
.texture_instance
|
||||
.update(device, queue, draws.textures.instances());
|
||||
rlayer.texture_slots.clear();
|
||||
rlayer
|
||||
.texture_slots
|
||||
.extend(draws.textures.instances().iter().map(|inst| inst.idx));
|
||||
draws.updated = false;
|
||||
}
|
||||
}
|
||||
if ui.masks.changed {
|
||||
@@ -312,9 +319,8 @@ impl UiRenderNode {
|
||||
})
|
||||
}
|
||||
|
||||
/// Group 2: the texture a run of instances samples, and the sampler. No
|
||||
/// `count` on either entry -- plain Vulkan 1.0 / GLES sampling, unlike the
|
||||
/// `binding_array` layout this replaced.
|
||||
/// Group 2: the texture this draw samples, and the sampler. No `count` on
|
||||
/// either entry -- plain Vulkan 1.0 / GLES sampling is all this needs.
|
||||
fn texture_layout(device: &Device) -> BindGroupLayout {
|
||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
@@ -372,24 +378,3 @@ impl UiRenderNode {
|
||||
self.textures.count()
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderLayer {
|
||||
/// Only a texture instance breaks a run, so a ui without images plans one
|
||||
/// draw however many rects and glyphs it has.
|
||||
fn plan_draws(&mut self, instances: &[PrimitiveInstance]) {
|
||||
self.draws.clear();
|
||||
for (i, inst) in instances.iter().enumerate() {
|
||||
let i = i as u32;
|
||||
let texture = (inst.binding == TEXTURE_BINDING).then_some(inst.idx);
|
||||
match self.draws.last_mut() {
|
||||
Some(draw) if draw.texture.is_none() && texture.is_none() => {
|
||||
draw.instances.end = i + 1;
|
||||
}
|
||||
_ => self.draws.push(Draw {
|
||||
texture,
|
||||
instances: i..i + 1,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-30
@@ -1,18 +1,17 @@
|
||||
use image::EncodableLayout;
|
||||
use wgpu::*;
|
||||
|
||||
use crate::GlyphAtlas;
|
||||
|
||||
use super::{
|
||||
atlas::PAGE,
|
||||
texture::{array_view, texture_group},
|
||||
texture::{array_view, texture_group, write_region},
|
||||
};
|
||||
|
||||
/// The glyph atlas on the GPU: one array texture whose layers are the pages
|
||||
/// `GlyphAtlas` packs.
|
||||
///
|
||||
/// An array rather than a `binding_array<texture_2d<f32>>` because a layer
|
||||
/// index is ordinary Vulkan 1.0 / GLES sampling, while a binding array needs
|
||||
/// One array rather than a texture per page because a layer index is ordinary
|
||||
/// Vulkan 1.0 / GLES sampling, where a `binding_array` would need
|
||||
/// `VK_EXT_descriptor_indexing`, which a real share of Android GPUs lack.
|
||||
pub struct GpuPages {
|
||||
device: Device,
|
||||
@@ -43,33 +42,17 @@ impl GpuPages {
|
||||
self.grow(atlas.page_count(), layout, sampler);
|
||||
}
|
||||
for (upload, page) in atlas.uploads() {
|
||||
let rect = upload.rect;
|
||||
// `write_texture` wants tightly packed rows; the page is wider.
|
||||
let sub =
|
||||
image::imageops::crop_imm(page, rect.x, rect.y, rect.width, rect.height).to_image();
|
||||
self.queue.write_texture(
|
||||
TexelCopyTextureInfo {
|
||||
texture: &self.texture,
|
||||
mip_level: 0,
|
||||
origin: Origin3d {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
z: upload.layer,
|
||||
},
|
||||
aspect: TextureAspect::All,
|
||||
let dst = TexelCopyTextureInfo {
|
||||
texture: &self.texture,
|
||||
mip_level: 0,
|
||||
origin: Origin3d {
|
||||
x: upload.rect.x,
|
||||
y: upload.rect.y,
|
||||
z: upload.layer,
|
||||
},
|
||||
sub.as_bytes(),
|
||||
TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(rect.width * 4),
|
||||
rows_per_image: Some(rect.height),
|
||||
},
|
||||
Extent3d {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
aspect: TextureAspect::All,
|
||||
};
|
||||
write_region(&self.queue, dst, page, upload.rect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+143
-87
@@ -11,30 +11,89 @@ use crate::{
|
||||
use bytemuck::Pod;
|
||||
use wgpu::*;
|
||||
|
||||
pub struct Primitives {
|
||||
instances: Vec<PrimitiveInstance>,
|
||||
assoc: Vec<WidgetId>,
|
||||
data: PrimitiveData,
|
||||
free: Vec<usize>,
|
||||
/// Everything one layer draws. A texture binds its own group 2, so it draws on
|
||||
/// its own and cannot join the instanced draw the primitives share.
|
||||
pub struct LayerDraws {
|
||||
pub primitives: Primitives,
|
||||
pub textures: InstanceList,
|
||||
pub updated: bool,
|
||||
}
|
||||
|
||||
impl Default for Primitives {
|
||||
impl Default for LayerDraws {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
instances: Default::default(),
|
||||
assoc: Default::default(),
|
||||
data: Default::default(),
|
||||
free: Vec::new(),
|
||||
primitives: Default::default(),
|
||||
textures: Default::default(),
|
||||
updated: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Not in `primitives!` and with no group-1 buffer: a texture instance's `idx`
|
||||
/// names the texture to bind, so there is nothing to look up per-instance.
|
||||
/// Which of a layer's two lists an instance is in. They index independently,
|
||||
/// so a handle or a renumbering naming only a position would be ambiguous.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum InstanceKind {
|
||||
Primitive,
|
||||
Texture,
|
||||
}
|
||||
|
||||
/// A texture instance's `idx` names the texture to bind rather than a group-1
|
||||
/// entry, so it is not in `primitives!` and has no buffer of its own.
|
||||
pub const TEXTURE_BINDING: u32 = 2;
|
||||
|
||||
/// Instances, the widget each belongs to, and the slots waiting to be reused.
|
||||
#[derive(Default)]
|
||||
pub struct InstanceList {
|
||||
instances: Vec<PrimitiveInstance>,
|
||||
assoc: Vec<WidgetId>,
|
||||
free: Vec<usize>,
|
||||
}
|
||||
|
||||
impl InstanceList {
|
||||
pub fn instances(&self) -> &Vec<PrimitiveInstance> {
|
||||
&self.instances
|
||||
}
|
||||
|
||||
fn push(&mut self, id: WidgetId, inst: PrimitiveInstance) -> usize {
|
||||
if let Some(i) = self.free.pop() {
|
||||
self.instances[i] = inst;
|
||||
self.assoc[i] = id;
|
||||
i
|
||||
} else {
|
||||
let i = self.instances.len();
|
||||
self.instances.push(inst);
|
||||
self.assoc.push(id);
|
||||
i
|
||||
}
|
||||
}
|
||||
|
||||
fn free(&mut self, i: usize) -> MaskIdx {
|
||||
self.free.push(i);
|
||||
self.instances[i].mask_idx
|
||||
}
|
||||
|
||||
fn apply_free(&mut self, kind: InstanceKind) -> impl Iterator<Item = PrimitiveChange> {
|
||||
self.free.sort_by(|a, b| b.cmp(a));
|
||||
let instances = &mut self.instances;
|
||||
let assoc = &mut self.assoc;
|
||||
self.free.drain(..).filter_map(move |i| {
|
||||
instances.swap_remove(i);
|
||||
assoc.swap_remove(i);
|
||||
if i == instances.len() {
|
||||
return None;
|
||||
}
|
||||
let id = assoc[i];
|
||||
let old = instances.len();
|
||||
Some(PrimitiveChange {
|
||||
id,
|
||||
kind,
|
||||
old,
|
||||
new: i,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Primitive: Pod {
|
||||
const BINDING: u32;
|
||||
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self>;
|
||||
@@ -89,8 +148,6 @@ macro_rules! primitives {
|
||||
}
|
||||
|
||||
$(
|
||||
// Each is uploaded as its WGSL counterpart, so its fields have to
|
||||
// sit at WGSL's offsets -- including an align Rust would not pick.
|
||||
unsafe impl bytemuck::Pod for $ty {}
|
||||
unsafe impl bytemuck::Zeroable for $ty {}
|
||||
impl Primitive for $ty {
|
||||
@@ -112,32 +169,63 @@ pub struct PrimitiveInst<P> {
|
||||
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 {
|
||||
pub fn write<P: Primitive>(
|
||||
fn write<P: Primitive>(
|
||||
&mut self,
|
||||
layer: usize,
|
||||
PrimitiveInst {
|
||||
id,
|
||||
primitive,
|
||||
region,
|
||||
mask_idx,
|
||||
}: PrimitiveInst<P>,
|
||||
) -> PrimitiveHandle {
|
||||
let data_idx = P::vec(&mut self.data).add(primitive);
|
||||
self.push(
|
||||
layer,
|
||||
) -> usize {
|
||||
let idx = P::vec(&mut self.data).add(primitive) as u32;
|
||||
self.list.push(
|
||||
id,
|
||||
PrimitiveInstance {
|
||||
region,
|
||||
idx: data_idx as u32,
|
||||
idx,
|
||||
mask_idx,
|
||||
binding: P::BINDING,
|
||||
},
|
||||
Some(data_idx),
|
||||
)
|
||||
}
|
||||
|
||||
/// Writes an instance that samples `texture` instead of a group-1 buffer.
|
||||
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 {
|
||||
pub fn write<P: Primitive>(&mut self, layer: usize, inst: PrimitiveInst<P>) -> PrimitiveHandle {
|
||||
self.updated = true;
|
||||
PrimitiveHandle {
|
||||
layer,
|
||||
kind: InstanceKind::Primitive,
|
||||
inst_idx: self.primitives.write(inst),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes an instance that samples the texture in slot `texture` instead of
|
||||
/// reading a group-1 buffer.
|
||||
pub fn write_texture(
|
||||
&mut self,
|
||||
layer: usize,
|
||||
@@ -145,86 +233,59 @@ impl Primitives {
|
||||
texture: u32,
|
||||
region: UiRegion,
|
||||
mask_idx: MaskIdx,
|
||||
) -> PrimitiveHandle {
|
||||
self.push(
|
||||
layer,
|
||||
id,
|
||||
PrimitiveInstance {
|
||||
region,
|
||||
idx: texture,
|
||||
mask_idx,
|
||||
binding: TEXTURE_BINDING,
|
||||
},
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn push(
|
||||
&mut self,
|
||||
layer: usize,
|
||||
id: WidgetId,
|
||||
inst: PrimitiveInstance,
|
||||
data_idx: Option<usize>,
|
||||
) -> PrimitiveHandle {
|
||||
self.updated = true;
|
||||
let inst_idx = if let Some(i) = self.free.pop() {
|
||||
self.instances[i] = inst;
|
||||
self.assoc[i] = id;
|
||||
i
|
||||
} else {
|
||||
let i = self.instances.len();
|
||||
self.instances.push(inst);
|
||||
self.assoc.push(id);
|
||||
i
|
||||
};
|
||||
PrimitiveHandle {
|
||||
layer,
|
||||
inst_idx,
|
||||
data_idx,
|
||||
binding: inst.binding,
|
||||
kind: InstanceKind::Texture,
|
||||
inst_idx: self.textures.push(
|
||||
id,
|
||||
PrimitiveInstance {
|
||||
region,
|
||||
idx: texture,
|
||||
mask_idx,
|
||||
binding: TEXTURE_BINDING,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// returns (old index, new index)
|
||||
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
||||
self.free.sort_by(|a, b| b.cmp(a));
|
||||
self.free.drain(..).filter_map(|i| {
|
||||
self.instances.swap_remove(i);
|
||||
self.assoc.swap_remove(i);
|
||||
if i == self.instances.len() {
|
||||
return None;
|
||||
}
|
||||
let id = self.assoc[i];
|
||||
let old = self.instances.len();
|
||||
Some(PrimitiveChange { id, old, new: i })
|
||||
})
|
||||
let Self {
|
||||
primitives,
|
||||
textures,
|
||||
..
|
||||
} = self;
|
||||
primitives
|
||||
.list
|
||||
.apply_free(InstanceKind::Primitive)
|
||||
.chain(textures.apply_free(InstanceKind::Texture))
|
||||
}
|
||||
|
||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||
self.updated = true;
|
||||
if let Some(i) = h.data_idx {
|
||||
self.data.free(h.binding, i);
|
||||
match h.kind {
|
||||
InstanceKind::Primitive => self.primitives.free(h.inst_idx),
|
||||
InstanceKind::Texture => self.textures.free(h.inst_idx),
|
||||
}
|
||||
self.free.push(h.inst_idx);
|
||||
self.instances[h.inst_idx].mask_idx
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &PrimitiveData {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn instances(&self) -> &Vec<PrimitiveInstance> {
|
||||
&self.instances
|
||||
}
|
||||
|
||||
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
|
||||
self.updated = true;
|
||||
&mut self.instances[h.inst_idx].region
|
||||
&mut self.list_mut(h.kind).instances[h.inst_idx].region
|
||||
}
|
||||
|
||||
fn list_mut(&mut self, kind: InstanceKind) -> &mut InstanceList {
|
||||
match kind {
|
||||
InstanceKind::Primitive => &mut self.primitives.list,
|
||||
InstanceKind::Texture => &mut self.textures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PrimitiveChange {
|
||||
pub id: WidgetId,
|
||||
pub kind: InstanceKind,
|
||||
pub old: usize,
|
||||
pub new: usize,
|
||||
}
|
||||
@@ -232,10 +293,8 @@ pub struct PrimitiveChange {
|
||||
#[derive(Debug)]
|
||||
pub struct PrimitiveHandle {
|
||||
pub layer: usize,
|
||||
pub kind: InstanceKind,
|
||||
pub inst_idx: usize,
|
||||
/// `None` for a texture instance, which has no group-1 entry to free.
|
||||
pub data_idx: Option<usize>,
|
||||
pub binding: u32,
|
||||
}
|
||||
|
||||
primitives!(
|
||||
@@ -265,9 +324,6 @@ impl RectPrimitive {
|
||||
|
||||
/// `color` is multiplied by the atlas alpha for a mask glyph; a colour glyph
|
||||
/// takes the texel unchanged, which `GlyphEntry::IS_COLORED` selects.
|
||||
///
|
||||
/// `align(8)` because `vec2<f32>` aligns `GlyphInfo` to 8, which pads it to
|
||||
/// 32 bytes.
|
||||
#[repr(C, align(8))]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct GlyphPrimitive {
|
||||
|
||||
@@ -45,8 +45,8 @@ struct UiVec2 {
|
||||
abs: vec2<f32>,
|
||||
}
|
||||
|
||||
// Whatever this run of instances samples: the glyph atlas, whose layers are
|
||||
// its pages, or one standalone image as an array of one.
|
||||
// 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)
|
||||
|
||||
+46
-32
@@ -1,4 +1,4 @@
|
||||
use image::{DynamicImage, EncodableLayout, GenericImageView};
|
||||
use image::{DynamicImage, EncodableLayout, GenericImageView, RgbaImage};
|
||||
use wgpu::{util::DeviceExt, *};
|
||||
|
||||
use crate::{PatchRect, TextureUpdate, Textures};
|
||||
@@ -90,41 +90,55 @@ impl GpuTextures {
|
||||
let Some(Some(slot)) = self.slots.get(i as usize) else {
|
||||
return;
|
||||
};
|
||||
if rect.width == 0 || rect.height == 0 {
|
||||
return;
|
||||
let dst = TexelCopyTextureInfo {
|
||||
texture: &slot.texture,
|
||||
mip_level: 0,
|
||||
origin: Origin3d {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
z: 0,
|
||||
},
|
||||
aspect: TextureAspect::All,
|
||||
};
|
||||
match image.as_rgba8() {
|
||||
Some(rgba) => write_region(&self.queue, dst, rgba, rect),
|
||||
// The texture is rgba8, so any other layout has to be converted --
|
||||
// and converting the rectangle is cheaper than the whole image.
|
||||
None => {
|
||||
let sub = image
|
||||
.view(rect.x, rect.y, rect.width, rect.height)
|
||||
.to_image();
|
||||
write_region(&self.queue, dst, &sub, PatchRect { x: 0, y: 0, ..rect });
|
||||
}
|
||||
}
|
||||
// `write_texture` requires tightly packed rows, unlike the source image.
|
||||
let sub = image
|
||||
.view(rect.x, rect.y, rect.width, rect.height)
|
||||
.to_image();
|
||||
self.queue.write_texture(
|
||||
TexelCopyTextureInfo {
|
||||
texture: &slot.texture,
|
||||
mip_level: 0,
|
||||
origin: Origin3d {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
z: 0,
|
||||
},
|
||||
aspect: TextureAspect::All,
|
||||
},
|
||||
sub.as_bytes(),
|
||||
TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(rect.width * 4),
|
||||
rows_per_image: Some(rect.height),
|
||||
},
|
||||
Extent3d {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// One array texture and one sampler, so the atlas and a standalone image bind
|
||||
/// the same layout and the shader samples whichever is bound. An image is a
|
||||
/// 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) {
|
||||
if rect.width == 0 || rect.height == 0 {
|
||||
return;
|
||||
}
|
||||
let stride = src.width() * 4;
|
||||
queue.write_texture(
|
||||
dst,
|
||||
src.as_bytes(),
|
||||
TexelCopyBufferLayout {
|
||||
offset: (rect.y * stride + rect.x * 4) as u64,
|
||||
bytes_per_row: Some(stride),
|
||||
rows_per_image: Some(rect.height),
|
||||
},
|
||||
Extent3d {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 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,
|
||||
|
||||
@@ -33,6 +33,9 @@ impl<T: Pod> ArrBuf<T> {
|
||||
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data));
|
||||
resized
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
|
||||
let mut size = size as u64;
|
||||
if usage.contains(BufferUsages::STORAGE) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use crate::{
|
||||
ActiveData, Axis, IdLike, MaskIdx, Painter, PixelRegion, PrimitiveLayers, SizeCtx,
|
||||
StrongWidget, UiRegion, UiRsc, UiVec2, WidgetId, Widgets,
|
||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, Painter, PixelRegion, SizeCtx, StrongWidget,
|
||||
UiRegion, UiRsc, UiVec2, WidgetId, Widgets,
|
||||
ui::cache::Cache,
|
||||
util::{HashMap, HashSet, Vec2, forget_ref},
|
||||
};
|
||||
|
||||
pub struct UiRenderState {
|
||||
pub active: HashMap<WidgetId, ActiveData>,
|
||||
pub layers: PrimitiveLayers,
|
||||
pub layers: DrawLayers,
|
||||
pub(super) output_size: Vec2,
|
||||
pub cache: Cache,
|
||||
|
||||
@@ -243,14 +243,11 @@ impl UiRenderState {
|
||||
}
|
||||
|
||||
pub fn debug_layers(&self) {
|
||||
for ((idx, depth), primitives) in self.layers.iter_depth() {
|
||||
for ((idx, depth), draws) in self.layers.iter_depth() {
|
||||
let indent = " ".repeat(depth * 2);
|
||||
let len = primitives.instances().len();
|
||||
print!("{indent}{idx}: {len} primitives");
|
||||
if len >= 1 {
|
||||
print!(" ({})", primitives.instances()[0].binding);
|
||||
}
|
||||
println!();
|
||||
let primitives = draws.primitives.instances().len();
|
||||
let textures = draws.textures.instances().len();
|
||||
println!("{indent}{idx}: {primitives} primitives, {textures} textures");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user