Compare commits
	
		
			3 Commits
		
	
	
		
			221d544a01
			...
			68843293ff
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 68843293ff | |||
| 0a1e0f98c6 | |||
| a94ab87eab | 
							
								
								
									
										1427
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1427
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -5,48 +5,47 @@ use rand::Rng; | ||||
| use rand::distributions::Standard; | ||||
| use rand::prelude::Distribution; | ||||
| use rand_xoshiro::Xoshiro128Plus; | ||||
| use spirv_std::spirv; | ||||
| use spirv_std::num_traits::Float; | ||||
| use spirv_std::spirv; | ||||
|  | ||||
| #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] | ||||
| #[repr(C)] | ||||
| pub struct ImageConstants { | ||||
|     accum_width: i32, | ||||
|     accum_height: i32, | ||||
|     viewport_width: i32, | ||||
|     viewport_height: i32, | ||||
|     background_color: Vec4, | ||||
| pub struct ViewportConstants { | ||||
|     offset_x: i32, | ||||
|     offset_y: i32, | ||||
|     width: i32, | ||||
|     height: i32, | ||||
| } | ||||
|  | ||||
| impl ImageConstants { | ||||
| impl ViewportConstants { | ||||
|     pub fn new( | ||||
|         accum_width: i32, | ||||
|         accum_height: i32, | ||||
|         viewport_width: i32, | ||||
|         viewport_height: i32, | ||||
|         background_color: Vec4, | ||||
|         offset_x: i32, | ||||
|         offset_y: i32, | ||||
|         width: i32, | ||||
|         height: i32, | ||||
|     ) -> Self { | ||||
|         Self { | ||||
|             accum_width, | ||||
|             accum_height, | ||||
|             viewport_width, | ||||
|             viewport_height, | ||||
|             background_color, | ||||
|             offset_x, offset_y, width, height | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] | ||||
| #[repr(C)] | ||||
| pub struct AccumConstants { | ||||
|     pub width: i32, | ||||
|     pub height: i32, | ||||
| } | ||||
|  | ||||
| impl AccumConstants { | ||||
|     pub fn new(width: i32, height: i32) -> Self { | ||||
|         Self { | ||||
|             width, height | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn viewport_dimensions(&self) -> glam::IVec2 { | ||||
|         glam::ivec2(self.viewport_width, self.viewport_height) | ||||
|     } | ||||
|  | ||||
|     pub fn with_accumulate(&mut self, width: i32, height: i32) { | ||||
|         self.accum_width = width; | ||||
|         self.accum_height = height; | ||||
|     } | ||||
|  | ||||
|     pub fn with_viewport(&mut self, width: i32, height: i32) { | ||||
|         self.viewport_width = width; | ||||
|         self.viewport_height = height; | ||||
|     pub fn min_dimension(&self) -> i32 { | ||||
|         if self.width < self.height { self.width } else { self.height } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -61,11 +60,22 @@ pub struct CameraConstants { | ||||
| } | ||||
|  | ||||
| impl CameraConstants { | ||||
|     pub fn new(scale: f32) -> Self { | ||||
|         Self { | ||||
|             scale, | ||||
|             zoom: 0.0, | ||||
|             rotate: 0.0, | ||||
|             offset_x: 0.0, | ||||
|             offset_y: 0.0, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn camera(&self, image_width: i32, image_height: i32) -> glam::Affine2 { | ||||
|         let zoom_factor = 2f32.powf(self.zoom); | ||||
|         let zoom_rotate_offset = glam::Affine2::from_scale_angle_translation( | ||||
|             glam::Vec2::splat(2f32.powf(self.zoom)), | ||||
|             glam::Vec2::splat(zoom_factor), | ||||
|             self.rotate.to_radians(), | ||||
|             -vec2(self.offset_x, self.offset_y), | ||||
|             -vec2(self.offset_x, self.offset_y) * zoom_factor, | ||||
|         ); | ||||
|         let ifs_to_pixel = glam::Affine2::from_scale_angle_translation( | ||||
|             glam::Vec2::splat(self.scale), | ||||
| @ -119,13 +129,23 @@ impl ThreadState { | ||||
|         unsafe { core::mem::transmute::<_, Xoshiro128Plus>(*rng_state) } | ||||
|     } | ||||
|  | ||||
|     pub fn new(rng: &mut Xoshiro128Plus) -> Self { | ||||
|     pub fn new<R: Rng>(rng: &mut R) -> Self { | ||||
|         let mut rng_state: [u32; 4] = [0; 4]; | ||||
|         rng.fill(&mut rng_state); | ||||
|         let point = vec2(rng.sample(BiUnit), rng.sample(BiUnit)); | ||||
|         let rng_state = Self::xoshiro_to_state(rng); | ||||
|         rng.jump(); | ||||
|         Self { rng_state, point } | ||||
|     } | ||||
|  | ||||
|     pub fn jump(&self) -> Self { | ||||
|         let mut rng = self.get_rng(); | ||||
|         rng.jump(); | ||||
|         let point = vec2(rng.sample(BiUnit), rng.sample(BiUnit)); | ||||
|         Self { | ||||
|             rng_state: Self::xoshiro_to_state(&rng), | ||||
|             point, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn get_rng(&self) -> Xoshiro128Plus { | ||||
|         Self::state_to_xoshiro(&self.rng_state) | ||||
|     } | ||||
| @ -237,11 +257,13 @@ fn next_transform<R: Rng + ?Sized>( | ||||
|  | ||||
| #[spirv(compute(threads(1)))] | ||||
| pub fn main_cs( | ||||
|     #[spirv(push_constant)] image_constants: &ImageConstants, | ||||
|     #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] thread_state: &mut [ThreadState], | ||||
|     #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] transforms: &[Transform], | ||||
|     #[spirv(storage_buffer, descriptor_set = 0, binding = 2)] variations: &[Variation], | ||||
|     #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] _viewport_constants: &ViewportConstants, | ||||
|     #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] accum_constants: &AccumConstants, | ||||
|     #[spirv(storage_buffer, descriptor_set = 0, binding = 2)] _camera_constants: &CameraConstants, | ||||
|     #[spirv(storage_buffer, descriptor_set = 0, binding = 3)] accum_image: &mut [Vec4], | ||||
|     #[spirv(storage_buffer, descriptor_set = 1, binding = 0)] thread_state: &mut [ThreadState], | ||||
|     #[spirv(storage_buffer, descriptor_set = 1, binding = 1)] transforms: &[Transform], | ||||
|     #[spirv(storage_buffer, descriptor_set = 1, binding = 2)] variations: &[Variation], | ||||
| ) { | ||||
|     let mut rng = thread_state[0].get_rng(); | ||||
|     let mut point = thread_state[0].point; | ||||
| @ -256,18 +278,23 @@ pub fn main_cs( | ||||
|         point = next_transform(&mut rng, transform_weight, transforms).apply(variations, point); | ||||
|     } | ||||
|  | ||||
|     // ...because `<i32>.max(<i32>)` has compilation issues. | ||||
|     let max_dimension = if image_constants.accum_width > image_constants.accum_height { image_constants.accum_width } else { image_constants.accum_height }; | ||||
|     // ...because `<i32>.min(<i32>)` has compilation issues. | ||||
|     let min_dimension = if accum_constants.width < accum_constants.height { | ||||
|         accum_constants.width | ||||
|     } else { | ||||
|         accum_constants.height | ||||
|     }; | ||||
|  | ||||
|     // Fixed camera, should be provided by a uniform in the future | ||||
|     let camera = CameraConstants { | ||||
|         scale: max_dimension as f32 / 4.0, | ||||
|         zoom: 0.0, | ||||
|         scale: min_dimension as f32 / 4.0, | ||||
|         zoom: 2.0, | ||||
|         rotate: 0.0, | ||||
|         offset_x: 0.0, | ||||
|         offset_y: 0.0, | ||||
|         offset_x: 0.5, | ||||
|         offset_y: 0.5, | ||||
|     } | ||||
|     .camera(image_constants.accum_width, image_constants.accum_height); | ||||
|     .camera(accum_constants.width, accum_constants.height); | ||||
|     // let camera = camera_constants.camera(image_constants.accum_width, image_constants.accum_height); | ||||
|  | ||||
|     // Iterate 100,000, should be provided by a uniform in the future | ||||
|     for _i in 0..100_000 { | ||||
| @ -275,9 +302,9 @@ pub fn main_cs( | ||||
|  | ||||
|         let pixel_coordinates = camera.transform_point2(point).as_ivec2(); | ||||
|         if pixel_coordinates.x < 0 | ||||
|             || pixel_coordinates.x >= image_constants.accum_width | ||||
|             || pixel_coordinates.x >= accum_constants.width | ||||
|             || pixel_coordinates.y < 0 | ||||
|             || pixel_coordinates.y >= image_constants.accum_height | ||||
|             || pixel_coordinates.y >= accum_constants.height | ||||
|         { | ||||
|             continue; | ||||
|         } | ||||
| @ -285,7 +312,7 @@ pub fn main_cs( | ||||
|         let ii = image_index( | ||||
|             pixel_coordinates.x, | ||||
|             pixel_coordinates.y, | ||||
|             image_constants.accum_height, | ||||
|             accum_constants.width, | ||||
|         ); | ||||
|         accum_image[ii as usize] = Color::WHITE; | ||||
|     } | ||||
| @ -310,18 +337,24 @@ pub fn main_vs( | ||||
| #[spirv(fragment)] | ||||
| pub fn main_fs( | ||||
|     #[spirv(frag_coord)] frag_coord: Vec4, | ||||
|     #[spirv(push_constant)] ifs_constants: &ImageConstants, | ||||
|     #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] accum_image: &[Vec4], | ||||
|     #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] viewport_constants: &ViewportConstants, | ||||
|     #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] accum_constants: &AccumConstants, | ||||
|     #[spirv(storage_buffer, descriptor_set = 0, binding = 2)] _accum_image: &mut [Vec4], | ||||
|     output: &mut Vec4, | ||||
| ) { | ||||
|     // Bootleg texture sampling; map from viewport image pixel coordinates to accumulator image | ||||
|     // pixel coordinates | ||||
|     let viewport_coordinate = frag_coord.xy().as_uvec2(); | ||||
|     let viewport_offset = glam::ivec2( | ||||
|         viewport_constants.offset_x, | ||||
|         viewport_constants.offset_y, | ||||
|     ) | ||||
|     .as_uvec2(); | ||||
|     let viewport_coordinate = frag_coord.xy().as_uvec2() - viewport_offset; | ||||
|  | ||||
|     let a_width = ifs_constants.accum_width as f32; | ||||
|     let a_height = ifs_constants.accum_height as f32; | ||||
|     let v_width = ifs_constants.viewport_width as f32; | ||||
|     let v_height = ifs_constants.viewport_height as f32; | ||||
|     let a_width = accum_constants.width as f32; | ||||
|     let a_height = accum_constants.height as f32; | ||||
|     let v_width = viewport_constants.width as f32; | ||||
|     let v_height = viewport_constants.height as f32; | ||||
|  | ||||
|     // Scale both width and height by the same factor; preserves aspect ratio | ||||
|     let scale_width = a_width / v_width; | ||||
| @ -332,8 +365,9 @@ pub fn main_fs( | ||||
|     let offset_x = (v_width * scale - a_width) / 2.0; | ||||
|     let offset_y = (v_height * scale - a_height) / 2.0; | ||||
|  | ||||
|     let accum_coordinate = viewport_coordinate.as_vec2() * scale - vec2(offset_x, offset_y); | ||||
|     let _accum_coordinate = viewport_coordinate.as_vec2() * scale - vec2(offset_x, offset_y); | ||||
|  | ||||
|     /* | ||||
|     if accum_coordinate.x < 0.0 | ||||
|         || accum_coordinate.x >= ifs_constants.accum_width as f32 | ||||
|         || accum_coordinate.y < 0.0 | ||||
| @ -347,4 +381,13 @@ pub fn main_fs( | ||||
|             ifs_constants.accum_width, | ||||
|         ) as usize]; | ||||
|     } | ||||
|      */ | ||||
|  | ||||
|     if viewport_coordinate.x == 0 | ||||
|         || viewport_coordinate.x == viewport_constants.width as u32 - 1 | ||||
|         || viewport_coordinate.y == 0 | ||||
|         || viewport_coordinate.y == viewport_constants.height as u32 - 1 | ||||
|     { | ||||
|         *output = glam::Vec4::splat(1.); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -8,6 +8,9 @@ license.workspace = true | ||||
| [dependencies] | ||||
| anyhow.workspace = true | ||||
| bytemuck.workspace = true | ||||
| eframe = { version = "0.31", features = ["wgpu"]} | ||||
| egui = "0.31" | ||||
| egui-wgpu = "0.31" | ||||
| env_logger.workspace = true | ||||
| flare-shader = { path = "../flare-shader" } | ||||
| futures-executor.workspace = true | ||||
|  | ||||
| @ -1,38 +1,219 @@ | ||||
| use flare_shader::{Coefs, Color, ImageConstants, ThreadState, Transform, Variation, VariationKind}; | ||||
| use futures_executor::block_on; | ||||
| use glam::Vec4; | ||||
| use rand::SeedableRng; | ||||
| use rand_xoshiro::Xoshiro128Plus; | ||||
| use std::sync::Arc; | ||||
| use eframe::Frame; | ||||
| use eframe::epaint::PaintCallbackInfo; | ||||
| use egui::Context; | ||||
| use egui_wgpu::{CallbackResources, CallbackTrait, ScreenDescriptor}; | ||||
| use flare_shader::{CameraConstants, Coefs, ViewportConstants, ThreadState, Transform, Variation, VariationKind, AccumConstants}; | ||||
| use rand::thread_rng; | ||||
| use wgpu::util::DeviceExt; | ||||
| use wgpu::{ | ||||
|     Adapter, Device, Features, Instance, Queue, ShaderModule, Surface, SurfaceConfiguration, | ||||
| }; | ||||
| use winit::event::MouseButton; | ||||
| use winit::{ | ||||
|     application::ApplicationHandler, | ||||
|     dpi::LogicalSize, | ||||
|     event::WindowEvent, | ||||
|     event_loop::{ActiveEventLoop, EventLoop}, | ||||
|     window::{Window, WindowAttributes, WindowId}, | ||||
| }; | ||||
| use wgpu::{CommandBuffer, CommandEncoder, Device, Queue, RenderPass}; | ||||
|  | ||||
| struct AccumulatePipeline { | ||||
|     device: Device, | ||||
| struct RenderGroup { | ||||
|     device: wgpu::Device, | ||||
|     queue: wgpu::Queue, | ||||
|     bind_group_layout: wgpu::BindGroupLayout, | ||||
|     pipeline: wgpu::ComputePipeline, | ||||
|     bind_group: wgpu::BindGroup, | ||||
|     viewport_constants_buffer: wgpu::Buffer, | ||||
|     accum_constants: AccumConstants, | ||||
|     accum_constants_buffer: wgpu::Buffer, | ||||
|     camera_constants_buffer: wgpu::Buffer, | ||||
|     image_accum_buffer: wgpu::Buffer, | ||||
| } | ||||
|  | ||||
|     thread_state: Option<wgpu::Buffer>, | ||||
|     transforms: Option<wgpu::Buffer>, | ||||
|     variations: Option<wgpu::Buffer>, | ||||
|     accum_image: Option<wgpu::Buffer>, | ||||
| impl RenderGroup { | ||||
|     pub fn bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { | ||||
|         device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { | ||||
|             label: Some("render"), | ||||
|             entries: &[ | ||||
|                 // image_constants | ||||
|                 wgpu::BindGroupLayoutEntry { | ||||
|                     binding: 0, | ||||
|                     visibility: wgpu::ShaderStages::COMPUTE | wgpu::ShaderStages::FRAGMENT, | ||||
|                     ty: wgpu::BindingType::Buffer { | ||||
|                         ty: wgpu::BufferBindingType::Storage { read_only: true }, | ||||
|                         has_dynamic_offset: false, | ||||
|                         min_binding_size: None, | ||||
|                     }, | ||||
|                     count: None, | ||||
|                 }, | ||||
|                 // accum_constants | ||||
|                 wgpu::BindGroupLayoutEntry { | ||||
|                     binding: 1, | ||||
|                     visibility: wgpu::ShaderStages::COMPUTE | wgpu::ShaderStages::FRAGMENT, | ||||
|                     ty: wgpu::BindingType::Buffer { | ||||
|                         ty: wgpu::BufferBindingType::Storage { read_only: true }, | ||||
|                         has_dynamic_offset: false, | ||||
|                         min_binding_size: None, | ||||
|                     }, | ||||
|                     count: None, | ||||
|                 }, | ||||
|                 // camera_constants | ||||
|                 wgpu::BindGroupLayoutEntry { | ||||
|                     binding: 2, | ||||
|                     visibility: wgpu::ShaderStages::COMPUTE | wgpu::ShaderStages::FRAGMENT, | ||||
|                     ty: wgpu::BindingType::Buffer { | ||||
|                         ty: wgpu::BufferBindingType::Storage { read_only: true }, | ||||
|                         has_dynamic_offset: false, | ||||
|                         min_binding_size: None, | ||||
|                     }, | ||||
|                     count: None, | ||||
|                 }, | ||||
|                 // accum_image | ||||
|                 wgpu::BindGroupLayoutEntry { | ||||
|                     binding: 3, | ||||
|                     visibility: wgpu::ShaderStages::COMPUTE | wgpu::ShaderStages::FRAGMENT, | ||||
|                     ty: wgpu::BindingType::Buffer { | ||||
|                         ty: wgpu::BufferBindingType::Storage { read_only: false }, | ||||
|                         has_dynamic_offset: false, | ||||
|                         min_binding_size: None, | ||||
|                     }, | ||||
|                     count: None, | ||||
|                 }, | ||||
|             ], | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn build_image_accum_buffer(device: &wgpu::Device, width: i32, height: i32) -> wgpu::Buffer { | ||||
|         let pixel_count = (width * height) as u64; | ||||
|         let size = pixel_count * size_of::<f32>() as u64; | ||||
|         device.create_buffer(&wgpu::BufferDescriptor { | ||||
|             label: Some("accum_image"), | ||||
|             size, | ||||
|             usage: wgpu::BufferUsages::STORAGE, | ||||
|             mapped_at_creation: false, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn build_bind_group( | ||||
|         device: &wgpu::Device, | ||||
|         layout: &wgpu::BindGroupLayout, | ||||
|         image_constants_buffer: &wgpu::Buffer, | ||||
|         accum_constants_buffer: &wgpu::Buffer, | ||||
|         camera_constants_buffer: &wgpu::Buffer, | ||||
|         image_accum_buffer: &wgpu::Buffer, | ||||
|     ) -> wgpu::BindGroup { | ||||
|         device.create_bind_group(&wgpu::BindGroupDescriptor { | ||||
|             label: Some("render"), | ||||
|             layout, | ||||
|             entries: &[ | ||||
|                 wgpu::BindGroupEntry { | ||||
|                     binding: 0, | ||||
|                     resource: image_constants_buffer.as_entire_binding(), | ||||
|                 }, | ||||
|                 wgpu::BindGroupEntry { | ||||
|                     binding: 1, | ||||
|                     resource: accum_constants_buffer.as_entire_binding(), | ||||
|                 }, | ||||
|                 wgpu::BindGroupEntry { | ||||
|                     binding: 2, | ||||
|                     resource: camera_constants_buffer.as_entire_binding(), | ||||
|                 }, | ||||
|                 wgpu::BindGroupEntry { | ||||
|                     binding: 3, | ||||
|                     resource: image_accum_buffer.as_entire_binding(), | ||||
|                 }, | ||||
|             ], | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn new( | ||||
|         device: &wgpu::Device, | ||||
|         queue: &wgpu::Queue, | ||||
|         width: i32, | ||||
|         height: i32, | ||||
|     ) -> Self { | ||||
|         let bind_group_layout = Self::bind_group_layout(device); | ||||
|  | ||||
|         let viewport_constants_buffer = device.create_buffer(&wgpu::BufferDescriptor { | ||||
|             label: Some("viewport_constants"), | ||||
|             size: size_of::<ViewportConstants>() as u64, | ||||
|             usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, | ||||
|             mapped_at_creation: false, | ||||
|         }); | ||||
|  | ||||
|         let accum_constants = AccumConstants::new(width, height); | ||||
|         let accum_constants_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { | ||||
|             label: Some("accum_constants"), | ||||
|             contents: bytemuck::cast_slice(&[accum_constants.clone()]), | ||||
|             usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, | ||||
|         }); | ||||
|  | ||||
|         let min_dimension = width.min(height); | ||||
|         let initial_camera = CameraConstants::new(min_dimension as f32); | ||||
|         let camera_constants_buffer = | ||||
|             device.create_buffer_init(&wgpu::util::BufferInitDescriptor { | ||||
|                 label: Some("camera_constants"), | ||||
|                 contents: bytemuck::cast_slice(&[initial_camera]), | ||||
|                 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, | ||||
|             }); | ||||
|  | ||||
|         let image_accum_buffer = | ||||
|             Self::build_image_accum_buffer(device, width, height); | ||||
|  | ||||
|         let bind_group = Self::build_bind_group( | ||||
|             device, | ||||
|             &bind_group_layout, | ||||
|             &viewport_constants_buffer, | ||||
|             &accum_constants_buffer, | ||||
|             &camera_constants_buffer, | ||||
|             &image_accum_buffer, | ||||
|         ); | ||||
|  | ||||
|         Self { | ||||
|             device: device.clone(), | ||||
|             queue: queue.clone(), | ||||
|             bind_group_layout, | ||||
|             viewport_constants_buffer, | ||||
|             accum_constants, | ||||
|             accum_constants_buffer, | ||||
|             camera_constants_buffer, | ||||
|             image_accum_buffer, | ||||
|             bind_group, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn set_accum_dimensions(&mut self, width: i32, height: i32) { | ||||
|         self.accum_constants = AccumConstants::new(width, height); | ||||
|         self.queue.write_buffer( | ||||
|             &self.accum_constants_buffer, | ||||
|             0, | ||||
|             bytemuck::cast_slice(&[self.accum_constants]), | ||||
|         ); | ||||
|  | ||||
|         self.image_accum_buffer = Self::build_image_accum_buffer(&self.device, width, height); | ||||
|         self.bind_group = Self::build_bind_group( | ||||
|             &self.device, | ||||
|             &self.bind_group_layout, | ||||
|             &self.viewport_constants_buffer, | ||||
|             &self.accum_constants_buffer, | ||||
|             &self.camera_constants_buffer, | ||||
|             &self.image_accum_buffer, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     pub fn set_viewport_dimensions(&mut self, rect: egui::Rect) { | ||||
|         let offset = rect.left_top(); | ||||
|         let viewport_constants = ViewportConstants::new(offset.x as i32, offset.y as i32, rect.width() as i32, rect.height() as i32); | ||||
|         self.queue.write_buffer(&self.viewport_constants_buffer, 0, bytemuck::cast_slice(&[viewport_constants])) | ||||
|     } | ||||
|  | ||||
|     pub fn bind_group(&self) -> &wgpu::BindGroup { | ||||
|         &self.bind_group | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct IfsGroup { | ||||
|     device: wgpu::Device, | ||||
|     bind_group_layout: wgpu::BindGroupLayout, | ||||
|     thread_state_buffer: wgpu::Buffer, | ||||
|     transforms_buffer: Option<wgpu::Buffer>, | ||||
|     variations_buffer: Option<wgpu::Buffer>, | ||||
|     bind_group: Option<wgpu::BindGroup>, | ||||
| } | ||||
|  | ||||
| impl AccumulatePipeline { | ||||
|     const BIND_GROUP_LAYOUT_DESCRIPTOR: wgpu::BindGroupLayoutDescriptor<'static> = | ||||
|         wgpu::BindGroupLayoutDescriptor { | ||||
|             label: Some("accumulate"), | ||||
| impl IfsGroup { | ||||
|     pub fn bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { | ||||
|         device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { | ||||
|             label: Some("accum"), | ||||
|             entries: &[ | ||||
|                 // thread_state | ||||
|                 wgpu::BindGroupLayoutEntry { | ||||
| @ -67,35 +248,105 @@ impl AccumulatePipeline { | ||||
|                     }, | ||||
|                     count: None, | ||||
|                 }, | ||||
|                 // accum_image | ||||
|                 wgpu::BindGroupLayoutEntry { | ||||
|                     binding: 3, | ||||
|                     visibility: wgpu::ShaderStages::COMPUTE, | ||||
|                     ty: wgpu::BindingType::Buffer { | ||||
|                         ty: wgpu::BufferBindingType::Storage { read_only: false }, | ||||
|                         has_dynamic_offset: false, | ||||
|                         min_binding_size: None, | ||||
|                     }, | ||||
|                     count: None, | ||||
|                 }, | ||||
|             ], | ||||
|         }; | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     const N_THREADS: usize = 1; | ||||
|     pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Self { | ||||
|         let mut seed_rng = thread_rng(); | ||||
|         let mut thread_states = vec![ThreadState::new(&mut seed_rng)]; | ||||
|         while thread_states.len() < 64 { | ||||
|             thread_states.push(thread_states[thread_states.len() - 1].jump()); | ||||
|         } | ||||
|  | ||||
|     pub fn new(device: &Device, module: &ShaderModule) -> Self { | ||||
|         let bind_group_layout = | ||||
|             device.create_bind_group_layout(&Self::BIND_GROUP_LAYOUT_DESCRIPTOR); | ||||
|         let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { | ||||
|             label: Some("accumulate"), | ||||
|             bind_group_layouts: &[&bind_group_layout], | ||||
|             push_constant_ranges: &[wgpu::PushConstantRange { | ||||
|                 stages: wgpu::ShaderStages::COMPUTE, | ||||
|                 range: 0..size_of::<ImageConstants>() as u32, | ||||
|             }], | ||||
|         let thread_state_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { | ||||
|             label: Some("thread_state"), | ||||
|             contents: bytemuck::cast_slice(thread_states.as_slice()), | ||||
|             usage: wgpu::BufferUsages::STORAGE, | ||||
|         }); | ||||
|  | ||||
|         Self { | ||||
|             device: device.clone(), | ||||
|             bind_group_layout: Self::bind_group_layout(device), | ||||
|             thread_state_buffer, | ||||
|             transforms_buffer: None, | ||||
|             variations_buffer: None, | ||||
|             bind_group: None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn set_transforms(&mut self, transforms: &[Transform]) { | ||||
|         let transforms_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { | ||||
|             label: Some("transforms"), | ||||
|             contents: bytemuck::cast_slice(transforms), | ||||
|             usage: wgpu::BufferUsages::STORAGE, | ||||
|         }); | ||||
|         let _ = self.transforms_buffer.insert(transforms_buffer); | ||||
|     } | ||||
|  | ||||
|     pub fn set_variations(&mut self, variations: &[Variation]) { | ||||
|         let variations_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { | ||||
|             label: Some("variations"), | ||||
|             contents: bytemuck::cast_slice(variations), | ||||
|             usage: wgpu::BufferUsages::STORAGE, | ||||
|         }); | ||||
|         let _ = self.variations_buffer.insert(variations_buffer); | ||||
|     } | ||||
|  | ||||
|     pub fn bind_group(&mut self) -> &wgpu::BindGroup { | ||||
|         if self.bind_group.is_none() { | ||||
|             let transforms_buffer = self | ||||
|                 .transforms_buffer | ||||
|                 .as_ref() | ||||
|                 .expect("No transforms available"); | ||||
|             let variations_buffer = self | ||||
|                 .variations_buffer | ||||
|                 .as_ref() | ||||
|                 .expect("No variations available"); | ||||
|  | ||||
|             let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { | ||||
|                 label: Some("ifs"), | ||||
|                 layout: &self.bind_group_layout, | ||||
|                 entries: &[ | ||||
|                     wgpu::BindGroupEntry { | ||||
|                         binding: 0, | ||||
|                         resource: self.thread_state_buffer.as_entire_binding(), | ||||
|                     }, | ||||
|                     wgpu::BindGroupEntry { | ||||
|                         binding: 1, | ||||
|                         resource: transforms_buffer.as_entire_binding(), | ||||
|                     }, | ||||
|                     wgpu::BindGroupEntry { | ||||
|                         binding: 2, | ||||
|                         resource: variations_buffer.as_entire_binding(), | ||||
|                     }, | ||||
|                 ], | ||||
|             }); | ||||
|  | ||||
|             let _ = self.bind_group.insert(bind_group); | ||||
|         } | ||||
|  | ||||
|         self.bind_group.as_ref().unwrap() | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct AccumPipeline { | ||||
|     pipeline: wgpu::ComputePipeline, | ||||
| } | ||||
|  | ||||
| impl AccumPipeline { | ||||
|     pub fn new(device: &wgpu::Device, module: &wgpu::ShaderModule) -> Self { | ||||
|         let render_layout = RenderGroup::bind_group_layout(device); | ||||
|         let ifs_layout = IfsGroup::bind_group_layout(device); | ||||
|  | ||||
|         let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { | ||||
|             label: Some("accum"), | ||||
|             bind_group_layouts: &[&render_layout, &ifs_layout], | ||||
|             push_constant_ranges: &[], | ||||
|         }); | ||||
|  | ||||
|         let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { | ||||
|             label: Some("accumulate"), | ||||
|             label: Some("accum"), | ||||
|             layout: Some(&pipeline_layout), | ||||
|             module, | ||||
|             entry_point: Some("main_cs"), | ||||
| @ -103,157 +354,25 @@ impl AccumulatePipeline { | ||||
|             cache: None, | ||||
|         }); | ||||
|  | ||||
|         let rng = rand::thread_rng(); | ||||
|         let mut rng_xoshiro = Xoshiro128Plus::from_rng(rng).expect("Unable to seed thread_state"); | ||||
|         let mut thread_state_elements = vec![]; | ||||
|         for _i in 0..Self::N_THREADS { | ||||
|             thread_state_elements.push(ThreadState::new(&mut rng_xoshiro)); | ||||
|         } | ||||
|         let thread_state = Some( | ||||
|             device.create_buffer_init(&wgpu::util::BufferInitDescriptor { | ||||
|                 label: Some("accumulate/thread_state"), | ||||
|                 contents: bytemuck::cast_slice(&thread_state_elements), | ||||
|                 usage: wgpu::BufferUsages::STORAGE, | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         Self { | ||||
|             device: device.clone(), | ||||
|             bind_group_layout, | ||||
|             pipeline, | ||||
|             thread_state, | ||||
|             transforms: None, | ||||
|             variations: None, | ||||
|             accum_image: None, | ||||
|             bind_group: None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn set_transforms(&mut self, transforms: &[Transform]) { | ||||
|         // Should be smarter about allocation in the future | ||||
|         self.transforms = Some( | ||||
|             self.device | ||||
|                 .create_buffer_init(&wgpu::util::BufferInitDescriptor { | ||||
|                     label: Some("accumulate/transforms"), | ||||
|                     contents: bytemuck::cast_slice(transforms), | ||||
|                     usage: wgpu::BufferUsages::STORAGE, | ||||
|                 }), | ||||
|         ); | ||||
|  | ||||
|         // Setting a new buffer invalidates the existing bindings | ||||
|         self.bind_group.take(); | ||||
|     } | ||||
|  | ||||
|     pub fn set_variations(&mut self, variations: &[Variation]) { | ||||
|         self.variations = Some( | ||||
|             self.device | ||||
|                 .create_buffer_init(&wgpu::util::BufferInitDescriptor { | ||||
|                     label: Some("accumulate/variations"), | ||||
|                     contents: bytemuck::cast_slice(variations), | ||||
|                     usage: wgpu::BufferUsages::STORAGE, | ||||
|                 }), | ||||
|         ); | ||||
|  | ||||
|         self.bind_group.take(); | ||||
|     } | ||||
|  | ||||
|     pub fn set_accum_image(&mut self, accum_image: &wgpu::Buffer) { | ||||
|         self.accum_image = Some(accum_image.clone()); | ||||
|  | ||||
|         // Setting a new buffer invalidates the existing bindings | ||||
|         self.bind_group.take(); | ||||
|     } | ||||
|  | ||||
|     fn fetch_bind_group(&mut self) -> &wgpu::BindGroup { | ||||
|         self.bind_group.get_or_insert_with(|| { | ||||
|             self.device.create_bind_group(&wgpu::BindGroupDescriptor { | ||||
|                 label: Some("accumulate"), | ||||
|                 layout: &self.bind_group_layout, | ||||
|                 entries: &[ | ||||
|                     wgpu::BindGroupEntry { | ||||
|                         binding: 0, | ||||
|                         resource: self | ||||
|                             .thread_state | ||||
|                             .as_ref() | ||||
|                             .expect("thread_state missing") | ||||
|                             .as_entire_binding(), | ||||
|                     }, | ||||
|                     wgpu::BindGroupEntry { | ||||
|                         binding: 1, | ||||
|                         resource: self | ||||
|                             .transforms | ||||
|                             .as_ref() | ||||
|                             .expect("transforms missing") | ||||
|                             .as_entire_binding(), | ||||
|                     }, | ||||
|                     wgpu::BindGroupEntry { | ||||
|                         binding: 2, | ||||
|                         resource: self | ||||
|                             .variations | ||||
|                             .as_ref() | ||||
|                             .expect("variations missing") | ||||
|                             .as_entire_binding(), | ||||
|                     }, | ||||
|                     wgpu::BindGroupEntry { | ||||
|                         binding: 3, | ||||
|                         resource: self | ||||
|                             .accum_image | ||||
|                             .as_ref() | ||||
|                             .expect("accum_image missing") | ||||
|                             .as_entire_binding(), | ||||
|                     }, | ||||
|                 ], | ||||
|             }) | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn run(&mut self, encoder: &mut wgpu::CommandEncoder, constants: &ImageConstants) { | ||||
|         let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { | ||||
|             label: Some("accumulate"), | ||||
|             timestamp_writes: None, | ||||
|         }); | ||||
|         pass.set_pipeline(&self.pipeline); | ||||
|         pass.set_push_constants(0, bytemuck::cast_slice(&[*constants])); | ||||
|         pass.set_bind_group(0, self.fetch_bind_group(), &[]); | ||||
|         pass.dispatch_workgroups(1, 1, 1); | ||||
|         Self { pipeline } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct RenderPipeline { | ||||
|     device: Device, | ||||
|     bind_group_layout: wgpu::BindGroupLayout, | ||||
|     pipeline: wgpu::RenderPipeline, | ||||
|  | ||||
|     accum_image: Option<wgpu::Buffer>, | ||||
|     bind_group: Option<wgpu::BindGroup>, | ||||
| } | ||||
|  | ||||
| impl RenderPipeline { | ||||
|     const BIND_GROUP_LAYOUT_DESCRIPTOR: wgpu::BindGroupLayoutDescriptor<'static> = | ||||
|         wgpu::BindGroupLayoutDescriptor { | ||||
|             label: Some("render"), | ||||
|             entries: &[wgpu::BindGroupLayoutEntry { | ||||
|                 binding: 0, | ||||
|                 visibility: wgpu::ShaderStages::FRAGMENT, | ||||
|                 ty: wgpu::BindingType::Buffer { | ||||
|                     ty: wgpu::BufferBindingType::Storage { read_only: true }, | ||||
|                     has_dynamic_offset: false, | ||||
|                     min_binding_size: None, | ||||
|                 }, | ||||
|                 count: None, | ||||
|             }], | ||||
|         }; | ||||
|  | ||||
|     pub fn new(device: &Device, module: &ShaderModule, format: wgpu::TextureFormat) -> Self { | ||||
|         let bind_group_layout = | ||||
|             device.create_bind_group_layout(&Self::BIND_GROUP_LAYOUT_DESCRIPTOR); | ||||
|     pub fn new( | ||||
|         device: &wgpu::Device, | ||||
|         module: &wgpu::ShaderModule, | ||||
|         target: &wgpu::TextureFormat, | ||||
|     ) -> Self { | ||||
|         let render_layout = RenderGroup::bind_group_layout(device); | ||||
|         let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { | ||||
|             label: Some("render"), | ||||
|             bind_group_layouts: &[&bind_group_layout], | ||||
|             push_constant_ranges: &[wgpu::PushConstantRange { | ||||
|                 stages: wgpu::ShaderStages::FRAGMENT, | ||||
|                 range: 0..size_of::<ImageConstants>() as u32, | ||||
|             }], | ||||
|             bind_group_layouts: &[&render_layout], | ||||
|             push_constant_ranges: &[], | ||||
|         }); | ||||
|         let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { | ||||
|             label: Some("render"), | ||||
| @ -271,311 +390,161 @@ impl RenderPipeline { | ||||
|                 module, | ||||
|                 entry_point: Some("main_fs"), | ||||
|                 compilation_options: Default::default(), | ||||
|                 targets: &[Some(wgpu::ColorTargetState { | ||||
|                     format, | ||||
|                     blend: None, | ||||
|                     write_mask: Default::default(), | ||||
|                 })], | ||||
|                 targets: &[Some((*target).into())], | ||||
|             }), | ||||
|             multiview: None, | ||||
|             cache: None, | ||||
|         }); | ||||
|         Self { | ||||
|             device: device.clone(), | ||||
|             bind_group_layout, | ||||
|             pipeline, | ||||
|             accum_image: None, | ||||
|             bind_group: None, | ||||
|         } | ||||
|         Self { pipeline } | ||||
|     } | ||||
| } | ||||
|  | ||||
|     pub fn set_accum_image(&mut self, accum_image: &wgpu::Buffer) { | ||||
|         self.accum_image = Some(accum_image.clone()); | ||||
|         self.bind_group.take(); | ||||
|     } | ||||
|  | ||||
|     fn fetch_bind_group(&mut self) -> &wgpu::BindGroup { | ||||
|         self.bind_group.get_or_insert_with(|| { | ||||
|             self.device.create_bind_group(&wgpu::BindGroupDescriptor { | ||||
|                 label: Some("render"), | ||||
|                 layout: &self.bind_group_layout, | ||||
|                 entries: &[wgpu::BindGroupEntry { | ||||
|                     binding: 0, | ||||
|                     resource: self | ||||
|                         .accum_image | ||||
|                         .as_ref() | ||||
|                         .expect("accum_image missing") | ||||
|                         .as_entire_binding(), | ||||
|                 }], | ||||
|             }) | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn run( | ||||
|         &mut self, | ||||
|         encoder: &mut wgpu::CommandEncoder, | ||||
|         output_view: &wgpu::TextureView, | ||||
|         constants: &ImageConstants, | ||||
|     ) { | ||||
|         let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { | ||||
|             label: Some("render"), | ||||
|             color_attachments: &[Some(wgpu::RenderPassColorAttachment { | ||||
|                 view: output_view, | ||||
|                 resolve_target: None, | ||||
|                 ops: Default::default(), | ||||
|             })], | ||||
|             depth_stencil_attachment: None, | ||||
|             timestamp_writes: None, | ||||
|             occlusion_query_set: None, | ||||
|         }); | ||||
|         pass.set_pipeline(&self.pipeline); | ||||
|         pass.set_push_constants( | ||||
|             wgpu::ShaderStages::FRAGMENT, | ||||
|             0, | ||||
|             bytemuck::cast_slice(&[*constants]), | ||||
|         ); | ||||
|         pass.set_bind_group(0, self.fetch_bind_group(), &[]); | ||||
|         pass.draw(0..3, 0..1); | ||||
|     } | ||||
| struct FlareAssets { | ||||
|     render_group: RenderGroup, | ||||
|     ifs_group: IfsGroup, | ||||
|     accum_pipeline: AccumPipeline, | ||||
|     render_pipeline: RenderPipeline, | ||||
| } | ||||
|  | ||||
| struct Flare { | ||||
|     instance: Instance, | ||||
|     adapter: Adapter, | ||||
|     device: Device, | ||||
|     queue: Queue, | ||||
|     module: ShaderModule, | ||||
|     first_draw: bool, | ||||
| } | ||||
|  | ||||
| impl Flare { | ||||
|     pub fn new() -> Self { | ||||
|         let backends = wgpu::Backends::from_env().unwrap_or_default(); | ||||
|         let instance = Instance::new(&wgpu::InstanceDescriptor { | ||||
|             backends, | ||||
|             ..Default::default() | ||||
|         }); | ||||
|     pub fn new(cc: &eframe::CreationContext<'_>, width: i32, height: i32) -> Self { | ||||
|         let cc_wgpu = cc | ||||
|             .wgpu_render_state | ||||
|             .as_ref() | ||||
|             .expect("Unable to acquire wgpu render state"); | ||||
|         let device = cc_wgpu.device.clone(); | ||||
|         let queue = cc_wgpu.queue.clone(); | ||||
|  | ||||
|         let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions { | ||||
|             power_preference: wgpu::PowerPreference::HighPerformance, | ||||
|             force_fallback_adapter: false, | ||||
|             compatible_surface: None, | ||||
|         }); | ||||
|         let adapter = block_on(adapter).expect("Failed to find GPU adapter"); | ||||
|         let module_spirv = wgpu::include_spirv!(concat!(env!("OUT_DIR"), "/flare.spv")); | ||||
|         let module = device.create_shader_module(module_spirv); | ||||
|  | ||||
|         let required_limits = wgpu::Limits { | ||||
|             max_push_constant_size: size_of::<ImageConstants>() as u32, | ||||
|             ..Default::default() | ||||
|         }; | ||||
|  | ||||
|         let device = adapter.request_device( | ||||
|             &wgpu::DeviceDescriptor { | ||||
|                 label: Some("flare"), | ||||
|                 required_features: Features::TIMESTAMP_QUERY | Features::PUSH_CONSTANTS, | ||||
|                 required_limits, | ||||
|                 memory_hints: Default::default(), | ||||
|             }, | ||||
|             None, | ||||
|         ); | ||||
|         let (device, queue) = block_on(device).expect("Failed to find GPU device"); | ||||
|         let module = device | ||||
|             .create_shader_module(wgpu::include_spirv!(concat!(env!("OUT_DIR"), "/flare.spv"))); | ||||
|  | ||||
|         Flare { | ||||
|             instance, | ||||
|             adapter, | ||||
|             device, | ||||
|             queue, | ||||
|             module, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct FlareRender<'window> { | ||||
|     flare: Arc<Flare>, | ||||
|     surface: Surface<'window>, | ||||
|     surface_configuration: SurfaceConfiguration, | ||||
|     accumulate_pipeline: AccumulatePipeline, | ||||
|     render_pipeline: RenderPipeline, | ||||
|     ifs_constants: ImageConstants, | ||||
|     accum_image: wgpu::Buffer, | ||||
| } | ||||
|  | ||||
| impl FlareRender<'_> { | ||||
|     fn create_accum_image(device: &Device, width: u32, height: u32) -> wgpu::Buffer { | ||||
|         let pixels = (width * height) as u64; | ||||
|         let pixel_size = 4u64 * size_of::<f32>() as u64; | ||||
|         let size = pixels * pixel_size; | ||||
|         device.create_buffer(&wgpu::BufferDescriptor { | ||||
|             label: Some("accum_image"), | ||||
|             size, | ||||
|             usage: wgpu::BufferUsages::STORAGE, | ||||
|             mapped_at_creation: false, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn new(flare: Arc<Flare>, window: Arc<Window>) -> Self { | ||||
|         let window_size = window.inner_size(); | ||||
|  | ||||
|         let surface = flare | ||||
|             .instance | ||||
|             .create_surface(window.clone()) | ||||
|             .expect("Unable to create surface"); | ||||
|         let mut surface_configuration = surface | ||||
|             .get_default_config(&flare.adapter, window_size.width, window_size.height) | ||||
|             .expect("Unable to get surface config"); | ||||
|         surface_configuration.present_mode = wgpu::PresentMode::AutoVsync; | ||||
|         surface.configure(&flare.device, &surface_configuration); | ||||
|  | ||||
|         let mut accumulate_pipeline = AccumulatePipeline::new(&flare.device, &flare.module); | ||||
|         let mut render_pipeline = | ||||
|             RenderPipeline::new(&flare.device, &flare.module, surface_configuration.format); | ||||
|  | ||||
|         let ifs_constants = ImageConstants::new( | ||||
|             window_size.width as i32, | ||||
|             window_size.height as i32, | ||||
|             window_size.width as i32, | ||||
|             window_size.height as i32, | ||||
|             Vec4::BLACK, | ||||
|         ); | ||||
|  | ||||
|         let accum_image = | ||||
|             Self::create_accum_image(&flare.device, window_size.width, window_size.height); | ||||
|  | ||||
|         accumulate_pipeline.set_accum_image(&accum_image); | ||||
|         render_pipeline.set_accum_image(&accum_image); | ||||
|  | ||||
|         Self { | ||||
|             flare, | ||||
|             surface, | ||||
|             surface_configuration, | ||||
|             accumulate_pipeline, | ||||
|             render_pipeline, | ||||
|             ifs_constants, | ||||
|             accum_image, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn render(&mut self, run_accumulate: bool) { | ||||
|         let output = self | ||||
|             .surface | ||||
|             .get_current_texture() | ||||
|             .expect("Failed to get current texture"); | ||||
|         let output_view = output.texture.create_view(&Default::default()); | ||||
|         let mut encoder = self | ||||
|             .flare | ||||
|             .device | ||||
|             .create_command_encoder(&Default::default()); | ||||
|  | ||||
|         if run_accumulate { | ||||
|             self.accumulate_pipeline | ||||
|                 .run(&mut encoder, &self.ifs_constants); | ||||
|         } | ||||
|         self.render_pipeline | ||||
|             .run(&mut encoder, &output_view, &self.ifs_constants); | ||||
|  | ||||
|         self.flare.queue.submit(Some(encoder.finish())); | ||||
|         output.present(); | ||||
|     } | ||||
|  | ||||
|     pub fn resize_accumulate(&mut self) { | ||||
|         let vp_dimensions = self.ifs_constants.viewport_dimensions(); | ||||
|         self.ifs_constants | ||||
|             .with_accumulate(vp_dimensions.x, vp_dimensions.y); | ||||
|  | ||||
|         let accum_image = | ||||
|             Self::create_accum_image(&self.flare.device, vp_dimensions.x as u32, vp_dimensions.y as u32); | ||||
|         self.accumulate_pipeline.set_accum_image(&accum_image); | ||||
|         self.render_pipeline.set_accum_image(&accum_image); | ||||
|         self.accum_image = accum_image; | ||||
|     } | ||||
|  | ||||
|     pub fn resize_viewport(&mut self, width: u32, height: u32) { | ||||
|         self.surface_configuration.width = width; | ||||
|         self.surface_configuration.height = height; | ||||
|         self.surface | ||||
|             .configure(&self.flare.device, &self.surface_configuration); | ||||
|         self.ifs_constants.with_viewport(width as i32, height as i32); | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct Application<'window> { | ||||
|     flare: Arc<Flare>, | ||||
|     window: Option<Arc<Window>>, | ||||
|     flare_render: Option<FlareRender<'window>>, | ||||
| } | ||||
|  | ||||
| impl Application<'_> { | ||||
|     pub fn new() -> Self { | ||||
|         Self { | ||||
|             flare: Arc::new(Flare::new()), | ||||
|             window: None, | ||||
|             flare_render: None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ApplicationHandler for Application<'_> { | ||||
|     fn resumed(&mut self, event_loop: &ActiveEventLoop) { | ||||
|         let attributes = WindowAttributes::default() | ||||
|             .with_title("Flare") | ||||
|             .with_inner_size(LogicalSize::new(1024, 768)); | ||||
|  | ||||
|         let window = Arc::new( | ||||
|             event_loop | ||||
|                 .create_window(attributes) | ||||
|                 .expect("Failed to create window"), | ||||
|         ); | ||||
|  | ||||
|         self.window = Some(window.clone()); | ||||
|  | ||||
|         let mut flare_render = FlareRender::new(self.flare.clone(), window); | ||||
|         // Draw a gasket | ||||
|         let mut ifs_group = IfsGroup::new(&device, &queue); | ||||
|  | ||||
|         let f0 = Transform::new(Coefs::new(0.5, 0., 0., 0., 0.5, 0.), 0, 1, 1.0); | ||||
|         let f1 = Transform::new(Coefs::new(0.5, 0., 0.5, 0., 0.5, 0.), 0, 1, 1.0); | ||||
|         let f2 = Transform::new(Coefs::new(0.5, 0., 0., 0., 0.5, 0.5), 0, 1, 1.0); | ||||
|         let variation = Variation::new(VariationKind::Linear, 1.0); | ||||
|  | ||||
|         flare_render.accumulate_pipeline.set_transforms(&[f0, f1, f2]); | ||||
|         flare_render.accumulate_pipeline.set_variations(&[variation]); | ||||
|         ifs_group.set_transforms(&[f0, f1, f2]); | ||||
|         ifs_group.set_variations(&[variation]); | ||||
|  | ||||
|         flare_render.render(true); | ||||
|         cc_wgpu | ||||
|             .renderer | ||||
|             .write() | ||||
|             .callback_resources | ||||
|             .insert(FlareAssets { | ||||
|                 render_group: RenderGroup::new(&device, &queue, width, height), | ||||
|                 ifs_group, | ||||
|                 accum_pipeline: AccumPipeline::new(&device, &module), | ||||
|                 render_pipeline: RenderPipeline::new(&device, &module, &cc_wgpu.target_format), | ||||
|             }); | ||||
|  | ||||
|         self.flare_render = Some(flare_render); | ||||
|         Self { first_draw: true } | ||||
|     } | ||||
| } | ||||
|  | ||||
|     fn window_event( | ||||
|         &mut self, | ||||
|         event_loop: &ActiveEventLoop, | ||||
|         _window_id: WindowId, | ||||
|         event: WindowEvent, | ||||
|     ) { | ||||
|         match event { | ||||
|             WindowEvent::Resized(size) => { | ||||
|                 let flare_render = self.flare_render.as_mut().unwrap(); | ||||
|                 flare_render.resize_viewport(size.width, size.height); | ||||
|                 flare_render.render(false); | ||||
|             } | ||||
|             WindowEvent::MouseInput { | ||||
|                 button: MouseButton::Left, | ||||
|                 .. | ||||
|             } => { | ||||
|                 let flare_render = self.flare_render.as_mut().unwrap(); | ||||
|                 flare_render.resize_accumulate(); | ||||
|                 flare_render.render(true); | ||||
|             } | ||||
|             WindowEvent::CloseRequested => event_loop.exit(), | ||||
|             _ => (), | ||||
| impl eframe::App for Flare { | ||||
|     fn update(&mut self, ctx: &Context, _frame: &mut Frame) { | ||||
|         egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { | ||||
|             egui::menu::bar(ui, |ui| { | ||||
|                 ui.menu_button("File", |ui| { | ||||
|                     if ui.button("Quit").clicked() { | ||||
|                         ctx.send_viewport_cmd(egui::ViewportCommand::Close); | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         egui::CentralPanel::default().show(ctx, |ui| { | ||||
|             egui::Frame::canvas(ui.style()).show(ui, |ui| { | ||||
|                 let rect = ui.available_rect_before_wrap(); | ||||
|                 let response = ui.allocate_rect(rect, egui::Sense::click()); | ||||
|                 let painter = PaintIfs { | ||||
|                     run_accumulate: self.first_draw || response.clicked(), | ||||
|                     viewport: rect, | ||||
|                 }; | ||||
|                 ui.painter() | ||||
|                     .add(egui_wgpu::Callback::new_paint_callback(rect, painter)); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         self.first_draw = false | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct PaintIfs { | ||||
|     run_accumulate: bool, | ||||
|     viewport: egui::Rect, | ||||
| } | ||||
|  | ||||
| impl CallbackTrait for PaintIfs { | ||||
|     fn prepare( | ||||
|         &self, | ||||
|         _device: &Device, | ||||
|         _queue: &Queue, | ||||
|         _screen_descriptor: &ScreenDescriptor, | ||||
|         egui_encoder: &mut CommandEncoder, | ||||
|         callback_resources: &mut CallbackResources, | ||||
|     ) -> Vec<CommandBuffer> { | ||||
|         if self.run_accumulate { | ||||
|             let flare_assets = callback_resources | ||||
|                 .get_mut::<FlareAssets>() | ||||
|                 .expect("Missing assets"); | ||||
|  | ||||
|             flare_assets | ||||
|                 .render_group | ||||
|                 .set_accum_dimensions(self.viewport.width() as i32, self.viewport.height() as i32); | ||||
|  | ||||
|             let mut pass = egui_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { | ||||
|                 label: Some("accum"), | ||||
|                 timestamp_writes: None, | ||||
|             }); | ||||
|             pass.set_pipeline(&flare_assets.accum_pipeline.pipeline); | ||||
|             pass.set_bind_group(0, flare_assets.render_group.bind_group(), &[]); | ||||
|             pass.set_bind_group(1, flare_assets.ifs_group.bind_group(), &[]); | ||||
|             pass.dispatch_workgroups(1, 1, 1); | ||||
|         } | ||||
|  | ||||
|         let flare_assets = callback_resources.get_mut::<FlareAssets>().expect("Missing assets"); | ||||
|         flare_assets.render_group.set_viewport_dimensions(self.viewport); | ||||
|  | ||||
|         vec![] | ||||
|     } | ||||
|  | ||||
|     fn paint( | ||||
|         &self, | ||||
|         _info: PaintCallbackInfo, | ||||
|         render_pass: &mut RenderPass<'static>, | ||||
|         callback_resources: &CallbackResources, | ||||
|     ) { | ||||
|         let flare_assets = callback_resources | ||||
|             .get::<FlareAssets>() | ||||
|             .expect("Missing assets"); | ||||
|  | ||||
|         render_pass.set_pipeline(&flare_assets.render_pipeline.pipeline); | ||||
|         render_pass.set_bind_group(0, flare_assets.render_group.bind_group(), &[]); | ||||
|         render_pass.draw(0..3, 0..1); | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn main() -> anyhow::Result<()> { | ||||
| fn main() -> eframe::Result { | ||||
|     env_logger::init(); | ||||
|     let _module = wgpu::include_spirv!(concat!(env!("OUT_DIR"), "/flare.spv")); | ||||
|  | ||||
|     let event_loop = EventLoop::new()?; | ||||
|     let mut application = Application::new(); | ||||
|     event_loop.run_app(&mut application)?; | ||||
|     let initial_dimensions = egui::vec2(800., 600.); | ||||
|     let native_options = eframe::NativeOptions { | ||||
|         viewport: egui::ViewportBuilder::default().with_inner_size(initial_dimensions), | ||||
|         renderer: eframe::Renderer::Wgpu, | ||||
|         ..Default::default() | ||||
|     }; | ||||
|  | ||||
|     Ok(()) | ||||
|     eframe::run_native( | ||||
|         "flare", | ||||
|         native_options, | ||||
|         Box::new(|cc| Ok(Box::new(Flare::new(cc, initial_dimensions.x as i32, initial_dimensions.y as i32)))), | ||||
|     ) | ||||
| } | ||||
|  | ||||
							
								
								
									
										581
									
								
								crates/flare/src/main_winit.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										581
									
								
								crates/flare/src/main_winit.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,581 @@ | ||||
| use flare_shader::{Coefs, Color, ImageConstants, ThreadState, Transform, Variation, VariationKind}; | ||||
| use futures_executor::block_on; | ||||
| use glam::Vec4; | ||||
| use rand::SeedableRng; | ||||
| use rand_xoshiro::Xoshiro128Plus; | ||||
| use std::sync::Arc; | ||||
| use wgpu::util::DeviceExt; | ||||
| use wgpu::{ | ||||
|     Adapter, Device, Features, Instance, Queue, ShaderModule, Surface, SurfaceConfiguration, | ||||
| }; | ||||
| use winit::event::MouseButton; | ||||
| use winit::{ | ||||
|     application::ApplicationHandler, | ||||
|     dpi::LogicalSize, | ||||
|     event::WindowEvent, | ||||
|     event_loop::{ActiveEventLoop, EventLoop}, | ||||
|     window::{Window, WindowAttributes, WindowId}, | ||||
| }; | ||||
|  | ||||
| struct AccumulatePipeline { | ||||
|     device: Device, | ||||
|     bind_group_layout: wgpu::BindGroupLayout, | ||||
|     pipeline: wgpu::ComputePipeline, | ||||
|  | ||||
|     thread_state: Option<wgpu::Buffer>, | ||||
|     transforms: Option<wgpu::Buffer>, | ||||
|     variations: Option<wgpu::Buffer>, | ||||
|     accum_image: Option<wgpu::Buffer>, | ||||
|     bind_group: Option<wgpu::BindGroup>, | ||||
| } | ||||
|  | ||||
| impl AccumulatePipeline { | ||||
|     const BIND_GROUP_LAYOUT_DESCRIPTOR: wgpu::BindGroupLayoutDescriptor<'static> = | ||||
|         wgpu::BindGroupLayoutDescriptor { | ||||
|             label: Some("accumulate"), | ||||
|             entries: &[ | ||||
|                 // thread_state | ||||
|                 wgpu::BindGroupLayoutEntry { | ||||
|                     binding: 0, | ||||
|                     visibility: wgpu::ShaderStages::COMPUTE, | ||||
|                     ty: wgpu::BindingType::Buffer { | ||||
|                         ty: wgpu::BufferBindingType::Storage { read_only: false }, | ||||
|                         has_dynamic_offset: false, | ||||
|                         min_binding_size: None, | ||||
|                     }, | ||||
|                     count: None, | ||||
|                 }, | ||||
|                 // transforms | ||||
|                 wgpu::BindGroupLayoutEntry { | ||||
|                     binding: 1, | ||||
|                     visibility: wgpu::ShaderStages::COMPUTE, | ||||
|                     ty: wgpu::BindingType::Buffer { | ||||
|                         ty: wgpu::BufferBindingType::Storage { read_only: true }, | ||||
|                         has_dynamic_offset: false, | ||||
|                         min_binding_size: None, | ||||
|                     }, | ||||
|                     count: None, | ||||
|                 }, | ||||
|                 // variations | ||||
|                 wgpu::BindGroupLayoutEntry { | ||||
|                     binding: 2, | ||||
|                     visibility: wgpu::ShaderStages::COMPUTE, | ||||
|                     ty: wgpu::BindingType::Buffer { | ||||
|                         ty: wgpu::BufferBindingType::Storage { read_only: true }, | ||||
|                         has_dynamic_offset: false, | ||||
|                         min_binding_size: None, | ||||
|                     }, | ||||
|                     count: None, | ||||
|                 }, | ||||
|                 // accum_image | ||||
|                 wgpu::BindGroupLayoutEntry { | ||||
|                     binding: 3, | ||||
|                     visibility: wgpu::ShaderStages::COMPUTE, | ||||
|                     ty: wgpu::BindingType::Buffer { | ||||
|                         ty: wgpu::BufferBindingType::Storage { read_only: false }, | ||||
|                         has_dynamic_offset: false, | ||||
|                         min_binding_size: None, | ||||
|                     }, | ||||
|                     count: None, | ||||
|                 }, | ||||
|             ], | ||||
|         }; | ||||
|  | ||||
|     const N_THREADS: usize = 1; | ||||
|  | ||||
|     pub fn new(device: &Device, module: &ShaderModule) -> Self { | ||||
|         let bind_group_layout = | ||||
|             device.create_bind_group_layout(&Self::BIND_GROUP_LAYOUT_DESCRIPTOR); | ||||
|         let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { | ||||
|             label: Some("accumulate"), | ||||
|             bind_group_layouts: &[&bind_group_layout], | ||||
|             push_constant_ranges: &[wgpu::PushConstantRange { | ||||
|                 stages: wgpu::ShaderStages::COMPUTE, | ||||
|                 range: 0..size_of::<ImageConstants>() as u32, | ||||
|             }], | ||||
|         }); | ||||
|         let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { | ||||
|             label: Some("accumulate"), | ||||
|             layout: Some(&pipeline_layout), | ||||
|             module, | ||||
|             entry_point: Some("main_cs"), | ||||
|             compilation_options: Default::default(), | ||||
|             cache: None, | ||||
|         }); | ||||
|  | ||||
|         let rng = rand::thread_rng(); | ||||
|         let mut rng_xoshiro = Xoshiro128Plus::from_rng(rng).expect("Unable to seed thread_state"); | ||||
|         let mut thread_state_elements = vec![]; | ||||
|         for _i in 0..Self::N_THREADS { | ||||
|             thread_state_elements.push(ThreadState::new(&mut rng_xoshiro)); | ||||
|         } | ||||
|         let thread_state = Some( | ||||
|             device.create_buffer_init(&wgpu::util::BufferInitDescriptor { | ||||
|                 label: Some("accumulate/thread_state"), | ||||
|                 contents: bytemuck::cast_slice(&thread_state_elements), | ||||
|                 usage: wgpu::BufferUsages::STORAGE, | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         Self { | ||||
|             device: device.clone(), | ||||
|             bind_group_layout, | ||||
|             pipeline, | ||||
|             thread_state, | ||||
|             transforms: None, | ||||
|             variations: None, | ||||
|             accum_image: None, | ||||
|             bind_group: None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn set_transforms(&mut self, transforms: &[Transform]) { | ||||
|         // Should be smarter about allocation in the future | ||||
|         self.transforms = Some( | ||||
|             self.device | ||||
|                 .create_buffer_init(&wgpu::util::BufferInitDescriptor { | ||||
|                     label: Some("accumulate/transforms"), | ||||
|                     contents: bytemuck::cast_slice(transforms), | ||||
|                     usage: wgpu::BufferUsages::STORAGE, | ||||
|                 }), | ||||
|         ); | ||||
|  | ||||
|         // Setting a new buffer invalidates the existing bindings | ||||
|         self.bind_group.take(); | ||||
|     } | ||||
|  | ||||
|     pub fn set_variations(&mut self, variations: &[Variation]) { | ||||
|         self.variations = Some( | ||||
|             self.device | ||||
|                 .create_buffer_init(&wgpu::util::BufferInitDescriptor { | ||||
|                     label: Some("accumulate/variations"), | ||||
|                     contents: bytemuck::cast_slice(variations), | ||||
|                     usage: wgpu::BufferUsages::STORAGE, | ||||
|                 }), | ||||
|         ); | ||||
|  | ||||
|         self.bind_group.take(); | ||||
|     } | ||||
|  | ||||
|     pub fn set_accum_image(&mut self, accum_image: &wgpu::Buffer) { | ||||
|         self.accum_image = Some(accum_image.clone()); | ||||
|  | ||||
|         // Setting a new buffer invalidates the existing bindings | ||||
|         self.bind_group.take(); | ||||
|     } | ||||
|  | ||||
|     fn fetch_bind_group(&mut self) -> &wgpu::BindGroup { | ||||
|         self.bind_group.get_or_insert_with(|| { | ||||
|             self.device.create_bind_group(&wgpu::BindGroupDescriptor { | ||||
|                 label: Some("accumulate"), | ||||
|                 layout: &self.bind_group_layout, | ||||
|                 entries: &[ | ||||
|                     wgpu::BindGroupEntry { | ||||
|                         binding: 0, | ||||
|                         resource: self | ||||
|                             .thread_state | ||||
|                             .as_ref() | ||||
|                             .expect("thread_state missing") | ||||
|                             .as_entire_binding(), | ||||
|                     }, | ||||
|                     wgpu::BindGroupEntry { | ||||
|                         binding: 1, | ||||
|                         resource: self | ||||
|                             .transforms | ||||
|                             .as_ref() | ||||
|                             .expect("transforms missing") | ||||
|                             .as_entire_binding(), | ||||
|                     }, | ||||
|                     wgpu::BindGroupEntry { | ||||
|                         binding: 2, | ||||
|                         resource: self | ||||
|                             .variations | ||||
|                             .as_ref() | ||||
|                             .expect("variations missing") | ||||
|                             .as_entire_binding(), | ||||
|                     }, | ||||
|                     wgpu::BindGroupEntry { | ||||
|                         binding: 3, | ||||
|                         resource: self | ||||
|                             .accum_image | ||||
|                             .as_ref() | ||||
|                             .expect("accum_image missing") | ||||
|                             .as_entire_binding(), | ||||
|                     }, | ||||
|                 ], | ||||
|             }) | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn run(&mut self, encoder: &mut wgpu::CommandEncoder, constants: &ImageConstants) { | ||||
|         let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { | ||||
|             label: Some("accumulate"), | ||||
|             timestamp_writes: None, | ||||
|         }); | ||||
|         pass.set_pipeline(&self.pipeline); | ||||
|         pass.set_push_constants(0, bytemuck::cast_slice(&[*constants])); | ||||
|         pass.set_bind_group(0, self.fetch_bind_group(), &[]); | ||||
|         pass.dispatch_workgroups(1, 1, 1); | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct RenderPipeline { | ||||
|     device: Device, | ||||
|     bind_group_layout: wgpu::BindGroupLayout, | ||||
|     pipeline: wgpu::RenderPipeline, | ||||
|  | ||||
|     accum_image: Option<wgpu::Buffer>, | ||||
|     bind_group: Option<wgpu::BindGroup>, | ||||
| } | ||||
|  | ||||
| impl RenderPipeline { | ||||
|     const BIND_GROUP_LAYOUT_DESCRIPTOR: wgpu::BindGroupLayoutDescriptor<'static> = | ||||
|         wgpu::BindGroupLayoutDescriptor { | ||||
|             label: Some("render"), | ||||
|             entries: &[wgpu::BindGroupLayoutEntry { | ||||
|                 binding: 0, | ||||
|                 visibility: wgpu::ShaderStages::FRAGMENT, | ||||
|                 ty: wgpu::BindingType::Buffer { | ||||
|                     ty: wgpu::BufferBindingType::Storage { read_only: true }, | ||||
|                     has_dynamic_offset: false, | ||||
|                     min_binding_size: None, | ||||
|                 }, | ||||
|                 count: None, | ||||
|             }], | ||||
|         }; | ||||
|  | ||||
|     pub fn new(device: &Device, module: &ShaderModule, format: wgpu::TextureFormat) -> Self { | ||||
|         let bind_group_layout = | ||||
|             device.create_bind_group_layout(&Self::BIND_GROUP_LAYOUT_DESCRIPTOR); | ||||
|         let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { | ||||
|             label: Some("render"), | ||||
|             bind_group_layouts: &[&bind_group_layout], | ||||
|             push_constant_ranges: &[wgpu::PushConstantRange { | ||||
|                 stages: wgpu::ShaderStages::FRAGMENT, | ||||
|                 range: 0..size_of::<ImageConstants>() as u32, | ||||
|             }], | ||||
|         }); | ||||
|         let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { | ||||
|             label: Some("render"), | ||||
|             layout: Some(&pipeline_layout), | ||||
|             vertex: wgpu::VertexState { | ||||
|                 module, | ||||
|                 entry_point: Some("main_vs"), | ||||
|                 compilation_options: Default::default(), | ||||
|                 buffers: &[], | ||||
|             }, | ||||
|             primitive: Default::default(), | ||||
|             depth_stencil: None, | ||||
|             multisample: Default::default(), | ||||
|             fragment: Some(wgpu::FragmentState { | ||||
|                 module, | ||||
|                 entry_point: Some("main_fs"), | ||||
|                 compilation_options: Default::default(), | ||||
|                 targets: &[Some(wgpu::ColorTargetState { | ||||
|                     format, | ||||
|                     blend: None, | ||||
|                     write_mask: Default::default(), | ||||
|                 })], | ||||
|             }), | ||||
|             multiview: None, | ||||
|             cache: None, | ||||
|         }); | ||||
|         Self { | ||||
|             device: device.clone(), | ||||
|             bind_group_layout, | ||||
|             pipeline, | ||||
|             accum_image: None, | ||||
|             bind_group: None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn set_accum_image(&mut self, accum_image: &wgpu::Buffer) { | ||||
|         self.accum_image = Some(accum_image.clone()); | ||||
|         self.bind_group.take(); | ||||
|     } | ||||
|  | ||||
|     fn fetch_bind_group(&mut self) -> &wgpu::BindGroup { | ||||
|         self.bind_group.get_or_insert_with(|| { | ||||
|             self.device.create_bind_group(&wgpu::BindGroupDescriptor { | ||||
|                 label: Some("render"), | ||||
|                 layout: &self.bind_group_layout, | ||||
|                 entries: &[wgpu::BindGroupEntry { | ||||
|                     binding: 0, | ||||
|                     resource: self | ||||
|                         .accum_image | ||||
|                         .as_ref() | ||||
|                         .expect("accum_image missing") | ||||
|                         .as_entire_binding(), | ||||
|                 }], | ||||
|             }) | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn run( | ||||
|         &mut self, | ||||
|         encoder: &mut wgpu::CommandEncoder, | ||||
|         output_view: &wgpu::TextureView, | ||||
|         constants: &ImageConstants, | ||||
|     ) { | ||||
|         let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { | ||||
|             label: Some("render"), | ||||
|             color_attachments: &[Some(wgpu::RenderPassColorAttachment { | ||||
|                 view: output_view, | ||||
|                 resolve_target: None, | ||||
|                 ops: Default::default(), | ||||
|             })], | ||||
|             depth_stencil_attachment: None, | ||||
|             timestamp_writes: None, | ||||
|             occlusion_query_set: None, | ||||
|         }); | ||||
|         pass.set_pipeline(&self.pipeline); | ||||
|         pass.set_push_constants( | ||||
|             wgpu::ShaderStages::FRAGMENT, | ||||
|             0, | ||||
|             bytemuck::cast_slice(&[*constants]), | ||||
|         ); | ||||
|         pass.set_bind_group(0, self.fetch_bind_group(), &[]); | ||||
|         pass.draw(0..3, 0..1); | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct Flare { | ||||
|     instance: Instance, | ||||
|     adapter: Adapter, | ||||
|     device: Device, | ||||
|     queue: Queue, | ||||
|     module: ShaderModule, | ||||
| } | ||||
|  | ||||
| impl Flare { | ||||
|     pub fn new() -> Self { | ||||
|         let backends = wgpu::Backends::from_env().unwrap_or_default(); | ||||
|         let instance = Instance::new(&wgpu::InstanceDescriptor { | ||||
|             backends, | ||||
|             ..Default::default() | ||||
|         }); | ||||
|  | ||||
|         let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions { | ||||
|             power_preference: wgpu::PowerPreference::HighPerformance, | ||||
|             force_fallback_adapter: false, | ||||
|             compatible_surface: None, | ||||
|         }); | ||||
|         let adapter = block_on(adapter).expect("Failed to find GPU adapter"); | ||||
|  | ||||
|         let required_limits = wgpu::Limits { | ||||
|             max_push_constant_size: size_of::<ImageConstants>() as u32, | ||||
|             ..Default::default() | ||||
|         }; | ||||
|  | ||||
|         let device = adapter.request_device( | ||||
|             &wgpu::DeviceDescriptor { | ||||
|                 label: Some("flare"), | ||||
|                 required_features: Features::TIMESTAMP_QUERY | Features::PUSH_CONSTANTS, | ||||
|                 required_limits, | ||||
|                 memory_hints: Default::default(), | ||||
|             }, | ||||
|             None, | ||||
|         ); | ||||
|         let (device, queue) = block_on(device).expect("Failed to find GPU device"); | ||||
|         let module = device | ||||
|             .create_shader_module(wgpu::include_spirv!(concat!(env!("OUT_DIR"), "/flare.spv"))); | ||||
|  | ||||
|         Flare { | ||||
|             instance, | ||||
|             adapter, | ||||
|             device, | ||||
|             queue, | ||||
|             module, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct FlareRender<'window> { | ||||
|     flare: Arc<Flare>, | ||||
|     surface: Surface<'window>, | ||||
|     surface_configuration: SurfaceConfiguration, | ||||
|     accumulate_pipeline: AccumulatePipeline, | ||||
|     render_pipeline: RenderPipeline, | ||||
|     ifs_constants: ImageConstants, | ||||
|     accum_image: wgpu::Buffer, | ||||
| } | ||||
|  | ||||
| impl FlareRender<'_> { | ||||
|     fn create_accum_image(device: &Device, width: u32, height: u32) -> wgpu::Buffer { | ||||
|         let pixels = (width * height) as u64; | ||||
|         let pixel_size = 4u64 * size_of::<f32>() as u64; | ||||
|         let size = pixels * pixel_size; | ||||
|         device.create_buffer(&wgpu::BufferDescriptor { | ||||
|             label: Some("accum_image"), | ||||
|             size, | ||||
|             usage: wgpu::BufferUsages::STORAGE, | ||||
|             mapped_at_creation: false, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn new(flare: Arc<Flare>, window: Arc<Window>) -> Self { | ||||
|         let window_size = window.inner_size(); | ||||
|  | ||||
|         let surface = flare | ||||
|             .instance | ||||
|             .create_surface(window.clone()) | ||||
|             .expect("Unable to create surface"); | ||||
|         let mut surface_configuration = surface | ||||
|             .get_default_config(&flare.adapter, window_size.width, window_size.height) | ||||
|             .expect("Unable to get surface config"); | ||||
|         surface_configuration.present_mode = wgpu::PresentMode::AutoVsync; | ||||
|         surface.configure(&flare.device, &surface_configuration); | ||||
|  | ||||
|         let mut accumulate_pipeline = AccumulatePipeline::new(&flare.device, &flare.module); | ||||
|         let mut render_pipeline = | ||||
|             RenderPipeline::new(&flare.device, &flare.module, surface_configuration.format); | ||||
|  | ||||
|         let ifs_constants = ImageConstants::new( | ||||
|             window_size.width as i32, | ||||
|             window_size.height as i32, | ||||
|             window_size.width as i32, | ||||
|             window_size.height as i32, | ||||
|             Vec4::BLACK, | ||||
|         ); | ||||
|  | ||||
|         let accum_image = | ||||
|             Self::create_accum_image(&flare.device, window_size.width, window_size.height); | ||||
|  | ||||
|         accumulate_pipeline.set_accum_image(&accum_image); | ||||
|         render_pipeline.set_accum_image(&accum_image); | ||||
|  | ||||
|         Self { | ||||
|             flare, | ||||
|             surface, | ||||
|             surface_configuration, | ||||
|             accumulate_pipeline, | ||||
|             render_pipeline, | ||||
|             ifs_constants, | ||||
|             accum_image, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn render(&mut self, run_accumulate: bool) { | ||||
|         let output = self | ||||
|             .surface | ||||
|             .get_current_texture() | ||||
|             .expect("Failed to get current texture"); | ||||
|         let output_view = output.texture.create_view(&Default::default()); | ||||
|         let mut encoder = self | ||||
|             .flare | ||||
|             .device | ||||
|             .create_command_encoder(&Default::default()); | ||||
|  | ||||
|         if run_accumulate { | ||||
|             self.accumulate_pipeline | ||||
|                 .run(&mut encoder, &self.ifs_constants); | ||||
|         } | ||||
|         self.render_pipeline | ||||
|             .run(&mut encoder, &output_view, &self.ifs_constants); | ||||
|  | ||||
|         self.flare.queue.submit(Some(encoder.finish())); | ||||
|         output.present(); | ||||
|     } | ||||
|  | ||||
|     pub fn resize_accumulate(&mut self) { | ||||
|         let vp_dimensions = self.ifs_constants.viewport_dimensions(); | ||||
|         self.ifs_constants | ||||
|             .with_accumulate(vp_dimensions.x, vp_dimensions.y); | ||||
|  | ||||
|         let accum_image = | ||||
|             Self::create_accum_image(&self.flare.device, vp_dimensions.x as u32, vp_dimensions.y as u32); | ||||
|         self.accumulate_pipeline.set_accum_image(&accum_image); | ||||
|         self.render_pipeline.set_accum_image(&accum_image); | ||||
|         self.accum_image = accum_image; | ||||
|     } | ||||
|  | ||||
|     pub fn resize_viewport(&mut self, width: u32, height: u32) { | ||||
|         self.surface_configuration.width = width; | ||||
|         self.surface_configuration.height = height; | ||||
|         self.surface | ||||
|             .configure(&self.flare.device, &self.surface_configuration); | ||||
|         self.ifs_constants.with_viewport(width as i32, height as i32); | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct Application<'window> { | ||||
|     flare: Arc<Flare>, | ||||
|     window: Option<Arc<Window>>, | ||||
|     flare_render: Option<FlareRender<'window>>, | ||||
| } | ||||
|  | ||||
| impl Application<'_> { | ||||
|     pub fn new() -> Self { | ||||
|         Self { | ||||
|             flare: Arc::new(Flare::new()), | ||||
|             window: None, | ||||
|             flare_render: None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ApplicationHandler for Application<'_> { | ||||
|     fn resumed(&mut self, event_loop: &ActiveEventLoop) { | ||||
|         let attributes = WindowAttributes::default() | ||||
|             .with_title("Flare") | ||||
|             .with_inner_size(LogicalSize::new(1024, 768)); | ||||
|  | ||||
|         let window = Arc::new( | ||||
|             event_loop | ||||
|                 .create_window(attributes) | ||||
|                 .expect("Failed to create window"), | ||||
|         ); | ||||
|  | ||||
|         self.window = Some(window.clone()); | ||||
|  | ||||
|         let mut flare_render = FlareRender::new(self.flare.clone(), window); | ||||
|  | ||||
|         let f0 = Transform::new(Coefs::new(0.5, 0., 0., 0., 0.5, 0.), 0, 1, 1.0); | ||||
|         let f1 = Transform::new(Coefs::new(0.5, 0., 0.5, 0., 0.5, 0.), 0, 1, 1.0); | ||||
|         let f2 = Transform::new(Coefs::new(0.5, 0., 0., 0., 0.5, 0.5), 0, 1, 1.0); | ||||
|         let variation = Variation::new(VariationKind::Linear, 1.0); | ||||
|  | ||||
|         flare_render.accumulate_pipeline.set_transforms(&[f0, f1, f2]); | ||||
|         flare_render.accumulate_pipeline.set_variations(&[variation]); | ||||
|  | ||||
|         flare_render.render(true); | ||||
|  | ||||
|         self.flare_render = Some(flare_render); | ||||
|     } | ||||
|  | ||||
|     fn window_event( | ||||
|         &mut self, | ||||
|         event_loop: &ActiveEventLoop, | ||||
|         _window_id: WindowId, | ||||
|         event: WindowEvent, | ||||
|     ) { | ||||
|         match event { | ||||
|             WindowEvent::Resized(size) => { | ||||
|                 let flare_render = self.flare_render.as_mut().unwrap(); | ||||
|                 flare_render.resize_viewport(size.width, size.height); | ||||
|                 flare_render.render(false); | ||||
|             } | ||||
|             WindowEvent::MouseInput { | ||||
|                 button: MouseButton::Left, | ||||
|                 .. | ||||
|             } => { | ||||
|                 let flare_render = self.flare_render.as_mut().unwrap(); | ||||
|                 flare_render.resize_accumulate(); | ||||
|                 flare_render.render(true); | ||||
|             } | ||||
|             WindowEvent::CloseRequested => event_loop.exit(), | ||||
|             _ => (), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn main() -> anyhow::Result<()> { | ||||
|     env_logger::init(); | ||||
|     let _module = wgpu::include_spirv!(concat!(env!("OUT_DIR"), "/flare.spv")); | ||||
|  | ||||
|     let event_loop = EventLoop::new()?; | ||||
|     let mut application = Application::new(); | ||||
|     event_loop.run_app(&mut application)?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user