diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..732295e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,260 @@ +#![feature(gen_blocks)] + +use std::{convert::identity, error::Error, f32::consts::PI}; + +use glam::{Mat4, UVec2, Vec3, uvec2, vec3}; + +use crate::{ + camera::OrbitalCamera, + render::lines::{LookParams, Mesh, Pipeline, Vertex}, + trace::{Scene, Source, Sphere}, +}; + +mod camera; +mod ray; +mod render; +mod trace; + +const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; + +pub struct Gpu { + device: wgpu::Device, + queue: wgpu::Queue, + surface: wgpu::Surface<'static>, +} + +pub struct Core { + device: wgpu::Device, + queue: wgpu::Queue, + surface: wgpu::Surface<'static>, + + pipeline: Pipeline, + tripod: Mesh, +} + +pub fn new_tripod(device: &wgpu::Device) -> Mesh { + Mesh::new( + device, + &[ + Vertex::new(vec3(0., 0., 0.), vec3(1., 0., 0.)), + Vertex::new(vec3(1., 0., 0.), vec3(1., 0., 0.)), + Vertex::new(vec3(0., 0., 0.), vec3(0., 1., 0.)), + Vertex::new(vec3(0., 1., 0.), vec3(0., 1., 0.)), + Vertex::new(vec3(0., 0., 0.), vec3(0., 0., 1.)), + Vertex::new(vec3(0., 0., 1.), vec3(0., 0., 1.)), + ], + ) +} + +fn loop_list(iter: impl IntoIterator) -> impl Iterator { + loop_list_ex(iter, identity, identity) +} + +fn loop_list_ex( + iter: impl IntoIterator, + mut fa: impl FnMut(T) -> U, + mut fb: impl FnMut(T) -> U, +) -> impl Iterator { + gen move { + let mut iter = iter.into_iter(); + let Some(first) = iter.next() else { return }; + yield fa(first.clone()); + for item in iter { + yield fb(item.clone()); + yield fa(item); + } + yield fb(first); + } +} + +impl Core { + pub fn new(gpu: Gpu) -> Self { + let Gpu { + device, + queue, + surface, + } = gpu; + + let pipeline = Pipeline::new(&device, OUTPUT_FORMAT); + let tripod = new_tripod(&device); + queue.submit([]); // flush buffer updates + + let mut this = Self { + device, + queue, + surface, + pipeline, + tripod, + }; + this.configure(uvec2(1, 1)); // ensure we always have a valid surface to work with + this + } + + fn render(&self, output: &wgpu::Texture) { + let camera = OrbitalCamera { + position_yaw: PI / 4., + position_pitch: 0.5f32.sqrt().atan(), + distance: 3.0, + }; + let aspect = { + let size = output.size(); + let w = size.width as f32; + let h = size.height as f32; + w / h + }; + let perspective = Mat4::perspective_lh(PI / 3., aspect, 1e-2, 1e2); + self.pipeline.set_look( + &self.queue, + LookParams { + m: perspective * camera.transform(), + }, + ); + self.queue.submit([]); // flush buffer updates + + let view = output.create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.8, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + ..Default::default() + }); + self.pipeline.render(&mut pass, [&self.tripod]); + + let source = Source { + position_yaw: 0.0, + position_pitch: PI / 3., + distance: 1.0, + radius: 0.125, + spread: 0.125, + }; + + let contour: Vec = loop_list(source.contour(17)) + .map(|pos| Vertex { + pos, + color: vec3(1., 1., 1.), + }) + .collect(); + self.pipeline + .render(&mut pass, [&Mesh::new(&self.device, &contour)]); + + const BASE_R: f32 = 2.; + const BASE_POS: Vec3 = vec3(0., 0., -BASE_R); + const BASE: Sphere = Sphere { + position: vec3(0., 0., -BASE_R), + radius: BASE_R, + }; + fn sphere(pos: Vec3) -> Sphere { + Sphere { + position: pos, + radius: BASE_POS.distance(pos) - BASE_R, + } + } + let scene = Scene { + objects: vec![ + BASE, + sphere(vec3(0., 0., 0.1)), + sphere(vec3(0.3, 0., 0.1)), + sphere(vec3(0.1, 0.3, 0.1)), + ], + }; + + let mut prng = rand_pcg::Pcg64::new(42, 0); + let rays: Vec = (0..10000) + .flat_map(|_| { + let ray = source.make_ray(&mut prng); + if let Some(ray) = scene.trace_ray(ray) { + [ + Vertex { + pos: ray.base - 0.02 * ray.dir, + color: vec3(1., 1., 1.), + }, + Vertex { + pos: ray.base, + color: vec3(0., 1., 0.), + }, + ] + } else { + [ + Vertex { + pos: ray.base, + color: vec3(1., 1., 1.), + }, + Vertex { + pos: ray.base + 0.1 * ray.dir, + color: vec3(1., 0., 0.), + }, + ] + } + }) + .collect(); + self.pipeline + .render(&mut pass, [&Mesh::new(&self.device, &rays)]); + + drop(pass); + self.queue.submit(std::iter::once(encoder.finish())); + } + + pub fn configure(&mut self, pixel_size: UVec2) { + self.surface.configure( + &self.device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, + format: OUTPUT_FORMAT, + width: pixel_size.x, + height: pixel_size.y, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![], + desired_maximum_frame_latency: 2, + }, + ); + } + + pub fn redraw(&mut self) { + let output = self.surface.get_current_texture().unwrap(); + self.render(&output.texture); + output.present(); + } +} + +pub async fn init_gpu_inner( + make_surface: impl FnOnce(&wgpu::Instance) -> Result, E>, +) -> Result> { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() + }); + let surface = make_surface(&instance)?; + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor::default()) + .await + .unwrap(); + Ok(Gpu { + device, + queue, + surface, + }) +} diff --git a/src/main.rs b/src/main.rs index 4491e80..e32b546 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,7 @@ -#![feature(gen_blocks)] +use std::sync::Arc; -use std::{convert::identity, error::Error, f32::consts::PI, sync::Arc}; - -use glam::{Mat4, Vec3, vec3}; +use glam::uvec2; +use photon_light::{Core, init_gpu_inner}; use winit::{ application::ApplicationHandler, event::WindowEvent, @@ -10,236 +9,34 @@ use winit::{ window::Window, }; -use crate::{ - camera::OrbitalCamera, - render::lines::{LookParams, Mesh, Pipeline, Vertex}, - trace::{Scene, Source, Sphere}, -}; - -mod camera; -mod ray; -mod render; -mod trace; - const TITLE: &str = "WGPU example"; -const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; struct MainWindow { - handle: Arc, - device: wgpu::Device, - queue: wgpu::Queue, - surface: wgpu::Surface<'static>, - surface_configured: bool, - - pipeline: Pipeline, - tripod: Mesh, -} - -pub fn new_tripod(device: &wgpu::Device) -> Mesh { - Mesh::new( - device, - &[ - Vertex::new(vec3(0., 0., 0.), vec3(1., 0., 0.)), - Vertex::new(vec3(1., 0., 0.), vec3(1., 0., 0.)), - Vertex::new(vec3(0., 0., 0.), vec3(0., 1., 0.)), - Vertex::new(vec3(0., 1., 0.), vec3(0., 1., 0.)), - Vertex::new(vec3(0., 0., 0.), vec3(0., 0., 1.)), - Vertex::new(vec3(0., 0., 1.), vec3(0., 0., 1.)), - ], - ) -} - -fn loop_list(iter: impl IntoIterator) -> impl Iterator { - loop_list_ex(iter, identity, identity) -} - -fn loop_list_ex( - iter: impl IntoIterator, - mut fa: impl FnMut(T) -> U, - mut fb: impl FnMut(T) -> U, -) -> impl Iterator { - gen move { - let mut iter = iter.into_iter(); - let Some(first) = iter.next() else { return }; - yield fa(first.clone()); - for item in iter { - yield fb(item.clone()); - yield fa(item); - } - yield fb(first); - } + window: Arc, + core: Core, } impl MainWindow { fn new(event_loop: &ActiveEventLoop) -> Self { - let handle = event_loop + let window = event_loop .create_window(Window::default_attributes().with_title(TITLE)) .unwrap(); - let handle = Arc::new(handle); - - let (device, queue, surface) = pollster::block_on(init_gpu(Arc::clone(&handle))).unwrap(); - let pipeline = Pipeline::new(&device, OUTPUT_FORMAT); - let tripod = new_tripod(&device); - queue.submit([]); // flush buffer updates - - Self { - handle, - device, - queue, - surface, - surface_configured: false, - pipeline, - tripod, - } - } - - fn render(&self, output: &wgpu::Texture) { - let camera = OrbitalCamera { - position_yaw: PI / 4., - position_pitch: 0.5f32.sqrt().atan(), - distance: 3.0, - }; - let aspect = { - let size = output.size(); - let w = size.width as f32; - let h = size.height as f32; - w / h - }; - let perspective = Mat4::perspective_lh(PI / 3., aspect, 1e-2, 1e2); - self.pipeline.set_look( - &self.queue, - LookParams { - m: perspective * camera.transform(), - }, - ); - self.queue.submit([]); // flush buffer updates - - let view = output.create_view(&wgpu::TextureViewDescriptor::default()); - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - depth_slice: None, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.1, - g: 0.2, - b: 0.8, - a: 1.0, - }), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - ..Default::default() - }); - self.pipeline.render(&mut pass, [&self.tripod]); - - let source = Source { - position_yaw: 0.0, - position_pitch: PI / 3., - distance: 1.0, - radius: 0.125, - spread: 0.125, - }; - - let contour: Vec = loop_list(source.contour(17)) - .map(|pos| Vertex { - pos, - color: vec3(1., 1., 1.), - }) - .collect(); - self.pipeline - .render(&mut pass, [&Mesh::new(&self.device, &contour)]); - - const BASE_R: f32 = 2.; - const BASE_POS: Vec3 = vec3(0., 0., -BASE_R); - const BASE: Sphere = Sphere { - position: vec3(0., 0., -BASE_R), - radius: BASE_R, - }; - fn sphere(pos: Vec3) -> Sphere { - Sphere { - position: pos, - radius: BASE_POS.distance(pos) - BASE_R, - } - } - let scene = Scene { - objects: vec![ - BASE, - sphere(vec3(0., 0., 0.1)), - sphere(vec3(0.3, 0., 0.1)), - sphere(vec3(0.1, 0.3, 0.1)), - ], - }; - - let mut prng = rand_pcg::Pcg64::new(42, 0); - let rays: Vec = (0..10000) - .flat_map(|_| { - let ray = source.make_ray(&mut prng); - if let Some(ray) = scene.trace_ray(ray) { - [ - Vertex { - pos: ray.base - 0.02 * ray.dir, - color: vec3(1., 1., 1.), - }, - Vertex { - pos: ray.base, - color: vec3(0., 1., 0.), - }, - ] - } else { - [ - Vertex { - pos: ray.base, - color: vec3(1., 1., 1.), - }, - Vertex { - pos: ray.base + 0.1 * ray.dir, - color: vec3(1., 0., 0.), - }, - ] - } - }) - .collect(); - self.pipeline - .render(&mut pass, [&Mesh::new(&self.device, &rays)]); - - drop(pass); - self.queue.submit(std::iter::once(encoder.finish())); + let window = Arc::new(window); + let gpu = pollster::block_on(init_gpu_inner(|instance| { + instance.create_surface(Arc::clone(&window)) + })) + .unwrap(); + let core = Core::new(gpu); + Self { window, core } } fn event(&mut self, event_loop: &ActiveEventLoop, event: WindowEvent) { match event { WindowEvent::CloseRequested => event_loop.exit(), - WindowEvent::Resized(physical_size) => { - self.surface.configure( - &self.device, - &wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::COPY_DST, - format: OUTPUT_FORMAT, - width: physical_size.width, - height: physical_size.height, - present_mode: wgpu::PresentMode::Fifo, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![], - desired_maximum_frame_latency: 2, - }, - ); - self.surface_configured = true; - } - WindowEvent::RedrawRequested => { - if !self.surface_configured { - return; - } - let output = self.surface.get_current_texture().unwrap(); - self.render(&output.texture); - output.present(); - } + WindowEvent::Resized(physical_size) => self + .core + .configure(uvec2(physical_size.width, physical_size.height)), + WindowEvent::RedrawRequested => self.core.redraw(), _ => {} } } @@ -270,34 +67,11 @@ impl ApplicationHandler for Application { .main_window .as_mut() .expect("window must exist to recieve events"); - assert_eq!(window.handle.id(), window_id); + assert_eq!(window.window.id(), window_id); window.event(event_loop, event); } } -async fn init_gpu<'window>( - wnd: impl wgpu::WindowHandle + 'window, -) -> Result<(wgpu::Device, wgpu::Queue, wgpu::Surface<'window>), Box> { - let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - ..Default::default() - }); - let surface = instance.create_surface(wnd)?; - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .await - .unwrap(); - let (device, queue) = adapter - .request_device(&wgpu::DeviceDescriptor::default()) - .await - .unwrap(); - Ok((device, queue, surface)) -} - fn main() { let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Wait);