Skip to main content

veloren_voxygen/render/pipelines/
terrain.rs

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