Compare commits
No commits in common. "68843293ffa9386227b07f34a1b20791f2f530e6" and "221d544a01ad1cac561022c17a1561dc58752049" have entirely different histories.
68843293ff
...
221d544a01
1427
Cargo.lock
generated
1427
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -5,47 +5,48 @@ use rand::Rng;
|
||||
use rand::distributions::Standard;
|
||||
use rand::prelude::Distribution;
|
||||
use rand_xoshiro::Xoshiro128Plus;
|
||||
use spirv_std::num_traits::Float;
|
||||
use spirv_std::spirv;
|
||||
use spirv_std::num_traits::Float;
|
||||
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct ViewportConstants {
|
||||
offset_x: i32,
|
||||
offset_y: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
pub struct ImageConstants {
|
||||
accum_width: i32,
|
||||
accum_height: i32,
|
||||
viewport_width: i32,
|
||||
viewport_height: i32,
|
||||
background_color: Vec4,
|
||||
}
|
||||
|
||||
impl ViewportConstants {
|
||||
impl ImageConstants {
|
||||
pub fn new(
|
||||
offset_x: i32,
|
||||
offset_y: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
accum_width: i32,
|
||||
accum_height: i32,
|
||||
viewport_width: i32,
|
||||
viewport_height: i32,
|
||||
background_color: Vec4,
|
||||
) -> Self {
|
||||
Self {
|
||||
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
|
||||
accum_width,
|
||||
accum_height,
|
||||
viewport_width,
|
||||
viewport_height,
|
||||
background_color,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn min_dimension(&self) -> i32 {
|
||||
if self.width < self.height { self.width } else { self.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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,22 +61,11 @@ 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(zoom_factor),
|
||||
glam::Vec2::splat(2f32.powf(self.zoom)),
|
||||
self.rotate.to_radians(),
|
||||
-vec2(self.offset_x, self.offset_y) * zoom_factor,
|
||||
-vec2(self.offset_x, self.offset_y),
|
||||
);
|
||||
let ifs_to_pixel = glam::Affine2::from_scale_angle_translation(
|
||||
glam::Vec2::splat(self.scale),
|
||||
@ -129,21 +119,11 @@ impl ThreadState {
|
||||
unsafe { core::mem::transmute::<_, Xoshiro128Plus>(*rng_state) }
|
||||
}
|
||||
|
||||
pub fn new<R: Rng>(rng: &mut R) -> Self {
|
||||
let mut rng_state: [u32; 4] = [0; 4];
|
||||
rng.fill(&mut rng_state);
|
||||
pub fn new(rng: &mut Xoshiro128Plus) -> Self {
|
||||
let point = vec2(rng.sample(BiUnit), rng.sample(BiUnit));
|
||||
Self { rng_state, point }
|
||||
}
|
||||
|
||||
pub fn jump(&self) -> Self {
|
||||
let mut rng = self.get_rng();
|
||||
let rng_state = Self::xoshiro_to_state(rng);
|
||||
rng.jump();
|
||||
let point = vec2(rng.sample(BiUnit), rng.sample(BiUnit));
|
||||
Self {
|
||||
rng_state: Self::xoshiro_to_state(&rng),
|
||||
point,
|
||||
}
|
||||
Self { rng_state, point }
|
||||
}
|
||||
|
||||
pub fn get_rng(&self) -> Xoshiro128Plus {
|
||||
@ -257,13 +237,11 @@ fn next_transform<R: Rng + ?Sized>(
|
||||
|
||||
#[spirv(compute(threads(1)))]
|
||||
pub fn main_cs(
|
||||
#[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(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 = 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;
|
||||
@ -278,23 +256,18 @@ pub fn main_cs(
|
||||
point = next_transform(&mut rng, transform_weight, transforms).apply(variations, point);
|
||||
}
|
||||
|
||||
// ...because `<i32>.min(<i32>)` has compilation issues.
|
||||
let min_dimension = if accum_constants.width < accum_constants.height {
|
||||
accum_constants.width
|
||||
} else {
|
||||
accum_constants.height
|
||||
};
|
||||
// ...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 };
|
||||
|
||||
// Fixed camera, should be provided by a uniform in the future
|
||||
let camera = CameraConstants {
|
||||
scale: min_dimension as f32 / 4.0,
|
||||
zoom: 2.0,
|
||||
scale: max_dimension as f32 / 4.0,
|
||||
zoom: 0.0,
|
||||
rotate: 0.0,
|
||||
offset_x: 0.5,
|
||||
offset_y: 0.5,
|
||||
offset_x: 0.0,
|
||||
offset_y: 0.0,
|
||||
}
|
||||
.camera(accum_constants.width, accum_constants.height);
|
||||
// let camera = camera_constants.camera(image_constants.accum_width, image_constants.accum_height);
|
||||
.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 {
|
||||
@ -302,9 +275,9 @@ pub fn main_cs(
|
||||
|
||||
let pixel_coordinates = camera.transform_point2(point).as_ivec2();
|
||||
if pixel_coordinates.x < 0
|
||||
|| pixel_coordinates.x >= accum_constants.width
|
||||
|| pixel_coordinates.x >= image_constants.accum_width
|
||||
|| pixel_coordinates.y < 0
|
||||
|| pixel_coordinates.y >= accum_constants.height
|
||||
|| pixel_coordinates.y >= image_constants.accum_height
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -312,7 +285,7 @@ pub fn main_cs(
|
||||
let ii = image_index(
|
||||
pixel_coordinates.x,
|
||||
pixel_coordinates.y,
|
||||
accum_constants.width,
|
||||
image_constants.accum_height,
|
||||
);
|
||||
accum_image[ii as usize] = Color::WHITE;
|
||||
}
|
||||
@ -337,24 +310,18 @@ pub fn main_vs(
|
||||
#[spirv(fragment)]
|
||||
pub fn main_fs(
|
||||
#[spirv(frag_coord)] frag_coord: 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],
|
||||
#[spirv(push_constant)] ifs_constants: &ImageConstants,
|
||||
#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] accum_image: &[Vec4],
|
||||
output: &mut Vec4,
|
||||
) {
|
||||
// Bootleg texture sampling; map from viewport image pixel coordinates to accumulator image
|
||||
// pixel coordinates
|
||||
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 viewport_coordinate = frag_coord.xy().as_uvec2();
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
// Scale both width and height by the same factor; preserves aspect ratio
|
||||
let scale_width = a_width / v_width;
|
||||
@ -365,9 +332,8 @@ 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
|
||||
@ -381,13 +347,4 @@ 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,9 +8,6 @@ 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,219 +1,38 @@
|
||||
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 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::{CommandBuffer, CommandEncoder, Device, Queue, RenderPass};
|
||||
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 RenderGroup {
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
struct AccumulatePipeline {
|
||||
device: Device,
|
||||
bind_group_layout: wgpu::BindGroupLayout,
|
||||
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,
|
||||
}
|
||||
pipeline: wgpu::ComputePipeline,
|
||||
|
||||
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>,
|
||||
thread_state: Option<wgpu::Buffer>,
|
||||
transforms: Option<wgpu::Buffer>,
|
||||
variations: Option<wgpu::Buffer>,
|
||||
accum_image: Option<wgpu::Buffer>,
|
||||
bind_group: Option<wgpu::BindGroup>,
|
||||
}
|
||||
|
||||
impl IfsGroup {
|
||||
pub fn bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("accum"),
|
||||
impl AccumulatePipeline {
|
||||
const BIND_GROUP_LAYOUT_DESCRIPTOR: wgpu::BindGroupLayoutDescriptor<'static> =
|
||||
wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("accumulate"),
|
||||
entries: &[
|
||||
// thread_state
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
@ -248,105 +67,35 @@ impl IfsGroup {
|
||||
},
|
||||
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,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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);
|
||||
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("accum"),
|
||||
bind_group_layouts: &[&render_layout, &ifs_layout],
|
||||
push_constant_ranges: &[],
|
||||
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("accum"),
|
||||
label: Some("accumulate"),
|
||||
layout: Some(&pipeline_layout),
|
||||
module,
|
||||
entry_point: Some("main_cs"),
|
||||
@ -354,25 +103,157 @@ impl AccumPipeline {
|
||||
cache: None,
|
||||
});
|
||||
|
||||
Self { pipeline }
|
||||
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 {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
module: &wgpu::ShaderModule,
|
||||
target: &wgpu::TextureFormat,
|
||||
) -> Self {
|
||||
let render_layout = RenderGroup::bind_group_layout(device);
|
||||
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: &[&render_layout],
|
||||
push_constant_ranges: &[],
|
||||
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"),
|
||||
@ -390,161 +271,311 @@ impl RenderPipeline {
|
||||
module,
|
||||
entry_point: Some("main_fs"),
|
||||
compilation_options: Default::default(),
|
||||
targets: &[Some((*target).into())],
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: None,
|
||||
write_mask: Default::default(),
|
||||
})],
|
||||
}),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
Self { pipeline }
|
||||
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 FlareAssets {
|
||||
render_group: RenderGroup,
|
||||
ifs_group: IfsGroup,
|
||||
accum_pipeline: AccumPipeline,
|
||||
render_pipeline: RenderPipeline,
|
||||
}
|
||||
|
||||
struct Flare {
|
||||
first_draw: bool,
|
||||
instance: Instance,
|
||||
adapter: Adapter,
|
||||
device: Device,
|
||||
queue: Queue,
|
||||
module: ShaderModule,
|
||||
}
|
||||
|
||||
impl Flare {
|
||||
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();
|
||||
pub fn new() -> Self {
|
||||
let backends = wgpu::Backends::from_env().unwrap_or_default();
|
||||
let instance = Instance::new(&wgpu::InstanceDescriptor {
|
||||
backends,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let module_spirv = wgpu::include_spirv!(concat!(env!("OUT_DIR"), "/flare.spv"));
|
||||
let module = device.create_shader_module(module_spirv);
|
||||
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");
|
||||
|
||||
// Draw a gasket
|
||||
let mut ifs_group = IfsGroup::new(&device, &queue);
|
||||
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);
|
||||
|
||||
ifs_group.set_transforms(&[f0, f1, f2]);
|
||||
ifs_group.set_variations(&[variation]);
|
||||
flare_render.accumulate_pipeline.set_transforms(&[f0, f1, f2]);
|
||||
flare_render.accumulate_pipeline.set_variations(&[variation]);
|
||||
|
||||
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),
|
||||
});
|
||||
flare_render.render(true);
|
||||
|
||||
Self { first_draw: true }
|
||||
}
|
||||
}
|
||||
|
||||
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![]
|
||||
self.flare_render = Some(flare_render);
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&self,
|
||||
_info: PaintCallbackInfo,
|
||||
render_pass: &mut RenderPass<'static>,
|
||||
callback_resources: &CallbackResources,
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop: &ActiveEventLoop,
|
||||
_window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
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);
|
||||
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(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
env_logger::init();
|
||||
let _module = wgpu::include_spirv!(concat!(env!("OUT_DIR"), "/flare.spv"));
|
||||
|
||||
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()
|
||||
};
|
||||
let event_loop = EventLoop::new()?;
|
||||
let mut application = Application::new();
|
||||
event_loop.run_app(&mut application)?;
|
||||
|
||||
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)))),
|
||||
)
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,581 +0,0 @@
|
||||
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(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user