1use super::{
2 super::{AaMode, GlobalsLayouts, Mesh, TerrainLayout, Vertex as VertexTrait, buffer::Buffer},
3 GlobalModel, Texture, lod_terrain,
4};
5use bytemuck::{Pod, Zeroable};
6use std::mem;
7use vek::*;
8
9pub const VERT_PAGE_SIZE: u32 = 256;
10
11#[repr(C)]
12#[derive(Copy, Clone, Debug, Zeroable, Pod)]
13pub struct Vertex {
14 pos_norm: u32,
15 atlas_pos: u32,
18 }
25
26impl Vertex {
40 #[expect(clippy::collapsible_else_if)]
42 pub fn new(atlas_pos: Vec2<u16>, pos: Vec3<f32>, norm: Vec3<f32>) -> Self {
43 const VERT_EXTRA_NEG_XY: i32 = 128;
44 const VERT_EXTRA_NEG_Z: i32 = 128; #[expect(clippy::bool_to_int_with_if)]
47 let norm_bits = if norm.x != 0.0 {
48 if norm.x < 0.0 { 0 } else { 1 }
49 } else if norm.y != 0.0 {
50 if norm.y < 0.0 { 2 } else { 3 }
51 } else {
52 if norm.z < 0.0 { 4 } else { 5 }
53 };
54
55 Self {
56 pos_norm: (((pos.x as i32 + VERT_EXTRA_NEG_XY) & 0x00FF) as u32) | ((((pos.y as i32 + VERT_EXTRA_NEG_XY) & 0x00FF) as u32) << 8)
63 | ((((pos.z as i32 + VERT_EXTRA_NEG_Z).clamp(0, 1 << 12) as u32) & 0x0FFF) << 16)
64 | ((norm_bits & 0x7) << 29),
65 atlas_pos: ((atlas_pos.x as u32) & 0xFFFF) | (((atlas_pos.y as u32) & 0xFFFF) << 16),
66 }
67 }
68}
69
70impl Default for Vertex {
71 fn default() -> Self { Self::new(Vec2::zero(), Vec3::zero(), Vec3::zero()) }
72}
73
74impl VertexTrait for Vertex {
75 const QUADS_INDEX: Option<wgpu::IndexFormat> = Some(wgpu::IndexFormat::Uint16);
76 const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() as wgpu::BufferAddress;
77}
78
79pub struct SpriteVerts(Buffer<Vertex>);
80
81pub(in super::super) fn create_verts_buffer(
82 device: &wgpu::Device,
83 mesh: Mesh<Vertex>,
84) -> SpriteVerts {
85 SpriteVerts(Buffer::new(
87 device,
88 wgpu::BufferUsages::STORAGE,
89 mesh.vertices(),
90 ))
91}
92
93#[repr(C)]
94#[derive(Copy, Clone, Debug, Zeroable, Pod)]
95pub struct Instance {
96 inst_mat0: [f32; 4],
97 inst_mat1: [f32; 4],
98 inst_mat2: [f32; 4],
99 inst_mat3: [f32; 4],
100 pos_ori_door: u32,
101 inst_vert_page: u32,
102 inst_light: f32,
103 inst_glow: f32,
104 model_wind_sway: f32,
105 model_z_scale: f32,
106}
107
108impl Instance {
109 pub fn new(
110 mat: Mat4<f32>,
111 wind_sway: f32,
112 z_scale: f32,
113 pos: Vec3<i32>,
114 ori_bits: u8,
115 light: f32,
116 glow: f32,
117 vert_page: u32,
118 is_door: bool,
119 ) -> Self {
120 const EXTRA_NEG_Z: i32 = 32768;
121
122 let mat_arr = mat.into_col_arrays();
123 Self {
124 inst_mat0: mat_arr[0],
125 inst_mat1: mat_arr[1],
126 inst_mat2: mat_arr[2],
127 inst_mat3: mat_arr[3],
128 pos_ori_door: ((pos.x as u32) & 0x003F)
129 | (((pos.y as u32) & 0x003F) << 6)
130 | ((((pos.z + EXTRA_NEG_Z).clamp(0, 1 << 16) as u32) & 0xFFFF) << 12)
131 | ((u32::from(ori_bits) & 0x7) << 29)
132 | ((u32::from(is_door) & 1) << 28),
133 inst_vert_page: vert_page,
134 inst_light: light,
135 inst_glow: glow,
136 model_wind_sway: wind_sway,
137 model_z_scale: z_scale,
138 }
139 }
140
141 fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
142 const ATTRIBUTES: [wgpu::VertexAttribute; 10] = wgpu::vertex_attr_array![
143 0 => Float32x4,
144 1 => Float32x4,
145 2 => Float32x4,
146 3 => Float32x4,
147 4 => Uint32,
148 5 => Uint32,
149 6 => Float32,
150 7 => Float32,
151 8 => Float32,
152 9 => Float32,
153 ];
154 wgpu::VertexBufferLayout {
155 array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
156 step_mode: wgpu::VertexStepMode::Instance,
157 attributes: &ATTRIBUTES,
158 }
159 }
160}
161
162impl Default for Instance {
163 fn default() -> Self {
164 Self::new(
165 Mat4::identity(),
166 0.0,
167 0.0,
168 Vec3::zero(),
169 0,
170 1.0,
171 0.0,
172 0,
173 false,
174 )
175 }
176}
177
178pub struct Locals;
180
181pub struct SpriteGlobalsBindGroup {
182 pub(in super::super) bind_group: wgpu::BindGroup,
183}
184
185pub struct SpriteLayout {
186 pub globals: wgpu::BindGroupLayout,
187}
188
189impl SpriteLayout {
190 pub fn new(device: &wgpu::Device) -> Self {
191 let mut entries = GlobalsLayouts::base_globals_layout();
192 debug_assert_eq!(15, entries.len()); entries.extend_from_slice(&[
194 wgpu::BindGroupLayoutEntry {
196 binding: 15,
197 visibility: wgpu::ShaderStages::VERTEX,
198 ty: wgpu::BindingType::Buffer {
199 ty: wgpu::BufferBindingType::Storage { read_only: true },
200 has_dynamic_offset: false,
201 min_binding_size: core::num::NonZeroU64::new(mem::size_of::<Vertex>() as u64),
202 },
203 count: None,
204 },
205 ]);
206
207 Self {
208 globals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
209 label: None,
210 entries: &entries,
211 }),
212 }
213 }
214
215 fn bind_globals_inner(
216 &self,
217 device: &wgpu::Device,
218 global_model: &GlobalModel,
219 lod_data: &lod_terrain::LodData,
220 noise: &Texture,
221 sprite_verts: &SpriteVerts,
222 ) -> wgpu::BindGroup {
223 let mut entries = GlobalsLayouts::bind_base_globals(global_model, lod_data, noise);
224
225 entries.extend_from_slice(&[
226 wgpu::BindGroupEntry {
228 binding: 15,
229 resource: sprite_verts.0.buf.as_entire_binding(),
230 },
231 ]);
232
233 device.create_bind_group(&wgpu::BindGroupDescriptor {
234 label: None,
235 layout: &self.globals,
236 entries: &entries,
237 })
238 }
239
240 pub fn bind_globals(
241 &self,
242 device: &wgpu::Device,
243 global_model: &GlobalModel,
244 lod_data: &lod_terrain::LodData,
245 noise: &Texture,
246 sprite_verts: &SpriteVerts,
247 ) -> SpriteGlobalsBindGroup {
248 let bind_group =
249 self.bind_globals_inner(device, global_model, lod_data, noise, sprite_verts);
250
251 SpriteGlobalsBindGroup { bind_group }
252 }
253}
254
255pub struct SpritePipeline {
256 pub pipeline: wgpu::RenderPipeline,
257}
258
259impl SpritePipeline {
260 pub fn new(
261 device: &wgpu::Device,
262 vs_module: &wgpu::ShaderModule,
263 fs_module: &wgpu::ShaderModule,
264 global_layout: &GlobalsLayouts,
265 layout: &SpriteLayout,
266 terrain_layout: &TerrainLayout,
267 aa_mode: AaMode,
268 format: wgpu::TextureFormat,
269 ) -> Self {
270 common_base::span!(_guard, "SpritePipeline::new");
271 let render_pipeline_layout =
272 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
273 label: Some("Sprite pipeline layout"),
274 push_constant_ranges: &[],
275 bind_group_layouts: &[
276 &layout.globals,
277 &global_layout.shadow_textures,
278 global_layout.figure_sprite_atlas_layout.layout(),
280 &terrain_layout.locals,
281 ],
282 });
283
284 let samples = aa_mode.samples();
285
286 let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
287 label: Some("Sprite pipeline"),
288 layout: Some(&render_pipeline_layout),
289 vertex: wgpu::VertexState {
290 module: vs_module,
291 entry_point: "main",
292 buffers: &[Instance::desc()],
293 },
294 primitive: wgpu::PrimitiveState {
295 topology: wgpu::PrimitiveTopology::TriangleList,
296 strip_index_format: None,
297 front_face: wgpu::FrontFace::Ccw,
298 cull_mode: Some(wgpu::Face::Back),
299 unclipped_depth: false,
300 polygon_mode: wgpu::PolygonMode::Fill,
301 conservative: false,
302 },
303 depth_stencil: Some(wgpu::DepthStencilState {
304 format: wgpu::TextureFormat::Depth32Float,
305 depth_write_enabled: true,
306 depth_compare: wgpu::CompareFunction::GreaterEqual,
307 stencil: wgpu::StencilState {
308 front: wgpu::StencilFaceState::IGNORE,
309 back: wgpu::StencilFaceState::IGNORE,
310 read_mask: !0,
311 write_mask: 0,
312 },
313 bias: wgpu::DepthBiasState {
314 constant: 0,
315 slope_scale: 0.0,
316 clamp: 0.0,
317 },
318 }),
319 multisample: wgpu::MultisampleState {
320 count: samples,
321 mask: !0,
322 alpha_to_coverage_enabled: false,
323 },
324 fragment: Some(wgpu::FragmentState {
325 module: fs_module,
326 entry_point: "main",
327 targets: &[
328 Some(wgpu::ColorTargetState {
329 format,
330 blend: Some(wgpu::BlendState {
332 color: wgpu::BlendComponent {
333 src_factor: wgpu::BlendFactor::SrcAlpha,
334 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
335 operation: wgpu::BlendOperation::Add,
336 },
337 alpha: wgpu::BlendComponent {
338 src_factor: wgpu::BlendFactor::One,
339 dst_factor: wgpu::BlendFactor::One,
340 operation: wgpu::BlendOperation::Add,
341 },
342 }),
343 write_mask: wgpu::ColorWrites::ALL,
344 }),
345 Some(wgpu::ColorTargetState {
346 format: wgpu::TextureFormat::Rgba8Uint,
347 blend: None,
348 write_mask: wgpu::ColorWrites::ALL,
349 }),
350 ],
351 }),
352 multiview: None,
353 });
354
355 Self {
356 pipeline: render_pipeline,
357 }
358 }
359}