Skip to main content

lumen_engine_gpu/
lib.rs

1//! Operation-agnostic GPU render-plan core for Lumen.
2//!
3//! This crate deliberately does not model semantic nodes such as blur,
4//! transform, text, or composite. Lumen owns those concepts and compiles them
5//! into resources, shader programs, and generic passes that this crate can
6//! allocate and execute.
7
8mod binding;
9mod geometry;
10mod id;
11mod pass;
12mod plan;
13mod program;
14mod renderer;
15mod resource;
16mod update;
17#[cfg(all(target_os = "linux", feature = "vulkan"))]
18mod vulkan_export;
19
20pub use binding::*;
21pub use geometry::*;
22pub use id::*;
23pub use pass::*;
24pub use plan::*;
25pub use program::*;
26pub use renderer::*;
27pub use resource::*;
28pub use update::*;
29#[cfg(all(target_os = "linux", feature = "vulkan"))]
30pub use vulkan_export::*;
31pub use wgpu;
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36
37    const COPY_SHADER: &str = r#"
38@group(0) @binding(0) var input_tex: texture_2d<f32>;
39@group(0) @binding(1) var input_sampler: sampler;
40
41struct VsOut {
42    @builtin(position) position: vec4<f32>,
43    @location(0) uv: vec2<f32>,
44}
45
46@vertex
47fn vs_main(@builtin(vertex_index) vertex: u32) -> VsOut {
48    let positions = array<vec2<f32>, 3>(
49        vec2<f32>(-1.0, -1.0),
50        vec2<f32>(3.0, -1.0),
51        vec2<f32>(-1.0, 3.0)
52    );
53    let uvs = array<vec2<f32>, 3>(
54        vec2<f32>(0.0, 1.0),
55        vec2<f32>(2.0, 1.0),
56        vec2<f32>(0.0, -1.0)
57    );
58    var out: VsOut;
59    out.position = vec4<f32>(positions[vertex], 0.0, 1.0);
60    out.uv = uvs[vertex];
61    return out;
62}
63
64@fragment
65fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
66    return textureSample(input_tex, input_sampler, in.uv);
67}
68"#;
69
70    #[test]
71    fn render_plan_records_generic_shader_passes() {
72        let size = Size::new(64, 64);
73        let mut plan = RenderPlan::builder();
74        let source = plan.texture(
75            Some("source".to_string()),
76            TextureDesc::sampled(size, wgpu::TextureFormat::Rgba8Unorm),
77        );
78        let target = plan.texture(
79            Some("target".to_string()),
80            TextureDesc::render_target(size, wgpu::TextureFormat::Rgba8Unorm),
81        );
82        let sampler = plan.sampler(
83            Some("linear".to_string()),
84            wgpu::SamplerDescriptor::default(),
85        );
86        let program = plan.program(ProgramDesc::Render(RenderProgramDesc {
87            label: Some("copy".to_string()),
88            shader: COPY_SHADER.to_string(),
89            vertex_entry: "vs_main".to_string(),
90            fragment_entry: "fs_main".to_string(),
91            bind_groups: BindGroupLayoutSpec::single(vec![
92                BindingLayoutEntry::texture(0, wgpu::ShaderStages::FRAGMENT),
93                BindingLayoutEntry::sampler(1, wgpu::ShaderStages::FRAGMENT),
94            ]),
95            targets: vec![Some(wgpu::ColorTargetState {
96                format: wgpu::TextureFormat::Rgba8Unorm,
97                blend: Some(wgpu::BlendState::ALPHA_BLENDING),
98                write_mask: wgpu::ColorWrites::ALL,
99            })],
100            vertex_buffers: Vec::new(),
101            primitive: wgpu::PrimitiveState::default(),
102        }));
103        plan.render_pass(RenderPassDesc {
104            label: Some("copy pass".to_string()),
105            owner: Some(NodeKey(42)),
106            program,
107            targets: vec![RenderTargetRef {
108                texture: target,
109                load: LoadOp::Clear(wgpu::Color::TRANSPARENT),
110                store: wgpu::StoreOp::Store,
111            }],
112            bindings: vec![
113                Binding::sampled_texture(0, 0, source),
114                Binding::sampler(0, 1, sampler),
115            ],
116            vertex_buffers: Vec::new(),
117            index_buffer: None,
118            draw: DrawCommand::Draw(Draw {
119                vertices: 0..3,
120                instances: 0..1,
121            }),
122            scissor: None,
123        });
124        let plan = plan.build();
125
126        assert_eq!(plan.textures().len(), 2);
127        assert_eq!(plan.programs().len(), 1);
128        assert_eq!(plan.passes().len(), 1);
129    }
130
131    #[test]
132    fn frame_update_records_dynamic_uniform_uploads() {
133        let uniforms = BufferId(7);
134        let bytes = [1, 2, 3, 4];
135        let mut update = FrameUpdate::new();
136        update.write_buffer(uniforms, 0, &bytes);
137
138        assert_eq!(update.uploads().len(), 1);
139    }
140}