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