initial mask impl

This commit is contained in:
2025-11-10 14:45:22 -05:00
parent 5c2022396a
commit 1c49db1b89
15 changed files with 398 additions and 74 deletions

View File

@@ -1,6 +1,5 @@
use wgpu::VertexAttribute;
use crate::layout::UiRegion;
use crate::{layout::UiRegion, util::Id};
use wgpu::*;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
@@ -15,23 +14,37 @@ pub struct PrimitiveInstance {
pub region: UiRegion,
pub binding: u32,
pub idx: u32,
pub mask_idx: MaskIdx,
}
impl PrimitiveInstance {
const ATTRIBS: [VertexAttribute; 6] = wgpu::vertex_attr_array![
const ATTRIBS: [VertexAttribute; 7] = vertex_attr_array![
0 => Float32x2,
1 => Float32x2,
2 => Float32x2,
3 => Float32x2,
4 => Uint32,
5 => Uint32,
6 => Uint32,
];
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
pub fn desc() -> VertexBufferLayout<'static> {
VertexBufferLayout {
array_stride: std::mem::size_of::<Self>() as BufferAddress,
step_mode: VertexStepMode::Instance,
attributes: &Self::ATTRIBS,
}
}
}
pub type MaskIdx = Id<u32>;
impl MaskIdx {
pub const NONE: Self = Self::preset(u32::MAX);
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Mask {
pub region: UiRegion,
}

View File

@@ -17,6 +17,7 @@ mod primitive;
mod texture;
mod util;
pub use data::{Mask, MaskIdx};
pub use primitive::*;
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
@@ -33,6 +34,7 @@ pub struct UiRenderer {
active: Vec<usize>,
window_buffer: Buffer,
textures: GpuTextures,
masks: ArrBuf<Mask>,
}
struct RenderLayer {
@@ -99,7 +101,11 @@ impl UiRenderer {
}
}
if self.textures.update(&mut ui.data.textures) {
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures)
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks)
}
if ui.data.masks.changed {
ui.data.masks.changed = false;
self.masks.update(device, queue, &ui.data.masks[..]);
}
}
@@ -132,7 +138,7 @@ impl UiRenderer {
let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
@@ -162,8 +168,14 @@ impl UiRenderer {
});
let tex_manager = GpuTextures::new(device, queue);
let masks = ArrBuf::new(
device,
BufferUsages::STORAGE | BufferUsages::COPY_DST,
"ui masks",
);
let rsc_layout = Self::rsc_layout(device, &limits);
let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager);
let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager, &masks);
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some("UI Shape Pipeline Layout"),
@@ -218,6 +230,7 @@ impl UiRenderer {
layers: HashMap::default(),
active: Vec::new(),
textures: tex_manager,
masks,
}
}
@@ -270,6 +283,16 @@ impl UiRenderer {
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
count: Some(NonZero::new(limits.max_samplers).unwrap()),
},
BindGroupLayoutEntry {
binding: 2,
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 rsc"),
})
@@ -279,6 +302,7 @@ impl UiRenderer {
device: &Device,
layout: &BindGroupLayout,
tex_manager: &GpuTextures,
masks: &ArrBuf<Mask>,
) -> BindGroup {
device.create_bind_group(&BindGroupDescriptor {
layout,
@@ -291,6 +315,10 @@ impl UiRenderer {
binding: 1,
resource: BindingResource::SamplerArray(&tex_manager.samplers()),
},
BindGroupEntry {
binding: 2,
resource: masks.buffer.as_entire_binding(),
},
],
label: Some("ui rsc"),
})

View File

@@ -2,7 +2,10 @@ use std::ops::{Deref, DerefMut};
use crate::{
layout::{Color, UiRegion},
render::{ArrBuf, data::PrimitiveInstance},
render::{
ArrBuf,
data::{MaskIdx, PrimitiveInstance},
},
util::Id,
};
use bytemuck::Pod;
@@ -95,19 +98,30 @@ macro_rules! primitives {
(@count $t:tt) => { 1 };
}
pub struct PrimitiveInst<P> {
pub id: Id,
pub primitive: P,
pub region: UiRegion,
pub mask_idx: MaskIdx,
}
impl Primitives {
pub fn write<P: Primitive>(
&mut self,
layer: usize,
id: Id,
data: P,
region: UiRegion,
PrimitiveInst {
id,
primitive,
region,
mask_idx,
}: PrimitiveInst<P>,
) -> PrimitiveHandle {
let vec = P::vec(&mut self.data);
let i = vec.add(data);
let i = vec.add(primitive);
let inst = PrimitiveInstance {
region,
idx: i as u32,
mask_idx,
binding: P::BINDING,
};
let inst_i = if let Some(i) = self.free.pop() {
@@ -138,9 +152,10 @@ impl Primitives {
})
}
pub fn free(&mut self, h: &PrimitiveHandle) {
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
self.data.free(h.binding, h.data_idx);
self.free.push(h.inst_idx);
self.instances[h.inst_idx].mask_idx
}
pub fn data(&self) -> &PrimitiveData {

View File

@@ -20,10 +20,22 @@ struct TextureInfo {
sampler_idx: u32,
}
struct Mask {
top_left: UiVec2,
bot_right: UiVec2,
}
struct UiVec2 {
rel: vec2<f32>,
abs: vec2<f32>,
}
@group(2) @binding(0)
var views: binding_array<texture_2d<f32>>;
@group(2) @binding(1)
var samplers: binding_array<sampler>;
@group(2) @binding(2)
var<storage> masks: array<Mask>;
struct WindowUniform {
dim: vec2<f32>,
@@ -36,6 +48,7 @@ struct InstanceInput {
@location(3) bottom_right_offset: vec2<f32>,
@location(4) binding: u32,
@location(5) idx: u32,
@location(6) mask_idx: u32,
}
struct VertexOutput {
@@ -44,6 +57,7 @@ struct VertexOutput {
@location(2) uv: vec2<f32>,
@location(3) binding: u32,
@location(4) idx: u32,
@location(5) mask_idx: u32,
@builtin(position) clip_position: vec4<f32>,
};
@@ -76,6 +90,7 @@ fn vs_main(
out.idx = in.idx;
out.top_left = top_left;
out.bot_right = bot_right;
out.mask_idx = in.mask_idx;
return out;
}
@@ -87,17 +102,27 @@ fn fs_main(
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: {
return draw_rounded_rect(region, rects[i]);
color = draw_rounded_rect(region, rects[i]);
}
case TEXTURE: {
return draw_texture(region, textures[i]);
color = draw_texture(region, textures[i]);
}
default: {
return vec4(1.0, 0.0, 1.0, 1.0);
color = vec4(1.0, 0.0, 1.0, 1.0);
}
}
if in.mask_idx != 4294967295u {
let mask = masks[in.mask_idx];
let top_left = floor(mask.top_left.rel * window.dim) + floor(mask.top_left.abs);
let bot_right = floor(mask.bot_right.rel * window.dim) + floor(mask.bot_right.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;
}
// TODO: this seems really inefficient (per frag indexing)?