diff --git a/Cargo.lock b/Cargo.lock index bc0ded5..de61af6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -939,6 +939,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const_panic" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b" + [[package]] name = "core-foundation" version = "0.9.4" @@ -1051,7 +1057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" dependencies = [ "bitflags 2.5.0", - "libloading 0.7.4", + "libloading 0.8.3", "winapi", ] @@ -1230,6 +1236,37 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "encase" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9299a95fa5671ddf29ecc22b00e121843a65cb9ff24911e394b4ae556baf36" +dependencies = [ + "const_panic", + "encase_derive", + "thiserror", +] + +[[package]] +name = "encase_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e09decb3beb1fe2db6940f598957b2e1f7df6206a804d438ff6cb2a9cddc10" +dependencies = [ + "encase_derive_impl", +] + +[[package]] +name = "encase_derive_impl" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd31dbbd9743684d339f907a87fe212cb7b51d75b9e8e74181fe363199ee9b47" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "enumflags2" version = "0.7.10" @@ -3611,6 +3648,8 @@ dependencies = [ "eframe", "egui", "egui-wgpu", + "encase", + "nalgebra", "slicer", "wgpu", ] diff --git a/ui/Cargo.toml b/ui/Cargo.toml index 75b84e9..5621a3e 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -12,3 +12,5 @@ egui-wgpu = "0.27.2" wgpu = "0.19.4" slicer = { path = "../slicer" } +encase = "0.8.0" +nalgebra = "0.32.6" diff --git a/ui/src/main.rs b/ui/src/main.rs index f162f9e..274066f 100644 --- a/ui/src/main.rs +++ b/ui/src/main.rs @@ -2,20 +2,38 @@ use std::{fs::File, mem}; use anyhow::Result; use eframe::NativeOptions; -use egui::{CentralPanel, Frame, Sense, TopBottomPanel, Vec2, Window}; +use egui::{CentralPanel, Frame, Sense, Slider, Stroke, TopBottomPanel, Vec2, Window}; use egui_wgpu::{Callback, CallbackTrait}; -use slicer::mesh::Mesh; +use nalgebra::{Matrix4, Point3, Vector3}; +use slicer::{mesh::Mesh, Pos}; use wgpu::{ - BindGroup, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, Buffer, BufferAddress, BufferBindingType, BufferDescriptor, BufferSize, BufferUsages, ColorTargetState, ColorWrites, Face, FragmentState, IndexFormat, MultisampleState, PipelineLayoutDescriptor, PrimitiveState, RenderPipeline, RenderPipelineDescriptor, ShaderModuleDescriptor, ShaderStages, TextureFormat, VertexAttribute, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode + BindGroup, BindGroupEntry, BindGroupLayoutDescriptor, Buffer, BufferAddress, BufferBinding, + BufferDescriptor, BufferUsages, ColorTargetState, ColorWrites, FragmentState, IndexFormat, + MultisampleState, PipelineLayoutDescriptor, PrimitiveState, RenderPipeline, + RenderPipelineDescriptor, ShaderModuleDescriptor, TextureFormat, VertexAttribute, + VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }; const TEXTURE_FORMAT: TextureFormat = TextureFormat::Bgra8Unorm; -struct App {} +struct App { + camera: Camera, +} + +struct Camera { + eye: Point3, + target: Point3, + up: Vector3, + fovy: f32, + znear: f32, + zfar: f32, +} struct WorkspaceRenderResources { vertex_buffer: Buffer, index_buffer: Buffer, + uniform_buffer: Buffer, + render_pipeline: RenderPipeline, bind_group: BindGroup, @@ -51,9 +69,25 @@ fn main() -> Result<()> { source: wgpu::ShaderSource::Wgsl(include_str!("shader/workspace.wgsl").into()), }); + let uniform_buffer = device.create_buffer(&BufferDescriptor { + label: None, + size: mem::size_of::() as u64, + usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { label: None, - entries: &[], + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], }); let vertex_buffers = [VertexBufferLayout { @@ -107,7 +141,14 @@ fn main() -> Result<()> { let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &bind_group_layout, - entries: &[], + entries: &[BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(BufferBinding { + buffer: &uniform_buffer, + offset: 0, + size: None, + }), + }], }); render_state @@ -117,12 +158,24 @@ fn main() -> Result<()> { .insert(WorkspaceRenderResources { vertex_buffer, index_buffer, + uniform_buffer, + render_pipeline, bind_group, + modal: test_modal, }); - Box::new(App {}) + Box::new(App { + camera: Camera { + eye: Point3::new(0.0, -50.0, 5.0), + target: Point3::new(0.0, 50.0, 0.0), + up: Vector3::new(0.0, 1.0, 0.0), + fovy: 25.0, + znear: 0.1, + zfar: 100.0, + }, + }) }), ) .unwrap(); @@ -142,18 +195,43 @@ impl eframe::App for App { }); }); - CentralPanel::default().show(ctx, |ui| { - Frame::canvas(ui.style()).show(ui, |ui| { - let (rect, response) = ui.allocate_exact_size(Vec2::splat(300.0), Sense::drag()); + Window::new("Controls").show(ctx, |ui| {}); - let callback = Callback::new_paint_callback(rect, WorkspaceRenderCallback); + CentralPanel::default() + .frame(Frame::none()) + .show(ctx, |ui| { + let (rect, _response) = ui.allocate_exact_size(ui.available_size(), Sense::drag()); + + let callback = Callback::new_paint_callback( + rect, + WorkspaceRenderCallback { + transform: self + .camera + .build_view_projection_matrix(rect.width() / rect.height()), + }, + ); ui.painter().add(callback); }); - }); } } -struct WorkspaceRenderCallback; +impl Camera { + fn build_view_projection_matrix(&self, aspect: f32) -> Matrix4 { + const OPENGL_TO_WGPU_MATRIX: Matrix4 = Matrix4::new( + 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0, + ); + + let fov = self.fovy * std::f32::consts::PI / 180.0; + + let view = Matrix4::look_at_rh(&self.eye, &self.target, &self.up); + let proj = Matrix4::new_perspective(aspect, fov, self.znear, self.zfar); + OPENGL_TO_WGPU_MATRIX * proj * view + } +} + +struct WorkspaceRenderCallback { + transform: Matrix4, +} impl CallbackTrait for WorkspaceRenderCallback { fn prepare( @@ -186,6 +264,8 @@ impl CallbackTrait for WorkspaceRenderCallback { bytemuck::cast_slice(&resources.modal.faces), ); + queue.write_buffer(&resources.uniform_buffer, 0, &self.to_wgsl()); + Vec::new() } @@ -207,6 +287,13 @@ impl CallbackTrait for WorkspaceRenderCallback { } } +impl WorkspaceRenderCallback { + fn to_wgsl(&self) -> Vec { + let mut out = Vec::new(); + out.extend_from_slice(bytemuck::cast_slice(&self.transform.as_slice())); + out + } +} #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] pub struct ModelVertex { diff --git a/ui/src/shader/workspace.wgsl b/ui/src/shader/workspace.wgsl index a9ffcd6..0e18ca8 100644 --- a/ui/src/shader/workspace.wgsl +++ b/ui/src/shader/workspace.wgsl @@ -1,3 +1,9 @@ +@group(0) @binding(0) var context: Context; + +struct Context { + transform: mat4x4, +} + struct VertexOutput { @builtin(position) position: vec4, @@ -11,7 +17,7 @@ fn vert( @location(1) tex_coord: vec2, ) -> VertexOutput { var out: VertexOutput; - out.position = position; + out.position = context.transform * position; out.tex_coord = tex_coord; return out; }