From 3ecd4588d4aa85a93a06aa5a1a3a60b918a72557 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Fri, 10 Jan 2025 02:20:21 +0100 Subject: some progress on egui --- Cargo.lock | 1 + client/Cargo.toml | 3 +- client/src/camera.rs | 6 +- client/src/main.rs | 2 +- client/src/network.rs | 5 +- client/src/scene_prepare.rs | 10 +- client/src/ui.rs | 238 +++++++++++++++++++++++++++++++++++++++++--- client/src/ui.wgsl | 43 ++++++++ world/src/mesh.rs | 3 + 9 files changed, 284 insertions(+), 27 deletions(-) create mode 100644 client/src/ui.wgsl diff --git a/Cargo.lock b/Cargo.lock index 904c78a..0f36056 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3008,6 +3008,7 @@ version = "0.1.0" dependencies = [ "anyhow", "audiopus", + "bytemuck", "clap", "cpal", "egui", diff --git a/client/Cargo.toml b/client/Cargo.toml index de8bf01..a020315 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -18,4 +18,5 @@ rand = "0.9.0-beta.1" glam = "0.29.2" image = "0.25.5" egui-wgpu = "0.30.0" -egui = "0.30.0" +egui = { version = "0.30.0", features = ["bytemuck"] } +bytemuck = "1.21.0" diff --git a/client/src/camera.rs b/client/src/camera.rs index 3876e8a..d7a69c5 100644 --- a/client/src/camera.rs +++ b/client/src/camera.rs @@ -27,7 +27,7 @@ impl Camera { pub fn new() -> Self { Self { aspect: 1., - fov: 0.5, + fov: 1., pos: Vec3::Z * 3., rot: Vec3::ZERO, } @@ -41,10 +41,6 @@ impl Camera { Mat3::from_euler(EulerRot::YXZ, self.rot.x, self.rot.y, self.rot.z) } pub fn to_matrix(&self) -> Mat4 { - // let tdir = - // Mat3::from_euler(EulerRot::ZXY, self.rot.x, self.rot.y, self.rot.z) * Vec3::NEG_Z; - // * Mat4::look_at_rh(self.pos, self.pos + tdir, Vec3::Y) - Mat4::perspective_infinite_reverse_rh(self.fov, self.aspect, 0.1) * Mat4::from_mat3(self.rotation_mat().inverse()) * Mat4::from_translation(-self.pos) diff --git a/client/src/main.rs b/client/src/main.rs index 40c6e3e..0ee665c 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -23,7 +23,7 @@ pub mod scene_prepare; pub mod scene_render; pub mod state; pub mod window; -// pub mod ui; +pub mod ui; use anyhow::Result; use clap::Parser; diff --git a/client/src/network.rs b/client/src/network.rs index 57b637a..73c4ab2 100644 --- a/client/src/network.rs +++ b/client/src/network.rs @@ -14,15 +14,14 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ +use anyhow::Result; +use log::{debug, info, warn}; use std::{ io::{BufReader, BufWriter, Write}, net::TcpStream, sync::mpsc::{Receiver, Sender, channel}, thread::spawn, }; - -use anyhow::Result; -use log::{debug, info, warn}; use weareshared::{helper::ReadWrite, packets::Packet}; pub struct Network { diff --git a/client/src/scene_prepare.rs b/client/src/scene_prepare.rs index 1807e39..f74277c 100644 --- a/client/src/scene_prepare.rs +++ b/client/src/scene_prepare.rs @@ -30,9 +30,9 @@ use weareshared::{ resources::{AttributeArray, Image, IndexArray, MeshPart, Prefab}, }; use wgpu::{ - BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindingResource, Buffer, - BufferUsages, Device, Extent3d, Queue, SamplerDescriptor, Texture, TextureDescriptor, - TextureDimension, TextureFormat, TextureUsages, TextureViewDescriptor, + AddressMode, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindingResource, + Buffer, BufferUsages, Device, Extent3d, FilterMode, Queue, SamplerDescriptor, Texture, + TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureViewDescriptor, util::{BufferInitDescriptor, DeviceExt, TextureDataOrder}, }; @@ -299,6 +299,10 @@ fn create_texture( ); 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, ..Default::default() }); let bindgroup = device.create_bind_group(&BindGroupDescriptor { diff --git a/client/src/ui.rs b/client/src/ui.rs index b99ae19..638978a 100644 --- a/client/src/ui.rs +++ b/client/src/ui.rs @@ -14,57 +14,267 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -use egui::{Context, epaint::Primitive}; -use wgpu::{Buffer, BufferDescriptor, BufferUsages, Device, Queue}; +use egui::{ + Context, TextureId, + epaint::{Primitive, Vertex}, +}; +use std::{collections::HashMap, num::NonZeroU64}; +use wgpu::{ + BindGroup, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendState, Buffer, + BufferDescriptor, BufferUsages, Color, ColorTargetState, ColorWrites, CommandEncoder, + CompareFunction, DepthBiasState, DepthStencilState, Device, FragmentState, FrontFace, + IndexFormat, LoadOp, MultisampleState, Operations, PipelineCompilationOptions, + PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, PushConstantRange, + Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, + RenderPipelineDescriptor, SamplerBindingType, ShaderStages, StencilState, StoreOp, Texture, + TextureFormat, TextureSampleType, TextureView, TextureViewDimension, VertexAttribute, + VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, include_wgsl, +}; pub struct UiRenderer { ctx: Context, + + pipeline: RenderPipeline, + + index: Buffer, + vertex: Buffer, + textures: HashMap, } impl UiRenderer { - pub fn new(device: &Device) -> Self { + pub fn new(device: &Device, format: TextureFormat) -> Self { let index = device.create_buffer(&BufferDescriptor { label: None, - size: 1, + size: size_of::() as u64 * 1024 * 1024, usage: BufferUsages::INDEX | BufferUsages::COPY_DST, mapped_at_creation: false, }); let vertex = device.create_buffer(&BufferDescriptor { label: None, - size: 1, + size: size_of::() as u64 * 1024 * 1024, usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, mapped_at_creation: false, }); + let module = device.create_shader_module(include_wgsl!("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::() as u32), + stages: ShaderStages::VERTEX, + }], + }); + let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + fragment: Some(FragmentState { + module: &module, + entry_point: Some("fs_main"), + targets: &[Some(ColorTargetState { + blend: Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING), + format, + write_mask: ColorWrites::all(), + })], + compilation_options: PipelineCompilationOptions::default(), + }), + vertex: VertexState { + module: &module, + entry_point: Some("vs_main"), + buffers: &[VertexBufferLayout { + array_stride: size_of::() as u64, + step_mode: VertexStepMode::Vertex, + attributes: &[ + VertexAttribute { + format: VertexFormat::Float32x2, + offset: 0, + shader_location: 0, + }, + VertexAttribute { + format: VertexFormat::Float32x2, + offset: size_of::() as u64 * 2, + shader_location: 1, + }, + VertexAttribute { + format: VertexFormat::Float32x3, + offset: size_of::() as u64 * 4, + shader_location: 2, + }, + ], + }], + 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 { + depth_compare: CompareFunction::Greater, + depth_write_enabled: true, + format: TextureFormat::Depth32Float, + bias: DepthBiasState::default(), + stencil: StencilState::default(), + }), + multisample: MultisampleState::default(), + multiview: None, + cache: None, + }); Self { ctx: Context::default(), + pipeline, index, vertex, + textures: HashMap::new(), } } - pub fn draw(&self, queue: &Queue) { + pub fn create_texture() { + // 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: TextureFormat::Rgba8UnormSrgb, + // usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + // view_formats: &[], + // }, + // TextureDataOrder::LayerMajor, + // data, + // ); + // let textureview = texture.create_view(&TextureViewDescriptor::default()); + // let sampler = device.create_sampler(&SamplerDescriptor { + // ..Default::default() + // }); + // let bindgroup = device.create_bind_group(&BindGroupDescriptor { + // label: None, + // layout: &bgl, + // entries: &[ + // BindGroupEntry { + // binding: 0, + // resource: BindingResource::TextureView(&textureview), + // }, + // BindGroupEntry { + // binding: 1, + // resource: BindingResource::Sampler(&sampler), + // }, + // ], + // }); + } + + pub fn draw(&self, queue: &Queue, commands: &mut CommandEncoder, target: &TextureView) { let raw_input = egui::RawInput::default(); let full_output = self.ctx.run(raw_input, |ctx| { egui::CentralPanel::default().show(&ctx, |ui| { ui.label("Hello world!"); - if ui.button("Click me").clicked() { - // take some action here - } + ui.button("Click me").clicked(); }); }); + for (texid, delta) in full_output.textures_delta.set {} - // handle_platform_output(full_output.platform_output); 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 { + vertex_count += mesh.vertices.len(); + index_count += mesh.vertices.len(); + } + } + + // TODO realloc buffers if overflowing + + let mut mapped_index = queue + .write_buffer_with( + &self.index, + 0, + NonZeroU64::new((size_of::() * index_count) as u64).unwrap(), + ) + .expect("ui index buffer overflow"); + let mut mapped_vertex = queue + .write_buffer_with( + &self.index, + 0, + NonZeroU64::new((size_of::() * 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 { - match p.primitive { - Primitive::Mesh(mesh) => {} - _ => unreachable!(), + if let Primitive::Mesh(mesh) = p.primitive { + mapped_index[index_offset..index_offset + (mesh.indices.len() * size_of::())] + .copy_from_slice(bytemuck::cast_slice(&mesh.indices)); + mapped_vertex + [vertex_offset..vertex_offset + (mesh.vertices.len() * size_of::())] + .copy_from_slice(bytemuck::cast_slice(&mesh.vertices)); + index_offset += mesh.indices.len(); + vertex_offset += mesh.vertices.len(); + slices.push(( + index_offset as u32..index_offset as u32 + mesh.indices.len() as u32, + vertex_offset as i32, + )); } } - // paint(full_output.textures_delta, clipped_primitives); + + let mut rpass = commands.begin_render_pass(&RenderPassDescriptor { + label: None, + color_attachments: &[Some(RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: Operations { + store: StoreOp::Store, + load: LoadOp::Clear(Color { + r: 0.01, + g: 0.01, + b: 0.01, + a: 1., + }), + }, + })], + ..Default::default() + }); + + rpass.set_pipeline(&self.pipeline); + rpass.set_index_buffer(self.index.slice(..), IndexFormat::Uint32); + rpass.set_vertex_buffer(0, self.vertex.slice(..)); + for (index, base_vertex) in slices { + // rpass.set_bind_group(0, &self.bind_group, &[]); + rpass.draw_indexed(index, base_vertex, 0..1); + } } } diff --git a/client/src/ui.wgsl b/client/src/ui.wgsl new file mode 100644 index 0000000..174f20c --- /dev/null +++ b/client/src/ui.wgsl @@ -0,0 +1,43 @@ +// 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 . + +struct VertexIn { + @location(0) pos: vec2, + @location(1) uv: vec2, + @location(2) color: vec3, +} +struct VertexOut { + @builtin(position) clip: vec4, + @location(0) uv: vec2, + @location(1) color: vec3, +} + +@group(0) @binding(0) var texture: texture_2d; +@group(0) @binding(1) var texture_sampler: sampler; +var project: mat4x4; + +@vertex +fn vs_main(vi: VertexIn) -> VertexOut { + var clip = project * vec4(vi.pos, 0., -1.); + clip /= clip.w; + clip.x *= -1.; + clip.y *= -1.; + let vo = VertexOut(clip, vi.uv, vi.color); + return vo; +} +@fragment +fn fs_main(vo: VertexOut) -> @location(0) vec4 { + return textureSample(texture, texture_sampler, vo.uv) * vec4(vo.color, 1.); +} diff --git a/world/src/mesh.rs b/world/src/mesh.rs index 7d17cc9..c621753 100644 --- a/world/src/mesh.rs +++ b/world/src/mesh.rs @@ -314,6 +314,9 @@ pub fn import_mesh( att }); let g_refractive_index = p.material().ior(); + if let Some(i) = g_refractive_index { + info!("refractive index is {i}"); + } let g_thickness = p.material().volume().map(|v| v.thickness_factor()); let name = mesh.name().map(|e| e.to_owned()); -- cgit v1.2.3-70-g09d2