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 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 light: u8,
68 glow: u8,
70 col: Rgb<u8>,
71 ao: bool,
72 ) -> [u8; 4] {
73 [
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 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, ]
115 }
116
117 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 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)]
142pub 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 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
315pub 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 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 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}