diff options
author | metamuffin <metamuffin@disroot.org> | 2025-01-23 16:31:06 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-01-23 16:31:06 +0100 |
commit | 25280332af8dce146295b2dcb48c044c45a92eba (patch) | |
tree | e56713385ce2f26c7622a67d7e709e4d7ed6f26e /client/src/scene_prepare.rs | |
parent | c682a1315adfa4e90d661d25e55df6e99a884d40 (diff) | |
download | weareserver-25280332af8dce146295b2dcb48c044c45a92eba.tar weareserver-25280332af8dce146295b2dcb48c044c45a92eba.tar.bz2 weareserver-25280332af8dce146295b2dcb48c044c45a92eba.tar.zst |
implement texture mip levels
Diffstat (limited to 'client/src/scene_prepare.rs')
-rw-r--r-- | client/src/scene_prepare.rs | 295 |
1 files changed, 227 insertions, 68 deletions
diff --git a/client/src/scene_prepare.rs b/client/src/scene_prepare.rs index e7d3403..e50dd34 100644 --- a/client/src/scene_prepare.rs +++ b/client/src/scene_prepare.rs @@ -43,15 +43,16 @@ use weareshared::{ use wgpu::{ AddressMode, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, BlendState, - Buffer, BufferBindingType, BufferUsages, ColorTargetState, ColorWrites, CompareFunction, - DepthBiasState, DepthStencilState, Device, Extent3d, Face, FilterMode, FragmentState, - FrontFace, MultisampleState, PipelineCompilationOptions, PipelineLayoutDescriptor, PolygonMode, - PrimitiveState, PrimitiveTopology, PushConstantRange, Queue, RenderPipeline, - RenderPipelineDescriptor, SamplerBindingType, SamplerDescriptor, ShaderStages, StencilState, - Texture, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages, - TextureViewDescriptor, TextureViewDimension, VertexAttribute, VertexBufferLayout, VertexFormat, - VertexState, VertexStepMode, - util::{BufferInitDescriptor, DeviceExt, TextureDataOrder}, + 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<K, V> { @@ -100,7 +101,7 @@ pub struct ScenePreparer { shaders: SceneShaders, render_format: TextureFormat, - textures: DemandMap<(Resource<Image<'static>>, bool), (Arc<Texture>, Arc<BindGroup>)>, + textures: DemandMap<TextureSpec, (Arc<Texture>, Arc<BindGroup>)>, placeholder_textures: DemandMap<TextureIdentityKind, (Arc<Texture>, Arc<BindGroup>)>, index_buffers: DemandMap<Resource<Vec<[u32; 3]>>, (Arc<Buffer>, u32)>, vertex_buffers: DemandMap<Resource<Vec<f32>>, Arc<Buffer>>, @@ -109,12 +110,13 @@ pub struct ScenePreparer { generated_texcoord_buffers: DemandMap<TexcoordBufferSpec, Arc<Buffer>>, mesh_parts: DemandMap<Resource<MeshPart>, Arc<RMeshPart>>, materials: DemandMap<Material, Arc<BindGroup>>, - pipelines: DemandMap<PipelineConfig, Arc<RenderPipeline>>, + pipelines: DemandMap<PipelineSpec, Arc<RenderPipeline>>, + mip_generation_pipelines: DemandMap<TextureFormat, Arc<MipGenerationPipeline>>, pub prefabs: DemandMap<Resource<Prefab>, Arc<RPrefab>>, } #[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct PipelineConfig { +pub struct PipelineSpec { format: TextureFormat, skin: bool, backface_culling: bool, @@ -140,6 +142,12 @@ pub struct RMeshPart { } #[derive(Debug, Clone, Hash, PartialEq, Eq)] +struct TextureSpec { + data: Resource<Image<'static>>, + linear: bool, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] struct TangentBufferSpec { index: Resource<Vec<[u32; 3]>>, position: Resource<Vec<Vec3>>, @@ -177,6 +185,7 @@ struct Material { impl ScenePreparer { pub fn new(device: Arc<Device>, queue: Arc<Queue>, render_format: TextureFormat) -> Self { Self { + // TODO normal mipmap requires linear texture, also demand map? render_format, layouts: SceneBgLayouts::load(&device), shaders: SceneShaders::load(&device), @@ -193,6 +202,7 @@ impl ScenePreparer { generated_texcoord_buffers: DemandMap::new(), materials: DemandMap::new(), pipelines: DemandMap::new(), + mip_generation_pipelines: DemandMap::new(), } } pub fn update(&self, dls: &Downloader) -> Result<usize> { @@ -254,32 +264,47 @@ impl ScenePreparer { num_done += 1; } } - for (pres, linear) in self.textures.needed() { + 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(); - if let Some(buf) = dls.try_get(pres.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, - linear, - ); - self.textures - .insert((pres.clone(), linear), tex_bg, image.len()); - debug!( - "texture created (res={}x{}, took {:?})", - dims.0, - dims.1, - start.elapsed() - ); - num_done += 1; + 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() { @@ -294,7 +319,13 @@ impl ScenePreparer { &color, 1, 1, - linear, + if linear { + TextureFormat::Rgba8Unorm + } else { + TextureFormat::Rgba8UnormSrgb + }, + 1, + None, ); self.placeholder_textures.insert(kind, tex_bg, 4); num_done += 1; @@ -436,7 +467,10 @@ impl ScenePreparer { let mut tex_albedo = None; if let Some(albedores) = part.tex_albedo { - if let Some((_tex, bg)) = self.textures.try_get((albedores, false)) { + if let Some((_tex, bg)) = self.textures.try_get(TextureSpec { + data: albedores, + linear: false, + }) { tex_albedo = Some(bg) } } else { @@ -449,7 +483,10 @@ impl ScenePreparer { } let mut tex_normal = None; if let Some(normalres) = part.tex_normal { - if let Some((_tex, bg)) = self.textures.try_get((normalres, true)) { + if let Some((_tex, bg)) = self.textures.try_get(TextureSpec { + data: normalres, + linear: true, + }) { tex_normal = Some(bg) } } else { @@ -489,7 +526,7 @@ impl ScenePreparer { Some(None) }; - let pipeline = self.pipelines.try_get(PipelineConfig { + let pipeline = self.pipelines.try_get(PipelineSpec { format: self.render_format, skin: false, backface_culling: part.g_double_sided.is_none(), @@ -562,6 +599,44 @@ impl ScenePreparer { } } +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, @@ -569,40 +644,37 @@ fn create_texture( data: &[u8], width: u32, height: u32, - linear: bool, + format: TextureFormat, + mip_level_count: u32, + mipgen: Option<&MipGenerationPipeline>, ) -> (Arc<Texture>, Arc<BindGroup>) { - let texture = device.create_texture_with_data( - &queue, - &TextureDescriptor { - label: None, - size: Extent3d { - depth_or_array_layers: 1, - width, - height, - }, - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: if linear { - TextureFormat::Rgba8Unorm - } else { - TextureFormat::Rgba8UnormSrgb - }, - usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, - view_formats: &[], - }, - TextureDataOrder::LayerMajor, - data, - ); + 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 bindgroup = device.create_bind_group(&BindGroupDescriptor { + let bind_group = device.create_bind_group(&BindGroupDescriptor { label: None, layout: &bgl, entries: &[ @@ -616,7 +688,94 @@ fn create_texture( }, ], }); - (Arc::new(texture), Arc::new(bindgroup)) + + 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::<Vec<_>>(); + + 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 { @@ -678,7 +837,7 @@ impl SceneBgLayouts { } } -impl PipelineConfig { +impl PipelineSpec { pub fn create( &self, device: &Device, |