Skip to main content

oxiui_render_wgpu/gpu/
pipeline.rs

1//! Render-pipeline construction for the solid-fill / SDF shader and the
2//! gradient pipeline.
3//!
4//! [`SolidPipeline`] owns the compiled `solid.wgsl` module, the uniform bind
5//! group layout (viewport [`Globals`]), and the [`wgpu::RenderPipeline`].
6//!
7//! [`GradientPipeline`] owns the compiled `gradient.wgsl` module, a bind group
8//! layout for the viewport uniform plus a per-draw gradient uniform buffer, and
9//! the gradient render pipeline.
10//!
11//! Vertex attribute layouts are derived by hand to match the field offsets of
12//! [`Vertex`] / [`GradientVertex`] exactly — the compile-time size assertions
13//! in [`crate::gpu::buffer`] guard against drift.
14//!
15//! Solid pipeline vertex layout (56 bytes = 14 × f32):
16//!   pos      vec2  @0   offset  0
17//!   color    vec4  @1   offset  8
18//!   local    vec2  @2   offset 24
19//!   shape_xy vec2  @3   offset 32
20//!   shape_r  f32   @4   offset 40
21//!   kind     f32   @5   offset 44
22//!   extra    vec2  @6   offset 48
23//!
24//! [`Globals`]: crate::gpu::buffer::Globals
25//! [`Vertex`]: crate::gpu::buffer::Vertex
26//! [`GradientVertex`]: crate::gpu::buffer::GradientVertex
27
28use crate::gpu::buffer::{GradientVertex, Vertex};
29use crate::gpu::device::TARGET_FORMAT;
30
31// ── SolidPipeline ────────────────────────────────────────────────────────────
32
33/// The compiled solid-fill / SDF pipeline plus the bind-group layout its draws
34/// need.
35pub struct SolidPipeline {
36    /// The render pipeline (vertex + fragment stages, alpha blending).
37    pub pipeline: wgpu::RenderPipeline,
38    /// Layout of bind group 0 (the viewport uniform).
39    pub globals_layout: wgpu::BindGroupLayout,
40}
41
42impl SolidPipeline {
43    /// Build the solid pipeline for a colour target in [`TARGET_FORMAT`].
44    pub fn new(device: &wgpu::Device) -> Self {
45        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
46            label: Some("oxiui-render-wgpu solid.wgsl"),
47            source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/solid.wgsl").into()),
48        });
49
50        let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
51            label: Some("oxiui-render-wgpu globals layout"),
52            entries: &[wgpu::BindGroupLayoutEntry {
53                binding: 0,
54                visibility: wgpu::ShaderStages::VERTEX,
55                ty: wgpu::BindingType::Buffer {
56                    ty: wgpu::BufferBindingType::Uniform,
57                    has_dynamic_offset: false,
58                    min_binding_size: None,
59                },
60                count: None,
61            }],
62        });
63
64        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
65            label: Some("oxiui-render-wgpu solid pipeline layout"),
66            bind_group_layouts: &[Some(&globals_layout)],
67            immediate_size: 0,
68        });
69
70        // Vertex attributes mirror `Vertex` byte offsets exactly (56 bytes):
71        //   pos      vec2  @0   offset  0
72        //   color    vec4  @1   offset  8
73        //   local    vec2  @2   offset 24
74        //   shape_xy vec2  @3   offset 32
75        //   shape_r  f32   @4   offset 40
76        //   kind     f32   @5   offset 44
77        //   extra    vec2  @6   offset 48
78        let attrs = [
79            wgpu::VertexAttribute {
80                format: wgpu::VertexFormat::Float32x2,
81                offset: 0,
82                shader_location: 0,
83            },
84            wgpu::VertexAttribute {
85                format: wgpu::VertexFormat::Float32x4,
86                offset: 8,
87                shader_location: 1,
88            },
89            wgpu::VertexAttribute {
90                format: wgpu::VertexFormat::Float32x2,
91                offset: 24,
92                shader_location: 2,
93            },
94            wgpu::VertexAttribute {
95                format: wgpu::VertexFormat::Float32x2,
96                offset: 32,
97                shader_location: 3,
98            },
99            wgpu::VertexAttribute {
100                format: wgpu::VertexFormat::Float32,
101                offset: 40,
102                shader_location: 4,
103            },
104            wgpu::VertexAttribute {
105                format: wgpu::VertexFormat::Float32,
106                offset: 44,
107                shader_location: 5,
108            },
109            wgpu::VertexAttribute {
110                format: wgpu::VertexFormat::Float32x2,
111                offset: 48,
112                shader_location: 6,
113            },
114        ];
115
116        let vertex_layout = wgpu::VertexBufferLayout {
117            array_stride: core::mem::size_of::<Vertex>() as wgpu::BufferAddress,
118            step_mode: wgpu::VertexStepMode::Vertex,
119            attributes: &attrs,
120        };
121
122        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
123            label: Some("oxiui-render-wgpu solid pipeline"),
124            layout: Some(&pipeline_layout),
125            vertex: wgpu::VertexState {
126                module: &shader,
127                entry_point: Some("vs_main"),
128                buffers: &[vertex_layout],
129                compilation_options: wgpu::PipelineCompilationOptions::default(),
130            },
131            fragment: Some(wgpu::FragmentState {
132                module: &shader,
133                entry_point: Some("fs_main"),
134                targets: &[Some(wgpu::ColorTargetState {
135                    format: TARGET_FORMAT,
136                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
137                    write_mask: wgpu::ColorWrites::ALL,
138                })],
139                compilation_options: wgpu::PipelineCompilationOptions::default(),
140            }),
141            primitive: wgpu::PrimitiveState {
142                topology: wgpu::PrimitiveTopology::TriangleList,
143                strip_index_format: None,
144                front_face: wgpu::FrontFace::Ccw,
145                cull_mode: None,
146                unclipped_depth: false,
147                polygon_mode: wgpu::PolygonMode::Fill,
148                conservative: false,
149            },
150            depth_stencil: None,
151            multisample: wgpu::MultisampleState {
152                count: 1,
153                mask: !0,
154                alpha_to_coverage_enabled: false,
155            },
156            multiview_mask: None,
157            cache: None,
158        });
159
160        Self {
161            pipeline,
162            globals_layout,
163        }
164    }
165}
166
167// ── GradientPipeline ─────────────────────────────────────────────────────────
168
169/// The compiled gradient pipeline: gradient quads rendered via a per-draw
170/// uniform buffer carrying gradient type, stops, and geometry.
171pub struct GradientPipeline {
172    /// The render pipeline (vertex + fragment stages, alpha blending).
173    pub pipeline: wgpu::RenderPipeline,
174    /// Layout of bind group 0: viewport uniform (binding 0) and gradient
175    /// uniform buffer (binding 1).
176    pub bind_group_layout: wgpu::BindGroupLayout,
177}
178
179impl GradientPipeline {
180    /// Build the gradient pipeline for a colour target in [`TARGET_FORMAT`].
181    pub fn new(device: &wgpu::Device) -> Self {
182        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
183            label: Some("oxiui-render-wgpu gradient.wgsl"),
184            source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/gradient.wgsl").into()),
185        });
186
187        // Bind group 0:
188        //   binding 0 — Globals (viewport, vertex stage only)
189        //   binding 1 — GradientUniforms (fragment stage only)
190        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
191            label: Some("oxiui-render-wgpu gradient bind group layout"),
192            entries: &[
193                wgpu::BindGroupLayoutEntry {
194                    binding: 0,
195                    visibility: wgpu::ShaderStages::VERTEX,
196                    ty: wgpu::BindingType::Buffer {
197                        ty: wgpu::BufferBindingType::Uniform,
198                        has_dynamic_offset: false,
199                        min_binding_size: None,
200                    },
201                    count: None,
202                },
203                wgpu::BindGroupLayoutEntry {
204                    binding: 1,
205                    visibility: wgpu::ShaderStages::FRAGMENT,
206                    ty: wgpu::BindingType::Buffer {
207                        ty: wgpu::BufferBindingType::Uniform,
208                        has_dynamic_offset: false,
209                        min_binding_size: None,
210                    },
211                    count: None,
212                },
213            ],
214        });
215
216        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
217            label: Some("oxiui-render-wgpu gradient pipeline layout"),
218            bind_group_layouts: &[Some(&bind_group_layout)],
219            immediate_size: 0,
220        });
221
222        // Gradient vertex layout (16 bytes):
223        //   position  vec2  @0   offset 0
224        //   local     vec2  @1   offset 8
225        let attrs = [
226            wgpu::VertexAttribute {
227                format: wgpu::VertexFormat::Float32x2,
228                offset: 0,
229                shader_location: 0,
230            },
231            wgpu::VertexAttribute {
232                format: wgpu::VertexFormat::Float32x2,
233                offset: 8,
234                shader_location: 1,
235            },
236        ];
237
238        let vertex_layout = wgpu::VertexBufferLayout {
239            array_stride: core::mem::size_of::<GradientVertex>() as wgpu::BufferAddress,
240            step_mode: wgpu::VertexStepMode::Vertex,
241            attributes: &attrs,
242        };
243
244        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
245            label: Some("oxiui-render-wgpu gradient pipeline"),
246            layout: Some(&pipeline_layout),
247            vertex: wgpu::VertexState {
248                module: &shader,
249                entry_point: Some("vs_main"),
250                buffers: &[vertex_layout],
251                compilation_options: wgpu::PipelineCompilationOptions::default(),
252            },
253            fragment: Some(wgpu::FragmentState {
254                module: &shader,
255                entry_point: Some("fs_main"),
256                targets: &[Some(wgpu::ColorTargetState {
257                    format: TARGET_FORMAT,
258                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
259                    write_mask: wgpu::ColorWrites::ALL,
260                })],
261                compilation_options: wgpu::PipelineCompilationOptions::default(),
262            }),
263            primitive: wgpu::PrimitiveState {
264                topology: wgpu::PrimitiveTopology::TriangleList,
265                strip_index_format: None,
266                front_face: wgpu::FrontFace::Ccw,
267                cull_mode: None,
268                unclipped_depth: false,
269                polygon_mode: wgpu::PolygonMode::Fill,
270                conservative: false,
271            },
272            depth_stencil: None,
273            multisample: wgpu::MultisampleState {
274                count: 1,
275                mask: !0,
276                alpha_to_coverage_enabled: false,
277            },
278            multiview_mask: None,
279            cache: None,
280        });
281
282        Self {
283            pipeline,
284            bind_group_layout,
285        }
286    }
287}