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 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 light: u8,
71 glow: u8,
73 col: Rgb<u8>,
74 ao: bool,
75 ) -> [u8; 4] {
76 [
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 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, ]
117 }
118
119 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 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)]
144pub 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 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
327pub 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 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 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}