summaryrefslogtreecommitdiff
path: root/client/src/render/scene/textures.rs
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-01-23 22:45:35 +0100
committermetamuffin <metamuffin@disroot.org>2025-01-23 22:45:35 +0100
commit3344eb2d678f9c5973c8e38083760254b54c20fc (patch)
tree793678d4230dd012285169ba34005064690c7af0 /client/src/render/scene/textures.rs
parentdd40803458695abcd4100fffb874cc25a71ea758 (diff)
downloadweareserver-3344eb2d678f9c5973c8e38083760254b54c20fc.tar
weareserver-3344eb2d678f9c5973c8e38083760254b54c20fc.tar.bz2
weareserver-3344eb2d678f9c5973c8e38083760254b54c20fc.tar.zst
split scene_prepare to many files
Diffstat (limited to 'client/src/render/scene/textures.rs')
-rw-r--r--client/src/render/scene/textures.rs284
1 files changed, 284 insertions, 0 deletions
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 <https://www.gnu.org/licenses/>.
+*/
+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<Texture>, Arc<BindGroup>) {
+ 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::<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))
+}