diff options
author | metamuffin <metamuffin@disroot.org> | 2025-01-26 15:10:37 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-01-26 15:10:37 +0100 |
commit | 66930534a0647e2613360658a6a99eed945e2f0f (patch) | |
tree | 31a769910ef924a11206f1024b4004f74b1e396f /client/src/ui.rs | |
parent | 0163f8486ceca8bd6897c1074f6846f36827d040 (diff) | |
download | weareserver-66930534a0647e2613360658a6a99eed945e2f0f.tar weareserver-66930534a0647e2613360658a6a99eed945e2f0f.tar.bz2 weareserver-66930534a0647e2613360658a6a99eed945e2f0f.tar.zst |
move files around, graphics config, msaa
Diffstat (limited to 'client/src/ui.rs')
-rw-r--r-- | client/src/ui.rs | 523 |
1 files changed, 0 insertions, 523 deletions
diff --git a/client/src/ui.rs b/client/src/ui.rs deleted file mode 100644 index fd94dfb..0000000 --- a/client/src/ui.rs +++ /dev/null @@ -1,523 +0,0 @@ -/* - 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 crate::state::InputState; -use egui::{ - Context, Event, ImageData, PointerButton, TextureId, ViewportId, ViewportInfo, - epaint::{ImageDelta, Primitive, Vertex}, -}; -use glam::{Affine3A, Mat2, Mat3, Mat4, Vec2, Vec3, Vec3Swizzles, Vec4Swizzles, vec2, vec4}; -use log::{debug, warn}; -use rand::random; -use std::{ - collections::HashMap, - num::NonZeroU64, - sync::{Arc, RwLock}, -}; -use wgpu::{ - AddressMode, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, - BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, BlendState, - Buffer, BufferDescriptor, BufferUsages, ColorTargetState, ColorWrites, CommandEncoder, - CompareFunction, DepthStencilState, Device, Extent3d, FilterMode, FragmentState, FrontFace, - ImageCopyTexture, ImageDataLayout, IndexFormat, LoadOp, MultisampleState, Operations, Origin3d, - PipelineCompilationOptions, PipelineLayoutDescriptor, PolygonMode, PrimitiveState, - PrimitiveTopology, PushConstantRange, Queue, RenderPassColorAttachment, - RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipeline, - RenderPipelineDescriptor, SamplerBindingType, SamplerDescriptor, ShaderStages, StoreOp, - SurfaceConfiguration, Texture, TextureAspect, TextureDescriptor, TextureDimension, - TextureFormat, TextureSampleType, TextureUsages, TextureView, TextureViewDescriptor, - TextureViewDimension, VertexBufferLayout, VertexState, VertexStepMode, include_wgsl, - util::{DeviceExt, TextureDataOrder}, - vertex_attr_array, -}; -use winit::event::MouseButton; - -pub const UI_POSITION_OFFSET: f32 = 1000.; - -pub struct UiRenderer { - device: Arc<Device>, - queue: Arc<Queue>, - ctx: Context, - pipeline: RenderPipeline, - bind_group_layout: BindGroupLayout, - textures: RwLock<HashMap<TextureId, (BindGroup, Texture, [u32; 2])>>, - surfaces: RwLock<HashMap<ViewportId, UiSurface>>, - - last_pointer: Vec2, -} - -pub struct UiSurface { - pub transform: Affine3A, - pub content: Arc<dyn Fn(&Context) -> bool + Send + Sync + 'static>, - size: Vec2, - index: Buffer, - index_capacity: usize, - vertex: Buffer, - vertex_capacity: usize, -} - -pub enum UiEvent { - Click(Vec2, MouseButton, bool), -} - -impl UiRenderer { - pub fn new(device: Arc<Device>, queue: Arc<Queue>, format: TextureFormat) -> Self { - let frag_shader = device.create_shader_module(include_wgsl!("shaders/fragment_ui.wgsl")); - let vert_shader = device.create_shader_module(include_wgsl!("shaders/vertex_ui.wgsl")); - - let bind_group_layout = 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, - }); - let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[PushConstantRange { - range: 0..(4 * 4 * size_of::<f32>() as u32), - stages: ShaderStages::VERTEX, - }], - }); - let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { - label: None, - layout: Some(&pipeline_layout), - fragment: Some(FragmentState { - module: &frag_shader, - entry_point: Some("main"), - targets: &[Some(ColorTargetState { - blend: Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING), - format, - write_mask: ColorWrites::all(), - })], - compilation_options: PipelineCompilationOptions::default(), - }), - vertex: VertexState { - module: &vert_shader, - entry_point: Some("main"), - buffers: &[VertexBufferLayout { - array_stride: size_of::<Vertex>() as u64, - step_mode: VertexStepMode::Vertex, - attributes: &vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32], - }], - compilation_options: PipelineCompilationOptions::default(), - }, - primitive: PrimitiveState { - topology: PrimitiveTopology::TriangleList, - front_face: FrontFace::Ccw, - cull_mode: None, //Some(Face::Back), - polygon_mode: PolygonMode::Fill, - ..Default::default() - }, - depth_stencil: Some(DepthStencilState { - format: TextureFormat::Depth32Float, - depth_write_enabled: false, - depth_compare: CompareFunction::LessEqual, - stencil: Default::default(), - bias: Default::default(), - }), - multisample: MultisampleState::default(), - multiview: None, - cache: None, - }); - Self { - ctx: Context::default(), - pipeline, - device, - queue, - bind_group_layout, - last_pointer: Vec2::ZERO, - textures: HashMap::new().into(), - surfaces: HashMap::new().into(), - } - } - - pub fn add_surface( - &mut self, - transform: Affine3A, - content: impl Fn(&Context) -> bool + Send + Sync + 'static, - ) { - let index_capacity = 1024; - let vertex_capacity = 1024; - let index = self.device.create_buffer(&BufferDescriptor { - label: None, - size: (size_of::<f32>() * index_capacity) as u64, - usage: BufferUsages::INDEX | BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - let vertex = self.device.create_buffer(&BufferDescriptor { - label: None, - size: (size_of::<Vertex>() * vertex_capacity) as u64, - usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - let id = ViewportId::from_hash_of(random::<u128>()); - debug!("ui surface added: {id:?}"); - self.surfaces.write().unwrap().insert(id, UiSurface { - transform, - content: Arc::new(content), - index, - vertex, - index_capacity, - vertex_capacity, - size: Vec2::ZERO, - }); - } - - pub fn apply_texture_delta(&self, texid: TextureId, delta: ImageDelta) { - let mut textures = self.textures.write().unwrap(); - let size = Extent3d { - depth_or_array_layers: 1, - width: delta.image.width() as u32, - height: delta.image.height() as u32, - }; - let pixels = match &delta.image { - ImageData::Color(color_image) => color_image.pixels.clone(), - ImageData::Font(font_image) => font_image.srgba_pixels(None).collect(), - }; - - if let Some((_texbg, tex, texsize)) = textures.get_mut(&texid) { - let pos = delta.pos.unwrap_or([0, 0]); - debug!("updating UI texture at {pos:?}"); - self.queue.write_texture( - ImageCopyTexture { - texture: &tex, - mip_level: 0, - origin: Origin3d { - x: pos[0] as u32, - y: pos[1] as u32, - z: 0, - }, - aspect: TextureAspect::All, - }, - bytemuck::cast_slice::<_, u8>(&pixels), - ImageDataLayout { - offset: 0, - bytes_per_row: Some(texsize[0] * 4), - rows_per_image: None, - }, - size, - ); - } else { - assert_eq!( - delta.pos, None, - "partial update impossible; texture does not yet exist" - ); - debug!( - "uploading new UI texture: width={}, height={}", - delta.image.width(), - delta.image.height() - ); - - let texture = self.device.create_texture_with_data( - &self.queue, - &TextureDescriptor { - label: None, - size, - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: TextureFormat::Rgba8UnormSrgb, - usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, - view_formats: &[], - }, - TextureDataOrder::LayerMajor, - bytemuck::cast_slice::<_, u8>(&pixels), - ); - let textureview = texture.create_view(&TextureViewDescriptor::default()); - let sampler = self.device.create_sampler(&SamplerDescriptor { - address_mode_u: AddressMode::ClampToEdge, - address_mode_v: AddressMode::ClampToEdge, - mag_filter: FilterMode::Linear, - min_filter: FilterMode::Linear, - ..Default::default() - }); - let bindgroup = self.device.create_bind_group(&BindGroupDescriptor { - label: None, - layout: &self.bind_group_layout, - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(&textureview), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&sampler), - }, - ], - }); - textures.insert( - texid, - (bindgroup, texture, delta.image.size().map(|e| e as u32)), - ); - } - } - - pub fn draw( - &mut self, - commands: &mut CommandEncoder, - target: &TextureView, - depth: &TextureView, - projection: Mat4, - input_state: &mut InputState, - surface_configuration: &SurfaceConfiguration, - ) { - let mut surfaces = self.surfaces.write().unwrap(); - if surfaces.is_empty() { - return; - } - - let mut rpass = commands.begin_render_pass(&RenderPassDescriptor { - label: None, - color_attachments: &[Some(RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }, - })], - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &depth, - depth_ops: Some(Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }), - stencil_ops: None, - }), - ..Default::default() - }); - - rpass.set_pipeline(&self.pipeline); - - let mut raw_input = egui::RawInput::default(); - raw_input.viewport_id = surfaces.keys().next().copied().unwrap(); - raw_input.viewports = surfaces - .keys() - .map(|k| { - (*k, ViewportInfo { - native_pixels_per_point: Some(2.), - ..Default::default() - }) - }) - .collect(); - - let mut surfaces_closed = Vec::new(); - for (viewport_id, surf) in surfaces.iter_mut() { - let scale = 0.005; - let projection = projection - * Mat4::from_translation(surf.transform.translation.into()) - * Mat4::from_mat3a(surf.transform.matrix3) - * Mat4::from_mat3(Mat3::from_mat2(Mat2::from_cols_array(&[ - scale, 0., 0., -scale, - ]))) - * Mat4::from_translation(-Vec3::new(UI_POSITION_OFFSET, UI_POSITION_OFFSET, 0.)); - - let screen_size = vec2( - surface_configuration.width as f32, - surface_configuration.height as f32, - ); - - if projection.determinant() < 0e-4 { - warn!("bad UI projection") - } - - let unproject = projection.inverse(); - - let unproject_mouse = |pos: Vec2| { - let mouse_xy_clip = (pos / screen_size) * 2. - 1.; - - let mouse_clip_1 = vec4(mouse_xy_clip.x, -mouse_xy_clip.y, 0.0, 1.0); - let mouse_clip_2 = vec4(mouse_xy_clip.x, -mouse_xy_clip.y, 1.0, 1.0); - let mut mouse_world_1 = unproject * mouse_clip_1; - let mut mouse_world_2 = unproject * mouse_clip_2; - mouse_world_1 /= mouse_world_1.w; - mouse_world_2 /= mouse_world_2.w; - let mouse_world_1 = mouse_world_1.xyz(); - let mouse_world_2 = mouse_world_2.xyz(); - - let ray_norm = (mouse_world_2 - mouse_world_1).normalize(); - let ray_t = mouse_world_1.z / ray_norm.z; - let ray_hit = mouse_world_1 - ray_norm * ray_t; - - debug_assert!(ray_hit.z.abs() < 0.1, "mouse was not projected properly"); - - ray_hit.xy() - }; - - let cursor_pos = unproject_mouse(input_state.cursor_pos); - - let mut raw_input = raw_input.clone(); - if cursor_pos != self.last_pointer { - raw_input.events.push(Event::PointerMoved(egui::Pos2::new( - cursor_pos.x, - cursor_pos.y, - ))); - self.last_pointer = cursor_pos; - } - raw_input - .events - .extend(input_state.ui_events.iter().map(|e| match e { - UiEvent::Click(pos, button, down) => egui::Event::PointerButton { - pos: egui::Pos2::from(unproject_mouse(*pos).to_array()), - button: match button { - MouseButton::Left => PointerButton::Primary, - MouseButton::Right => PointerButton::Secondary, - MouseButton::Middle => PointerButton::Middle, - MouseButton::Back => PointerButton::Extra1, - MouseButton::Forward => PointerButton::Extra2, - MouseButton::Other(_) => PointerButton::Extra1, - }, - pressed: *down, - modifiers: egui::Modifiers::default(), - }, - })); - - let mut close = false; - let full_output = self.ctx.run(raw_input.clone(), |ctx| { - close = !(surf.content)(ctx); - surf.size = Vec2::new(ctx.used_size().x, ctx.used_size().y) - }); - if close { - surfaces_closed.push(*viewport_id) - } - - for (texid, delta) in full_output.textures_delta.set { - self.apply_texture_delta(texid, delta); - } - - let clipped_primitives = self - .ctx - .tessellate(full_output.shapes, full_output.pixels_per_point); - - let mut index_count = 0; - let mut vertex_count = 0; - for p in &clipped_primitives { - if let Primitive::Mesh(mesh) = &p.primitive { - index_count += mesh.indices.len(); - vertex_count += mesh.vertices.len(); - } - } - - if index_count == 0 || vertex_count == 0 { - return; - } - - while index_count > surf.index_capacity { - debug!( - "index buffer overflow ({index_count}). expanding {} -> {}", - surf.index_capacity, - surf.index_capacity * 2 - ); - surf.index_capacity *= 2; - surf.index = self.device.create_buffer(&BufferDescriptor { - label: None, - size: (size_of::<u32>() * surf.index_capacity) as u64, - usage: BufferUsages::INDEX | BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - } - while vertex_count > surf.vertex_capacity { - debug!( - "vertex buffer overflow ({vertex_count}). expanding {} -> {}", - surf.vertex_capacity, - surf.vertex_capacity * 2 - ); - surf.vertex_capacity *= 2; - surf.vertex = self.device.create_buffer(&BufferDescriptor { - label: None, - size: (size_of::<Vertex>() * surf.vertex_capacity) as u64, - usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - } - - let mut mapped_index = self - .queue - .write_buffer_with( - &surf.index, - 0, - NonZeroU64::new((size_of::<u32>() * index_count) as u64).unwrap(), - ) - .expect("ui index buffer overflow"); - let mut mapped_vertex = self - .queue - .write_buffer_with( - &surf.vertex, - 0, - NonZeroU64::new((size_of::<Vertex>() * vertex_count) as u64).unwrap(), - ) - .expect("ui vertex buffer overflow"); - - let mut index_offset = 0; - let mut vertex_offset = 0; - let mut slices = Vec::new(); - for p in clipped_primitives { - if let Primitive::Mesh(mesh) = p.primitive { - mapped_index[index_offset * size_of::<u32>() - ..(index_offset + mesh.indices.len()) * size_of::<u32>()] - .copy_from_slice(bytemuck::cast_slice(&mesh.indices)); - mapped_vertex[vertex_offset * size_of::<Vertex>() - ..(vertex_offset + mesh.vertices.len()) * size_of::<Vertex>()] - .copy_from_slice(bytemuck::cast_slice(&mesh.vertices)); - slices.push(( - index_offset as u32..index_offset as u32 + mesh.indices.len() as u32, - vertex_offset as i32, - mesh.texture_id, - )); - index_offset += mesh.indices.len(); - vertex_offset += mesh.vertices.len(); - } - } - - assert_eq!(index_count, index_offset); - assert_eq!(vertex_count, vertex_offset); - - let projection = projection.to_cols_array().map(|v| v.to_le_bytes()); - - rpass.set_push_constants(ShaderStages::VERTEX, 0, projection.as_flattened()); - rpass.set_index_buffer(surf.index.slice(..), IndexFormat::Uint32); - rpass.set_vertex_buffer(0, surf.vertex.slice(..)); - for (index, base_vertex, texid) in slices { - let tex_guard = self.textures.read().unwrap(); - let bind_group = &tex_guard.get(&texid).unwrap().0; - rpass.set_bind_group(0, bind_group, &[]); - rpass.draw_indexed(index, base_vertex, 0..1); - } - } - for s in surfaces_closed { - debug!("ui surface closed: {s:?}"); - surfaces.remove(&s); - } - - input_state.ui_events.clear(); - } -} |