Skip to main content

lumen_engine/node/vector/
renderer.rs

1use bytemuck::{Pod, Zeroable};
2
3use crate::{
4    gpu::{CompiledOutput, RasterHandle, RasterMetadata, compiler},
5    node::{NodeId, PortRef},
6};
7
8use crate::node::source::text::{CompiledText, Text};
9
10use super::{
11    path::{CompiledPath, Path},
12    shape::{CompiledShape, Shape},
13};
14
15pub(crate) const SHAPE_SHADER: &str = include_str!("shape_renderer.wgsl");
16pub(crate) const PATH_SHADER: &str = include_str!("path_renderer.wgsl");
17pub(crate) const TEXT_ATLAS_SIZE: lumen_gpu::Size = lumen_gpu::Size {
18    width: 2048,
19    height: 2048,
20};
21pub(crate) const MAX_TEXT_GLYPHS: usize = 2048;
22pub(crate) const MAX_PATH_POINTS: usize = 128;
23
24pub(crate) struct VectorRenderer<'a, 'b> {
25    ctx: &'a mut crate::gpu::CompileContext<'b>,
26}
27
28impl<'a, 'b> VectorRenderer<'a, 'b> {
29    pub(crate) fn new(ctx: &'a mut crate::gpu::CompileContext<'b>) -> Self {
30        Self { ctx }
31    }
32
33    pub(crate) fn compile_shape(
34        &mut self,
35        shape: &Shape,
36        port: &PortRef,
37    ) -> crate::Result<CompiledOutput> {
38        let (texture, params, size) = self.compile_vector_source(
39            shape.id,
40            port,
41            "shape",
42            SHAPE_SHADER,
43            std::mem::size_of::<ShapeParams>() as u64,
44        )?;
45        self.ctx.register_compiled_node(CompiledShape {
46            node_id: shape.id,
47            params: shape.params.clone(),
48            buffer: params,
49        });
50
51        Ok(CompiledOutput::Raster(RasterHandle {
52            texture,
53            domain: lumen_gpu::TextureDomain::full_frame(size),
54            metadata: RasterMetadata::default(),
55        }))
56    }
57
58    pub(crate) fn compile_path(
59        &mut self,
60        path: &Path,
61        port: &PortRef,
62    ) -> crate::Result<CompiledOutput> {
63        if port.port != "output" {
64            return Err(self.ctx.missing_output(path.id, &port.port));
65        }
66
67        let size = lumen_gpu::Size::new(
68            self.ctx.composition().render_settings.width.max(1),
69            self.ctx.composition().render_settings.height.max(1),
70        );
71        let texture = self.ctx.builder_mut().texture_for(
72            lumen_gpu::NodeKey(path.id.0),
73            Some(format!("path:{}:output", path.id.0)),
74            lumen_gpu::TextureDesc::storage(size, lumen_gpu::wgpu::TextureFormat::Rgba8Unorm),
75        );
76        let params = self.ctx.builder_mut().buffer_for(
77            lumen_gpu::NodeKey(path.id.0),
78            Some(format!("path:{}:params", path.id.0)),
79            lumen_gpu::BufferDesc::uniform(std::mem::size_of::<PathParams>() as u64),
80        );
81        let points = self.ctx.builder_mut().buffer_for(
82            lumen_gpu::NodeKey(path.id.0),
83            Some(format!("path:{}:points", path.id.0)),
84            lumen_gpu::BufferDesc::storage(
85                (MAX_PATH_POINTS * std::mem::size_of::<PathPoint>()) as u64,
86            ),
87        );
88        let program = self.ctx.builder_mut().program_for(
89            lumen_gpu::NodeKey(path.id.0),
90            lumen_gpu::ProgramDesc::Compute(lumen_gpu::ComputeProgramDesc {
91                label: Some("path".to_string()),
92                shader: PATH_SHADER.to_string(),
93                entry: "cs_main".to_string(),
94                bind_groups: lumen_gpu::BindGroupLayoutSpec::single(vec![
95                    lumen_gpu::BindingLayoutEntry::uniform(
96                        0,
97                        lumen_gpu::wgpu::ShaderStages::COMPUTE,
98                    ),
99                    lumen_gpu::BindingLayoutEntry::storage(
100                        1,
101                        lumen_gpu::wgpu::ShaderStages::COMPUTE,
102                        true,
103                    ),
104                    lumen_gpu::BindingLayoutEntry::storage_texture(
105                        2,
106                        lumen_gpu::wgpu::ShaderStages::COMPUTE,
107                        lumen_gpu::wgpu::TextureFormat::Rgba8Unorm,
108                        lumen_gpu::wgpu::StorageTextureAccess::WriteOnly,
109                    ),
110                ]),
111            }),
112        );
113        self.ctx
114            .builder_mut()
115            .compute_pass(lumen_gpu::ComputePassDesc {
116                label: Some(format!("path:{}:rasterize", path.id.0)),
117                owner: Some(lumen_gpu::NodeKey(path.id.0)),
118                program,
119                bindings: vec![
120                    lumen_gpu::Binding::uniform(0, 0, params),
121                    lumen_gpu::Binding::storage_buffer(0, 1, points),
122                    lumen_gpu::Binding::storage_texture(0, 2, texture),
123                ],
124                dispatch: compiler::dispatch_for(size).into(),
125            });
126        self.ctx.builder_mut().param(
127            lumen_gpu::ParamKey {
128                owner: lumen_gpu::NodeKey(path.id.0),
129                slot: 0,
130            },
131            lumen_gpu::ParamTarget::Buffer(params),
132        );
133        self.ctx.register_compiled_node(CompiledPath {
134            node_id: path.id,
135            params: path.params.clone(),
136            params_buffer: params,
137            points_buffer: points,
138            max_points: MAX_PATH_POINTS,
139        });
140
141        Ok(CompiledOutput::Raster(RasterHandle {
142            texture,
143            domain: lumen_gpu::TextureDomain::full_frame(size),
144            metadata: RasterMetadata::default(),
145        }))
146    }
147
148    pub(crate) fn compile_text(
149        &mut self,
150        text: &Text,
151        port: &PortRef,
152    ) -> crate::Result<CompiledOutput> {
153        if port.port != "output" {
154            return Err(self.ctx.missing_output(text.id, &port.port));
155        }
156        crate::node::source::text::clear_text_cache_for(text.id);
157
158        let size = lumen_gpu::Size::new(
159            self.ctx.composition().render_settings.width.max(1),
160            self.ctx.composition().render_settings.height.max(1),
161        );
162        let texture = self.ctx.builder_mut().texture_for(
163            lumen_gpu::NodeKey(text.id.0),
164            Some(format!("text:{}:output", text.id.0)),
165            lumen_gpu::TextureDesc::render_target(size, lumen_gpu::wgpu::TextureFormat::Rgba8Unorm),
166        );
167        let atlas_texture = self.ctx.builder_mut().texture_for(
168            lumen_gpu::NodeKey(text.id.0),
169            Some(format!("text:{}:atlas", text.id.0)),
170            lumen_gpu::TextureDesc::sampled(
171                TEXT_ATLAS_SIZE,
172                lumen_gpu::wgpu::TextureFormat::Rgba8Unorm,
173            ),
174        );
175        let globals_buffer =
176            self.ctx.builder_mut().buffer_for(
177                lumen_gpu::NodeKey(text.id.0),
178                Some(format!("text:{}:globals", text.id.0)),
179                lumen_gpu::BufferDesc::uniform(
180                    std::mem::size_of::<lumen_text::GpuTextGlobals>() as u64
181                ),
182            );
183        let instances_buffer = self.ctx.builder_mut().buffer_for(
184            lumen_gpu::NodeKey(text.id.0),
185            Some(format!("text:{}:instances", text.id.0)),
186            lumen_gpu::BufferDesc::storage(
187                (MAX_TEXT_GLYPHS * std::mem::size_of::<lumen_text::GpuGlyphInstance>()) as u64,
188            ),
189        );
190        let paint_buffer = self.ctx.builder_mut().buffer_for(
191            lumen_gpu::NodeKey(text.id.0),
192            Some(format!("text:{}:paint", text.id.0)),
193            lumen_gpu::BufferDesc::uniform(
194                std::mem::size_of::<crate::node::vector::paint::GpuPaint>() as u64,
195            ),
196        );
197        let atlas_sampler = self.ctx.builder_mut().sampler(
198            Some(format!("text:{}:atlas-sampler", text.id.0)),
199            lumen_gpu::wgpu::SamplerDescriptor {
200                label: Some("lumen text atlas sampler"),
201                address_mode_u: lumen_gpu::wgpu::AddressMode::ClampToEdge,
202                address_mode_v: lumen_gpu::wgpu::AddressMode::ClampToEdge,
203                address_mode_w: lumen_gpu::wgpu::AddressMode::ClampToEdge,
204                mag_filter: lumen_gpu::wgpu::FilterMode::Linear,
205                min_filter: lumen_gpu::wgpu::FilterMode::Linear,
206                mipmap_filter: lumen_gpu::wgpu::MipmapFilterMode::Nearest,
207                ..Default::default()
208            },
209        );
210        let program = self.ctx.builder_mut().program_for(
211            lumen_gpu::NodeKey(text.id.0),
212            lumen_gpu::ProgramDesc::Render(lumen_gpu::RenderProgramDesc {
213                label: Some("text".to_string()),
214                shader: lumen_text::ALPHA_TEXT_SHADER.to_string(),
215                vertex_entry: "vs_main".to_string(),
216                fragment_entry: "fs_main".to_string(),
217                bind_groups: lumen_gpu::BindGroupLayoutSpec::single(vec![
218                    lumen_gpu::BindingLayoutEntry::uniform(
219                        0,
220                        lumen_gpu::wgpu::ShaderStages::VERTEX_FRAGMENT,
221                    ),
222                    lumen_gpu::BindingLayoutEntry::texture(
223                        1,
224                        lumen_gpu::wgpu::ShaderStages::FRAGMENT,
225                    ),
226                    lumen_gpu::BindingLayoutEntry::sampler(
227                        2,
228                        lumen_gpu::wgpu::ShaderStages::FRAGMENT,
229                    ),
230                    lumen_gpu::BindingLayoutEntry::storage(
231                        3,
232                        lumen_gpu::wgpu::ShaderStages::VERTEX,
233                        true,
234                    ),
235                    lumen_gpu::BindingLayoutEntry::uniform(
236                        4,
237                        lumen_gpu::wgpu::ShaderStages::FRAGMENT,
238                    ),
239                ]),
240                targets: vec![Some(lumen_gpu::wgpu::ColorTargetState {
241                    format: lumen_gpu::wgpu::TextureFormat::Rgba8Unorm,
242                    blend: Some(lumen_gpu::wgpu::BlendState::ALPHA_BLENDING),
243                    write_mask: lumen_gpu::wgpu::ColorWrites::ALL,
244                })],
245                vertex_buffers: Vec::new(),
246                primitive: lumen_gpu::wgpu::PrimitiveState::default(),
247            }),
248        );
249        self.ctx
250            .builder_mut()
251            .render_pass(lumen_gpu::RenderPassDesc {
252                label: Some(format!("text:{}:render", text.id.0)),
253                owner: Some(lumen_gpu::NodeKey(text.id.0)),
254                program,
255                targets: vec![lumen_gpu::RenderTargetRef {
256                    texture,
257                    load: lumen_gpu::LoadOp::Clear(lumen_gpu::wgpu::Color::TRANSPARENT),
258                    store: lumen_gpu::wgpu::StoreOp::Store,
259                }],
260                bindings: vec![
261                    lumen_gpu::Binding::uniform(0, 0, globals_buffer),
262                    lumen_gpu::Binding::sampled_texture(0, 1, atlas_texture),
263                    lumen_gpu::Binding::sampler(0, 2, atlas_sampler),
264                    lumen_gpu::Binding::storage_buffer(0, 3, instances_buffer),
265                    lumen_gpu::Binding::uniform(0, 4, paint_buffer),
266                ],
267                vertex_buffers: Vec::new(),
268                index_buffer: None,
269                draw: lumen_gpu::DrawCommand::Draw(lumen_gpu::Draw {
270                    vertices: 0..6,
271                    instances: 0..MAX_TEXT_GLYPHS as u32,
272                }),
273                scissor: None,
274            });
275        self.ctx.register_compiled_node(CompiledText {
276            node_id: text.id,
277            params: text.params.clone(),
278            atlas_texture,
279            globals_buffer,
280            instances_buffer,
281            paint_buffer,
282            atlas_size: TEXT_ATLAS_SIZE,
283            max_glyphs: MAX_TEXT_GLYPHS,
284            size,
285        });
286
287        Ok(CompiledOutput::Raster(RasterHandle {
288            texture,
289            domain: lumen_gpu::TextureDomain::full_frame(size),
290            metadata: RasterMetadata::default(),
291        }))
292    }
293
294    fn compile_vector_source(
295        &mut self,
296        node_id: NodeId,
297        port: &PortRef,
298        label: &str,
299        shader: &str,
300        params_size: u64,
301    ) -> crate::Result<(lumen_gpu::TextureId, lumen_gpu::BufferId, lumen_gpu::Size)> {
302        if port.port != "output" {
303            return Err(self.ctx.missing_output(node_id, &port.port));
304        }
305
306        let size = lumen_gpu::Size::new(
307            self.ctx.composition().render_settings.width.max(1),
308            self.ctx.composition().render_settings.height.max(1),
309        );
310        let texture = self.ctx.builder_mut().texture_for(
311            lumen_gpu::NodeKey(node_id.0),
312            Some(format!("{label}:{}:output", node_id.0)),
313            lumen_gpu::TextureDesc::storage(size, lumen_gpu::wgpu::TextureFormat::Rgba8Unorm),
314        );
315        let params = self.ctx.builder_mut().buffer_for(
316            lumen_gpu::NodeKey(node_id.0),
317            Some(format!("{label}:{}:params", node_id.0)),
318            lumen_gpu::BufferDesc::uniform(params_size),
319        );
320        let program = self.ctx.builder_mut().program_for(
321            lumen_gpu::NodeKey(node_id.0),
322            lumen_gpu::ProgramDesc::Compute(lumen_gpu::ComputeProgramDesc {
323                label: Some(label.to_string()),
324                shader: shader.to_string(),
325                entry: "cs_main".to_string(),
326                bind_groups: lumen_gpu::BindGroupLayoutSpec::single(vec![
327                    lumen_gpu::BindingLayoutEntry::uniform(
328                        0,
329                        lumen_gpu::wgpu::ShaderStages::COMPUTE,
330                    ),
331                    lumen_gpu::BindingLayoutEntry::storage_texture(
332                        1,
333                        lumen_gpu::wgpu::ShaderStages::COMPUTE,
334                        lumen_gpu::wgpu::TextureFormat::Rgba8Unorm,
335                        lumen_gpu::wgpu::StorageTextureAccess::WriteOnly,
336                    ),
337                ]),
338            }),
339        );
340        self.ctx
341            .builder_mut()
342            .compute_pass(lumen_gpu::ComputePassDesc {
343                label: Some(format!("{label}:{}:rasterize", node_id.0)),
344                owner: Some(lumen_gpu::NodeKey(node_id.0)),
345                program,
346                bindings: vec![
347                    lumen_gpu::Binding::uniform(0, 0, params),
348                    lumen_gpu::Binding::storage_texture(0, 1, texture),
349                ],
350                dispatch: compiler::dispatch_for(size).into(),
351            });
352        self.ctx.builder_mut().param(
353            lumen_gpu::ParamKey {
354                owner: lumen_gpu::NodeKey(node_id.0),
355                slot: 0,
356            },
357            lumen_gpu::ParamTarget::Buffer(params),
358        );
359        Ok((texture, params, size))
360    }
361}
362
363#[repr(C)]
364#[derive(Debug, Clone, Copy, Pod, Zeroable)]
365pub(crate) struct ShapeParams {
366    pub(crate) fill_paint: super::paint::GpuPaint,
367    pub(crate) stroke_paint: super::paint::GpuPaint,
368    pub(crate) position: [f32; 2],
369    pub(crate) size: [f32; 2],
370    pub(crate) border_radius: f32,
371    pub(crate) stroke_width: f32,
372    pub(crate) geometry_kind: u32,
373    pub(crate) flags: u32,
374}
375
376#[repr(C)]
377#[derive(Debug, Clone, Copy, Pod, Zeroable)]
378pub(crate) struct PathParams {
379    pub(crate) fill_paint: super::paint::GpuPaint,
380    pub(crate) stroke_paint: super::paint::GpuPaint,
381    pub(crate) position: [f32; 2],
382    pub(crate) bounds_min: [f32; 2],
383    pub(crate) bounds_size: [f32; 2],
384    pub(crate) stroke_width: f32,
385    pub(crate) flags: u32,
386    pub(crate) point_count: u32,
387    pub(crate) _pad: [u32; 3],
388}
389
390#[repr(C)]
391#[derive(Debug, Clone, Copy, Pod, Zeroable)]
392pub(crate) struct PathPoint {
393    pub(crate) position: [f32; 2],
394}