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 atlas_sampler = self.ctx.builder_mut().sampler(
191 Some(format!("text:{}:atlas-sampler", text.id.0)),
192 lumen_gpu::wgpu::SamplerDescriptor {
193 label: Some("lumen text atlas sampler"),
194 address_mode_u: lumen_gpu::wgpu::AddressMode::ClampToEdge,
195 address_mode_v: lumen_gpu::wgpu::AddressMode::ClampToEdge,
196 address_mode_w: lumen_gpu::wgpu::AddressMode::ClampToEdge,
197 mag_filter: lumen_gpu::wgpu::FilterMode::Linear,
198 min_filter: lumen_gpu::wgpu::FilterMode::Linear,
199 mipmap_filter: lumen_gpu::wgpu::MipmapFilterMode::Nearest,
200 ..Default::default()
201 },
202 );
203 let program = self.ctx.builder_mut().program_for(
204 lumen_gpu::NodeKey(text.id.0),
205 lumen_gpu::ProgramDesc::Render(lumen_gpu::RenderProgramDesc {
206 label: Some("text".to_string()),
207 shader: lumen_text::ALPHA_TEXT_SHADER.to_string(),
208 vertex_entry: "vs_main".to_string(),
209 fragment_entry: "fs_main".to_string(),
210 bind_groups: lumen_gpu::BindGroupLayoutSpec::single(vec![
211 lumen_gpu::BindingLayoutEntry::uniform(
212 0,
213 lumen_gpu::wgpu::ShaderStages::VERTEX_FRAGMENT,
214 ),
215 lumen_gpu::BindingLayoutEntry::texture(
216 1,
217 lumen_gpu::wgpu::ShaderStages::FRAGMENT,
218 ),
219 lumen_gpu::BindingLayoutEntry::sampler(
220 2,
221 lumen_gpu::wgpu::ShaderStages::FRAGMENT,
222 ),
223 lumen_gpu::BindingLayoutEntry::storage(
224 3,
225 lumen_gpu::wgpu::ShaderStages::VERTEX,
226 true,
227 ),
228 ]),
229 targets: vec![Some(lumen_gpu::wgpu::ColorTargetState {
230 format: lumen_gpu::wgpu::TextureFormat::Rgba8Unorm,
231 blend: Some(lumen_gpu::wgpu::BlendState::ALPHA_BLENDING),
232 write_mask: lumen_gpu::wgpu::ColorWrites::ALL,
233 })],
234 vertex_buffers: Vec::new(),
235 primitive: lumen_gpu::wgpu::PrimitiveState::default(),
236 }),
237 );
238 self.ctx
239 .builder_mut()
240 .render_pass(lumen_gpu::RenderPassDesc {
241 label: Some(format!("text:{}:render", text.id.0)),
242 owner: Some(lumen_gpu::NodeKey(text.id.0)),
243 program,
244 targets: vec![lumen_gpu::RenderTargetRef {
245 texture,
246 load: lumen_gpu::LoadOp::Clear(lumen_gpu::wgpu::Color::TRANSPARENT),
247 store: lumen_gpu::wgpu::StoreOp::Store,
248 }],
249 bindings: vec![
250 lumen_gpu::Binding::uniform(0, 0, globals_buffer),
251 lumen_gpu::Binding::sampled_texture(0, 1, atlas_texture),
252 lumen_gpu::Binding::sampler(0, 2, atlas_sampler),
253 lumen_gpu::Binding::storage_buffer(0, 3, instances_buffer),
254 ],
255 vertex_buffers: Vec::new(),
256 index_buffer: None,
257 draw: lumen_gpu::DrawCommand::Draw(lumen_gpu::Draw {
258 vertices: 0..6,
259 instances: 0..MAX_TEXT_GLYPHS as u32,
260 }),
261 scissor: None,
262 });
263 self.ctx.register_compiled_node(CompiledText {
264 node_id: text.id,
265 params: text.params.clone(),
266 atlas_texture,
267 globals_buffer,
268 instances_buffer,
269 atlas_size: TEXT_ATLAS_SIZE,
270 max_glyphs: MAX_TEXT_GLYPHS,
271 size,
272 });
273
274 Ok(CompiledOutput::Raster(RasterHandle {
275 texture,
276 domain: lumen_gpu::TextureDomain::full_frame(size),
277 metadata: RasterMetadata::default(),
278 }))
279 }
280
281 fn compile_vector_source(
282 &mut self,
283 node_id: NodeId,
284 port: &PortRef,
285 label: &str,
286 shader: &str,
287 params_size: u64,
288 ) -> crate::Result<(lumen_gpu::TextureId, lumen_gpu::BufferId, lumen_gpu::Size)> {
289 if port.port != "output" {
290 return Err(self.ctx.missing_output(node_id, &port.port));
291 }
292
293 let size = lumen_gpu::Size::new(
294 self.ctx.composition().render_settings.width.max(1),
295 self.ctx.composition().render_settings.height.max(1),
296 );
297 let texture = self.ctx.builder_mut().texture_for(
298 lumen_gpu::NodeKey(node_id.0),
299 Some(format!("{label}:{}:output", node_id.0)),
300 lumen_gpu::TextureDesc::storage(size, lumen_gpu::wgpu::TextureFormat::Rgba8Unorm),
301 );
302 let params = self.ctx.builder_mut().buffer_for(
303 lumen_gpu::NodeKey(node_id.0),
304 Some(format!("{label}:{}:params", node_id.0)),
305 lumen_gpu::BufferDesc::uniform(params_size),
306 );
307 let program = self.ctx.builder_mut().program_for(
308 lumen_gpu::NodeKey(node_id.0),
309 lumen_gpu::ProgramDesc::Compute(lumen_gpu::ComputeProgramDesc {
310 label: Some(label.to_string()),
311 shader: shader.to_string(),
312 entry: "cs_main".to_string(),
313 bind_groups: lumen_gpu::BindGroupLayoutSpec::single(vec![
314 lumen_gpu::BindingLayoutEntry::uniform(
315 0,
316 lumen_gpu::wgpu::ShaderStages::COMPUTE,
317 ),
318 lumen_gpu::BindingLayoutEntry::storage_texture(
319 1,
320 lumen_gpu::wgpu::ShaderStages::COMPUTE,
321 lumen_gpu::wgpu::TextureFormat::Rgba8Unorm,
322 lumen_gpu::wgpu::StorageTextureAccess::WriteOnly,
323 ),
324 ]),
325 }),
326 );
327 self.ctx
328 .builder_mut()
329 .compute_pass(lumen_gpu::ComputePassDesc {
330 label: Some(format!("{label}:{}:rasterize", node_id.0)),
331 owner: Some(lumen_gpu::NodeKey(node_id.0)),
332 program,
333 bindings: vec![
334 lumen_gpu::Binding::uniform(0, 0, params),
335 lumen_gpu::Binding::storage_texture(0, 1, texture),
336 ],
337 dispatch: compiler::dispatch_for(size).into(),
338 });
339 self.ctx.builder_mut().param(
340 lumen_gpu::ParamKey {
341 owner: lumen_gpu::NodeKey(node_id.0),
342 slot: 0,
343 },
344 lumen_gpu::ParamTarget::Buffer(params),
345 );
346 Ok((texture, params, size))
347 }
348}
349
350#[repr(C)]
351#[derive(Debug, Clone, Copy, Pod, Zeroable)]
352pub(crate) struct ShapeParams {
353 pub(crate) fill_paint: super::paint::GpuPaint,
354 pub(crate) stroke_paint: super::paint::GpuPaint,
355 pub(crate) position: [f32; 2],
356 pub(crate) size: [f32; 2],
357 pub(crate) border_radius: f32,
358 pub(crate) stroke_width: f32,
359 pub(crate) geometry_kind: u32,
360 pub(crate) flags: u32,
361}
362
363#[repr(C)]
364#[derive(Debug, Clone, Copy, Pod, Zeroable)]
365pub(crate) struct PathParams {
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) bounds_min: [f32; 2],
370 pub(crate) bounds_size: [f32; 2],
371 pub(crate) stroke_width: f32,
372 pub(crate) flags: u32,
373 pub(crate) point_count: u32,
374 pub(crate) _pad: [u32; 3],
375}
376
377#[repr(C)]
378#[derive(Debug, Clone, Copy, Pod, Zeroable)]
379pub(crate) struct PathPoint {
380 pub(crate) position: [f32; 2],
381}