1use std::collections::HashMap;
2
3use scenix_core::{Color, LightId, MaterialId, MeshId, TextureId, ValidationError};
4use scenix_light::{AmbientLight, DirectionalLight, PointLight, SpotLight};
5use scenix_material::{
6 LambertMaterial, Material, NormalMaterial, PbrMaterial, PhysicalMaterial, PipelineKey,
7 ToonMaterial, UnlitMaterial, WireframeMaterial,
8};
9use scenix_math::{Aabb, Mat4, Vec2, Vec3, Vec4};
10use scenix_mesh::Geometry;
11use scenix_texture::{AddressMode, CompareFunction, FilterMode, Sampler, Texture2D, TextureFormat};
12use wgpu::util::DeviceExt;
13
14#[repr(C)]
16#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
17pub struct PackedVertex {
18 pub position: [f32; 3],
20 pub normal: [f32; 3],
22 pub uv: [f32; 2],
24 pub color: [f32; 4],
26 pub tangent: [f32; 4],
28}
29
30impl PackedVertex {
31 pub const fn layout() -> wgpu::VertexBufferLayout<'static> {
33 const ATTRIBUTES: [wgpu::VertexAttribute; 5] = wgpu::vertex_attr_array![
34 0 => Float32x3,
35 1 => Float32x3,
36 2 => Float32x2,
37 3 => Float32x4,
38 4 => Float32x4
39 ];
40 wgpu::VertexBufferLayout {
41 array_stride: core::mem::size_of::<PackedVertex>() as u64,
42 step_mode: wgpu::VertexStepMode::Vertex,
43 attributes: &ATTRIBUTES,
44 }
45 }
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51pub enum GpuIndexFormat {
52 Uint16,
54 Uint32,
56}
57
58impl GpuIndexFormat {
59 #[inline]
61 pub const fn to_wgpu(self) -> wgpu::IndexFormat {
62 match self {
63 Self::Uint16 => wgpu::IndexFormat::Uint16,
64 Self::Uint32 => wgpu::IndexFormat::Uint32,
65 }
66 }
67}
68
69#[derive(Clone, Debug, PartialEq)]
71pub struct PackedGeometry {
72 pub vertices: Vec<PackedVertex>,
74 pub index_bytes: Vec<u8>,
76 pub index_count: u32,
78 pub index_format: GpuIndexFormat,
80 pub aabb: Aabb,
82}
83
84#[derive(Debug)]
86pub struct GpuMesh {
87 vertex_buffer: wgpu::Buffer,
88 index_buffer: wgpu::Buffer,
89 packed: PackedGeometry,
90}
91
92impl GpuMesh {
93 #[inline]
95 pub const fn vertex_buffer(&self) -> &wgpu::Buffer {
96 &self.vertex_buffer
97 }
98
99 #[inline]
101 pub const fn index_buffer(&self) -> &wgpu::Buffer {
102 &self.index_buffer
103 }
104
105 #[inline]
107 pub const fn packed(&self) -> &PackedGeometry {
108 &self.packed
109 }
110}
111
112#[derive(Clone, Debug, PartialEq)]
114pub enum RendererMaterial {
115 Pbr(PbrMaterial),
117 Physical(PhysicalMaterial),
119 Unlit(UnlitMaterial),
121 Lambert(LambertMaterial),
123 Toon(ToonMaterial),
125 Wireframe(WireframeMaterial),
127 Normal(NormalMaterial),
129}
130
131impl RendererMaterial {
132 #[inline]
134 pub fn pipeline_key(&self) -> PipelineKey {
135 match self {
136 Self::Pbr(material) => material.pipeline_key(),
137 Self::Physical(material) => material.pipeline_key(),
138 Self::Unlit(material) => material.pipeline_key(),
139 Self::Lambert(material) => material.pipeline_key(),
140 Self::Toon(material) => material.pipeline_key(),
141 Self::Wireframe(material) => material.pipeline_key(),
142 Self::Normal(material) => material.pipeline_key(),
143 }
144 }
145
146 #[inline]
148 pub fn is_transparent(&self) -> bool {
149 match self {
150 Self::Pbr(material) => material.is_transparent(),
151 Self::Physical(material) => material.is_transparent(),
152 Self::Unlit(material) => material.is_transparent(),
153 Self::Lambert(material) => material.is_transparent(),
154 Self::Toon(material) => material.is_transparent(),
155 Self::Wireframe(material) => material.is_transparent(),
156 Self::Normal(material) => material.is_transparent(),
157 }
158 }
159
160 #[inline]
162 pub fn preview_color(&self) -> Color {
163 match self {
164 Self::Pbr(material) => material.albedo,
165 Self::Physical(material) => material.base.albedo,
166 Self::Unlit(material) => material.color,
167 Self::Lambert(material) => material.color,
168 Self::Toon(material) => material.color,
169 Self::Wireframe(material) => Color {
170 a: material.opacity,
171 ..material.color
172 },
173 Self::Normal(_) => Color::WHITE,
174 }
175 }
176
177 #[inline]
179 pub fn preview_shader_code(&self) -> f32 {
180 match self {
181 Self::Pbr(_) => 0.0,
182 Self::Physical(_) => 1.0,
183 Self::Unlit(_) => 2.0,
184 Self::Lambert(_) => 3.0,
185 Self::Toon(_) => 4.0,
186 Self::Wireframe(_) => 5.0,
187 Self::Normal(_) => 6.0,
188 }
189 }
190}
191
192#[derive(Clone, Debug, PartialEq, Eq)]
194pub struct GpuTexture {
195 pub width: u32,
197 pub height: u32,
199 pub format: TextureFormat,
201 pub wgpu_format: Option<wgpu::TextureFormat>,
203 pub sampler: Sampler,
205 pub mip_levels: u32,
207}
208
209pub type TextureStore = HashMap<TextureId, GpuTexture>;
211
212#[derive(Clone, Copy, Debug, PartialEq)]
214pub enum RendererLight {
215 Ambient(AmbientLight),
217 Directional(DirectionalLight),
219 Point(PointLight),
221 Spot(SpotLight),
223}
224
225#[derive(Clone, Copy, Debug, PartialEq)]
227pub struct DrawSubmission {
228 pub mesh_id: MeshId,
230 pub material_id: MaterialId,
232 pub world_matrix: Mat4,
234 pub world_aabb: Aabb,
236 pub distance_to_camera: f32,
238 pub transparent: bool,
240 pub render_order: u32,
242}
243
244#[derive(Debug, Default)]
246pub struct GpuScene {
247 meshes: HashMap<MeshId, GpuMesh>,
248 materials: HashMap<MaterialId, RendererMaterial>,
249 textures: TextureStore,
250 lights: HashMap<LightId, RendererLight>,
251}
252
253impl GpuScene {
254 #[inline]
256 pub fn new() -> Self {
257 Self::default()
258 }
259
260 pub fn pack_geometry(geometry: &Geometry) -> Result<PackedGeometry, ValidationError> {
262 geometry.validate()?;
263 let vertex_count = geometry.positions.len();
264 let mut vertices = Vec::with_capacity(vertex_count);
265 for index in 0..vertex_count {
266 let position = geometry.positions[index];
267 let normal = geometry.normals.get(index).copied().unwrap_or(Vec3::Y);
268 let uv = geometry.uvs.get(index).copied().unwrap_or(Vec2::ZERO);
269 let color = geometry.colors.get(index).copied().unwrap_or(Color::WHITE);
270 let tangent = geometry
271 .tangents
272 .get(index)
273 .copied()
274 .unwrap_or(Vec4::new(1.0, 0.0, 0.0, 1.0));
275 vertices.push(PackedVertex {
276 position: [position.x, position.y, position.z],
277 normal: [normal.x, normal.y, normal.z],
278 uv: [uv.x, uv.y],
279 color: color.to_array(),
280 tangent: [tangent.x, tangent.y, tangent.z, tangent.w],
281 });
282 }
283
284 let source_indices: Vec<u32> = if geometry.indices.is_empty() {
285 (0..vertex_count as u32).collect()
286 } else {
287 geometry.indices.clone()
288 };
289 let can_use_u16 = vertex_count <= u16::MAX as usize
290 && source_indices.iter().all(|index| *index <= u16::MAX as u32);
291 let (index_bytes, index_format) = if can_use_u16 {
292 let indices: Vec<u16> = source_indices.iter().map(|index| *index as u16).collect();
293 (
294 bytemuck::cast_slice(&indices).to_vec(),
295 GpuIndexFormat::Uint16,
296 )
297 } else {
298 (
299 bytemuck::cast_slice(source_indices.as_slice()).to_vec(),
300 GpuIndexFormat::Uint32,
301 )
302 };
303
304 Ok(PackedGeometry {
305 vertices,
306 index_bytes,
307 index_count: source_indices.len() as u32,
308 index_format,
309 aabb: geometry.aabb(),
310 })
311 }
312
313 pub fn register_mesh(
315 &mut self,
316 device: &wgpu::Device,
317 mesh_id: MeshId,
318 geometry: &Geometry,
319 ) -> Result<(), ValidationError> {
320 if mesh_id.is_null() {
321 return Err(ValidationError::InvalidId);
322 }
323 let packed = Self::pack_geometry(geometry)?;
324 let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
325 label: Some("scenix.mesh.vertices"),
326 contents: bytemuck::cast_slice(packed.vertices.as_slice()),
327 usage: wgpu::BufferUsages::VERTEX,
328 });
329 let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
330 label: Some("scenix.mesh.indices"),
331 contents: packed.index_bytes.as_slice(),
332 usage: wgpu::BufferUsages::INDEX,
333 });
334 self.meshes.insert(
335 mesh_id,
336 GpuMesh {
337 vertex_buffer,
338 index_buffer,
339 packed,
340 },
341 );
342 Ok(())
343 }
344
345 pub fn register_pbr_material(
347 &mut self,
348 material_id: MaterialId,
349 material: &PbrMaterial,
350 ) -> Result<(), ValidationError> {
351 self.register_material(material_id, RendererMaterial::Pbr(material.clone()))
352 }
353
354 pub fn register_physical_material(
356 &mut self,
357 material_id: MaterialId,
358 material: &PhysicalMaterial,
359 ) -> Result<(), ValidationError> {
360 self.register_material(material_id, RendererMaterial::Physical(material.clone()))
361 }
362
363 pub fn register_unlit_material(
365 &mut self,
366 material_id: MaterialId,
367 material: &UnlitMaterial,
368 ) -> Result<(), ValidationError> {
369 self.register_material(material_id, RendererMaterial::Unlit(material.clone()))
370 }
371
372 pub fn register_lambert_material(
374 &mut self,
375 material_id: MaterialId,
376 material: &LambertMaterial,
377 ) -> Result<(), ValidationError> {
378 self.register_material(material_id, RendererMaterial::Lambert(material.clone()))
379 }
380
381 pub fn register_toon_material(
383 &mut self,
384 material_id: MaterialId,
385 material: &ToonMaterial,
386 ) -> Result<(), ValidationError> {
387 self.register_material(material_id, RendererMaterial::Toon(material.clone()))
388 }
389
390 pub fn register_wireframe_material(
392 &mut self,
393 material_id: MaterialId,
394 material: &WireframeMaterial,
395 ) -> Result<(), ValidationError> {
396 self.register_material(material_id, RendererMaterial::Wireframe(*material))
397 }
398
399 pub fn register_normal_material(
401 &mut self,
402 material_id: MaterialId,
403 material: &NormalMaterial,
404 ) -> Result<(), ValidationError> {
405 self.register_material(material_id, RendererMaterial::Normal(*material))
406 }
407
408 pub fn register_material(
410 &mut self,
411 material_id: MaterialId,
412 material: RendererMaterial,
413 ) -> Result<(), ValidationError> {
414 if material_id.is_null() {
415 return Err(ValidationError::InvalidId);
416 }
417 self.materials.insert(material_id, material);
418 Ok(())
419 }
420
421 pub fn register_texture2d(
423 &mut self,
424 texture_id: TextureId,
425 texture: &Texture2D,
426 sampler: Sampler,
427 ) -> Result<(), ValidationError> {
428 if texture_id.is_null() {
429 return Err(ValidationError::InvalidId);
430 }
431 texture.validate()?;
432 self.textures.insert(
433 texture_id,
434 GpuTexture {
435 width: texture.width,
436 height: texture.height,
437 format: texture.format,
438 wgpu_format: to_wgpu_texture_format(texture.format),
439 sampler,
440 mip_levels: texture.mip_levels.max(1),
441 },
442 );
443 Ok(())
444 }
445
446 pub fn register_light(
448 &mut self,
449 light_id: LightId,
450 light: RendererLight,
451 ) -> Result<(), ValidationError> {
452 if light_id.is_null() {
453 return Err(ValidationError::InvalidId);
454 }
455 self.lights.insert(light_id, light);
456 Ok(())
457 }
458
459 #[inline]
461 pub fn mesh(&self, mesh_id: MeshId) -> Option<&GpuMesh> {
462 self.meshes.get(&mesh_id)
463 }
464
465 #[inline]
467 pub fn material(&self, material_id: MaterialId) -> Option<&RendererMaterial> {
468 self.materials.get(&material_id)
469 }
470
471 #[inline]
473 pub fn texture(&self, texture_id: TextureId) -> Option<&GpuTexture> {
474 self.textures.get(&texture_id)
475 }
476
477 #[inline]
479 pub const fn textures(&self) -> &TextureStore {
480 &self.textures
481 }
482
483 #[inline]
485 pub fn light_count(&self) -> usize {
486 self.lights.len()
487 }
488
489 #[inline]
491 pub fn mesh_count(&self) -> usize {
492 self.meshes.len()
493 }
494
495 #[inline]
497 pub fn material_count(&self) -> usize {
498 self.materials.len()
499 }
500}
501
502pub const fn to_wgpu_texture_format(format: TextureFormat) -> Option<wgpu::TextureFormat> {
504 match format {
505 TextureFormat::Rgba8Unorm => Some(wgpu::TextureFormat::Rgba8Unorm),
506 TextureFormat::Rgba8UnormSrgb => Some(wgpu::TextureFormat::Rgba8UnormSrgb),
507 TextureFormat::Rgba16Float => Some(wgpu::TextureFormat::Rgba16Float),
508 TextureFormat::Depth32Float => Some(wgpu::TextureFormat::Depth32Float),
509 TextureFormat::Bc7RgbaUnorm => Some(wgpu::TextureFormat::Bc7RgbaUnorm),
510 TextureFormat::Astc4x4RgbaUnorm => Some(wgpu::TextureFormat::Astc {
511 block: wgpu::AstcBlock::B4x4,
512 channel: wgpu::AstcChannel::Unorm,
513 }),
514 TextureFormat::Etc2Rgba8Unorm => Some(wgpu::TextureFormat::Etc2Rgba8Unorm),
515 }
516}
517
518pub const fn to_wgpu_filter_mode(filter: FilterMode) -> wgpu::FilterMode {
520 match filter {
521 FilterMode::Nearest => wgpu::FilterMode::Nearest,
522 FilterMode::Linear => wgpu::FilterMode::Linear,
523 }
524}
525
526pub const fn to_wgpu_address_mode(address: AddressMode) -> wgpu::AddressMode {
528 match address {
529 AddressMode::Repeat => wgpu::AddressMode::Repeat,
530 AddressMode::MirrorRepeat => wgpu::AddressMode::MirrorRepeat,
531 AddressMode::ClampToEdge => wgpu::AddressMode::ClampToEdge,
532 }
533}
534
535pub const fn to_wgpu_compare(compare: Option<CompareFunction>) -> Option<wgpu::CompareFunction> {
537 match compare {
538 Some(CompareFunction::Less) => Some(wgpu::CompareFunction::Less),
539 Some(CompareFunction::LessEqual) => Some(wgpu::CompareFunction::LessEqual),
540 Some(CompareFunction::Greater) => Some(wgpu::CompareFunction::Greater),
541 Some(CompareFunction::GreaterEqual) => Some(wgpu::CompareFunction::GreaterEqual),
542 Some(CompareFunction::Equal) => Some(wgpu::CompareFunction::Equal),
543 Some(CompareFunction::NotEqual) => Some(wgpu::CompareFunction::NotEqual),
544 Some(CompareFunction::Always) => Some(wgpu::CompareFunction::Always),
545 Some(CompareFunction::Never) => Some(wgpu::CompareFunction::Never),
546 None => None,
547 }
548}