Skip to main content

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    pub fn new(atlas_pos: Vec2<u16>, pos: Vec3<f32>, norm: Vec3<f32>) -> Self {
42        const VERT_EXTRA_NEG_XY: i32 = 128;
43        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)
44
45        #[expect(clippy::bool_to_int_with_if)]
46        let norm_bits = if norm.x != 0.0 {
47            if norm.x < 0.0 { 0 } else { 1 }
48        } else if norm.y != 0.0 {
49            if norm.y < 0.0 { 2 } else { 3 }
50        } else {
51            if norm.z < 0.0 { 4 } else { 5 }
52        };
53
54        Self {
55            // pos_norm: ((pos.x as u32) & 0x003F)
56            //     | ((pos.y as u32) & 0x003F) << 6
57            //     | (((pos + EXTRA_NEG_Z).z.max(0.0).min((1 << 16) as f32) as u32) & 0xFFFF) << 12
58            //     | if meta { 1 } else { 0 } << 28
59            //     | (norm_bits & 0x7) << 29,
60            pos_norm: (((pos.x as i32 + VERT_EXTRA_NEG_XY) & 0x00FF) as u32) // NOTE: temp hack, this doesn't need 8 bits
61                | ((((pos.y as i32 + VERT_EXTRA_NEG_XY) & 0x00FF) as u32) << 8)
62                | ((((pos.z as i32 + VERT_EXTRA_NEG_Z).clamp(0, 1 << 12) as u32) & 0x0FFF) << 16)
63                | ((norm_bits & 0x7) << 29),
64            atlas_pos: ((atlas_pos.x as u32) & 0xFFFF) | (((atlas_pos.y as u32) & 0xFFFF) << 16),
65        }
66    }
67}
68
69impl Default for Vertex {
70    fn default() -> Self { Self::new(Vec2::zero(), Vec3::zero(), Vec3::zero()) }
71}
72
73impl VertexTrait for Vertex {
74    const QUADS_INDEX: Option<wgpu::IndexFormat> = Some(wgpu::IndexFormat::Uint16);
75    const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() as wgpu::BufferAddress;
76}
77
78pub struct SpriteVerts(Buffer<Vertex>);
79
80pub(in super::super) fn create_verts_buffer(
81    device: &wgpu::Device,
82    mesh: Mesh<Vertex>,
83) -> SpriteVerts {
84    // TODO: type Buffer by wgpu::BufferUsage
85    SpriteVerts(Buffer::new(
86        device,
87        wgpu::BufferUsages::STORAGE,
88        mesh.vertices(),
89    ))
90}
91
92#[repr(C)]
93#[derive(Copy, Clone, Debug, Zeroable, Pod)]
94pub struct Instance {
95    inst_mat0: [f32; 4],
96    inst_mat1: [f32; 4],
97    inst_mat2: [f32; 4],
98    inst_mat3: [f32; 4],
99    inst_glow: [f32; 4],
100    pos_meta: u32,
101    inst_vert_page: u32,
102    inst_light: f32,
103    model_wind_sway: f32,
104    model_z_scale: f32,
105}
106
107impl Instance {
108    pub fn new(
109        mat: Mat4<f32>,
110        wind_sway: f32,
111        z_scale: f32,
112        pos: Vec3<i32>,
113        light: f32,
114        glow: (Vec3<f32>, f32),
115        vert_page: u32,
116        is_door: bool,
117        is_mirrored: bool,
118    ) -> Self {
119        const EXTRA_NEG_Z: i32 = 32768;
120
121        let mat_arr = mat.into_col_arrays();
122        Self {
123            inst_mat0: mat_arr[0],
124            inst_mat1: mat_arr[1],
125            inst_mat2: mat_arr[2],
126            inst_mat3: mat_arr[3],
127            inst_glow: [glow.0.x, glow.0.y, glow.0.z, glow.1],
128            pos_meta: ((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(is_door) & 1) << 28)
132                | ((u32::from(is_mirrored) & 1) << 29),
133            inst_vert_page: vert_page,
134            inst_light: light,
135            model_wind_sway: wind_sway,
136            model_z_scale: z_scale,
137        }
138    }
139
140    fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
141        const ATTRIBUTES: [wgpu::VertexAttribute; 10] = wgpu::vertex_attr_array![
142            0 => Float32x4,
143            1 => Float32x4,
144            2 => Float32x4,
145            3 => Float32x4,
146            4 => Float32x4,
147            5 => Uint32,
148            6 => Uint32,
149            7 => Float32,
150            8 => Float32,
151            9 => Float32,
152        ];
153        wgpu::VertexBufferLayout {
154            array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
155            step_mode: wgpu::VertexStepMode::Instance,
156            attributes: &ATTRIBUTES,
157        }
158    }
159}
160
161impl Default for Instance {
162    fn default() -> Self {
163        Self::new(
164            Mat4::identity(),
165            0.0,
166            0.0,
167            Vec3::zero(),
168            1.0,
169            (Vec3::zero(), 0.0),
170            0,
171            false,
172            false,
173        )
174    }
175}
176
177// TODO: ColLightsWrapper instead?
178pub struct Locals;
179
180pub struct SpriteGlobalsBindGroup {
181    pub(in super::super) bind_group: wgpu::BindGroup,
182}
183
184pub struct SpriteLayout {
185    pub globals: wgpu::BindGroupLayout,
186}
187
188impl SpriteLayout {
189    pub fn new(device: &wgpu::Device) -> Self {
190        let mut entries = GlobalsLayouts::base_globals_layout();
191        debug_assert_eq!(15, entries.len()); // To remember to adjust the bindings below
192        entries.extend_from_slice(&[
193            // sprite_verts
194            wgpu::BindGroupLayoutEntry {
195                binding: 15,
196                visibility: wgpu::ShaderStages::VERTEX,
197                ty: wgpu::BindingType::Buffer {
198                    ty: wgpu::BufferBindingType::Storage { read_only: true },
199                    has_dynamic_offset: false,
200                    min_binding_size: core::num::NonZeroU64::new(mem::size_of::<Vertex>() as u64),
201                },
202                count: None,
203            },
204        ]);
205
206        Self {
207            globals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
208                label: None,
209                entries: &entries,
210            }),
211        }
212    }
213
214    fn bind_globals_inner(
215        &self,
216        device: &wgpu::Device,
217        global_model: &GlobalModel,
218        lod_data: &lod_terrain::LodData,
219        noise: &Texture,
220        sprite_verts: &SpriteVerts,
221    ) -> wgpu::BindGroup {
222        let mut entries = GlobalsLayouts::bind_base_globals(global_model, lod_data, noise);
223
224        entries.extend_from_slice(&[
225            // sprite_verts
226            wgpu::BindGroupEntry {
227                binding: 15,
228                resource: sprite_verts.0.buf.as_entire_binding(),
229            },
230        ]);
231
232        device.create_bind_group(&wgpu::BindGroupDescriptor {
233            label: None,
234            layout: &self.globals,
235            entries: &entries,
236        })
237    }
238
239    pub fn bind_globals(
240        &self,
241        device: &wgpu::Device,
242        global_model: &GlobalModel,
243        lod_data: &lod_terrain::LodData,
244        noise: &Texture,
245        sprite_verts: &SpriteVerts,
246    ) -> SpriteGlobalsBindGroup {
247        let bind_group =
248            self.bind_globals_inner(device, global_model, lod_data, noise, sprite_verts);
249
250        SpriteGlobalsBindGroup { bind_group }
251    }
252}
253
254pub struct SpritePipeline {
255    pub pipeline: wgpu::RenderPipeline,
256}
257
258impl SpritePipeline {
259    pub fn new(
260        device: &wgpu::Device,
261        vs_module: &wgpu::ShaderModule,
262        fs_module: &wgpu::ShaderModule,
263        global_layout: &GlobalsLayouts,
264        layout: &SpriteLayout,
265        terrain_layout: &TerrainLayout,
266        aa_mode: AaMode,
267        format: wgpu::TextureFormat,
268    ) -> Self {
269        common_base::span!(_guard, "SpritePipeline::new");
270        let render_pipeline_layout =
271            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
272                label: Some("Sprite pipeline layout"),
273                push_constant_ranges: &[],
274                bind_group_layouts: &[
275                    &layout.globals,
276                    &global_layout.shadow_textures,
277                    // Note: mergable with globals
278                    global_layout.figure_sprite_atlas_layout.layout(),
279                    &terrain_layout.locals,
280                ],
281            });
282
283        let samples = aa_mode.samples();
284
285        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
286            label: Some("Sprite pipeline"),
287            layout: Some(&render_pipeline_layout),
288            vertex: wgpu::VertexState {
289                module: vs_module,
290                entry_point: Some("main"),
291                buffers: &[Instance::desc()],
292                compilation_options: Default::default(),
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: Some("main"),
327                targets: &[
328                    Some(wgpu::ColorTargetState {
329                        format,
330                        // TODO: can we remove sprite transparency?
331                        blend: None, /*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                compilation_options: Default::default(),
352            }),
353            multiview: None,
354            cache: None,
355        });
356
357        Self {
358            pipeline: render_pipeline,
359        }
360    }
361}