From 3344eb2d678f9c5973c8e38083760254b54c20fc Mon Sep 17 00:00:00 2001 From: metamuffin Date: Thu, 23 Jan 2025 22:45:35 +0100 Subject: split scene_prepare to many files --- client/src/render/scene/textures.rs | 284 ++++++++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 client/src/render/scene/textures.rs (limited to 'client/src/render/scene/textures.rs') diff --git a/client/src/render/scene/textures.rs b/client/src/render/scene/textures.rs new file mode 100644 index 0000000..0a042bd --- /dev/null +++ b/client/src/render/scene/textures.rs @@ -0,0 +1,284 @@ +/* + 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 super::{GraphicsConfig, ScenePreparer, TextureIdentityKind}; +use anyhow::Result; +use image::ImageReader; +use log::debug; +use std::{io::Cursor, sync::Arc, time::Instant}; +use wgpu::{ + AddressMode, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindingResource, + Color, ColorTargetState, ColorWrites, CommandEncoderDescriptor, Device, Extent3d, FilterMode, + ImageDataLayout, LoadOp, Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor, + RenderPipeline, SamplerDescriptor, StoreOp, Texture, TextureAspect, TextureDescriptor, + TextureDimension, TextureFormat, TextureUsages, TextureViewDescriptor, include_wgsl, +}; + +pub 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("mip 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 } + } +} + +impl ScenePreparer { + pub fn update_textures(&self, num_done: &mut usize) -> Result<()> { + for format in self.mip_generation_pipelines.needed() { + self.mip_generation_pipelines.insert( + format, + Arc::new(MipGenerationPipeline::load(&self.device, format)), + 0, + ); + *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 + }, + None, + &self.config, + ); + self.placeholder_textures.insert(kind, tex_bg, 4); + *num_done += 1; + } + 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) = self.downloader.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, + Some(&mipgen), + &self.config, + ); + self.textures.insert(spec, tex_bg, image.len()); + debug!( + "texture created (res={}x{}, took {:?})", + dims.0, + dims.1, + start.elapsed() + ); + *num_done += 1; + } + } + } + Ok(()) + } +} + +fn create_texture( + device: &Device, + queue: &Queue, + bgl: &BindGroupLayout, + data: &[u8], + width: u32, + height: u32, + format: TextureFormat, + mipgen: Option<&MipGenerationPipeline>, + config: &GraphicsConfig, +) -> (Arc, Arc) { + let mip_level_count = (width.ilog2().max(4) - 3).min(config.max_mip_count); + + 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, + anisotropy_clamp: config.max_anisotropy, + ..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: 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)) +} -- cgit v1.2.3-70-g09d2