summaryrefslogtreecommitdiff
path: root/client/src/ui.rs
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-01-26 15:10:37 +0100
committermetamuffin <metamuffin@disroot.org>2025-01-26 15:10:37 +0100
commit66930534a0647e2613360658a6a99eed945e2f0f (patch)
tree31a769910ef924a11206f1024b4004f74b1e396f /client/src/ui.rs
parent0163f8486ceca8bd6897c1074f6846f36827d040 (diff)
downloadweareserver-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.rs523
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();
- }
-}