Skip to main content

zenthra_render/
rect_pipeline.rs

1use bytemuck::{Pod, Zeroable};
2use wgpu::util::DeviceExt;
3
4/// One rectangle — uploaded as a GPU instance.
5/// Field order matches shader @location order exactly.
6///
7/// offset  0  loc 0  pos           [f32;2]
8/// offset  8  loc 1  size          [f32;2]
9/// offset 16  loc 2  color         [f32;4]
10/// offset 32  loc 3  radius        [f32;4]
11/// offset 48  loc 4  border_width  f32
12/// offset 52  loc 5  border_color  [f32;4]
13/// offset 68  loc 6  shadow_color  [f32;4]
14/// offset 84  loc 7  shadow_offset [f32;2]
15/// offset 92  loc 8  shadow_blur   f32
16/// offset 96  loc 9  clip_rect     [f32;4]
17/// offset 112 loc 10 grayscale     f32
18/// offset 116 loc 11 brightness    f32
19/// offset 120 loc 12 opacity       f32
20/// total: 124 bytes
21#[repr(C)]
22#[derive(Copy, Clone, Debug, Pod, Zeroable)]
23pub struct RectInstance {
24    pub pos: [f32; 2],           
25    pub size: [f32; 2],          
26    pub color: [f32; 4],         
27    pub radius: [f32; 4],        
28    pub border_width: f32,       // offset 36
29    pub border_color: [f32; 4],  // offset 40
30    pub shadow_color: [f32; 4],  // offset 56
31    pub shadow_offset: [f32; 2], // offset 72
32    pub shadow_blur: f32,        // offset 80
33    pub clip_rect: [f32; 4],     // offset 84
34    pub grayscale: f32,          // offset 100
35    pub brightness: f32,         
36    pub opacity: f32,            
37    pub border_alignment: f32,   
38}
39
40impl Default for RectInstance {
41    fn default() -> Self {
42        Self {
43            pos: [0.0; 2],
44            size: [100.0, 100.0],
45            color: [1.0, 1.0, 1.0, 1.0],
46            radius: [0.0; 4],
47            border_width: 0.0,
48            border_color: [0.0; 4],
49            shadow_color: [0.0; 4],
50            shadow_offset: [0.0; 2],
51            shadow_blur: 0.0,
52            clip_rect: [0.0, 0.0, 9999.0, 9999.0],
53            grayscale: 0.0,
54            brightness: 1.0,
55            opacity: 1.0,
56            border_alignment: 0.0, // Default: Inside
57        }
58    }
59}
60
61impl RectInstance {
62    pub const fn desc() -> wgpu::VertexBufferLayout<'static> {
63        wgpu::VertexBufferLayout {
64            array_stride: std::mem::size_of::<RectInstance>() as wgpu::BufferAddress,
65            step_mode: wgpu::VertexStepMode::Instance,
66            attributes: &[
67                wgpu::VertexAttribute {
68                    offset: 0,
69                    shader_location: 0,
70                    format: wgpu::VertexFormat::Float32x2,
71                },
72                wgpu::VertexAttribute {
73                    offset: 8,
74                    shader_location: 1,
75                    format: wgpu::VertexFormat::Float32x2,
76                },
77                wgpu::VertexAttribute {
78                    offset: 16,
79                    shader_location: 2,
80                    format: wgpu::VertexFormat::Float32x4,
81                },
82                wgpu::VertexAttribute {
83                    offset: 32,
84                    shader_location: 3,
85                    format: wgpu::VertexFormat::Float32x4,
86                },
87                wgpu::VertexAttribute {
88                    offset: 48,
89                    shader_location: 4,
90                    format: wgpu::VertexFormat::Float32,
91                },
92                wgpu::VertexAttribute {
93                    offset: 52,
94                    shader_location: 5,
95                    format: wgpu::VertexFormat::Float32x4,
96                },
97                wgpu::VertexAttribute {
98                    offset: 68,
99                    shader_location: 6,
100                    format: wgpu::VertexFormat::Float32x4,
101                },
102                wgpu::VertexAttribute {
103                    offset: 84,
104                    shader_location: 7,
105                    format: wgpu::VertexFormat::Float32x2,
106                },
107                wgpu::VertexAttribute {
108                    offset: 92,
109                    shader_location: 8,
110                    format: wgpu::VertexFormat::Float32,
111                },
112                wgpu::VertexAttribute {
113                    offset: 96,
114                    shader_location: 9,
115                    format: wgpu::VertexFormat::Float32x4,
116                },
117                wgpu::VertexAttribute {
118                    offset: 112,
119                    shader_location: 10,
120                    format: wgpu::VertexFormat::Float32,
121                },
122                wgpu::VertexAttribute {
123                    offset: 116,
124                    shader_location: 11,
125                    format: wgpu::VertexFormat::Float32,
126                },
127                wgpu::VertexAttribute {
128                    offset: 120,
129                    shader_location: 12,
130                    format: wgpu::VertexFormat::Float32,
131                },
132                wgpu::VertexAttribute {
133                    offset: 124,
134                    shader_location: 13,
135                    format: wgpu::VertexFormat::Float32,
136                },
137            ],
138        }
139    }
140}
141
142#[repr(C)]
143#[derive(Copy, Clone, Pod, Zeroable)]
144struct ScreenUniforms {
145    screen_size: [f32; 2],
146    _padding: [f32; 2],
147}
148
149pub struct RectPipeline {
150    pub pipeline: wgpu::RenderPipeline,
151    pub uniform_buffer: wgpu::Buffer,
152    pub uniform_bg: wgpu::BindGroup,
153    pub instance_buffer: Option<wgpu::Buffer>,
154    pub instance_count: u32,
155}
156
157impl RectPipeline {
158    pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
159        let shader = device.create_shader_module(wgpu::include_wgsl!("shaders/rect.wgsl"));
160
161        let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
162            label: Some("Rect Uniform Buffer"),
163            size: std::mem::size_of::<ScreenUniforms>() as u64,
164            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
165            mapped_at_creation: false,
166        });
167
168        let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
169            label: Some("Rect BGL"),
170            entries: &[wgpu::BindGroupLayoutEntry {
171                binding: 0,
172                visibility: wgpu::ShaderStages::VERTEX,
173                ty: wgpu::BindingType::Buffer {
174                    ty: wgpu::BufferBindingType::Uniform,
175                    has_dynamic_offset: false,
176                    min_binding_size: None,
177                },
178                count: None,
179            }],
180        });
181
182        let uniform_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
183            label: Some("Rect Uniform BG"),
184            layout: &bgl,
185            entries: &[wgpu::BindGroupEntry {
186                binding: 0,
187                resource: uniform_buffer.as_entire_binding(),
188            }],
189        });
190
191        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
192            label: Some("Rect Pipeline Layout"),
193            bind_group_layouts: &[Some(&bgl)],
194            immediate_size: 0,
195        });
196
197        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
198            label: Some("Rect Pipeline"),
199            layout: Some(&layout),
200            vertex: wgpu::VertexState {
201                module: &shader,
202                entry_point: Some("vs_main"),
203                buffers: &[RectInstance::desc()],
204                compilation_options: Default::default(),
205            },
206            fragment: Some(wgpu::FragmentState {
207                module: &shader,
208                entry_point: Some("fs_main"),
209                targets: &[Some(wgpu::ColorTargetState {
210                    format,
211                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
212                    write_mask: wgpu::ColorWrites::ALL,
213                })],
214                compilation_options: Default::default(),
215            }),
216            primitive: wgpu::PrimitiveState::default(),
217            depth_stencil: None,
218            multisample: wgpu::MultisampleState::default(),
219            multiview_mask: None,
220            cache: None,
221        });
222
223        Self {
224            pipeline,
225            uniform_buffer,
226            uniform_bg,
227            instance_buffer: None,
228            instance_count: 0,
229        }
230    }
231
232    pub fn prepare(
233        &mut self,
234        device: &wgpu::Device,
235        queue: &wgpu::Queue,
236        width: u32,
237        height: u32,
238        rects: &[RectInstance],
239    ) {
240        queue.write_buffer(
241            &self.uniform_buffer,
242            0,
243            bytemuck::bytes_of(&ScreenUniforms {
244                screen_size: [width as f32, height as f32],
245                _padding: [0.0; 2],
246            }),
247        );
248
249        if rects.is_empty() {
250            self.instance_count = 0;
251            return;
252        }
253
254        self.instance_buffer = Some(
255            device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
256                label: Some("Rect Instance Buffer"),
257                contents: bytemuck::cast_slice(rects),
258                usage: wgpu::BufferUsages::VERTEX,
259            }),
260        );
261        self.instance_count = rects.len() as u32;
262    }
263
264    pub fn draw<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>) {
265        if self.instance_count == 0 {
266            return;
267        }
268        if let Some(buf) = &self.instance_buffer {
269            pass.set_pipeline(&self.pipeline);
270            pass.set_bind_group(0, &self.uniform_bg, &[]);
271            pass.set_vertex_buffer(0, buf.slice(..));
272            pass.draw(0..6, 0..self.instance_count);
273        }
274    }
275}