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