veloren_voxygen/render/pipelines/
sprite.rs

1use super::{
2    super::{AaMode, GlobalsLayouts, Mesh, TerrainLayout, Vertex as VertexTrait, buffer::Buffer},
3    GlobalModel, Texture, lod_terrain,
4};
5use bytemuck::{Pod, Zeroable};
6use std::mem;
7use vek::*;
8
9pub const VERT_PAGE_SIZE: u32 = 256;
10
11#[repr(C)]
12#[derive(Copy, Clone, Debug, Zeroable, Pod)]
13pub struct Vertex {
14    pos_norm: u32,
15    // Because we try to restrict terrain sprite data to a 128×128 block
16    // we need an offset into the texture atlas.
17    atlas_pos: u32,
18    /* ____BBBBBBBBGGGGGGGGRRRRRRRR
19     * col: u32 = "v_col",
20     * .....NNN
21     * A = AO
22     * N = Normal
23     *norm: u32, */
24}
25
26// TODO: fix?
27/*impl fmt::Display for Vertex {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        f.debug_struct("Vertex")
30            .field("pos_norm", &Vec3::<f32>::from(self.pos))
31            .field(
32                "atlas_pos",
33                &Vec2::new(self.atlas_pos & 0xFFFF, (self.atlas_pos >> 16) & 0xFFFF),
34            )
35            .finish()
36    }
37}*/
38
39impl Vertex {
40    // NOTE: Limit to 16 (x) × 16 (y) × 32 (z).
41    #[expect(clippy::collapsible_else_if)]
42    pub fn new(atlas_pos: Vec2<u16>, pos: Vec3<f32>, norm: Vec3<f32>) -> Self {
43        const VERT_EXTRA_NEG_XY: i32 = 128;
44        const VERT_EXTRA_NEG_Z: i32 = 128; // NOTE: change if number of bits changes below, also we might not need this if meshing always produces positives values for sprites (I have no idea)
45
46        #[expect(clippy::bool_to_int_with_if)]
47        let norm_bits = if norm.x != 0.0 {
48            if norm.x < 0.0 { 0 } else { 1 }
49        } else if norm.y != 0.0 {
50            if norm.y < 0.0 { 2 } else { 3 }
51        } else {
52            if norm.z < 0.0 { 4 } else { 5 }
53        };
54
55        Self {
56            // pos_norm: ((pos.x as u32) & 0x003F)
57            //     | ((pos.y as u32) & 0x003F) << 6
58            //     | (((pos + EXTRA_NEG_Z).z.max(0.0).min((1 << 16) as f32) as u32) & 0xFFFF) << 12
59            //     | if meta { 1 } else { 0 } << 28
60            //     | (norm_bits & 0x7) << 29,
61            pos_norm: (((pos.x as i32 + VERT_EXTRA_NEG_XY) & 0x00FF) as u32) // NOTE: temp hack, this doesn't need 8 bits
62                | ((((pos.y as i32 + VERT_EXTRA_NEG_XY) & 0x00FF) as u32) << 8)
63                | ((((pos.z as i32 + VERT_EXTRA_NEG_Z).clamp(0, 1 << 12) as u32) & 0x0FFF) << 16)
64                | ((norm_bits & 0x7) << 29),
65            atlas_pos: ((atlas_pos.x as u32) & 0xFFFF) | (((atlas_pos.y as u32) & 0xFFFF) << 16),
66        }
67    }
68}
69
70impl Default for Vertex {
71    fn default() -> Self { Self::new(Vec2::zero(), Vec3::zero(), Vec3::zero()) }
72}
73
74impl VertexTrait for Vertex {
75    const QUADS_INDEX: Option<wgpu::IndexFormat> = Some(wgpu::IndexFormat::Uint16);
76    const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() as wgpu::BufferAddress;
77}
78
79pub struct SpriteVerts(Buffer<Vertex>);
80
81pub(in super::super) fn create_verts_buffer(
82    device: &wgpu::Device,
83    mesh: Mesh<Vertex>,
84) -> SpriteVerts {
85    // TODO: type Buffer by wgpu::BufferUsage
86    SpriteVerts(Buffer::new(
87        device,
88        wgpu::BufferUsages::STORAGE,
89        mesh.vertices(),
90    ))
91}
92
93#[repr(C)]
94#[derive(Copy, Clone, Debug, Zeroable, Pod)]
95pub struct Instance {
96    inst_mat0: [f32; 4],
97    inst_mat1: [f32; 4],
98    inst_mat2: [f32; 4],
99    inst_mat3: [f32; 4],
100    pos_ori_door: u32,
101    inst_vert_page: u32,
102    inst_light: f32,
103    inst_glow: f32,
104    model_wind_sway: f32,
105    model_z_scale: f32,
106}
107
108impl Instance {
109    pub fn new(
110        mat: Mat4<f32>,
111        wind_sway: f32,
112        z_scale: f32,
113        pos: Vec3<i32>,
114        ori_bits: u8,
115        light: f32,
116        glow: f32,
117        vert_page: u32,
118        is_door: bool,
119    ) -> Self {
120        const EXTRA_NEG_Z: i32 = 32768;
121
122        let mat_arr = mat.into_col_arrays();
123        Self {
124            inst_mat0: mat_arr[0],
125            inst_mat1: mat_arr[1],
126            inst_mat2: mat_arr[2],
127            inst_mat3: mat_arr[3],
128            pos_ori_door: ((pos.x as u32) & 0x003F)
129                | (((pos.y as u32) & 0x003F) << 6)
130                | ((((pos.z + EXTRA_NEG_Z).clamp(0, 1 << 16) as u32) & 0xFFFF) << 12)
131                | ((u32::from(ori_bits) & 0x7) << 29)
132                | ((u32::from(is_door) & 1) << 28),
133            inst_vert_page: vert_page,
134            inst_light: light,
135            inst_glow: glow,
136            model_wind_sway: wind_sway,
137            model_z_scale: z_scale,
138        }
139    }
140
141    fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
142        const ATTRIBUTES: [wgpu::VertexAttribute; 10] = wgpu::vertex_attr_array![
143            0 => Float32x4,
144            1 => Float32x4,
145            2 => Float32x4,
146            3 => Float32x4,
147            4 => Uint32,
148            5 => Uint32,
149            6 => Float32,
150            7 => Float32,
151            8 => Float32,
152            9 => Float32,
153        ];
154        wgpu::VertexBufferLayout {
155            array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
156            step_mode: wgpu::VertexStepMode::Instance,
157            attributes: &ATTRIBUTES,
158        }
159    }
160}
161
162impl Default for Instance {
163    fn default() -> Self {
164        Self::new(
165            Mat4::identity(),
166            0.0,
167            0.0,
168            Vec3::zero(),
169            0,
170            1.0,
171            0.0,
172            0,
173            false,
174        )
175    }
176}
177
178// TODO: ColLightsWrapper instead?
179pub struct Locals;
180
181pub struct SpriteGlobalsBindGroup {
182    pub(in super::super) bind_group: wgpu::BindGroup,
183}
184
185pub struct SpriteLayout {
186    pub globals: wgpu::BindGroupLayout,
187}
188
189impl SpriteLayout {
190    pub fn new(device: &wgpu::Device) -> Self {
191        let mut entries = GlobalsLayouts::base_globals_layout();
192        debug_assert_eq!(15, entries.len()); // To remember to adjust the bindings below
193        entries.extend_from_slice(&[
194            // sprite_verts
195            wgpu::BindGroupLayoutEntry {
196                binding: 15,
197                visibility: wgpu::ShaderStages::VERTEX,
198                ty: wgpu::BindingType::Buffer {
199                    ty: wgpu::BufferBindingType::Storage { read_only: true },
200                    has_dynamic_offset: false,
201                    min_binding_size: core::num::NonZeroU64::new(mem::size_of::<Vertex>() as u64),
202                },
203                count: None,
204            },
205        ]);
206
207        Self {
208            globals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
209                label: None,
210                entries: &entries,
211            }),
212        }
213    }
214
215    fn bind_globals_inner(
216        &self,
217        device: &wgpu::Device,
218        global_model: &GlobalModel,
219        lod_data: &lod_terrain::LodData,
220        noise: &Texture,
221        sprite_verts: &SpriteVerts,
222    ) -> wgpu::BindGroup {
223        let mut entries = GlobalsLayouts::bind_base_globals(global_model, lod_data, noise);
224
225        entries.extend_from_slice(&[
226            // sprite_verts
227            wgpu::BindGroupEntry {
228                binding: 15,
229                resource: sprite_verts.0.buf.as_entire_binding(),
230            },
231        ]);
232
233        device.create_bind_group(&wgpu::BindGroupDescriptor {
234            label: None,
235            layout: &self.globals,
236            entries: &entries,
237        })
238    }
239
240    pub fn bind_globals(
241        &self,
242        device: &wgpu::Device,
243        global_model: &GlobalModel,
244        lod_data: &lod_terrain::LodData,
245        noise: &Texture,
246        sprite_verts: &SpriteVerts,
247    ) -> SpriteGlobalsBindGroup {
248        let bind_group =
249            self.bind_globals_inner(device, global_model, lod_data, noise, sprite_verts);
250
251        SpriteGlobalsBindGroup { bind_group }
252    }
253}
254
255pub struct SpritePipeline {
256    pub pipeline: wgpu::RenderPipeline,
257}
258
259impl SpritePipeline {
260    pub fn new(
261        device: &wgpu::Device,
262        vs_module: &wgpu::ShaderModule,
263        fs_module: &wgpu::ShaderModule,
264        global_layout: &GlobalsLayouts,
265        layout: &SpriteLayout,
266        terrain_layout: &TerrainLayout,
267        aa_mode: AaMode,
268        format: wgpu::TextureFormat,
269    ) -> Self {
270        common_base::span!(_guard, "SpritePipeline::new");
271        let render_pipeline_layout =
272            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
273                label: Some("Sprite pipeline layout"),
274                push_constant_ranges: &[],
275                bind_group_layouts: &[
276                    &layout.globals,
277                    &global_layout.shadow_textures,
278                    // Note: mergable with globals
279                    global_layout.figure_sprite_atlas_layout.layout(),
280                    &terrain_layout.locals,
281                ],
282            });
283
284        let samples = aa_mode.samples();
285
286        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
287            label: Some("Sprite pipeline"),
288            layout: Some(&render_pipeline_layout),
289            vertex: wgpu::VertexState {
290                module: vs_module,
291                entry_point: "main",
292                buffers: &[Instance::desc()],
293            },
294            primitive: wgpu::PrimitiveState {
295                topology: wgpu::PrimitiveTopology::TriangleList,
296                strip_index_format: None,
297                front_face: wgpu::FrontFace::Ccw,
298                cull_mode: Some(wgpu::Face::Back),
299                unclipped_depth: false,
300                polygon_mode: wgpu::PolygonMode::Fill,
301                conservative: false,
302            },
303            depth_stencil: Some(wgpu::DepthStencilState {
304                format: wgpu::TextureFormat::Depth32Float,
305                depth_write_enabled: true,
306                depth_compare: wgpu::CompareFunction::GreaterEqual,
307                stencil: wgpu::StencilState {
308                    front: wgpu::StencilFaceState::IGNORE,
309                    back: wgpu::StencilFaceState::IGNORE,
310                    read_mask: !0,
311                    write_mask: 0,
312                },
313                bias: wgpu::DepthBiasState {
314                    constant: 0,
315                    slope_scale: 0.0,
316                    clamp: 0.0,
317                },
318            }),
319            multisample: wgpu::MultisampleState {
320                count: samples,
321                mask: !0,
322                alpha_to_coverage_enabled: false,
323            },
324            fragment: Some(wgpu::FragmentState {
325                module: fs_module,
326                entry_point: "main",
327                targets: &[
328                    Some(wgpu::ColorTargetState {
329                        format,
330                        // TODO: can we remove sprite transparency?
331                        blend: Some(wgpu::BlendState {
332                            color: wgpu::BlendComponent {
333                                src_factor: wgpu::BlendFactor::SrcAlpha,
334                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
335                                operation: wgpu::BlendOperation::Add,
336                            },
337                            alpha: wgpu::BlendComponent {
338                                src_factor: wgpu::BlendFactor::One,
339                                dst_factor: wgpu::BlendFactor::One,
340                                operation: wgpu::BlendOperation::Add,
341                            },
342                        }),
343                        write_mask: wgpu::ColorWrites::ALL,
344                    }),
345                    Some(wgpu::ColorTargetState {
346                        format: wgpu::TextureFormat::Rgba8Uint,
347                        blend: None,
348                        write_mask: wgpu::ColorWrites::ALL,
349                    }),
350                ],
351            }),
352            multiview: None,
353        });
354
355        Self {
356            pipeline: render_pipeline,
357        }
358    }
359}