diff --git a/src/layout/painter.rs b/src/layout/painter.rs index a60078b..671d8cb 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -1,6 +1,6 @@ use crate::{ ActiveSensors, SensorMap, UiRegion, WidgetId, Widgets, - primitive::{PrimitiveData, PrimitiveInstance, Primitives}, + primitive::{Primitive, Primitives}, }; pub struct Painter<'a, Ctx: 'static> { @@ -28,16 +28,8 @@ impl<'a, Ctx> Painter<'a, Ctx> { region: UiRegion::full(), } } - pub fn write(&mut self, data: Data) { - let ptr = self.primitives.data.len() as u32; - let region = self.region; - self.primitives - .instances - .push(PrimitiveInstance { region, ptr }); - self.primitives.data.push(Data::DISCRIM); - self.primitives - .data - .extend_from_slice(bytemuck::cast_slice::<_, u32>(&[data])); + pub fn write(&mut self, data: P) { + self.primitives.write(data, self.region); } pub fn draw(&mut self, id: &WidgetId) diff --git a/src/render/data.rs b/src/render/data.rs index 299299b..907b3a4 100644 --- a/src/render/data.rs +++ b/src/render/data.rs @@ -13,16 +13,18 @@ pub struct WindowUniform { #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub struct PrimitiveInstance { pub region: UiRegion, - pub ptr: u32, + pub binding: u32, + pub idx: u32, } impl PrimitiveInstance { - const ATTRIBS: [VertexAttribute; 5] = wgpu::vertex_attr_array![ + const ATTRIBS: [VertexAttribute; 6] = wgpu::vertex_attr_array![ 0 => Float32x2, 1 => Float32x2, 2 => Float32x2, 3 => Float32x2, 4 => Uint32, + 5 => Uint32, ]; pub fn desc() -> wgpu::VertexBufferLayout<'static> { diff --git a/src/render/mod.rs b/src/render/mod.rs index cc963e4..08328a6 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,6 +1,6 @@ use crate::{ - primitive::{PrimitiveInstance, Primitives}, - render::util::ArrBuf, + primitive::{PrimitiveBuffers, Primitives}, + render::{data::PrimitiveInstance, util::ArrBuf}, }; use data::WindowUniform; use wgpu::{ @@ -16,40 +16,34 @@ mod util; const SHAPE_SHADER: &str = include_str!("./shader.wgsl"); pub struct UIRenderNode { - bind_group_layout: BindGroupLayout, - bind_group: BindGroup, + layout0: BindGroupLayout, + group0: BindGroup, + primitive_layout: BindGroupLayout, + primitive_group: BindGroup, pipeline: RenderPipeline, window_buffer: Buffer, instance: ArrBuf, - data: ArrBuf, + primitives: PrimitiveBuffers, } impl UIRenderNode { pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) { - pass.set_pipeline(&self.pipeline); - pass.set_bind_group(0, &self.bind_group, &[]); if self.instance.len() != 0 { + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &self.group0, &[]); + pass.set_bind_group(1, &self.primitive_group, &[]); pass.set_vertex_buffer(0, self.instance.buffer.slice(..)); pass.draw(0..4, 0..self.instance.len() as u32); } } - pub fn update( - &mut self, - device: &Device, - queue: &Queue, - primitives: Option<&Primitives>, - ) { + pub fn update(&mut self, device: &Device, queue: &Queue, primitives: Option<&Primitives>) { if let Some(primitives) = primitives { self.instance.update(device, queue, &primitives.instances); - self.data.update(device, queue, &primitives.data); - self.bind_group = Self::bind_group( - device, - &self.bind_group_layout, - &self.window_buffer, - &self.data.buffer, - ) + self.primitives.update(device, queue, &primitives.data); + self.primitive_group = + Self::primitive_group(device, &self.primitive_layout, self.primitives.buffers()) } } @@ -69,7 +63,7 @@ impl UIRenderNode { let window_uniform = WindowUniform::default(); let window_buffer = device.create_buffer_init(&BufferInitDescriptor { - label: Some("Camera Buffer"), + label: Some("window"), contents: bytemuck::cast_slice(&[window_uniform]), usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, }); @@ -79,26 +73,28 @@ impl UIRenderNode { BufferUsages::VERTEX | BufferUsages::COPY_DST, "instance", ); - let data = ArrBuf::new( - device, - BufferUsages::STORAGE | BufferUsages::COPY_DST, - "data", - ); + let primitives = PrimitiveBuffers::new(device); - let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[ - BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::VERTEX, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, + let layout0 = device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, }, + count: None, + }], + label: Some("window"), + }); + + let group0 = Self::bind_group_0(device, &layout0, &window_buffer); + + let primitive_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &core::array::from_fn::<_, { PrimitiveBuffers::LEN }, _>(|i| { BindGroupLayoutEntry { - binding: 1, + binding: i as u32, visibility: ShaderStages::FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Storage { read_only: true }, @@ -106,16 +102,17 @@ impl UIRenderNode { min_binding_size: None, }, count: None, - }, - ], - label: Some("camera_bind_group_layout"), + } + }), + label: Some("primitive"), }); - let bind_group = Self::bind_group(device, &bind_group_layout, &window_buffer, &data.buffer); + let primitive_group = + Self::primitive_group(device, &primitive_layout, primitives.buffers()); let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { label: Some("UI Shape Pipeline Layout"), - bind_group_layouts: &[&bind_group_layout], + bind_group_layouts: &[&layout0, &primitive_layout], push_constant_ranges: &[], }); let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { @@ -157,34 +154,44 @@ impl UIRenderNode { }); Self { - bind_group_layout, - bind_group, + layout0, + group0, + primitive_layout, + primitive_group, pipeline, window_buffer, instance, - data, + primitives, } } - pub fn bind_group( + pub fn bind_group_0( device: &Device, layout: &BindGroupLayout, window_buffer: &Buffer, - data: &Buffer, ) -> BindGroup { device.create_bind_group(&BindGroupDescriptor { layout, - entries: &[ - BindGroupEntry { - binding: 0, - resource: window_buffer.as_entire_binding(), - }, - BindGroupEntry { - binding: 1, - resource: data.as_entire_binding(), - }, - ], - label: Some("ui_bind_group"), + entries: &[BindGroupEntry { + binding: 0, + resource: window_buffer.as_entire_binding(), + }], + label: Some("ui window"), + }) + } + + pub fn primitive_group( + device: &Device, + layout: &BindGroupLayout, + buffers: [&Buffer; PrimitiveBuffers::LEN], + ) -> BindGroup { + device.create_bind_group(&BindGroupDescriptor { + layout, + entries: &buffers.each_ref().map(|b| BindGroupEntry { + binding: 0, + resource: b.as_entire_binding(), + }), + label: Some("ui primitives"), }) } } diff --git a/src/render/primitive.rs b/src/render/primitive.rs index 2ab1ba1..ab8980a 100644 --- a/src/render/primitive.rs +++ b/src/render/primitive.rs @@ -1,27 +1,93 @@ -use crate::Color; - -pub use super::data::PrimitiveInstance; +use crate::{ + Color, UiRegion, + render::{ArrBuf, data::PrimitiveInstance}, +}; +use bytemuck::Pod; +use wgpu::*; #[derive(Default)] pub struct Primitives { - pub instances: Vec, - pub data: Vec, + pub(super) instances: Vec, + pub(super) data: PrimitiveData, } -/// NOTE: Self must have at least u32 alignment -pub trait PrimitiveData: bytemuck::Pod { - const DISCRIM: u32; +pub trait Primitive: Pod { + const BINDING: u32; + fn vec(data: &mut PrimitiveData) -> &mut Vec; +} + +macro_rules! primitives { + ($($name:ident: $ty:ty => $binding:expr,)*) => { + #[derive(Default)] + pub struct PrimitiveData { + $($name: Vec<$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 fn buffers(&self) -> [&Buffer; Self::LEN] { + [ + $(&self.$name.buffer)* + ] + } + pub fn new(device: &Device) -> Self { + Self { + $($name: ArrBuf::new( + device, + BufferUsages::STORAGE | BufferUsages::COPY_DST, + stringify!($name), + ))* + } + } + } + + $( + 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 Vec { + &mut data.$name + } + } + )* + }; + (@count $t1:tt, $($t:tt),+) => { 1 + gen!(@count $($t),+) }; + (@count $t:tt) => { 1 }; +} + +primitives!( + rects: RoundedRectData => 0, +); + +impl Primitives { + pub fn write(&mut self, data: P, region: UiRegion) { + let vec = P::vec(&mut self.data); + let i = vec.len() as u32; + self.instances.push(PrimitiveInstance { + region, + idx: i, + binding: P::BINDING, + }); + vec.push(data); + } } #[repr(C)] -#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +#[derive(Copy, Clone)] pub struct RoundedRectData { pub color: Color, pub radius: f32, pub thickness: f32, pub inner_radius: f32, } - -impl PrimitiveData for RoundedRectData { - const DISCRIM: u32 = 0; -} diff --git a/src/render/shader.wgsl b/src/render/shader.wgsl index b48ff5a..5ba65ed 100644 --- a/src/render/shader.wgsl +++ b/src/render/shader.wgsl @@ -1,7 +1,9 @@ +const RECT: u32 = 0; + @group(0) @binding(0) var window: WindowUniform; -@group(0) @binding(1) -var data: array; +@group(1) @binding(RECT) +var rects: array; struct WindowUniform { dim: vec2, @@ -12,7 +14,8 @@ struct InstanceInput { @location(1) top_left_offset: vec2, @location(2) bottom_right_anchor: vec2, @location(3) bottom_right_offset: vec2, - @location(4) pointer: u32, + @location(4) binding: u32, + @location(5) idx: u32, } struct RoundedRect { @@ -23,9 +26,10 @@ struct RoundedRect { } struct VertexOutput { - @location(0) pointer: u32, - @location(1) top_left: vec2, - @location(2) bot_right: vec2, + @location(0) top_left: vec2, + @location(1) bot_right: vec2, + @location(2) binding: u32, + @location(3) idx: u32, @builtin(position) clip_position: vec4, }; @@ -52,7 +56,8 @@ fn vs_main( ) * size; pos = pos / window.dim * 2.0 - 1.0; out.clip_position = vec4(pos.x, -pos.y, 0.0, 1.0); - out.pointer = in.pointer; + out.binding = in.binding; + out.idx = in.idx; out.top_left = top_left; out.bot_right = bot_right; @@ -64,25 +69,20 @@ fn fs_main( in: VertexOutput ) -> @location(0) vec4 { let pos = in.clip_position.xy; - let ty = data[in.pointer]; - let dp = in.pointer + 1u; let region = Region(pos, in.top_left, in.bot_right); - switch ty { - case 0u: { - return draw_rounded_rect(region, RoundedRect( - data[dp + 0u], - bitcast(data[dp + 1u]), - bitcast(data[dp + 2u]), - bitcast(data[dp + 3u]), - )); + let i = in.idx; + switch in.binding { + case RECT: { + return draw_rounded_rect(region, rects[i]); + } + default: { + return vec4(1.0, 0.0, 1.0, 1.0); } - default: {} } - return vec4(1.0, 0.0, 1.0, 1.0); } fn draw_rounded_rect(region: Region, rect: RoundedRect) -> vec4 { - var color = unpack4x8unorm(rect.color); + var color = read_color(rect.color); let edge = 0.5; @@ -108,3 +108,21 @@ fn distance_from_rect(pixel_pos: vec2, rect_center: vec2, rect_corner: let q = abs(p) - (rect_corner - radius); return length(max(q, vec2(0.0, 0.0))) - radius; } + +fn read_color(c: u32) -> vec4 { + let color = unpack4x8unorm(c); + return vec4( + srgb_to_linear(color.r), + srgb_to_linear(color.g), + srgb_to_linear(color.b), + color.a, + ); +} + +fn srgb_to_linear(c: f32) -> f32 { + if c <= 0.04045 { + return c / 12.92; + } else { + return pow((c + 0.055) / 1.055, 2.4); + } +}