Put in a GUI, provide the viewport information
This commit is contained in:
parent
0a1e0f98c6
commit
68843293ff
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::distributions::Standard;
|
||||||
use rand::prelude::Distribution;
|
use rand::prelude::Distribution;
|
||||||
use rand_xoshiro::Xoshiro128Plus;
|
use rand_xoshiro::Xoshiro128Plus;
|
||||||
use spirv_std::spirv;
|
|
||||||
use spirv_std::num_traits::Float;
|
use spirv_std::num_traits::Float;
|
||||||
|
use spirv_std::spirv;
|
||||||
|
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct ImageConstants {
|
pub struct ViewportConstants {
|
||||||
accum_width: i32,
|
offset_x: i32,
|
||||||
accum_height: i32,
|
offset_y: i32,
|
||||||
viewport_width: i32,
|
width: i32,
|
||||||
viewport_height: i32,
|
height: i32,
|
||||||
background_color: Vec4,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImageConstants {
|
impl ViewportConstants {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
accum_width: i32,
|
offset_x: i32,
|
||||||
accum_height: i32,
|
offset_y: i32,
|
||||||
viewport_width: i32,
|
width: i32,
|
||||||
viewport_height: i32,
|
height: i32,
|
||||||
background_color: Vec4,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
accum_width,
|
offset_x, offset_y, width, height
|
||||||
accum_height,
|
}
|
||||||
viewport_width,
|
}
|
||||||
viewport_height,
|
}
|
||||||
background_color,
|
|
||||||
|
#[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 {
|
pub fn min_dimension(&self) -> i32 {
|
||||||
glam::ivec2(self.viewport_width, self.viewport_height)
|
if self.width < self.height { self.width } else { self.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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +60,16 @@ pub struct CameraConstants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
pub fn camera(&self, image_width: i32, image_height: i32) -> glam::Affine2 {
|
||||||
let zoom_factor = 2f32.powf(self.zoom);
|
let zoom_factor = 2f32.powf(self.zoom);
|
||||||
let zoom_rotate_offset = glam::Affine2::from_scale_angle_translation(
|
let zoom_rotate_offset = glam::Affine2::from_scale_angle_translation(
|
||||||
@ -120,13 +129,23 @@ impl ThreadState {
|
|||||||
unsafe { core::mem::transmute::<_, Xoshiro128Plus>(*rng_state) }
|
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 point = vec2(rng.sample(BiUnit), rng.sample(BiUnit));
|
||||||
let rng_state = Self::xoshiro_to_state(rng);
|
|
||||||
rng.jump();
|
|
||||||
Self { rng_state, point }
|
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 {
|
pub fn get_rng(&self) -> Xoshiro128Plus {
|
||||||
Self::state_to_xoshiro(&self.rng_state)
|
Self::state_to_xoshiro(&self.rng_state)
|
||||||
}
|
}
|
||||||
@ -238,11 +257,13 @@ fn next_transform<R: Rng + ?Sized>(
|
|||||||
|
|
||||||
#[spirv(compute(threads(1)))]
|
#[spirv(compute(threads(1)))]
|
||||||
pub fn main_cs(
|
pub fn main_cs(
|
||||||
#[spirv(push_constant)] image_constants: &ImageConstants,
|
#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] _viewport_constants: &ViewportConstants,
|
||||||
#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] thread_state: &mut [ThreadState],
|
#[spirv(storage_buffer, descriptor_set = 0, binding = 1)] accum_constants: &AccumConstants,
|
||||||
#[spirv(storage_buffer, descriptor_set = 0, binding = 1)] transforms: &[Transform],
|
#[spirv(storage_buffer, descriptor_set = 0, binding = 2)] _camera_constants: &CameraConstants,
|
||||||
#[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 = 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 rng = thread_state[0].get_rng();
|
||||||
let mut point = thread_state[0].point;
|
let mut point = thread_state[0].point;
|
||||||
@ -258,7 +279,11 @@ pub fn main_cs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ...because `<i32>.min(<i32>)` has compilation issues.
|
// ...because `<i32>.min(<i32>)` has compilation issues.
|
||||||
let min_dimension = if image_constants.accum_width < image_constants.accum_height { image_constants.accum_width } else { image_constants.accum_height };
|
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
|
// Fixed camera, should be provided by a uniform in the future
|
||||||
let camera = CameraConstants {
|
let camera = CameraConstants {
|
||||||
@ -268,7 +293,8 @@ pub fn main_cs(
|
|||||||
offset_x: 0.5,
|
offset_x: 0.5,
|
||||||
offset_y: 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
|
// Iterate 100,000, should be provided by a uniform in the future
|
||||||
for _i in 0..100_000 {
|
for _i in 0..100_000 {
|
||||||
@ -276,9 +302,9 @@ pub fn main_cs(
|
|||||||
|
|
||||||
let pixel_coordinates = camera.transform_point2(point).as_ivec2();
|
let pixel_coordinates = camera.transform_point2(point).as_ivec2();
|
||||||
if pixel_coordinates.x < 0
|
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 < 0
|
||||||
|| pixel_coordinates.y >= image_constants.accum_height
|
|| pixel_coordinates.y >= accum_constants.height
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -286,7 +312,7 @@ pub fn main_cs(
|
|||||||
let ii = image_index(
|
let ii = image_index(
|
||||||
pixel_coordinates.x,
|
pixel_coordinates.x,
|
||||||
pixel_coordinates.y,
|
pixel_coordinates.y,
|
||||||
image_constants.accum_width,
|
accum_constants.width,
|
||||||
);
|
);
|
||||||
accum_image[ii as usize] = Color::WHITE;
|
accum_image[ii as usize] = Color::WHITE;
|
||||||
}
|
}
|
||||||
@ -311,18 +337,24 @@ pub fn main_vs(
|
|||||||
#[spirv(fragment)]
|
#[spirv(fragment)]
|
||||||
pub fn main_fs(
|
pub fn main_fs(
|
||||||
#[spirv(frag_coord)] frag_coord: Vec4,
|
#[spirv(frag_coord)] frag_coord: Vec4,
|
||||||
#[spirv(push_constant)] ifs_constants: &ImageConstants,
|
#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] viewport_constants: &ViewportConstants,
|
||||||
#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] accum_image: &[Vec4],
|
#[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,
|
output: &mut Vec4,
|
||||||
) {
|
) {
|
||||||
// Bootleg texture sampling; map from viewport image pixel coordinates to accumulator image
|
// Bootleg texture sampling; map from viewport image pixel coordinates to accumulator image
|
||||||
// pixel coordinates
|
// 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_width = accum_constants.width as f32;
|
||||||
let a_height = ifs_constants.accum_height as f32;
|
let a_height = accum_constants.height as f32;
|
||||||
let v_width = ifs_constants.viewport_width as f32;
|
let v_width = viewport_constants.width as f32;
|
||||||
let v_height = ifs_constants.viewport_height as f32;
|
let v_height = viewport_constants.height as f32;
|
||||||
|
|
||||||
// Scale both width and height by the same factor; preserves aspect ratio
|
// Scale both width and height by the same factor; preserves aspect ratio
|
||||||
let scale_width = a_width / v_width;
|
let scale_width = a_width / v_width;
|
||||||
@ -333,8 +365,9 @@ pub fn main_fs(
|
|||||||
let offset_x = (v_width * scale - a_width) / 2.0;
|
let offset_x = (v_width * scale - a_width) / 2.0;
|
||||||
let offset_y = (v_height * scale - a_height) / 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
|
if accum_coordinate.x < 0.0
|
||||||
|| accum_coordinate.x >= ifs_constants.accum_width as f32
|
|| accum_coordinate.x >= ifs_constants.accum_width as f32
|
||||||
|| accum_coordinate.y < 0.0
|
|| accum_coordinate.y < 0.0
|
||||||
@ -348,4 +381,13 @@ pub fn main_fs(
|
|||||||
ifs_constants.accum_width,
|
ifs_constants.accum_width,
|
||||||
) as usize];
|
) 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]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
bytemuck.workspace = true
|
bytemuck.workspace = true
|
||||||
|
eframe = { version = "0.31", features = ["wgpu"]}
|
||||||
|
egui = "0.31"
|
||||||
|
egui-wgpu = "0.31"
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
flare-shader = { path = "../flare-shader" }
|
flare-shader = { path = "../flare-shader" }
|
||||||
futures-executor.workspace = true
|
futures-executor.workspace = true
|
||||||
|
@ -1,38 +1,219 @@
|
|||||||
use flare_shader::{Coefs, Color, ImageConstants, ThreadState, Transform, Variation, VariationKind};
|
use eframe::Frame;
|
||||||
use futures_executor::block_on;
|
use eframe::epaint::PaintCallbackInfo;
|
||||||
use glam::Vec4;
|
use egui::Context;
|
||||||
use rand::SeedableRng;
|
use egui_wgpu::{CallbackResources, CallbackTrait, ScreenDescriptor};
|
||||||
use rand_xoshiro::Xoshiro128Plus;
|
use flare_shader::{CameraConstants, Coefs, ViewportConstants, ThreadState, Transform, Variation, VariationKind, AccumConstants};
|
||||||
use std::sync::Arc;
|
use rand::thread_rng;
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
use wgpu::{
|
use wgpu::{CommandBuffer, CommandEncoder, Device, Queue, RenderPass};
|
||||||
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 {
|
struct RenderGroup {
|
||||||
device: Device,
|
device: wgpu::Device,
|
||||||
|
queue: wgpu::Queue,
|
||||||
bind_group_layout: wgpu::BindGroupLayout,
|
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>,
|
impl RenderGroup {
|
||||||
transforms: Option<wgpu::Buffer>,
|
pub fn bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
||||||
variations: Option<wgpu::Buffer>,
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
accum_image: Option<wgpu::Buffer>,
|
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>,
|
bind_group: Option<wgpu::BindGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccumulatePipeline {
|
impl IfsGroup {
|
||||||
const BIND_GROUP_LAYOUT_DESCRIPTOR: wgpu::BindGroupLayoutDescriptor<'static> =
|
pub fn bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
||||||
wgpu::BindGroupLayoutDescriptor {
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
label: Some("accumulate"),
|
label: Some("accum"),
|
||||||
entries: &[
|
entries: &[
|
||||||
// thread_state
|
// thread_state
|
||||||
wgpu::BindGroupLayoutEntry {
|
wgpu::BindGroupLayoutEntry {
|
||||||
@ -67,35 +248,105 @@ impl AccumulatePipeline {
|
|||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
// accum_image
|
],
|
||||||
wgpu::BindGroupLayoutEntry {
|
})
|
||||||
binding: 3,
|
}
|
||||||
visibility: wgpu::ShaderStages::COMPUTE,
|
|
||||||
ty: wgpu::BindingType::Buffer {
|
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Self {
|
||||||
ty: wgpu::BufferBindingType::Storage { read_only: false },
|
let mut seed_rng = thread_rng();
|
||||||
has_dynamic_offset: false,
|
let mut thread_states = vec![ThreadState::new(&mut seed_rng)];
|
||||||
min_binding_size: None,
|
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(),
|
||||||
},
|
},
|
||||||
count: None,
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: transforms_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: variations_buffer.as_entire_binding(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
|
||||||
|
|
||||||
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 _ = 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 {
|
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||||
label: Some("accumulate"),
|
label: Some("accum"),
|
||||||
layout: Some(&pipeline_layout),
|
layout: Some(&pipeline_layout),
|
||||||
module,
|
module,
|
||||||
entry_point: Some("main_cs"),
|
entry_point: Some("main_cs"),
|
||||||
@ -103,157 +354,25 @@ impl AccumulatePipeline {
|
|||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
let rng = rand::thread_rng();
|
Self { pipeline }
|
||||||
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 {
|
struct RenderPipeline {
|
||||||
device: Device,
|
|
||||||
bind_group_layout: wgpu::BindGroupLayout,
|
|
||||||
pipeline: wgpu::RenderPipeline,
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
|
||||||
accum_image: Option<wgpu::Buffer>,
|
|
||||||
bind_group: Option<wgpu::BindGroup>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderPipeline {
|
impl RenderPipeline {
|
||||||
const BIND_GROUP_LAYOUT_DESCRIPTOR: wgpu::BindGroupLayoutDescriptor<'static> =
|
pub fn new(
|
||||||
wgpu::BindGroupLayoutDescriptor {
|
device: &wgpu::Device,
|
||||||
label: Some("render"),
|
module: &wgpu::ShaderModule,
|
||||||
entries: &[wgpu::BindGroupLayoutEntry {
|
target: &wgpu::TextureFormat,
|
||||||
binding: 0,
|
) -> Self {
|
||||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
let render_layout = RenderGroup::bind_group_layout(device);
|
||||||
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 {
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("render"),
|
label: Some("render"),
|
||||||
bind_group_layouts: &[&bind_group_layout],
|
bind_group_layouts: &[&render_layout],
|
||||||
push_constant_ranges: &[wgpu::PushConstantRange {
|
push_constant_ranges: &[],
|
||||||
stages: wgpu::ShaderStages::FRAGMENT,
|
|
||||||
range: 0..size_of::<ImageConstants>() as u32,
|
|
||||||
}],
|
|
||||||
});
|
});
|
||||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
label: Some("render"),
|
label: Some("render"),
|
||||||
@ -271,311 +390,161 @@ impl RenderPipeline {
|
|||||||
module,
|
module,
|
||||||
entry_point: Some("main_fs"),
|
entry_point: Some("main_fs"),
|
||||||
compilation_options: Default::default(),
|
compilation_options: Default::default(),
|
||||||
targets: &[Some(wgpu::ColorTargetState {
|
targets: &[Some((*target).into())],
|
||||||
format,
|
|
||||||
blend: None,
|
|
||||||
write_mask: Default::default(),
|
|
||||||
})],
|
|
||||||
}),
|
}),
|
||||||
multiview: None,
|
multiview: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
Self {
|
Self { pipeline }
|
||||||
device: device.clone(),
|
|
||||||
bind_group_layout,
|
|
||||||
pipeline,
|
|
||||||
accum_image: None,
|
|
||||||
bind_group: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_accum_image(&mut self, accum_image: &wgpu::Buffer) {
|
struct FlareAssets {
|
||||||
self.accum_image = Some(accum_image.clone());
|
render_group: RenderGroup,
|
||||||
self.bind_group.take();
|
ifs_group: IfsGroup,
|
||||||
}
|
accum_pipeline: AccumPipeline,
|
||||||
|
render_pipeline: RenderPipeline,
|
||||||
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 {
|
struct Flare {
|
||||||
instance: Instance,
|
first_draw: bool,
|
||||||
adapter: Adapter,
|
|
||||||
device: Device,
|
|
||||||
queue: Queue,
|
|
||||||
module: ShaderModule,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Flare {
|
impl Flare {
|
||||||
pub fn new() -> Self {
|
pub fn new(cc: &eframe::CreationContext<'_>, width: i32, height: i32) -> Self {
|
||||||
let backends = wgpu::Backends::from_env().unwrap_or_default();
|
let cc_wgpu = cc
|
||||||
let instance = Instance::new(&wgpu::InstanceDescriptor {
|
.wgpu_render_state
|
||||||
backends,
|
.as_ref()
|
||||||
..Default::default()
|
.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 {
|
let module_spirv = wgpu::include_spirv!(concat!(env!("OUT_DIR"), "/flare.spv"));
|
||||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
let module = device.create_shader_module(module_spirv);
|
||||||
force_fallback_adapter: false,
|
|
||||||
compatible_surface: None,
|
|
||||||
});
|
|
||||||
let adapter = block_on(adapter).expect("Failed to find GPU adapter");
|
|
||||||
|
|
||||||
let required_limits = wgpu::Limits {
|
// Draw a gasket
|
||||||
max_push_constant_size: size_of::<ImageConstants>() as u32,
|
let mut ifs_group = IfsGroup::new(&device, &queue);
|
||||||
..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 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 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 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);
|
let variation = Variation::new(VariationKind::Linear, 1.0);
|
||||||
|
|
||||||
flare_render.accumulate_pipeline.set_transforms(&[f0, f1, f2]);
|
ifs_group.set_transforms(&[f0, f1, f2]);
|
||||||
flare_render.accumulate_pipeline.set_variations(&[variation]);
|
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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_event(
|
let flare_assets = callback_resources.get_mut::<FlareAssets>().expect("Missing assets");
|
||||||
&mut self,
|
flare_assets.render_group.set_viewport_dimensions(self.viewport);
|
||||||
event_loop: &ActiveEventLoop,
|
|
||||||
_window_id: WindowId,
|
vec![]
|
||||||
event: WindowEvent,
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&self,
|
||||||
|
_info: PaintCallbackInfo,
|
||||||
|
render_pass: &mut RenderPass<'static>,
|
||||||
|
callback_resources: &CallbackResources,
|
||||||
) {
|
) {
|
||||||
match event {
|
let flare_assets = callback_resources
|
||||||
WindowEvent::Resized(size) => {
|
.get::<FlareAssets>()
|
||||||
let flare_render = self.flare_render.as_mut().unwrap();
|
.expect("Missing assets");
|
||||||
flare_render.resize_viewport(size.width, size.height);
|
|
||||||
flare_render.render(false);
|
render_pass.set_pipeline(&flare_assets.render_pipeline.pipeline);
|
||||||
}
|
render_pass.set_bind_group(0, flare_assets.render_group.bind_group(), &[]);
|
||||||
WindowEvent::MouseInput {
|
render_pass.draw(0..3, 0..1);
|
||||||
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<()> {
|
fn main() -> eframe::Result {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
let _module = wgpu::include_spirv!(concat!(env!("OUT_DIR"), "/flare.spv"));
|
|
||||||
|
|
||||||
let event_loop = EventLoop::new()?;
|
let initial_dimensions = egui::vec2(800., 600.);
|
||||||
let mut application = Application::new();
|
let native_options = eframe::NativeOptions {
|
||||||
event_loop.run_app(&mut application)?;
|
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(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user