diff --git a/src/bloom.rs b/src/bloom.rs index ab36996..bc9a933 100644 --- a/src/bloom.rs +++ b/src/bloom.rs @@ -1,236 +1,53 @@ -use crate::kawase_blur::{KawaseDownsampling, KawaseUpsampling}; +use crate::{blur::Blur, copy::Copy, remix::Remix}; pub struct Bloom { - pub full_image_input_texture: wgpu::Texture, - pub full_image_input_texture_view: wgpu::TextureView, - - pub blurred_blackout_input_texture: wgpu::Texture, - pub blurred_blackout_texture_view: wgpu::TextureView, - - pub downsampling: KawaseDownsampling, - pub upsampling: KawaseUpsampling, - - pub final_remix_texture_sampler: wgpu::Sampler, - - pub final_remix_bind_group_layout: wgpu::BindGroupLayout, - pub final_remix_bind_group: wgpu::BindGroup, - pub final_remix_render_pipeline: wgpu::RenderPipeline, + pub blurs: Vec, + pub copies: Vec, + pub remixes: Vec, + pub final_remix: Remix, + levels: usize, } impl Bloom { - pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self { - let (full_image_input_texture, full_image_input_texture_view) = Self::create_input_texture(device, config); - - let downsampling = KawaseDownsampling::new(device, config); - let upsampling = KawaseUpsampling::new(device, config); - let final_remix_texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }); - - let (blurred_blackout_input_texture, blurred_blackout_input_texture_view) = - Self::create_input_texture(device, config); - - let screen_triangle_shader_module = - device.create_shader_module(wgpu::include_wgsl!("screen_triangle.wgsl")); - let final_remix_shader_module = - device.create_shader_module(wgpu::include_wgsl!("final_remix.wgsl")); - - let final_remix_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("final remix bind group layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 3, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - }); - - let final_remix_bind_group = Self::create_final_remix_bind_group( - device, - &final_remix_bind_group_layout, - &full_image_input_texture_view, - &upsampling.textures[0].1, - &final_remix_texture_sampler, - ); - - let final_remix_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("final remix pipeline layout"), - bind_group_layouts: &[&final_remix_bind_group_layout], - push_constant_ranges: &[], - }); - - let final_remix_render_pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("final remix Pipeline"), - layout: Some(&final_remix_pipeline_layout), - vertex: wgpu::VertexState { - module: &screen_triangle_shader_module, - entry_point: "main", - buffers: &[], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &final_remix_shader_module, - entry_point: "main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - + pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, levels: usize) -> Self { + let mut blurs = Vec::new(); + let mut copies = Vec::new(); + let mut remixes = Vec::new(); + for level in 1..=levels { + blurs.push(Blur::new(device, config, level)); + copies.push(Copy::new(device, config)); + remixes.push(Remix::new(device, config)); + } + let final_remix = Remix::new(device, config); Self { - full_image_input_texture, - full_image_input_texture_view, - - blurred_blackout_input_texture, - blurred_blackout_texture_view: blurred_blackout_input_texture_view, - - downsampling, - upsampling, - - final_remix_texture_sampler, - - final_remix_bind_group_layout, - final_remix_bind_group, - final_remix_render_pipeline, + blurs, + copies, + remixes, + final_remix, + levels, } } pub fn full_image_input_texture_view(&self) -> &wgpu::TextureView { - &self.full_image_input_texture_view + &self.final_remix.input_texture_0_view() } pub fn blackout_input_texture_view(&self) -> &wgpu::TextureView { - &self.downsampling.input_texture_view() + &self.copies[0].input_texture_view() } - fn create_input_texture( + pub fn resize( + &mut self, device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, - ) -> (wgpu::Texture, wgpu::TextureView) { - let input_texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("bloom input texture"), - mip_level_count: 1, - size: wgpu::Extent3d { - width: config.width, - height: config.height, - depth_or_array_layers: 1, - }, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - dimension: wgpu::TextureDimension::D2, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, - sample_count: 1, - view_formats: &[], - }); - let input_texture_view = input_texture.create_view(&wgpu::TextureViewDescriptor::default()); - (input_texture, input_texture_view) - } - - // this creates all the bind groups for the final re-mix - fn create_final_remix_bind_group( - device: &wgpu::Device, - layout: &wgpu::BindGroupLayout, - full_image_texture_view: &wgpu::TextureView, - blurred_blackout_texture_view: &wgpu::TextureView, - texture_sampler: &wgpu::Sampler, - ) -> wgpu::BindGroup { - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("final remix bind group"), - layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(full_image_texture_view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(texture_sampler), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::TextureView(blurred_blackout_texture_view), - }, - wgpu::BindGroupEntry { - binding: 3, - resource: wgpu::BindingResource::Sampler(texture_sampler), - }, - ], - }); - bind_group - } - - pub fn resize(&mut self, device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, queue: &wgpu::Queue) { - (self.full_image_input_texture, self.full_image_input_texture_view) = Self::create_input_texture(device, config); - ( - self.blurred_blackout_input_texture, - self.blurred_blackout_texture_view, - ) = Self::create_input_texture(device, config); - self.downsampling.resize(device, config, queue); - self.upsampling - .resize(device, config, queue); - self.final_remix_bind_group = Self::create_final_remix_bind_group( - device, - &self.final_remix_bind_group_layout, - &self.full_image_input_texture_view, - &self.blurred_blackout_texture_view, - &self.final_remix_texture_sampler, - ); + queue: &wgpu::Queue, + ) { + for blur in &mut self.blurs { + blur.resize(device, config, queue); + } + for remix in &mut self.remixes { + remix.resize(device, config); + } } pub fn render( @@ -238,35 +55,18 @@ impl Bloom { encoder: &mut wgpu::CommandEncoder, output_view: Option<&wgpu::TextureView>, ) { - self.downsampling - .render(encoder, Some(self.upsampling.input_texture_view())); - // self.upsampling.render(encoder, output_view); - self.upsampling.render(encoder, Some(&self.blurred_blackout_texture_view)); - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("blur render pass"), - color_attachments: &[output_view.map(|output_view| { - wgpu::RenderPassColorAttachment { - view: output_view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.0, - g: 0.0, - b: 1.0, - a: 1.0, - }), - store: wgpu::StoreOp::Store, - }, - } - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - render_pass.set_pipeline(&self.final_remix_render_pipeline); - render_pass.set_bind_group(0, &self.final_remix_bind_group, &[]); - render_pass.draw(0..3, 0..1); + for level in 0..self.levels - 1 { + self.copies[0].render(encoder, Some(self.blurs[0].input_texture_view())); + self.copies[0].render(encoder, Some(self.remixes[0].input_texture_0_view())); + self.blurs[0].render(encoder, Some(self.remixes[0].input_texture_1_view())); + self.remixes[0].render(encoder, Some(self.copies[level + 1].input_texture_view())); } + + self.copies[self.levels - 1].render(encoder, Some(self.blurs[self.levels - 1].input_texture_view())); + self.copies[self.levels - 1].render(encoder, Some(self.remixes[self.levels - 1].input_texture_0_view())); + self.blurs[self.levels - 1].render(encoder, Some(self.remixes[self.levels - 1].input_texture_1_view())); + self.remixes[self.levels - 1].render(encoder, Some(self.final_remix.input_texture_1_view())); + + self.final_remix.render(encoder, output_view); } } diff --git a/src/blur.rs b/src/blur.rs new file mode 100644 index 0000000..6af29ad --- /dev/null +++ b/src/blur.rs @@ -0,0 +1,46 @@ +// use crate::{kawase_downsampling::KawaseDownsampling, kawase_mixing_upsampling::KawaseMixingUpsampling}; + +use crate::{kawase_downsampling::KawaseDownsampling, kawase_upsampling::KawaseUpsampling}; + +pub struct Blur { + pub downsampling: KawaseDownsampling, + pub upsampling: KawaseUpsampling, + levels: usize, +} + +impl Blur { + pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, levels: usize) -> Self { + let downsampling = KawaseDownsampling::new(device, config, levels); + let upsampling = KawaseUpsampling::new(device, config, levels); + + Self { + downsampling, + upsampling, + levels, + } + } + + pub fn input_texture_view(&self) -> &wgpu::TextureView { + &self.downsampling.input_texture_view() + } + + pub fn resize( + &mut self, + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + queue: &wgpu::Queue, + ) { + self.downsampling.resize(device, config, queue); + self.upsampling.resize(device, config, queue); + } + + pub fn render( + &self, + encoder: &mut wgpu::CommandEncoder, + output_view: Option<&wgpu::TextureView>, + ) { + self.downsampling + .render(encoder, Some(self.upsampling.input_texture_view())); + self.upsampling.render(encoder, output_view); + } +} diff --git a/src/copy.rs b/src/copy.rs new file mode 100644 index 0000000..660aac2 --- /dev/null +++ b/src/copy.rs @@ -0,0 +1,209 @@ + +pub struct Copy { + pub input_texture: wgpu::Texture, + pub input_texture_view: wgpu::TextureView, + + pub texture_sampler: wgpu::Sampler, + + pub bind_group_layout: wgpu::BindGroupLayout, + pub bind_group: wgpu::BindGroup, + pub render_pipeline: wgpu::RenderPipeline, +} + +impl Copy { + pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self { + let (input_texture, input_texture_view) = Self::create_input_texture(device, config); + + let texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + let screen_triangle_shader_module = + device.create_shader_module(wgpu::include_wgsl!("screen_triangle.wgsl")); + let remix_shader_module = device.create_shader_module(wgpu::include_wgsl!("copy.wgsl")); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("remix bind group layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + let bind_group = Self::create_bind_group( + device, + &bind_group_layout, + &input_texture_view, + &texture_sampler, + ); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("remix pipeline layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("remix render pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &screen_triangle_shader_module, + entry_point: "main", + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &remix_shader_module, + entry_point: "main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + Self { + input_texture, + input_texture_view, + + texture_sampler, + + bind_group_layout, + bind_group, + render_pipeline, + } + } + + pub fn input_texture_view(&self) -> &wgpu::TextureView { + &self.input_texture_view + } + + fn create_input_texture( + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + ) -> (wgpu::Texture, wgpu::TextureView) { + let input_texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("bloom input texture"), + mip_level_count: 1, + size: wgpu::Extent3d { + width: config.width, + height: config.height, + depth_or_array_layers: 1, + }, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + dimension: wgpu::TextureDimension::D2, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, + sample_count: 1, + view_formats: &[], + }); + let input_texture_view = input_texture.create_view(&wgpu::TextureViewDescriptor::default()); + (input_texture, input_texture_view) + } + + // this creates all the bind groups for the final re-mix + fn create_bind_group( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + texture_view: &wgpu::TextureView, + texture_sampler: &wgpu::Sampler, + ) -> wgpu::BindGroup { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("remix bind group"), + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(texture_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(texture_sampler), + }, + ], + }); + bind_group + } + + pub fn resize(&mut self, device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) { + (self.input_texture, self.input_texture_view) = + Self::create_input_texture(device, config); + self.bind_group = Self::create_bind_group( + device, + &self.bind_group_layout, + &self.input_texture_view, + &self.texture_sampler, + ); + } + + pub fn render( + &self, + encoder: &mut wgpu::CommandEncoder, + output_view: Option<&wgpu::TextureView>, + ) { + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("blur render pass"), + color_attachments: &[output_view.map(|output_view| { + wgpu::RenderPassColorAttachment { + view: output_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.0, + g: 0.0, + b: 1.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + } + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.bind_group, &[]); + render_pass.draw(0..3, 0..1); + } + } +} diff --git a/src/copy.wgsl b/src/copy.wgsl new file mode 100644 index 0000000..693508f --- /dev/null +++ b/src/copy.wgsl @@ -0,0 +1,20 @@ +struct VertexOutput { + // Mark output position as invariant so it's safe to use it with depth test Equal. + // Without @invariant, different usages in different render pipelines might optimize differently, + // causing slightly different results. + @invariant @builtin(position) + position: vec4f, + @location(0) + texcoord: vec2f, +}; + +@group(0) @binding(0) +var input_texture: texture_2d; +@group(0) @binding(1) +var input_texture_sampler: sampler; + +@fragment +fn main(in: VertexOutput) -> @location(0) vec4 { + let col = textureSampleLevel(input_texture, input_texture_sampler, in.texcoord, 0.0); + return col; +} \ No newline at end of file diff --git a/src/kawase_blur.rs b/src/kawase_blur.rs deleted file mode 100644 index 4f1edfc..0000000 --- a/src/kawase_blur.rs +++ /dev/null @@ -1,587 +0,0 @@ -use std::fmt::Debug; - -use glam::{uvec2, UVec2}; -use wgpu::{core::device::queue, util::DeviceExt, Queue}; - -use crate::otheruniforms::BufferContent; - -const LEVELS: usize = 3; - -pub struct KawaseDownsampling { - pub texture_sampler: wgpu::Sampler, - - pub textures: Vec<(wgpu::Texture, wgpu::TextureView)>, - pub resolutions: Vec, - pub resolution_uniform_buffers: Vec, - - pub bind_group_layout: wgpu::BindGroupLayout, - pub bind_groups: Vec, - pub render_pipeline: wgpu::RenderPipeline, -} - -impl KawaseDownsampling { - pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self { - let texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, - ..Default::default() - }); - - let mut resolutions = Vec::new(); - let mut dim = uvec2(config.width, config.height); - for _ in 0..LEVELS { - dim.x = dim.x.max(1); - dim.y = dim.y.max(1); - resolutions.push(dim); - dim = uvec2(dim.x / 2, dim.y / 2); - } - dbg!(&resolutions); - let mut resolution_uniform_buffers = Vec::new(); - for resolution in &resolutions { - resolution_uniform_buffers.push(device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("kawase downsampling resolution uniform buffer"), - contents: &resolution.uniform_buffer_content(), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - })); - } - let textures = Self::create_textures(device, &resolutions); - - let screen_triangle_shader_module = - device.create_shader_module(wgpu::include_wgsl!("screen_triangle.wgsl")); - let downsample_shader_module = - device.create_shader_module(wgpu::include_wgsl!("kawase_downsample.wgsl")); - - let downsampling_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("downsampling bind group layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], - }); - - let downsampling_bind_groups = Self::create_bind_groups( - device, - &downsampling_bind_group_layout, - &textures, - &texture_sampler, - &resolution_uniform_buffers, - ); - - let downsampling_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("downsampling pipeline layout"), - bind_group_layouts: &[&downsampling_bind_group_layout], - push_constant_ranges: &[], - }); - - let downsampling_render_pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("scene Pipeline"), - layout: Some(&downsampling_pipeline_layout), - vertex: wgpu::VertexState { - module: &screen_triangle_shader_module, - entry_point: "main", - buffers: &[], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &downsample_shader_module, - entry_point: "main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - Self { - texture_sampler, - - textures, - resolutions, - resolution_uniform_buffers, - - bind_group_layout: downsampling_bind_group_layout, - bind_groups: downsampling_bind_groups, - render_pipeline: downsampling_render_pipeline, - } - } - - pub fn input_texture_view(&self) -> &wgpu::TextureView { - &self.textures[0].1 - } - - fn create_textures( - device: &wgpu::Device, - resolutions: &Vec, - ) -> Vec<(wgpu::Texture, wgpu::TextureView)> { - // downsample happens first so the first (input) texture is the original size - let mut result = Vec::new(); - for level in 0..LEVELS { - let texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some(&format!("downsample texture {}", level)), - mip_level_count: 1, - size: wgpu::Extent3d { - width: resolutions[level].x, - height: resolutions[level].y, - depth_or_array_layers: 1, - }, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - dimension: wgpu::TextureDimension::D2, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::TEXTURE_BINDING, - sample_count: 1, - view_formats: &[], - }); - let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - result.push((texture, texture_view)); - } - result - } - - fn create_bind_groups( - device: &wgpu::Device, - layout: &wgpu::BindGroupLayout, - textures: &Vec<(wgpu::Texture, wgpu::TextureView)>, - texture_sampler: &wgpu::Sampler, - resolution_uniform_buffers: &Vec, - ) -> Vec { - let mut result = Vec::new(); - for level in 0..LEVELS { - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some(&format!("downsampling bind group {}", level)), - layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&textures[level].1), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(texture_sampler), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: resolution_uniform_buffers[level].as_entire_binding(), - } - ], - }); - result.push(bind_group); - } - result - } - - pub fn resize(&mut self, device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, queue: &wgpu::Queue) { - self.resolutions = Vec::new(); - let mut dim = uvec2(config.width, config.height); - for _ in 0..LEVELS { - dim.x = dim.x.max(1); - dim.y = dim.y.max(1); - self.resolutions.push(dim); - dim = uvec2(dim.x / 2, dim.y / 2); - } - for level in 0..LEVELS { - queue.write_buffer(&self.resolution_uniform_buffers[level], 0, &self.resolutions[level].uniform_buffer_content()); - } - self.textures = Self::create_textures(device, &self.resolutions); - self.bind_groups = Self::create_bind_groups( - device, - &self.bind_group_layout, - &self.textures, - &self.texture_sampler, - &self.resolution_uniform_buffers, - ); - } - - pub fn render( - &self, - encoder: &mut wgpu::CommandEncoder, - output_view: Option<&wgpu::TextureView>, - ) { - for level in 1..LEVELS { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some(&format!("kawase downsampling render pass {}", level)), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &self.textures[level].1, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 1.0, - g: 0.0, - b: 1.0, - a: 1.0, - }), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - render_pass.set_pipeline(&self.render_pipeline); - render_pass.set_bind_group(0, &self.bind_groups[level - 1], &[]); - render_pass.draw(0..3, 0..1); - } - - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("kawase downsampling final render pass"), - color_attachments: &[output_view.map(|output_view| { - wgpu::RenderPassColorAttachment { - view: output_view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.0, - g: 0.0, - b: 1.0, - a: 1.0, - }), - store: wgpu::StoreOp::Store, - }, - } - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - render_pass.set_pipeline(&self.render_pipeline); - render_pass.set_bind_group(0, &self.bind_groups[LEVELS - 1], &[]); - render_pass.draw(0..3, 0..1); - } - } -} - -pub struct KawaseUpsampling { - pub texture_sampler: wgpu::Sampler, - - pub textures: Vec<(wgpu::Texture, wgpu::TextureView)>, - pub resolutions: Vec, - pub resolution_uniform_buffers: Vec, - - pub bind_group_layout: wgpu::BindGroupLayout, - pub bind_groups: Vec, - pub render_pipeline: wgpu::RenderPipeline, -} - -impl KawaseUpsampling { - pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self { - let texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, - ..Default::default() - }); - - let mut resolutions = Vec::new(); - let mut dim = uvec2(config.width, config.height); - for _ in 0..LEVELS { - dim.x = dim.x.max(1); - dim.y = dim.y.max(1); - resolutions.push(dim); - dim = uvec2(dim.x / 2, dim.y / 2); - } - let mut resolution_uniform_buffers = Vec::new(); - for resolution in &resolutions { - resolution_uniform_buffers.push(device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("kawase downsampling resolution uniform buffer"), - contents: &resolution.uniform_buffer_content(), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - })); - } - let textures = Self::create_textures(device, &resolutions); - - let screen_triangle_shader_module = - device.create_shader_module(wgpu::include_wgsl!("screen_triangle.wgsl")); - let upsample_shader_module = - device.create_shader_module(wgpu::include_wgsl!("kawase_upsample.wgsl")); - - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("kawase upsampling bind group layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], - }); - - let bind_groups = - Self::create_bind_groups(device, &bind_group_layout, &textures, &texture_sampler, &resolution_uniform_buffers); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("upsampling pipeline layout"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("scene Pipeline"), - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &screen_triangle_shader_module, - entry_point: "main", - buffers: &[], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &upsample_shader_module, - entry_point: "main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - Self { - texture_sampler, - - textures, - resolutions, - resolution_uniform_buffers, - - bind_group_layout: bind_group_layout, - bind_groups: bind_groups, - render_pipeline: render_pipeline, - } - } - - pub fn input_texture_view(&self) -> &wgpu::TextureView { - &self.textures[LEVELS - 1].1 - } - - // this creates all the levels of texture for downsampling - fn create_textures( - device: &wgpu::Device, - resolutions: &Vec, - ) -> Vec<(wgpu::Texture, wgpu::TextureView)> { - let mut result = Vec::new(); - for level in 0..LEVELS { - let texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some(&format!("upsample texture {}", level)), - mip_level_count: 1, - size: wgpu::Extent3d { - width: resolutions[level].x, - height: resolutions[level].y, - depth_or_array_layers: 1, - }, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - dimension: wgpu::TextureDimension::D2, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::TEXTURE_BINDING, - sample_count: 1, - view_formats: &[], - }); - let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - result.push((texture, texture_view)); - } - result - } - - // this creates all the bind groups for all the upsampling shader calls - fn create_bind_groups( - device: &wgpu::Device, - layout: &wgpu::BindGroupLayout, - textures: &Vec<(wgpu::Texture, wgpu::TextureView)>, - texture_sampler: &wgpu::Sampler, - resolution_uniform_buffers: &Vec, - ) -> Vec { - let mut result = Vec::new(); - for level in 0..LEVELS { - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some(&format!("kawase upsampling bind group {}", level)), - layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView( - &textures[LEVELS - level - 1].1, - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(texture_sampler), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: resolution_uniform_buffers[level].as_entire_binding(), - } - ], - }); - result.push(bind_group); - } - result - } - - pub fn resize(&mut self, device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, queue: &wgpu::Queue) { - self.resolutions = Vec::new(); - let mut dim = uvec2(config.width, config.height); - for _ in 0..LEVELS { - dim.x = dim.x.max(1); - dim.y = dim.y.max(1); - self.resolutions.push(dim); - dim = uvec2(dim.x / 2, dim.y / 2); - } - for level in 0..LEVELS { - queue.write_buffer(&self.resolution_uniform_buffers[level], 0, &self.resolutions[level].uniform_buffer_content()); - } - self.textures = Self::create_textures(device, &self.resolutions); - self.bind_groups = Self::create_bind_groups( - device, - &self.bind_group_layout, - &self.textures, - &self.texture_sampler, - &self.resolution_uniform_buffers, - ); - } - - pub fn render( - &self, - encoder: &mut wgpu::CommandEncoder, - output_view: Option<&wgpu::TextureView>, - ) { - for level in 0..LEVELS - 1 { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some(&format!("kawase upsampling render pass {}", level)), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &self.textures[LEVELS - level - 2].1, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 1.0, - g: 0.0, - b: 0.0, - a: 1.0, - }), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - render_pass.set_pipeline(&self.render_pipeline); - render_pass.set_bind_group(0, &self.bind_groups[level], &[]); - render_pass.draw(0..3, 0..1); - } - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("final kawase upsampling render pass"), - color_attachments: &[output_view.map(|output_view| { - wgpu::RenderPassColorAttachment { - view: output_view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.0, - g: 0.0, - b: 1.0, - a: 1.0, - }), - store: wgpu::StoreOp::Store, - }, - } - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - render_pass.set_pipeline(&self.render_pipeline); - render_pass.set_bind_group(0, &self.bind_groups[LEVELS - 1], &[]); - render_pass.draw(0..3, 0..1); - } - } -} diff --git a/src/kawase_downsample.wgsl b/src/kawase_downsample.wgsl index bae189b..388bf96 100644 --- a/src/kawase_downsample.wgsl +++ b/src/kawase_downsample.wgsl @@ -32,5 +32,6 @@ fn main( in: VertexOutput ) -> @location(0) vec4f sum += textureSample(input_texture, texture_sampler, uv + vec2f(halfpixel.x, -halfpixel.y) * offset); sum += textureSample(input_texture, texture_sampler, uv - vec2f(halfpixel.x, -halfpixel.y) * offset); - return sum / 8.0; + // return sum / 8.0; + return textureSample(input_texture, texture_sampler, uv); } \ No newline at end of file diff --git a/src/kawase_downsampling.rs b/src/kawase_downsampling.rs new file mode 100644 index 0000000..169f8aa --- /dev/null +++ b/src/kawase_downsampling.rs @@ -0,0 +1,307 @@ +use std::fmt::Debug; + +use glam::{uvec2, UVec2}; +use wgpu::{core::device::queue, util::DeviceExt, Queue}; + +use crate::otheruniforms::BufferContent; + +pub struct KawaseDownsampling { + pub texture_sampler: wgpu::Sampler, + + pub textures: Vec<(wgpu::Texture, wgpu::TextureView)>, + pub resolutions: Vec, + pub resolution_uniform_buffers: Vec, + + pub bind_group_layout: wgpu::BindGroupLayout, + pub bind_groups: Vec, + pub render_pipeline: wgpu::RenderPipeline, + + levels: usize +} + +impl KawaseDownsampling { + pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, levels: usize) -> Self { + let texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + let mut resolutions = Vec::new(); + let mut dim = uvec2(config.width, config.height); + for _ in 0..levels { + dim.x = dim.x.max(1); + dim.y = dim.y.max(1); + resolutions.push(dim); + dim = uvec2(dim.x / 2, dim.y / 2); + } + dbg!(&resolutions); + let mut resolution_uniform_buffers = Vec::new(); + for resolution in &resolutions { + resolution_uniform_buffers.push(device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("kawase downsampling resolution uniform buffer"), + contents: &resolution.uniform_buffer_content(), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + })); + } + let textures = Self::create_textures(device, &resolutions, levels); + + let screen_triangle_shader_module = + device.create_shader_module(wgpu::include_wgsl!("screen_triangle.wgsl")); + let downsample_shader_module = + device.create_shader_module(wgpu::include_wgsl!("kawase_downsample.wgsl")); + + let downsampling_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("downsampling bind group layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + + let downsampling_bind_groups = Self::create_bind_groups( + device, + &downsampling_bind_group_layout, + &textures, + &texture_sampler, + &resolution_uniform_buffers, + levels + ); + + let downsampling_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("downsampling pipeline layout"), + bind_group_layouts: &[&downsampling_bind_group_layout], + push_constant_ranges: &[], + }); + + let downsampling_render_pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("scene Pipeline"), + layout: Some(&downsampling_pipeline_layout), + vertex: wgpu::VertexState { + module: &screen_triangle_shader_module, + entry_point: "main", + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &downsample_shader_module, + entry_point: "main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + Self { + texture_sampler, + + textures, + resolutions, + resolution_uniform_buffers, + + bind_group_layout: downsampling_bind_group_layout, + bind_groups: downsampling_bind_groups, + render_pipeline: downsampling_render_pipeline, + + levels, + } + } + + pub fn input_texture_view(&self) -> &wgpu::TextureView { + &self.textures[0].1 + } + + fn create_textures( + device: &wgpu::Device, + resolutions: &Vec, + levels: usize + ) -> Vec<(wgpu::Texture, wgpu::TextureView)> { + // downsample happens first so the first (input) texture is the original size + let mut result = Vec::new(); + for level in 0..levels { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some(&format!("downsample texture {}", level)), + mip_level_count: 1, + size: wgpu::Extent3d { + width: resolutions[level].x, + height: resolutions[level].y, + depth_or_array_layers: 1, + }, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + dimension: wgpu::TextureDimension::D2, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::TEXTURE_BINDING, + sample_count: 1, + view_formats: &[], + }); + let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + result.push((texture, texture_view)); + } + result + } + + fn create_bind_groups( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + textures: &Vec<(wgpu::Texture, wgpu::TextureView)>, + texture_sampler: &wgpu::Sampler, + resolution_uniform_buffers: &Vec, + levels: usize + ) -> Vec { + let mut result = Vec::new(); + for level in 0..levels { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some(&format!("downsampling bind group {}", level)), + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&textures[level].1), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(texture_sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: resolution_uniform_buffers[level].as_entire_binding(), + } + ], + }); + result.push(bind_group); + } + result + } + + pub fn resize(&mut self, device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, queue: &wgpu::Queue) { + self.resolutions = Vec::new(); + let mut dim = uvec2(config.width, config.height); + for _ in 0..self.levels { + dim.x = dim.x.max(1); + dim.y = dim.y.max(1); + self.resolutions.push(dim); + dim = uvec2(dim.x / 2, dim.y / 2); + } + for level in 0..self.levels { + queue.write_buffer(&self.resolution_uniform_buffers[level], 0, &self.resolutions[level].uniform_buffer_content()); + } + self.textures = Self::create_textures(device, &self.resolutions, self.levels); + self.bind_groups = Self::create_bind_groups( + device, + &self.bind_group_layout, + &self.textures, + &self.texture_sampler, + &self.resolution_uniform_buffers, + self.levels + ); + } + + pub fn render( + &self, + encoder: &mut wgpu::CommandEncoder, + output_view: Option<&wgpu::TextureView>, + ) { + for level in 1..self.levels { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some(&format!("kawase downsampling render pass {}", level)), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &self.textures[level].1, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 1.0, + g: 0.0, + b: 1.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.bind_groups[level - 1], &[]); + render_pass.draw(0..3, 0..1); + } + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("kawase downsampling final render pass"), + color_attachments: &[output_view.map(|output_view| { + wgpu::RenderPassColorAttachment { + view: output_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.0, + g: 0.0, + b: 1.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + } + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.bind_groups[self.levels - 1], &[]); + render_pass.draw(0..3, 0..1); + } + } +} diff --git a/src/kawase_upsampling.rs b/src/kawase_upsampling.rs new file mode 100644 index 0000000..db89bea --- /dev/null +++ b/src/kawase_upsampling.rs @@ -0,0 +1,297 @@ +use glam::{uvec2, UVec2}; +use wgpu::util::DeviceExt; + +use crate::otheruniforms::BufferContent; + +pub struct KawaseUpsampling { + pub texture_sampler: wgpu::Sampler, + + pub textures: Vec<(wgpu::Texture, wgpu::TextureView)>, + pub resolutions: Vec, + pub resolution_uniform_buffers: Vec, + + pub bind_group_layout: wgpu::BindGroupLayout, + pub bind_groups: Vec, + pub render_pipeline: wgpu::RenderPipeline, + + levels: usize, +} + +impl KawaseUpsampling { + pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, levels: usize) -> Self { + let texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + let mut resolutions = Vec::new(); + let mut dim = uvec2(config.width, config.height); + for _ in 0..levels { + dim.x = dim.x.max(1); + dim.y = dim.y.max(1); + resolutions.push(dim); + dim = uvec2(dim.x / 2, dim.y / 2); + } + let mut resolution_uniform_buffers = Vec::new(); + for resolution in &resolutions { + resolution_uniform_buffers.push(device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("kawase downsampling resolution uniform buffer"), + contents: &resolution.uniform_buffer_content(), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + })); + } + let textures = Self::create_textures(device, &resolutions, levels); + + let screen_triangle_shader_module = + device.create_shader_module(wgpu::include_wgsl!("screen_triangle.wgsl")); + let upsample_shader_module = + device.create_shader_module(wgpu::include_wgsl!("kawase_upsample.wgsl")); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("kawase upsampling bind group layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + + let bind_groups = + Self::create_bind_groups(device, &bind_group_layout, &textures, &texture_sampler, &resolution_uniform_buffers, levels); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("upsampling pipeline layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("scene Pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &screen_triangle_shader_module, + entry_point: "main", + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &upsample_shader_module, + entry_point: "main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + Self { + texture_sampler, + + textures, + resolutions, + resolution_uniform_buffers, + + bind_group_layout: bind_group_layout, + bind_groups: bind_groups, + render_pipeline: render_pipeline, + + levels, + } + } + + pub fn input_texture_view(&self) -> &wgpu::TextureView { + &self.textures[self.levels - 1].1 + } + + // this creates all the levels of texture for downsampling + fn create_textures( + device: &wgpu::Device, + resolutions: &Vec, + levels: usize + ) -> Vec<(wgpu::Texture, wgpu::TextureView)> { + let mut result = Vec::new(); + for level in 0..levels { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some(&format!("upsample texture {}", level)), + mip_level_count: 1, + size: wgpu::Extent3d { + width: resolutions[level].x, + height: resolutions[level].y, + depth_or_array_layers: 1, + }, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + dimension: wgpu::TextureDimension::D2, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::TEXTURE_BINDING, + sample_count: 1, + view_formats: &[], + }); + let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + result.push((texture, texture_view)); + } + result + } + + // this creates all the bind groups for all the upsampling shader calls + fn create_bind_groups( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + textures: &Vec<(wgpu::Texture, wgpu::TextureView)>, + texture_sampler: &wgpu::Sampler, + resolution_uniform_buffers: &Vec, + levels: usize, + ) -> Vec { + let mut result = Vec::new(); + for level in 0..levels { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some(&format!("kawase upsampling bind group {}", level)), + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &textures[levels - level - 1].1, + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(texture_sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: resolution_uniform_buffers[level].as_entire_binding(), + } + ], + }); + result.push(bind_group); + } + result + } + + pub fn resize(&mut self, device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, queue: &wgpu::Queue) { + self.resolutions = Vec::new(); + let mut dim = uvec2(config.width, config.height); + for _ in 0..self.levels { + dim.x = dim.x.max(1); + dim.y = dim.y.max(1); + self.resolutions.push(dim); + dim = uvec2(dim.x / 2, dim.y / 2); + } + for level in 0..self.levels { + queue.write_buffer(&self.resolution_uniform_buffers[level], 0, &self.resolutions[level].uniform_buffer_content()); + } + self.textures = Self::create_textures(device, &self.resolutions, self.levels); + self.bind_groups = Self::create_bind_groups( + device, + &self.bind_group_layout, + &self.textures, + &self.texture_sampler, + &self.resolution_uniform_buffers, + self.levels + ); + } + + pub fn render( + &self, + encoder: &mut wgpu::CommandEncoder, + output_view: Option<&wgpu::TextureView>, + ) { + for level in 0..self.levels - 1 { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some(&format!("kawase upsampling render pass {}", level)), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &self.textures[self.levels - level - 2].1, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 1.0, + g: 0.0, + b: 0.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.bind_groups[level], &[]); + render_pass.draw(0..3, 0..1); + } + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("final kawase upsampling render pass"), + color_attachments: &[output_view.map(|output_view| { + wgpu::RenderPassColorAttachment { + view: output_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.0, + g: 0.0, + b: 1.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + } + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.bind_groups[self.levels - 1], &[]); + render_pass.draw(0..3, 0..1); + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 968d06a..54c555e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ mod smart_include; mod bloom; mod camera; -mod downsampling; +// mod downsampling; mod indices; mod otheruniforms; mod podbool; @@ -34,10 +34,15 @@ mod uniformscontroller; // mod upsampling; mod vertex; mod vertices; -mod gaussian_blur; -mod kawase_blur; +// mod gaussian_blur; +mod kawase_downsampling; +mod kawase_upsampling; +// mod kawase_mixing_upsampling; mod state; +mod blur; +mod remix; +mod copy; use state::State; #[derive(Default)] diff --git a/src/old/bloom.rs b/src/old/bloom.rs new file mode 100644 index 0000000..5f04bca --- /dev/null +++ b/src/old/bloom.rs @@ -0,0 +1,272 @@ +use crate::{kawase_downsampling::KawaseDownsampling, kawase_mixing_upsampling::KawaseMixingUpsampling}; + +pub struct Bloom { + pub full_image_input_texture: wgpu::Texture, + pub full_image_input_texture_view: wgpu::TextureView, + + pub blurred_blackout_input_texture: wgpu::Texture, + pub blurred_blackout_texture_view: wgpu::TextureView, + + pub downsampling: KawaseDownsampling, + pub upsampling: KawaseMixingUpsampling, + + pub final_remix_texture_sampler: wgpu::Sampler, + + pub final_remix_bind_group_layout: wgpu::BindGroupLayout, + pub final_remix_bind_group: wgpu::BindGroup, + pub final_remix_render_pipeline: wgpu::RenderPipeline, +} + +impl Bloom { + pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self { + let (full_image_input_texture, full_image_input_texture_view) = Self::create_input_texture(device, config); + + let downsampling = KawaseDownsampling::new(device, config); + let upsampling = KawaseMixingUpsampling::new(device, config, downsampling.input_texture_view()); + let final_remix_texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let (blurred_blackout_input_texture, blurred_blackout_input_texture_view) = + Self::create_input_texture(device, config); + + let screen_triangle_shader_module = + device.create_shader_module(wgpu::include_wgsl!("screen_triangle.wgsl")); + let final_remix_shader_module = + device.create_shader_module(wgpu::include_wgsl!("final_remix.wgsl")); + + let final_remix_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("final remix bind group layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + let final_remix_bind_group = Self::create_final_remix_bind_group( + device, + &final_remix_bind_group_layout, + &full_image_input_texture_view, + &upsampling.textures[0].1, + &final_remix_texture_sampler, + ); + + let final_remix_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("final remix pipeline layout"), + bind_group_layouts: &[&final_remix_bind_group_layout], + push_constant_ranges: &[], + }); + + let final_remix_render_pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("final remix Pipeline"), + layout: Some(&final_remix_pipeline_layout), + vertex: wgpu::VertexState { + module: &screen_triangle_shader_module, + entry_point: "main", + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &final_remix_shader_module, + entry_point: "main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + Self { + full_image_input_texture, + full_image_input_texture_view, + + blurred_blackout_input_texture, + blurred_blackout_texture_view: blurred_blackout_input_texture_view, + + downsampling, + upsampling, + + final_remix_texture_sampler, + + final_remix_bind_group_layout, + final_remix_bind_group, + final_remix_render_pipeline, + } + } + + pub fn full_image_input_texture_view(&self) -> &wgpu::TextureView { + &self.full_image_input_texture_view + } + + pub fn blackout_input_texture_view(&self) -> &wgpu::TextureView { + &self.downsampling.input_texture_view() + } + + fn create_input_texture( + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + ) -> (wgpu::Texture, wgpu::TextureView) { + let input_texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("bloom input texture"), + mip_level_count: 1, + size: wgpu::Extent3d { + width: config.width, + height: config.height, + depth_or_array_layers: 1, + }, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + dimension: wgpu::TextureDimension::D2, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, + sample_count: 1, + view_formats: &[], + }); + let input_texture_view = input_texture.create_view(&wgpu::TextureViewDescriptor::default()); + (input_texture, input_texture_view) + } + + // this creates all the bind groups for the final re-mix + fn create_final_remix_bind_group( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + full_image_texture_view: &wgpu::TextureView, + blurred_blackout_texture_view: &wgpu::TextureView, + texture_sampler: &wgpu::Sampler, + ) -> wgpu::BindGroup { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("final remix bind group"), + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(full_image_texture_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(texture_sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::TextureView(blurred_blackout_texture_view), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::Sampler(texture_sampler), + }, + ], + }); + bind_group + } + + pub fn resize(&mut self, device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, queue: &wgpu::Queue) { + (self.full_image_input_texture, self.full_image_input_texture_view) = Self::create_input_texture(device, config); + ( + self.blurred_blackout_input_texture, + self.blurred_blackout_texture_view, + ) = Self::create_input_texture(device, config); + self.downsampling.resize(device, config, queue); + self.upsampling + .resize(device, config, queue, self.downsampling.input_texture_view()); + self.final_remix_bind_group = Self::create_final_remix_bind_group( + device, + &self.final_remix_bind_group_layout, + &self.full_image_input_texture_view, + &self.blurred_blackout_texture_view, + &self.final_remix_texture_sampler, + ); + } + + pub fn render( + &self, + encoder: &mut wgpu::CommandEncoder, + output_view: Option<&wgpu::TextureView>, + ) { + self.downsampling + .render(encoder, Some(self.upsampling.input_texture_view())); + self.upsampling.render(encoder, output_view); + // self.upsampling.render(encoder, Some(&self.blurred_blackout_texture_view)); + // { + // let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + // label: Some("blur render pass"), + // color_attachments: &[output_view.map(|output_view| { + // wgpu::RenderPassColorAttachment { + // view: output_view, + // resolve_target: None, + // ops: wgpu::Operations { + // load: wgpu::LoadOp::Clear(wgpu::Color { + // r: 0.0, + // g: 0.0, + // b: 1.0, + // a: 1.0, + // }), + // store: wgpu::StoreOp::Store, + // }, + // } + // })], + // depth_stencil_attachment: None, + // timestamp_writes: None, + // occlusion_query_set: None, + // }); + // render_pass.set_pipeline(&self.final_remix_render_pipeline); + // render_pass.set_bind_group(0, &self.final_remix_bind_group, &[]); + // render_pass.draw(0..3, 0..1); + // } + } +} diff --git a/src/final_remix.wgsl b/src/old/final_remix.wgsl similarity index 100% rename from src/final_remix.wgsl rename to src/old/final_remix.wgsl diff --git a/src/gaussian_blur.rs b/src/old/gaussian_blur.rs similarity index 100% rename from src/gaussian_blur.rs rename to src/old/gaussian_blur.rs diff --git a/src/gaussian_blur.wgsl b/src/old/gaussian_blur.wgsl similarity index 100% rename from src/gaussian_blur.wgsl rename to src/old/gaussian_blur.wgsl diff --git a/src/old/kawase_mixing_upsample.wgsl b/src/old/kawase_mixing_upsample.wgsl new file mode 100644 index 0000000..a1b9444 --- /dev/null +++ b/src/old/kawase_mixing_upsample.wgsl @@ -0,0 +1,48 @@ +struct VertexOutput { + // Mark output position as invariant so it's safe to use it with depth test Equal. + // Without @invariant, different usages in different render pipelines might optimize differently, + // causing slightly different results. + @invariant @builtin(position) + position: vec4f, + @location(0) + texcoord: vec2f, +}; + +@group(0) @binding(0) +var input_texture: texture_2d; +@group(0) @binding(1) +var input_texture_sampler: sampler; + +@group(0) @binding(2) +var resolution: vec2u; + +@group(0) @binding(3) +var original_texture: texture_2d; +@group(0) @binding(4) +var original_texture_sampler: sampler; + +// https://www.shadertoy.com/view/3td3W8 +@fragment +fn main( in: VertexOutput ) -> @location(0) vec4f +{ + // vec2 uv = vec2(fragCoord.xy / (iResolution.xy * 2.0)); + let uv = in.texcoord; + // vec2 halfpixel = 0.5 / (iResolution.xy * 2.0); + let halfpixel = 0.5 / (vec2f(resolution)); + let offset = 3.0; + + var sum = textureSample(input_texture, input_texture_sampler, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset); + + sum += textureSample(input_texture, input_texture_sampler, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0; + sum += textureSample(input_texture, input_texture_sampler, uv + vec2(0.0, halfpixel.y * 2.0) * offset); + sum += textureSample(input_texture, input_texture_sampler, uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0; + sum += textureSample(input_texture, input_texture_sampler, uv + vec2(halfpixel.x * 2.0, 0.0) * offset); + sum += textureSample(input_texture, input_texture_sampler, uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0; + sum += textureSample(input_texture, input_texture_sampler, uv + vec2(0.0, -halfpixel.y * 2.0) * offset); + sum += textureSample(input_texture, input_texture_sampler, uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0; + + let original_col = textureSample(original_texture, original_texture_sampler, uv); + + // return sum / 12.0; + return original_col + sum / 12.0; +} \ No newline at end of file diff --git a/src/old/kawase_mixing_upsampling.rs b/src/old/kawase_mixing_upsampling.rs new file mode 100644 index 0000000..6dc33b6 --- /dev/null +++ b/src/old/kawase_mixing_upsampling.rs @@ -0,0 +1,316 @@ +use glam::{uvec2, UVec2}; +use wgpu::util::DeviceExt; + +use crate::otheruniforms::BufferContent; + +pub struct KawaseMixingUpsampling { + pub texture_sampler: wgpu::Sampler, + + pub textures: Vec<(wgpu::Texture, wgpu::TextureView)>, + pub resolutions: Vec, + pub resolution_uniform_buffers: Vec, + + pub bind_group_layout: wgpu::BindGroupLayout, + pub bind_groups: Vec, + pub render_pipeline: wgpu::RenderPipeline, +} + +impl KawaseMixingUpsampling { + pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, original_texture: &wgpu::TextureView) -> Self { + let texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + let mut resolutions = Vec::new(); + let mut dim = uvec2(config.width, config.height); + for _ in 0..LEVELS { + dim.x = dim.x.max(1); + dim.y = dim.y.max(1); + resolutions.push(dim); + dim = uvec2(dim.x / 2, dim.y / 2); + } + let mut resolution_uniform_buffers = Vec::new(); + for resolution in &resolutions { + resolution_uniform_buffers.push(device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("kawase mixing upsampling resolution uniform buffer"), + contents: &resolution.uniform_buffer_content(), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + })); + } + let textures = Self::create_textures(device, &resolutions); + + let screen_triangle_shader_module = + device.create_shader_module(wgpu::include_wgsl!("screen_triangle.wgsl")); + let upsample_shader_module = + device.create_shader_module(wgpu::include_wgsl!("kawase_mixing_upsample.wgsl")); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("kawase mixing upsampling bind group layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 4, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + let bind_groups = + Self::create_bind_groups(device, &bind_group_layout, &textures, &texture_sampler, &resolution_uniform_buffers, original_texture); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("upsampling pipeline layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("scene Pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &screen_triangle_shader_module, + entry_point: "main", + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &upsample_shader_module, + entry_point: "main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + Self { + texture_sampler, + + textures, + resolutions, + resolution_uniform_buffers, + + bind_group_layout: bind_group_layout, + bind_groups: bind_groups, + render_pipeline: render_pipeline, + } + } + + pub fn input_texture_view(&self) -> &wgpu::TextureView { + &self.textures[LEVELS - 1].1 + } + + // this creates all the levels of texture for downsampling + fn create_textures( + device: &wgpu::Device, + resolutions: &Vec, + ) -> Vec<(wgpu::Texture, wgpu::TextureView)> { + let mut result = Vec::new(); + for level in 0..LEVELS { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some(&format!("upsample texture {}", level)), + mip_level_count: 1, + size: wgpu::Extent3d { + width: resolutions[level].x, + height: resolutions[level].y, + depth_or_array_layers: 1, + }, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + dimension: wgpu::TextureDimension::D2, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::TEXTURE_BINDING, + sample_count: 1, + view_formats: &[], + }); + let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + result.push((texture, texture_view)); + } + result + } + + // this creates all the bind groups for all the upsampling shader calls + fn create_bind_groups( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + textures: &Vec<(wgpu::Texture, wgpu::TextureView)>, + texture_sampler: &wgpu::Sampler, + resolution_uniform_buffers: &Vec, + original_texture: &wgpu::TextureView, + ) -> Vec { + let mut result = Vec::new(); + for level in 0..LEVELS { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some(&format!("kawase upsampling bind group {}", level)), + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &textures[LEVELS - level - 1].1, + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(texture_sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: resolution_uniform_buffers[level].as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::TextureView(original_texture), + }, + wgpu::BindGroupEntry { + binding: 4, + resource: wgpu::BindingResource::Sampler(texture_sampler), + }, + ], + }); + result.push(bind_group); + } + result + } + + pub fn resize(&mut self, device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, queue: &wgpu::Queue, original_texture: &wgpu::TextureView) { + self.resolutions = Vec::new(); + let mut dim = uvec2(config.width, config.height); + for _ in 0..LEVELS { + dim.x = dim.x.max(1); + dim.y = dim.y.max(1); + self.resolutions.push(dim); + dim = uvec2(dim.x / 2, dim.y / 2); + } + for level in 0..LEVELS { + queue.write_buffer(&self.resolution_uniform_buffers[level], 0, &self.resolutions[level].uniform_buffer_content()); + } + self.textures = Self::create_textures(device, &self.resolutions); + self.bind_groups = Self::create_bind_groups( + device, + &self.bind_group_layout, + &self.textures, + &self.texture_sampler, + &self.resolution_uniform_buffers, + original_texture, + ); + } + + pub fn render( + &self, + encoder: &mut wgpu::CommandEncoder, + output_view: Option<&wgpu::TextureView>, + ) { + for level in 0..LEVELS - 1 { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some(&format!("kawase upsampling render pass {}", level)), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &self.textures[LEVELS - level - 2].1, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 1.0, + g: 0.0, + b: 0.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.bind_groups[level], &[]); + render_pass.draw(0..3, 0..1); + } + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("final kawase upsampling render pass"), + color_attachments: &[output_view.map(|output_view| { + wgpu::RenderPassColorAttachment { + view: output_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.0, + g: 0.0, + b: 1.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + } + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.bind_groups[LEVELS - 1], &[]); + render_pass.draw(0..3, 0..1); + } + } +} \ No newline at end of file diff --git a/src/remix.rs b/src/remix.rs new file mode 100644 index 0000000..6bfce21 --- /dev/null +++ b/src/remix.rs @@ -0,0 +1,249 @@ + +pub struct Remix { + pub input_texture_0: wgpu::Texture, + pub input_texture_0_view: wgpu::TextureView, + + pub input_texture_1: wgpu::Texture, + pub input_texture_1_view: wgpu::TextureView, + + pub texture_sampler: wgpu::Sampler, + + pub bind_group_layout: wgpu::BindGroupLayout, + pub bind_group: wgpu::BindGroup, + pub render_pipeline: wgpu::RenderPipeline, +} + +impl Remix { + pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self { + let (input_texture_0, input_texture_0_view) = Self::create_input_texture(device, config); + let (input_texture_1, input_texture_1_view) = Self::create_input_texture(device, config); + + let texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let screen_triangle_shader_module = + device.create_shader_module(wgpu::include_wgsl!("screen_triangle.wgsl")); + let remix_shader_module = device.create_shader_module(wgpu::include_wgsl!("remix.wgsl")); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("remix bind group layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + let bind_group = Self::create_bind_group( + device, + &bind_group_layout, + &input_texture_0_view, + &input_texture_1_view, + &texture_sampler, + ); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("remix pipeline layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("remix render pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &screen_triangle_shader_module, + entry_point: "main", + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &remix_shader_module, + entry_point: "main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + Self { + input_texture_0, + input_texture_0_view, + + input_texture_1, + input_texture_1_view, + + texture_sampler, + + bind_group_layout, + bind_group, + render_pipeline, + } + } + + pub fn input_texture_0_view(&self) -> &wgpu::TextureView { + &self.input_texture_0_view + } + + pub fn input_texture_1_view(&self) -> &wgpu::TextureView { + &self.input_texture_1_view + } + + fn create_input_texture( + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + ) -> (wgpu::Texture, wgpu::TextureView) { + let input_texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("bloom input texture"), + mip_level_count: 1, + size: wgpu::Extent3d { + width: config.width, + height: config.height, + depth_or_array_layers: 1, + }, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + dimension: wgpu::TextureDimension::D2, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, + sample_count: 1, + view_formats: &[], + }); + let input_texture_view = input_texture.create_view(&wgpu::TextureViewDescriptor::default()); + (input_texture, input_texture_view) + } + + // this creates all the bind groups for the final re-mix + fn create_bind_group( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + full_image_texture_view: &wgpu::TextureView, + blurred_blackout_texture_view: &wgpu::TextureView, + texture_sampler: &wgpu::Sampler, + ) -> wgpu::BindGroup { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("remix bind group"), + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(full_image_texture_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(texture_sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::TextureView(blurred_blackout_texture_view), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::Sampler(texture_sampler), + }, + ], + }); + bind_group + } + + pub fn resize(&mut self, device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) { + (self.input_texture_0, self.input_texture_0_view) = + Self::create_input_texture(device, config); + (self.input_texture_1, self.input_texture_1_view) = + Self::create_input_texture(device, config); + self.bind_group = Self::create_bind_group( + device, + &self.bind_group_layout, + &self.input_texture_0_view, + &self.input_texture_1_view, + &self.texture_sampler, + ); + } + + pub fn render( + &self, + encoder: &mut wgpu::CommandEncoder, + output_view: Option<&wgpu::TextureView>, + ) { + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("blur render pass"), + color_attachments: &[output_view.map(|output_view| { + wgpu::RenderPassColorAttachment { + view: output_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.0, + g: 0.0, + b: 1.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + } + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.bind_group, &[]); + render_pass.draw(0..3, 0..1); + } + } +} diff --git a/src/remix.wgsl b/src/remix.wgsl new file mode 100644 index 0000000..4df7e2b --- /dev/null +++ b/src/remix.wgsl @@ -0,0 +1,26 @@ +struct VertexOutput { + // Mark output position as invariant so it's safe to use it with depth test Equal. + // Without @invariant, different usages in different render pipelines might optimize differently, + // causing slightly different results. + @invariant @builtin(position) + position: vec4f, + @location(0) + texcoord: vec2f, +}; + +@group(0) @binding(0) +var input_texture_0: texture_2d; +@group(0) @binding(1) +var input_texture_sampler_0: sampler; +@group(0) @binding(2) +var input_texture_1: texture_2d; +@group(0) @binding(3) +var input_texture_sampler_1: sampler; + +@fragment +fn main(in: VertexOutput) -> @location(0) vec4 { + let col_0 = textureSampleLevel(input_texture_0, input_texture_sampler_0, in.texcoord, 0.0); + let col_1 = textureSampleLevel(input_texture_1, input_texture_sampler_1, in.texcoord, 0.0); + let col = col_0 + col_1 * 0.5; + return col; +} \ No newline at end of file diff --git a/src/state.rs b/src/state.rs index 4d98c8e..96bf1a8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -9,9 +9,9 @@ use winit::{event::*, window::Window}; use crate::bloom::Bloom; // use crate::bloom::Bloom; -use crate::downsampling::{self, Downsampling}; -use crate::gaussian_blur::GaussianBlur; -use crate::kawase_blur::{KawaseDownsampling, KawaseUpsampling}; +// use crate::downsampling::{self, Downsampling}; +// use crate::gaussian_blur::GaussianBlur; +use crate::kawase_downsampling::KawaseDownsampling; use crate::time_replacement::{Duration, Instant}; // use crate::upsampling::Upsampling; use std::thread::sleep; @@ -23,8 +23,6 @@ use crate::settings::{Settings, SettingsController}; use crate::scene::Scene; -const LEVELS: usize = 2; - pub struct State<'a> { // wgpu and winit setup pub window: Arc, @@ -124,7 +122,7 @@ impl State<'_> { // let kawase_downsampling = KawaseDownsampling::new(&device, &config); // let kawase_upsampling = KawaseUpsampling::new(&device, &config); - let bloom = Bloom::new(&device, &config); + let bloom = Bloom::new(&device, &config, 3); let last_frame_time = Instant::now();