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}