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: Some("main"),
258                buffers: &[Vertex::desc()],
259                compilation_options: Default::default(),
260            },
261            primitive: wgpu::PrimitiveState {
262                topology: wgpu::PrimitiveTopology::TriangleList,
263                strip_index_format: None,
264                front_face: wgpu::FrontFace::Ccw,
265                cull_mode: Some(wgpu::Face::Back),
266                unclipped_depth: false,
267                polygon_mode: wgpu::PolygonMode::Fill,
268                conservative: false,
269            },
270            depth_stencil: Some(wgpu::DepthStencilState {
271                format: wgpu::TextureFormat::Depth32Float,
272                depth_write_enabled: true,
273                depth_compare: wgpu::CompareFunction::GreaterEqual,
274                stencil: wgpu::StencilState {
275                    front: wgpu::StencilFaceState::IGNORE,
276                    back: wgpu::StencilFaceState::IGNORE,
277                    read_mask: !0,
278                    write_mask: 0,
279                },
280                bias: wgpu::DepthBiasState {
281                    constant: 0,
282                    slope_scale: 0.0,
283                    clamp: 0.0,
284                },
285            }),
286            multisample: wgpu::MultisampleState {
287                count: samples,
288                mask: !0,
289                alpha_to_coverage_enabled: false,
290            },
291            fragment: Some(wgpu::FragmentState {
292                module: fs_module,
293                entry_point: Some("main"),
294                targets: &[
295                    Some(wgpu::ColorTargetState {
296                        format,
297                        blend: None,
298                        write_mask: wgpu::ColorWrites::ALL,
299                    }),
300                    Some(wgpu::ColorTargetState {
301                        format: wgpu::TextureFormat::Rgba8Uint,
302                        blend: None,
303                        write_mask: wgpu::ColorWrites::ALL,
304                    }),
305                ],
306                compilation_options: Default::default(),
307            }),
308            multiview: None,
309            cache: None,
310        });
311
312        Self {
313            pipeline: render_pipeline,
314        }
315    }
316}
317
318/// Represents texture that can be converted into texture atlases for terrain.
319pub struct TerrainAtlasData {
320    pub col_lights: Vec<[u8; 4]>,
321    pub kinds: Vec<u8>,
322}
323
324impl AtlasData for TerrainAtlasData {
325    type SliceMut<'a> =
326        std::iter::Zip<std::slice::IterMut<'a, [u8; 4]>, std::slice::IterMut<'a, u8>>;
327
328    const TEXTURES: usize = 2;
329
330    fn blank_with_size(sz: Vec2<u16>) -> Self {
331        let col_lights =
332            vec![Vertex::make_col_light(254, 0, Rgb::broadcast(254), true); sz.as_().product()];
333        let kinds = vec![0; sz.as_().product()];
334        Self { col_lights, kinds }
335    }
336
337    fn as_texture_data(&self) -> [(wgpu::TextureFormat, &[u8]); Self::TEXTURES] {
338        [
339            (
340                wgpu::TextureFormat::Rgba8Unorm,
341                bytemuck::cast_slice(&self.col_lights),
342            ),
343            (wgpu::TextureFormat::R8Uint, &self.kinds),
344        ]
345    }
346
347    fn layout() -> Vec<wgpu::BindGroupLayoutEntry> {
348        vec![
349            // col lights
350            wgpu::BindGroupLayoutEntry {
351                binding: 0,
352                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
353                ty: wgpu::BindingType::Texture {
354                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
355                    view_dimension: wgpu::TextureViewDimension::D2,
356                    multisampled: false,
357                },
358                count: None,
359            },
360            wgpu::BindGroupLayoutEntry {
361                binding: 1,
362                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
363                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
364                count: None,
365            },
366            // kind
367            wgpu::BindGroupLayoutEntry {
368                binding: 2,
369                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
370                ty: wgpu::BindingType::Texture {
371                    sample_type: wgpu::TextureSampleType::Uint,
372                    view_dimension: wgpu::TextureViewDimension::D2,
373                    multisampled: false,
374                },
375                count: None,
376            },
377            wgpu::BindGroupLayoutEntry {
378                binding: 3,
379                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
380                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
381                count: None,
382            },
383        ]
384    }
385
386    fn slice_mut(&mut self, range: std::ops::Range<usize>) -> Self::SliceMut<'_> {
387        self.col_lights[range.clone()]
388            .iter_mut()
389            .zip(self.kinds[range].iter_mut())
390    }
391}