Skip to main content

veloren_voxygen/render/pipelines/
sprite.rs

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