/* wearechat - generic multiplayer game with voip Copyright (C) 2025 metamuffin This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License only. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ use crate::{ armature::RArmature, download::Downloader, meshops::{generate_normals, generate_tangents, generate_texcoords}, shaders::SceneShaders, }; use anyhow::Result; use bytemuck::{Pod, Zeroable}; use egui::{Grid, Widget}; use glam::{UVec3, UVec4, Vec2, Vec3, Vec3A, uvec3, uvec4}; use humansize::DECIMAL; use image::ImageReader; use log::{debug, trace}; use std::{ collections::{HashMap, HashSet}, hash::Hash, io::Cursor, marker::PhantomData, sync::{Arc, RwLock}, time::Instant, }; use weareshared::{ Affine3A, packets::Resource, resources::{Image, MeshPart, Prefab}, }; use wgpu::{ AddressMode, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, BlendState, Buffer, BufferBindingType, BufferUsages, Color, ColorTargetState, ColorWrites, CommandEncoderDescriptor, CompareFunction, DepthBiasState, DepthStencilState, Device, Extent3d, Face, FilterMode, FragmentState, FrontFace, ImageDataLayout, LoadOp, MultisampleState, Operations, PipelineCompilationOptions, PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, PushConstantRange, Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, SamplerBindingType, SamplerDescriptor, ShaderStages, StencilState, StoreOp, Texture, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureViewDescriptor, TextureViewDimension, VertexAttribute, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, include_wgsl, util::{BufferInitDescriptor, DeviceExt}, }; pub struct DemandMap { inner: RwLock>, } struct DemandMapState { values: HashMap, needed: HashSet, size_metric: usize, } impl DemandMap { pub fn new() -> Self { Self { inner: DemandMapState { needed: HashSet::new(), values: HashMap::new(), size_metric: 0, } .into(), } } pub fn needed(&self) -> Vec { self.inner.read().unwrap().needed.iter().cloned().collect() } pub fn insert(&self, key: K, value: V, size: usize) { let mut s = self.inner.write().unwrap(); s.needed.remove(&key); s.values.insert(key, value); s.size_metric += size; } pub fn try_get(&self, key: K) -> Option { let mut s = self.inner.write().unwrap(); if let Some(k) = s.values.get(&key) { Some(k.to_owned()) } else { s.needed.insert(key); None } } } pub struct ScenePreparer { device: Arc, queue: Arc, layouts: SceneBgLayouts, shaders: SceneShaders, render_format: TextureFormat, textures: DemandMap, Arc)>, placeholder_textures: DemandMap, Arc)>, index_buffers: DemandMap>, (Arc, u32)>, vertex_buffers: DemandMap>, Arc>, generated_tangent_buffers: DemandMap>, generated_normal_buffers: DemandMap>, generated_texcoord_buffers: DemandMap>, mesh_parts: DemandMap, Arc>, materials: DemandMap>, pipelines: DemandMap>, mip_generation_pipelines: DemandMap>, pub prefabs: DemandMap, Arc>, } #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct PipelineSpec { format: TextureFormat, skin: bool, backface_culling: bool, } pub struct RPrefab(pub Vec<(Affine3A, Arc)>); pub struct RMeshPart { pub pipeline: Arc, pub index_count: u32, pub index: Arc, pub va_position: Arc, pub va_normal: Arc, pub va_tangent: Arc, pub va_texcoord: Arc, pub tex_albedo: Arc, pub tex_normal: Arc, pub material: Arc, pub double_sided: bool, pub va_joint_index: Option>, pub va_joint_weight: Option>, pub joint_uniform: Option>, } #[derive(Debug, Clone, Hash, PartialEq, Eq)] struct TextureSpec { data: Resource>, linear: bool, } #[derive(Debug, Clone, Hash, PartialEq, Eq)] struct TangentBufferSpec { index: Resource>, position: Resource>, texcoord: Option>>, } #[derive(Debug, Clone, Hash, PartialEq, Eq)] struct NormalBufferSpec { index: Resource>, position: Resource>, } #[derive(Debug, Clone, Hash, PartialEq, Eq)] struct TexcoordBufferSpec { index: Resource>, position: Resource>, } #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] enum TextureIdentityKind { Normal, Multiply, } #[derive(Debug, Clone, Copy, Pod, Zeroable, Hash, PartialEq, Eq)] #[repr(C)] struct Material { roughness: u32, metallic: u32, _pad1: [u32; 2], albedo_alpha: UVec4, emission: UVec3, _pad2: u32, } impl ScenePreparer { pub fn new(device: Arc, queue: Arc, render_format: TextureFormat) -> Self { Self { // TODO normal mipmap requires linear texture, also demand map? render_format, layouts: SceneBgLayouts::load(&device), shaders: SceneShaders::load(&device), device, queue, index_buffers: DemandMap::new(), vertex_buffers: DemandMap::new(), mesh_parts: DemandMap::new(), prefabs: DemandMap::new(), textures: DemandMap::new(), placeholder_textures: DemandMap::new(), generated_tangent_buffers: DemandMap::new(), generated_normal_buffers: DemandMap::new(), generated_texcoord_buffers: DemandMap::new(), materials: DemandMap::new(), pipelines: DemandMap::new(), mip_generation_pipelines: DemandMap::new(), } } pub fn update(&self, dls: &Downloader) -> Result { let mut num_done = 0; for pres in self.prefabs.needed() { if let Some(prefab) = dls.try_get(pres.clone())? { let mut rprefab = RPrefab(Vec::new()); for (aff, partres) in &prefab.mesh { if let Some(part) = self.mesh_parts.try_get(partres.clone()) { rprefab.0.push((*aff, part.clone())); } } if rprefab.0.len() == prefab.mesh.len() { self.prefabs.insert(pres.clone(), Arc::new(rprefab), 0); debug!("prefab created ({pres})"); num_done += 1; } } } for pres in self.index_buffers.needed() { let start = Instant::now(); if let Some(buf) = dls.try_get(pres.clone())? { let buffer = self.device.create_buffer_init(&BufferInitDescriptor { label: Some("index"), contents: bytemuck::cast_slice(buf.as_slice()), usage: BufferUsages::INDEX | BufferUsages::COPY_DST, }); self.index_buffers.insert( pres.clone(), (Arc::new(buffer), (buf.len() * 3) as u32), buf.len() * size_of::() * 3, ); debug!( "index buffer created (len={}, took {:?}) {pres}", buf.len() / size_of::(), start.elapsed(), ); num_done += 1; } } for pres in self.vertex_buffers.needed() { let start = Instant::now(); if let Some(buf) = dls.try_get(pres.clone())? { let buffer = self.device.create_buffer_init(&BufferInitDescriptor { contents: bytemuck::cast_slice(buf.as_slice()), label: Some("vertex attribute"), usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, }); self.vertex_buffers.insert( pres.clone(), Arc::new(buffer), buf.len() * size_of::(), ); debug!( "vertex attribute buffer created (len={}, took {:?}) {pres}", buf.len() / size_of::(), start.elapsed() ); num_done += 1; } } for format in self.mip_generation_pipelines.needed() { self.mip_generation_pipelines.insert( format, Arc::new(MipGenerationPipeline::load(&self.device, format)), 0, ); } for spec in self.textures.needed() { let start = Instant::now(); let format = if spec.linear { TextureFormat::Rgba8Unorm } else { TextureFormat::Rgba8UnormSrgb }; if let Some(mipgen) = self.mip_generation_pipelines.try_get(format) { if let Some(buf) = dls.try_get(spec.data.clone())? { let image = ImageReader::new(Cursor::new(buf.0)).with_guessed_format()?; let image = image.decode()?; let dims = (image.width(), image.height()); let image = image.into_rgba8(); let image = image.into_vec(); let tex_bg = create_texture( &self.device, &self.queue, &self.layouts.texture, &image, dims.0, dims.1, format, dims.0.ilog2().max(4) - 3, Some(&mipgen), ); self.textures.insert(spec, tex_bg, image.len()); debug!( "texture created (res={}x{}, took {:?})", dims.0, dims.1, start.elapsed() ); num_done += 1; } } } for kind in self.placeholder_textures.needed() { let (linear, color) = match kind { TextureIdentityKind::Normal => (true, [128, 128, 255, 255]), TextureIdentityKind::Multiply => (false, [255, 255, 255, 255]), }; let tex_bg = create_texture( &self.device, &self.queue, &self.layouts.texture, &color, 1, 1, if linear { TextureFormat::Rgba8Unorm } else { TextureFormat::Rgba8UnormSrgb }, 1, None, ); self.placeholder_textures.insert(kind, tex_bg, 4); num_done += 1; } for spec in self.generated_tangent_buffers.needed() { if let (Some(index), Some(position), texcoord) = ( dls.try_get(spec.index.clone())?, dls.try_get(spec.position.clone())?, spec.texcoord.clone().map(|r| dls.try_get(r)).transpose()?, ) { let texcoord = match texcoord { Some(Some(x)) => Some(x), Some(None) => continue, // tangents provided but still loading None => None, }; let texcoord = texcoord.unwrap_or_else(|| generate_texcoords(&index, &position)); let tangents = generate_tangents(&index, &position, &texcoord); let buffer = self.device.create_buffer_init(&BufferInitDescriptor { label: Some("generated tangent"), usage: BufferUsages::COPY_DST | BufferUsages::VERTEX, contents: bytemuck::cast_slice(tangents.as_slice()), }); self.generated_tangent_buffers.insert( spec, Arc::new(buffer), size_of::() * tangents.len() * 3, ); } } for spec in self.generated_normal_buffers.needed() { if let (Some(index), Some(position)) = ( dls.try_get(spec.index.clone())?, dls.try_get(spec.position.clone())?, ) { let normals = generate_normals(&index, &position); let buffer = self.device.create_buffer_init(&BufferInitDescriptor { label: Some("generated normal"), usage: BufferUsages::COPY_DST | BufferUsages::VERTEX, contents: bytemuck::cast_slice(normals.as_slice()), }); self.generated_normal_buffers.insert( spec, Arc::new(buffer), size_of::() * normals.len() * 3, ); } } for spec in self.generated_texcoord_buffers.needed() { if let (Some(index), Some(position)) = ( dls.try_get(spec.index.clone())?, dls.try_get(spec.position.clone())?, ) { let texcoords = generate_texcoords(&index, &position); let buffer = self.device.create_buffer_init(&BufferInitDescriptor { label: Some("generated texcoord"), usage: BufferUsages::COPY_DST | BufferUsages::VERTEX, contents: bytemuck::cast_slice(texcoords.as_slice()), }); self.generated_texcoord_buffers.insert( spec, Arc::new(buffer), size_of::() * texcoords.len() * 3, ); } } for spec in self.materials.needed() { let buffer = self.device.create_buffer_init(&BufferInitDescriptor { label: Some("material props"), usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM, contents: bytemuck::cast_slice(&[spec]), }); let bind_group = self.device.create_bind_group(&BindGroupDescriptor { label: Some("material"), layout: &self.layouts.material, entries: &[BindGroupEntry { binding: 0, resource: buffer.as_entire_binding(), }], }); self.materials.insert(spec, Arc::new(bind_group), 0); } for spec in self.pipelines.needed() { self.pipelines.insert( spec.clone(), Arc::new(spec.create(&self.device, &self.layouts, &self.shaders)), 0, ); } for pres in self.mesh_parts.needed() { let start = Instant::now(); if let Some(part) = dls.try_get(pres.clone())? { if let (Some(indexres), Some(positionres)) = (part.index, part.va_position) { let index = self.index_buffers.try_get(indexres.clone()); let position = self .vertex_buffers .try_get(Resource(positionres.0, PhantomData)); let normal = if let Some(res) = part.va_normal.clone() { self.vertex_buffers.try_get(Resource(res.0, PhantomData)) } else { self.generated_normal_buffers.try_get(NormalBufferSpec { index: indexres.clone(), position: Resource(positionres.0, PhantomData), }) }; let texcoord = if let Some(res) = part.va_texcoord.clone() { self.vertex_buffers.try_get(Resource(res.0, PhantomData)) } else { self.generated_texcoord_buffers.try_get(TexcoordBufferSpec { index: indexres.clone(), position: Resource(positionres.0, PhantomData), }) }; let tangent = if let Some(res) = part.va_tangent.clone() { self.vertex_buffers.try_get(Resource(res.0, PhantomData)) } else { self.generated_tangent_buffers.try_get(TangentBufferSpec { index: indexres, position: Resource(positionres.0, PhantomData), texcoord: part.va_texcoord, }) }; let joint_weight = if let Some(res) = part.va_joint_weight.clone() { self.vertex_buffers .try_get(Resource(res.0, PhantomData)) .map(Some) } else { Some(None) }; let joint_index = if let Some(res) = part.va_joint_index.clone() { self.vertex_buffers .try_get(Resource(res.0, PhantomData)) .map(Some) } else { Some(None) }; let mut tex_albedo = None; if let Some(albedores) = part.tex_albedo { if let Some((_tex, bg)) = self.textures.try_get(TextureSpec { data: albedores, linear: false, }) { tex_albedo = Some(bg) } } else { if let Some((_tex, bg)) = self .placeholder_textures .try_get(TextureIdentityKind::Multiply) { tex_albedo = Some(bg) } } let mut tex_normal = None; if let Some(normalres) = part.tex_normal { if let Some((_tex, bg)) = self.textures.try_get(TextureSpec { data: normalres, linear: true, }) { tex_normal = Some(bg) } } else { if let Some((_tex, bg)) = self .placeholder_textures .try_get(TextureIdentityKind::Normal) { tex_normal = Some(bg) } } let material = self.materials.try_get({ let albedo = part.g_albedo.unwrap_or(Vec3A::ONE); let emission = part.g_emission.unwrap_or(Vec3A::ONE); Material { roughness: part.g_roughness.unwrap_or(1.).to_bits(), metallic: part.g_metallic.unwrap_or(0.).to_bits(), _pad1: [0, 0], albedo_alpha: uvec4( albedo.x.to_bits(), albedo.y.to_bits(), albedo.z.to_bits(), part.g_alpha.unwrap_or(1.).to_bits(), ), emission: uvec3( emission.x.to_bits(), emission.y.to_bits(), emission.z.to_bits(), ), _pad2: 0, } }); let armature = if let Some(res) = part.armature.clone() { Some(dls.try_get(res)?) } else { Some(None) }; let pipeline = self.pipelines.try_get(PipelineSpec { format: self.render_format, skin: false, backface_culling: part.g_double_sided.is_none(), }); if let ( Some(pipeline), Some((index, index_count)), Some(va_normal), Some(va_tangent), Some(va_texcoord), Some(va_position), Some(va_joint_index), Some(va_joint_weight), Some(armature), Some(tex_normal), Some(tex_albedo), Some(material), ) = ( pipeline, index, normal, tangent, texcoord, position, joint_index, joint_weight, armature, tex_normal, tex_albedo, material, ) { let double_sided = part.g_double_sided.is_some(); let joint_uniform = if let Some(a) = armature { let ra = RArmature::new(&self.device, a); Some(ra.joint_mat_uniform_buffer.clone()) } else { None }; debug!("part created (took {:?}) {pres}", start.elapsed()); self.mesh_parts.insert( pres, Arc::new(RMeshPart { pipeline, index_count, index, va_normal, va_tangent, va_position, va_texcoord, va_joint_index, va_joint_weight, tex_albedo, tex_normal, material, double_sided, joint_uniform, }), 0, ); num_done += 1; } } } } self.print_missing(); Ok(num_done) } } struct MipGenerationPipeline { pipeline: RenderPipeline, } impl MipGenerationPipeline { pub fn load(device: &Device, format: TextureFormat) -> Self { let shader = device.create_shader_module(include_wgsl!("shaders/texture_copy.wgsl")); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("map generator"), layout: None, vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[Some(ColorTargetState { format, blend: None, write_mask: ColorWrites::ALL, })], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview: None, cache: None, }); Self { pipeline } } } fn create_texture( device: &Device, queue: &Queue, bgl: &BindGroupLayout, data: &[u8], width: u32, height: u32, format: TextureFormat, mip_level_count: u32, mipgen: Option<&MipGenerationPipeline>, ) -> (Arc, Arc) { let extent = Extent3d { depth_or_array_layers: 1, width, height, }; let texture = device.create_texture(&TextureDescriptor { label: None, size: extent, mip_level_count, sample_count: 1, dimension: TextureDimension::D2, format, usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }); let textureview = texture.create_view(&TextureViewDescriptor::default()); let sampler = device.create_sampler(&SamplerDescriptor { address_mode_u: AddressMode::Repeat, address_mode_v: AddressMode::Repeat, mag_filter: FilterMode::Linear, min_filter: FilterMode::Linear, mipmap_filter: FilterMode::Linear, ..Default::default() }); let bind_group = device.create_bind_group(&BindGroupDescriptor { label: None, layout: &bgl, entries: &[ BindGroupEntry { binding: 0, resource: BindingResource::TextureView(&textureview), }, BindGroupEntry { binding: 1, resource: BindingResource::Sampler(&sampler), }, ], }); let level_views = (0..mip_level_count) .map(|mip| { texture.create_view(&TextureViewDescriptor { label: Some("mip generation level view"), format: None, dimension: None, aspect: wgpu::TextureAspect::All, base_mip_level: mip, mip_level_count: Some(1), base_array_layer: 0, array_layer_count: None, }) }) .collect::>(); let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor { label: None }); // TODO why does copy_buffer_to_texture have more restrictive alignment requirements?! // let upload_buffer = device.create_buffer_init(&BufferInitDescriptor { // label: Some("texture upload"), // contents: data, // usage: BufferUsages::COPY_DST | BufferUsages::COPY_SRC, // }); // encoder.copy_buffer_to_texture( // ImageCopyBuffer { // buffer: &upload_buffer, // layout: ImageDataLayout { // offset: 0, // bytes_per_row: Some(width * 4), // rows_per_image: None, // }, // }, // texture.as_image_copy(), // extent, // ); queue.write_texture( texture.as_image_copy(), data, ImageDataLayout { bytes_per_row: Some(width * 4), rows_per_image: None, offset: 0, }, extent, ); for level in 1..mip_level_count { let mip_pipeline = &mipgen.unwrap().pipeline; let source_view = &level_views[level as usize - 1]; let target_view = &level_views[level as usize]; let mip_bind_group = device.create_bind_group(&BindGroupDescriptor { layout: &mip_pipeline.get_bind_group_layout(0), entries: &[ BindGroupEntry { binding: 0, resource: BindingResource::TextureView(source_view), }, BindGroupEntry { binding: 1, resource: BindingResource::Sampler(&sampler), }, ], label: None, }); let mut rpass = encoder.begin_render_pass(&RenderPassDescriptor { label: None, color_attachments: &[Some(RenderPassColorAttachment { view: target_view, resolve_target: None, ops: Operations { load: LoadOp::Clear(Color::WHITE), store: StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, }); rpass.set_pipeline(&mip_pipeline); rpass.set_bind_group(0, &mip_bind_group, &[]); rpass.draw(0..3, 0..1); } queue.submit(Some(encoder.finish())); (Arc::new(texture), Arc::new(bind_group)) } pub struct SceneBgLayouts { pub texture: BindGroupLayout, pub material: BindGroupLayout, pub joints: BindGroupLayout, } impl SceneBgLayouts { pub fn load(device: &Device) -> Self { Self { texture: device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ BindGroupLayoutEntry { binding: 0, count: None, visibility: ShaderStages::FRAGMENT, ty: BindingType::Texture { sample_type: TextureSampleType::Float { filterable: true }, view_dimension: TextureViewDimension::D2, multisampled: false, }, }, BindGroupLayoutEntry { binding: 1, count: None, visibility: ShaderStages::FRAGMENT, ty: BindingType::Sampler(SamplerBindingType::Filtering), }, ], label: None, }), material: device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { binding: 0, count: None, visibility: ShaderStages::FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, }], label: None, }), joints: device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { binding: 0, count: None, visibility: ShaderStages::VERTEX, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, }], label: None, }), } } } impl PipelineSpec { pub fn create( &self, device: &Device, layouts: &SceneBgLayouts, shaders: &SceneShaders, ) -> RenderPipeline { let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { label: None, bind_group_layouts: &[&layouts.texture, &layouts.texture, &layouts.material], push_constant_ranges: &[PushConstantRange { range: 0..((4 * 4 + 3 * 4) * size_of::() as u32), stages: ShaderStages::VERTEX, }], }); device.create_render_pipeline(&RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), fragment: Some(FragmentState { module: &shaders.fragment_pbr, entry_point: Some("main"), targets: &[Some(ColorTargetState { blend: Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING), format: self.format, write_mask: ColorWrites::all(), })], compilation_options: PipelineCompilationOptions::default(), }), vertex: VertexState { module: if self.skin { &shaders.vertex_world_skin } else { &shaders.vertex_world }, entry_point: Some("main"), buffers: &[ // position VertexBufferLayout { step_mode: VertexStepMode::Vertex, array_stride: 3 * size_of::() as u64, attributes: &[VertexAttribute { format: VertexFormat::Float32x3, offset: 0, shader_location: 0, }], }, // normal VertexBufferLayout { step_mode: VertexStepMode::Vertex, array_stride: 3 * size_of::() as u64, attributes: &[VertexAttribute { format: VertexFormat::Float32x3, offset: 0, shader_location: 1, }], }, // tangent VertexBufferLayout { step_mode: VertexStepMode::Vertex, array_stride: 3 * size_of::() as u64, attributes: &[VertexAttribute { format: VertexFormat::Float32x3, offset: 0, shader_location: 2, }], }, // texcoord VertexBufferLayout { step_mode: VertexStepMode::Vertex, array_stride: 2 * size_of::() as u64, attributes: &[VertexAttribute { format: VertexFormat::Float32x2, offset: 0, shader_location: 3, }], }, ], compilation_options: PipelineCompilationOptions::default(), }, primitive: PrimitiveState { topology: PrimitiveTopology::TriangleList, front_face: FrontFace::Ccw, cull_mode: if self.backface_culling { Some(Face::Back) } else { None }, polygon_mode: PolygonMode::Fill, ..Default::default() }, depth_stencil: Some(DepthStencilState { depth_write_enabled: true, depth_compare: CompareFunction::Less, format: TextureFormat::Depth32Float, bias: DepthBiasState::default(), stencil: StencilState::default(), }), multisample: MultisampleState::default(), multiview: None, cache: None, }) } } impl Widget for &DemandMap { fn ui(self, ui: &mut egui::Ui) -> egui::Response { let state = self.inner.read().unwrap(); ui.label(state.needed.len().to_string()); ui.label(state.values.len().to_string()); ui.label(humansize::format_size(state.size_metric, DECIMAL)); ui.end_row(); ui.response() } } impl ScenePreparer { pub fn print_missing(&self) { fn visit(name: &str, m: &DemandMap) where K: Clone, K: Hash, K: std::cmp::Eq, V: Clone, { let nl = m.needed().len(); if nl > 0 { trace!("{name}: need {nl}") } } visit("prefabs", &self.prefabs); visit("mesh_parts", &self.mesh_parts); visit("vertex_buffers", &self.vertex_buffers); visit("index_buffers", &self.index_buffers); visit("placeholder_textures", &self.placeholder_textures); visit("generated_tangent_buffers", &self.generated_tangent_buffers); visit("generated_normal_buffers", &self.generated_normal_buffers); visit( "generated_texcoord_buffers", &self.generated_texcoord_buffers, ); visit("textures", &self.textures); visit("materials", &self.materials); visit("pipelines", &self.pipelines); } } impl Widget for &ScenePreparer { fn ui(self, ui: &mut egui::Ui) -> egui::Response { Grid::new("sp") .num_columns(4) .show(ui, |ui| { ui.label("prefabs"); self.prefabs.ui(ui); ui.label("mesh_parts"); self.mesh_parts.ui(ui); ui.label("vertex_buffers"); self.vertex_buffers.ui(ui); ui.label("index_buffers"); self.index_buffers.ui(ui); ui.label("placeholder_textures"); self.placeholder_textures.ui(ui); ui.label("generated_tangent_buffers"); self.generated_tangent_buffers.ui(ui); ui.label("generated_normal_buffers"); self.generated_normal_buffers.ui(ui); ui.label("generated_texcoord_buffers"); self.generated_texcoord_buffers.ui(ui); ui.label("textures"); self.textures.ui(ui); ui.label("materials"); self.materials.ui(ui); ui.label("pipelines"); self.pipelines.ui(ui); }) .response } }