Skip to main content

anvilkit_render/renderer/
line.rs

1//! # 线段渲染器
2//!
3//! 提供 3D 空间中的线段渲染功能,用于瞄准线、调试可视化和辅助线。
4//!
5//! ## 使用示例
6//!
7//! ```rust,no_run
8//! use anvilkit_render::renderer::line::LineRenderer;
9//! use glam::{Vec3, Mat4};
10//!
11//! // 创建 LineRenderer(需要 GPU device 和 surface format)
12//! // let renderer = LineRenderer::new(&device, format);
13//!
14//! // 渲染线段
15//! // let lines = vec![
16//! //     (Vec3::ZERO, Vec3::new(5.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)),
17//! // ];
18//! // renderer.render(&device, &mut encoder, &target_view, &lines, &view_proj);
19//! ```
20
21use glam::{Mat4, Vec3};
22use wgpu::{
23    BindGroup, Buffer, CommandEncoder, RenderPipeline, TextureView,
24};
25
26use crate::renderer::RenderDevice;
27use crate::renderer::buffer::{ColorVertex, Vertex, create_uniform_buffer};
28use crate::renderer::pipeline::RenderPipelineBuilder;
29
30/// Line shader source
31const LINE_SHADER: &str = include_str!("../shaders/line.wgsl");
32
33/// 线段渲染器
34///
35/// 使用 `PrimitiveTopology::LineList` 渲染 3D 线段。
36/// 支持单帧动态线段列表,每帧重新上传顶点数据。
37pub struct LineRenderer {
38    pipeline: RenderPipeline,
39    scene_buffer: Buffer,
40    scene_bind_group: BindGroup,
41    /// Cached vertex buffer for per-frame reuse
42    cached_vb: Option<(Buffer, u64)>,
43}
44
45/// 视图-投影 uniform 数据(64 字节,mat4x4<f32>)
46#[repr(C)]
47#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
48struct LineSceneUniform {
49    view_proj: [[f32; 4]; 4],
50}
51
52impl LineRenderer {
53    /// 创建线段渲染器
54    ///
55    /// # 参数
56    ///
57    /// - `device`: GPU 渲染设备
58    /// - `format`: 渲染目标纹理格式
59    pub fn new(device: &RenderDevice, format: wgpu::TextureFormat) -> Self {
60        let scene_uniform = LineSceneUniform {
61            view_proj: Mat4::IDENTITY.to_cols_array_2d(),
62        };
63        let scene_buffer = create_uniform_buffer(
64            device,
65            "Line Scene Uniform",
66            bytemuck::bytes_of(&scene_uniform),
67        );
68
69        let scene_bind_group_layout =
70            device
71                .device()
72                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
73                    label: Some("Line Scene BGL"),
74                    entries: &[wgpu::BindGroupLayoutEntry {
75                        binding: 0,
76                        visibility: wgpu::ShaderStages::VERTEX,
77                        ty: wgpu::BindingType::Buffer {
78                            ty: wgpu::BufferBindingType::Uniform,
79                            has_dynamic_offset: false,
80                            min_binding_size: None,
81                        },
82                        count: None,
83                    }],
84                });
85
86        let scene_bind_group = device.device().create_bind_group(&wgpu::BindGroupDescriptor {
87            label: Some("Line Scene BG"),
88            layout: &scene_bind_group_layout,
89            entries: &[wgpu::BindGroupEntry {
90                binding: 0,
91                resource: scene_buffer.as_entire_binding(),
92            }],
93        });
94
95        // Reuse the bind group layout for the pipeline (identical structure)
96        let pipeline = RenderPipelineBuilder::new()
97            .with_vertex_shader(LINE_SHADER)
98            .with_fragment_shader(LINE_SHADER)
99            .with_format(format)
100            .with_vertex_layouts(vec![ColorVertex::layout()])
101            .with_bind_group_layouts(vec![scene_bind_group_layout])
102            .with_topology(wgpu::PrimitiveTopology::LineList)
103            .with_label("Line Pipeline")
104            .build(device)
105            .expect("创建 Line 管线失败")
106            .into_pipeline();
107
108        Self {
109            pipeline,
110            scene_buffer,
111            scene_bind_group,
112            cached_vb: None,
113        }
114    }
115
116    /// 渲染线段列表
117    ///
118    /// # 参数
119    ///
120    /// - `device`: GPU 渲染设备
121    /// - `encoder`: 命令编码器
122    /// - `target`: 渲染目标纹理视图
123    /// - `lines`: 线段列表,每项为 `(start, end, color)` 的 RGB 颜色
124    /// - `view_proj`: 视图-投影矩阵
125    pub fn render(
126        &mut self,
127        device: &RenderDevice,
128        encoder: &mut CommandEncoder,
129        target: &TextureView,
130        lines: &[(Vec3, Vec3, Vec3)],
131        view_proj: &Mat4,
132    ) {
133        if lines.is_empty() {
134            return;
135        }
136
137        // Update view_proj uniform
138        let uniform = LineSceneUniform {
139            view_proj: view_proj.to_cols_array_2d(),
140        };
141        device
142            .queue()
143            .write_buffer(&self.scene_buffer, 0, bytemuck::bytes_of(&uniform));
144
145        // Build vertex data
146        let mut vertices = Vec::with_capacity(lines.len() * 2);
147        for (start, end, color) in lines {
148            vertices.push(ColorVertex {
149                position: [start.x, start.y, start.z],
150                color: [color.x, color.y, color.z],
151            });
152            vertices.push(ColorVertex {
153                position: [end.x, end.y, end.z],
154                color: [color.x, color.y, color.z],
155            });
156        }
157
158        // Reuse cached buffer if large enough
159        let data: &[u8] = bytemuck::cast_slice(&vertices);
160        let needed = data.len() as u64;
161        let reuse = self.cached_vb.as_ref().map_or(false, |(_, cap)| *cap >= needed);
162        if !reuse {
163            self.cached_vb = Some((
164                device.device().create_buffer(&wgpu::BufferDescriptor {
165                    label: Some("Line VB (cached)"),
166                    size: needed,
167                    usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
168                    mapped_at_creation: false,
169                }),
170                needed,
171            ));
172        }
173        let vertex_buffer = &self.cached_vb.as_ref().unwrap().0;
174        device.queue().write_buffer(vertex_buffer, 0, data);
175
176        {
177            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
178                label: Some("Line Pass"),
179                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
180                    view: target,
181                    resolve_target: None,
182                    ops: wgpu::Operations {
183                        load: wgpu::LoadOp::Load,
184                        store: wgpu::StoreOp::Store,
185                    },
186                })],
187                depth_stencil_attachment: None,
188                timestamp_writes: None,
189                occlusion_query_set: None,
190            });
191
192            render_pass.set_pipeline(&self.pipeline);
193            render_pass.set_bind_group(0, &self.scene_bind_group, &[]);
194            render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
195            render_pass.draw(0..vertices.len() as u32, 0..1);
196        }
197    }
198}