veloren_voxygen/render/pipelines/
terrain.rs

1use super::{
2    super::{AaMode, Bound, Consts, GlobalsLayouts, Vertex as VertexTrait},
3    AtlasData,
4};
5use bytemuck::{Pod, Zeroable};
6use std::mem;
7use vek::*;
8
9#[repr(C)]
10#[derive(Copy, Clone, Debug, Zeroable, Pod)]
11pub struct Vertex {
12    pos_norm: u32,
13    atlas_pos: u32,
14}
15
16impl Vertex {
17    /// NOTE: meta is true when the terrain vertex is touching water.
18    pub fn new(atlas_pos: Vec2<u16>, pos: Vec3<f32>, norm: Vec3<f32>, meta: bool) -> Self {
19        const EXTRA_NEG_Z: f32 = 32768.0;
20
21        #[expect(clippy::bool_to_int_with_if)]
22        let norm_bits = if norm.x != 0.0 {
23            if norm.x < 0.0 { 0 } else { 1 }
24        } else if norm.y != 0.0 {
25            if norm.y < 0.0 { 2 } else { 3 }
26        } else if norm.z < 0.0 {
27            4
28        } else {
29            5
30        };
31        Self {
32            pos_norm: (((pos.x as u32) & 0x003F) << 0)
33                | (((pos.y as u32) & 0x003F) << 6)
34                | ((((pos + EXTRA_NEG_Z).z.clamp(0.0, (1 << 16) as f32) as u32) & 0xFFFF) << 12)
35                | (u32::from(meta) << 28)
36                | ((norm_bits & 0x7) << 29),
37            atlas_pos: (((atlas_pos.x as u32) & 0xFFFF) << 0)
38                | (((atlas_pos.y as u32) & 0xFFFF) << 16),
39        }
40    }
41
42    pub fn new_figure(atlas_pos: Vec2<u16>, pos: Vec3<f32>, norm: Vec3<f32>, bone_idx: u8) -> Self {
43        let norm_bits = u32::from(norm.x.min(norm.y).min(norm.z) >= 0.0);
44        let axis_bits = if norm.x != 0.0 {
45            0
46        } else if norm.y != 0.0 {
47            1
48        } else {
49            2
50        };
51        Self {
52            pos_norm: pos
53                .map2(Vec3::new(0, 9, 18), |e, shift| {
54                    (((e * 2.0 + 256.0) as u32) & 0x1FF) << shift
55                })
56                .reduce_bitor()
57                | (((bone_idx & 0xF) as u32) << 27)
58                | (norm_bits << 31),
59            atlas_pos: (((atlas_pos.x as u32) & 0x7FFF) << 2)
60                | (((atlas_pos.y as u32) & 0x7FFF) << 17)
61                | axis_bits & 3,
62        }
63    }
64
65    pub fn make_col_light(
66        // 0 to 31
67        light: u8,
68        // 0 to 31
69        glow: u8,
70        col: Rgb<u8>,
71        ao: bool,
72    ) -> [u8; 4] {
73        //[col.r, col.g, col.b, light]
74        // It would be nice for this to be cleaner, but we want to squeeze 5 fields into
75        // 4. We can do this because both `light` and `glow` go from 0 to 31,
76        // meaning that they can both fit into 5 bits. If we steal a bit from
77        // red and blue each (not green, human eyes are more sensitive to
78        // changes in green) then we get just enough to expand the nibbles of
79        // the alpha field enough to fit both `light` and `glow`.
80        //
81        // However, we now have a problem. In the shader code with use hardware
82        // filtering to get at the `light` and `glow` attributes (but not
83        // colour, that remains constant across a block). How do we resolve this
84        // if we're twiddling bits? The answer is to very carefully manipulate
85        // the bit pattern such that the fields we want to filter (`light` and
86        // `glow`) always sit as the higher bits of the fields. Then, we can do
87        // some modulation magic to extract them from the filtering unharmed and use
88        // unfiltered texture access (i.e: `texelFetch`) to access the colours, plus a
89        // little bit-fiddling.
90        //
91        // TODO: This isn't currently working (no idea why). See `srgb.glsl` for current
92        // impl that intead does manual bit-twiddling and filtering.
93        [
94            (light.min(31) << 3) | ((col.r >> 1) & 0b111),
95            (glow.min(31) << 3) | ((col.b >> 1) & 0b111),
96            (col.r & 0b11110000) | (col.b >> 4),
97            (col.g & 0xFE) | ao as u8,
98        ]
99    }
100
101    pub fn make_col_light_figure(
102        // 0 to 31
103        light: u8,
104        glowy: bool,
105        shiny: bool,
106        col: Rgb<u8>,
107    ) -> [u8; 4] {
108        let attr = 0 | ((glowy as u8) << 0) | ((shiny as u8) << 1);
109        [
110            (light.min(31) << 3) | ((col.r >> 1) & 0b111),
111            (attr.min(31) << 3) | ((col.b >> 1) & 0b111),
112            (col.r & 0b11110000) | (col.b >> 4),
113            col.g, // Green is lucky, it remains unscathed
114        ]
115    }
116
117    /// Set the bone_idx for an existing figure vertex.
118    pub fn set_bone_idx(&mut self, bone_idx: u8) {
119        self.pos_norm = (self.pos_norm & !(0xF << 27)) | ((bone_idx as u32 & 0xF) << 27);
120    }
121
122    pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
123        const ATTRIBUTES: [wgpu::VertexAttribute; 2] =
124            wgpu::vertex_attr_array![0 => Uint32,1 => Uint32];
125        wgpu::VertexBufferLayout {
126            array_stride: Self::STRIDE,
127            step_mode: wgpu::VertexStepMode::Vertex,
128            attributes: &ATTRIBUTES,
129        }
130    }
131}
132
133impl VertexTrait for Vertex {
134    // Note: I think it's u32 due to figures??
135    // potentiall optimize by splitting
136    const QUADS_INDEX: Option<wgpu::IndexFormat> = Some(wgpu::IndexFormat::Uint32);
137    const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() as wgpu::BufferAddress;
138}
139
140#[repr(C)]
141#[derive(Copy, Clone, Debug, Zeroable, Pod)]
142// TODO: new function and private fields??
143pub struct Locals {
144    model_mat: [f32; 16],
145    atlas_offs: [i32; 4],
146    load_time: f32,
147    _dummy: [f32; 3],
148}
149
150impl Locals {
151    pub fn new(
152        model_offs: Vec3<f32>,
153        ori: Quaternion<f32>,
154        atlas_offs: Vec2<u32>,
155        load_time: f32,
156    ) -> Self {
157        let mat = Mat4::from(ori).translated_3d(model_offs);
158        Self {
159            model_mat: mat.into_col_array(),
160            load_time,
161            atlas_offs: Vec4::new(atlas_offs.x as i32, atlas_offs.y as i32, 0, 0).into_array(),
162            _dummy: [0.0; 3],
163        }
164    }
165}
166
167impl Default for Locals {
168    fn default() -> Self {
169        Self {
170            model_mat: Mat4::identity().into_col_array(),
171            load_time: 0.0,
172            atlas_offs: [0; 4],
173            _dummy: [0.0; 3],
174        }
175    }
176}
177
178pub type BoundLocals = Bound<Consts<Locals>>;
179
180pub struct TerrainLayout {
181    pub locals: wgpu::BindGroupLayout,
182}
183
184impl TerrainLayout {
185    pub fn new(device: &wgpu::Device) -> Self {
186        Self {
187            locals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
188                label: None,
189                entries: &[
190                    // locals
191                    wgpu::BindGroupLayoutEntry {
192                        binding: 0,
193                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
194                        ty: wgpu::BindingType::Buffer {
195                            ty: wgpu::BufferBindingType::Uniform,
196                            has_dynamic_offset: false,
197                            min_binding_size: None,
198                        },
199                        count: None,
200                    },
201                ],
202            }),
203        }
204    }
205
206    pub fn bind_locals(&self, device: &wgpu::Device, locals: Consts<Locals>) -> BoundLocals {
207        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
208            label: None,
209            layout: &self.locals,
210            entries: &[wgpu::BindGroupEntry {
211                binding: 0,
212                resource: locals.buf().as_entire_binding(),
213            }],
214        });
215
216        BoundLocals {
217            bind_group,
218            with: locals,
219        }
220    }
221}
222
223pub struct TerrainPipeline {
224    pub pipeline: wgpu::RenderPipeline,
225}
226
227impl TerrainPipeline {
228    pub fn new(
229        device: &wgpu::Device,
230        vs_module: &wgpu::ShaderModule,
231        fs_module: &wgpu::ShaderModule,
232        global_layout: &GlobalsLayouts,
233        layout: &TerrainLayout,
234        aa_mode: AaMode,
235        format: wgpu::TextureFormat,
236    ) -> Self {
237        common_base::span!(_guard, "TerrainPipeline::new");
238        let render_pipeline_layout =
239            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
240                label: Some("Terrain pipeline layout"),
241                push_constant_ranges: &[],
242                bind_group_layouts: &[
243                    &global_layout.globals,
244                    &global_layout.shadow_textures,
245                    global_layout.terrain_atlas_layout.layout(),
246                    &layout.locals,
247                ],
248            });
249
250        let samples = aa_mode.samples();
251
252        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
253            label: Some("Terrain pipeline"),
254            layout: Some(&render_pipeline_layout),
255            vertex: wgpu::VertexState {
256                module: vs_module,
257                entry_point: "main",
258                buffers: &[Vertex::desc()],
259            },
260            primitive: wgpu::PrimitiveState {
261                topology: wgpu::PrimitiveTopology::TriangleList,
262                strip_index_format: None,
263                front_face: wgpu::FrontFace::Ccw,
264                cull_mode: Some(wgpu::Face::Back),
265                unclipped_depth: false,
266                polygon_mode: wgpu::PolygonMode::Fill,
267                conservative: false,
268            },
269            depth_stencil: Some(wgpu::DepthStencilState {
270                format: wgpu::TextureFormat::Depth32Float,
271                depth_write_enabled: true,
272                depth_compare: wgpu::CompareFunction::GreaterEqual,
273                stencil: wgpu::StencilState {
274                    front: wgpu::StencilFaceState::IGNORE,
275                    back: wgpu::StencilFaceState::IGNORE,
276                    read_mask: !0,
277                    write_mask: 0,
278                },
279                bias: wgpu::DepthBiasState {
280                    constant: 0,
281                    slope_scale: 0.0,
282                    clamp: 0.0,
283                },
284            }),
285            multisample: wgpu::MultisampleState {
286                count: samples,
287                mask: !0,
288                alpha_to_coverage_enabled: false,
289            },
290            fragment: Some(wgpu::FragmentState {
291                module: fs_module,
292                entry_point: "main",
293                targets: &[
294                    Some(wgpu::ColorTargetState {
295                        format,
296                        blend: None,
297                        write_mask: wgpu::ColorWrites::ALL,
298                    }),
299                    Some(wgpu::ColorTargetState {
300                        format: wgpu::TextureFormat::Rgba8Uint,
301                        blend: None,
302                        write_mask: wgpu::ColorWrites::ALL,
303                    }),
304                ],
305            }),
306            multiview: None,
307        });
308
309        Self {
310            pipeline: render_pipeline,
311        }
312    }
313}
314
315/// Represents texture that can be converted into texture atlases for terrain.
316pub struct TerrainAtlasData {
317    pub col_lights: Vec<[u8; 4]>,
318    pub kinds: Vec<u8>,
319}
320
321impl AtlasData for TerrainAtlasData {
322    type SliceMut<'a> =
323        std::iter::Zip<std::slice::IterMut<'a, [u8; 4]>, std::slice::IterMut<'a, u8>>;
324
325    const TEXTURES: usize = 2;
326
327    fn blank_with_size(sz: Vec2<u16>) -> Self {
328        let col_lights =
329            vec![Vertex::make_col_light(254, 0, Rgb::broadcast(254), true); sz.as_().product()];
330        let kinds = vec![0; sz.as_().product()];
331        Self { col_lights, kinds }
332    }
333
334    fn as_texture_data(&self) -> [(wgpu::TextureFormat, &[u8]); Self::TEXTURES] {
335        [
336            (
337                wgpu::TextureFormat::Rgba8Unorm,
338                bytemuck::cast_slice(&self.col_lights),
339            ),
340            (wgpu::TextureFormat::R8Uint, &self.kinds),
341        ]
342    }
343
344    fn layout() -> Vec<wgpu::BindGroupLayoutEntry> {
345        vec![
346            // col lights
347            wgpu::BindGroupLayoutEntry {
348                binding: 0,
349                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
350                ty: wgpu::BindingType::Texture {
351                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
352                    view_dimension: wgpu::TextureViewDimension::D2,
353                    multisampled: false,
354                },
355                count: None,
356            },
357            wgpu::BindGroupLayoutEntry {
358                binding: 1,
359                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
360                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
361                count: None,
362            },
363            // kind
364            wgpu::BindGroupLayoutEntry {
365                binding: 2,
366                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
367                ty: wgpu::BindingType::Texture {
368                    sample_type: wgpu::TextureSampleType::Uint,
369                    view_dimension: wgpu::TextureViewDimension::D2,
370                    multisampled: false,
371                },
372                count: None,
373            },
374            wgpu::BindGroupLayoutEntry {
375                binding: 3,
376                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
377                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
378                count: None,
379            },
380        ]
381    }
382
383    fn slice_mut(&mut self, range: std::ops::Range<usize>) -> Self::SliceMut<'_> {
384        self.col_lights[range.clone()]
385            .iter_mut()
386            .zip(self.kinds[range].iter_mut())
387    }
388}