diff --git a/Cargo.lock b/Cargo.lock index 134f2495..db2dfd69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -274,15 +274,30 @@ dependencies = [ "virtue", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", +] + [[package]] name = "bit-set" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" dependencies = [ - "bit-vec", + "bit-vec 0.9.1", ] +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bit-vec" version = "0.9.1" @@ -1732,6 +1747,7 @@ version = "0.11.2" dependencies = [ "arc-swap", "array-concat", + "bit-set 0.8.0", "bytemuck", "image", "librashader-common", @@ -2035,7 +2051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd91265cc2454558f659b3b4b9640f0ddb8cc6521277f166b8a8c181c898079" dependencies = [ "arrayvec", - "bit-set", + "bit-set 0.9.1", "bitflags 2.12.1", "cfg-if", "cfg_aliases 0.2.1", @@ -3959,8 +3975,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02da3ad1b568337f25513b317870960ef87073ea0945502e44b864b67a8c77b7" dependencies = [ "arrayvec", - "bit-set", - "bit-vec", + "bit-set 0.9.1", + "bit-vec 0.9.1", "bitflags 2.12.1", "bytemuck", "cfg_aliases 0.2.1", @@ -4021,7 +4037,7 @@ dependencies = [ "android_system_properties", "arrayvec", "ash", - "bit-set", + "bit-set 0.9.1", "bitflags 2.12.1", "block2 0.6.2", "bytemuck", diff --git a/librashader-common/src/lib.rs b/librashader-common/src/lib.rs index a5369eea..3da34f88 100644 --- a/librashader-common/src/lib.rs +++ b/librashader-common/src/lib.rs @@ -232,6 +232,12 @@ impl Size { } } +impl AsPrimitive for Size { + fn as_(self) -> u64 { + ((self.width as u64) << 32) | (self.height as u64) + } +} + impl> Size { /// Get the aspect ratio of the size. #[inline(always)] diff --git a/librashader-runtime-d3d11/src/filter_chain.rs b/librashader-runtime-d3d11/src/filter_chain.rs index 6f15d095..b49fba11 100644 --- a/librashader-runtime-d3d11/src/filter_chain.rs +++ b/librashader-runtime-d3d11/src/filter_chain.rs @@ -30,7 +30,7 @@ use librashader_presets::context::VideoDriver; use librashader_reflect::reflect::cross::SpirvCross; use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact}; use librashader_runtime::binding::{BindingUtil, TextureInput}; -use librashader_runtime::framebuffer::FramebufferInit; +use librashader_runtime::framebuffer::{FramebufferInit, FramebufferPool}; use librashader_runtime::quad::QuadType; use librashader_runtime::render_target::RenderTarget; use librashader_runtime::scaling::ScaleFramebuffer; @@ -48,8 +48,8 @@ use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM; pub struct FilterChainD3D11 { pub(crate) common: FilterCommon, passes: Vec, - output_framebuffers: Box<[OwnedImage]>, - feedback_framebuffers: Box<[OwnedImage]>, + output_framebuffers: FramebufferPool, + feedback_framebuffers: FramebufferPool, history_framebuffers: VecDeque, state: D3D11State, default_options: FrameOptionsD3D11, @@ -223,8 +223,7 @@ impl FilterChainD3D11 { let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?; // initialize feedback framebuffers - let (feedback_framebuffers, feedback_textures) = - framebuffer_init.init_output_framebuffers()?; + let (feedback_framebuffers, feedback_textures) = framebuffer_init.init_feedback_framebuffers()?; // initialize history let (history_framebuffers, history_textures) = framebuffer_init.init_history()?; @@ -493,117 +492,105 @@ impl FilterChainD3D11 { let mut source = original.clone(); - // rescale render buffers to ensure all bindings are valid. - OwnedImage::scale_framebuffers( - source.size(), - viewport.output.size()?, - original.view.size()?, - &mut self.output_framebuffers, + let viewport_size = viewport.output.size()?; + let original_size = original.view.size()?; + let source_size = source.size(); + let passes_len = passes.len(); + + OwnedImage::scale_feedback_framebuffers( + source_size, + viewport_size, + original_size, &mut self.feedback_framebuffers, passes, - None, + |index, pass, fb| { + self.common.feedback_textures[index] = Some(InputTexture::from_framebuffer( + fb, + pass.meta.wrap_mode, + pass.meta.filter, + )?); + Ok(()) + }, )?; - // Refresh inputs for feedback textures. - // Don't need to do this for outputs because they are yet to be bound. - for ((texture, fbo), pass) in self - .common - .feedback_textures - .iter_mut() - .zip(self.feedback_framebuffers.iter()) - .zip(passes.iter()) - { - *texture = Some(InputTexture::from_framebuffer( - fbo, - pass.meta.wrap_mode, - pass.meta.filter, - )?); - } - - let passes_len = passes.len(); - let (pass, last) = passes.split_at_mut(passes_len - 1); - let state_guard = self.state.enter_filter_state(ctx); self.common.draw_quad.bind_vbo_for_frame(ctx); - for (index, pass) in pass.iter_mut().enumerate() { - source.filter = pass.meta.filter; - source.wrap_mode = pass.meta.wrap_mode; - let target = &self.output_framebuffers[index]; - pass.draw( - ctx, - index, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - RenderTarget::identity(&target.create_render_target_view()?)?, - None, - QuadType::Offscreen, - )?; - - source = InputTexture { - view: target.create_shader_resource_view()?, - filter: pass.meta.filter, - wrap_mode: pass.meta.wrap_mode, - }; - self.common.output_textures[index] = Some(source.clone()); - } + OwnedImage::scale_output_framebuffers( + source_size, + viewport_size, + original_size, + &mut self.output_framebuffers, + passes, + |index, pass, target, size| { + source.filter = pass.meta.filter; + source.wrap_mode = pass.meta.wrap_mode; + let is_last = index == passes_len - 1; + let frame_count_pass = pass.meta.get_frame_count(frame_count); + + if is_last && !self.draw_last_pass_feedback { + pass.draw( + ctx, + index, + &self.common, + frame_count_pass, + options, + viewport, + &original, + &source, + RenderTarget::viewport(viewport), + None, + QuadType::Final, + )?; + return Ok(()); + } - // try to hint the optimizer - assert_eq!(last.len(), 1); - if let Some(pass) = last.iter_mut().next() { - let index = passes_len - 1; - source.filter = pass.meta.filter; - source.wrap_mode = pass.meta.wrap_mode; - - // When feedback is enabled, render the last pass to the intermediate - // framebuffer first then render to the viewport with the OutputSize semantic - // overridden to the FB scale. - // - // Shaders need to see the pass's declared scale rather than the viewport size, - // or they won't render correctly for feedback. - let feedback_target = &self.output_framebuffers[index]; - let output_size_override = if self.draw_last_pass_feedback { + let target_rtv = target.create_render_target_view()?; pass.draw( - &ctx, + ctx, index, &self.common, - pass.meta.get_frame_count(frame_count), + frame_count_pass, options, viewport, &original, &source, - RenderTarget::identity(&feedback_target.create_render_target_view()?)?, + RenderTarget::identity(&target_rtv)?, None, QuadType::Offscreen, )?; - Some(feedback_target.size) - } else { - None - }; - pass.draw( - &ctx, - index, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - RenderTarget::viewport(viewport), - output_size_override, - QuadType::Final, - )?; - } + if is_last { + pass.draw( + ctx, + index, + &self.common, + frame_count_pass, + options, + viewport, + &original, + &source, + RenderTarget::viewport(viewport), + Some(size), + QuadType::Final, + )?; + } else { + source = InputTexture { + view: target.create_shader_resource_view()?, + filter: pass.meta.filter, + wrap_mode: pass.meta.wrap_mode, + }; + self.common.output_textures[index] = Some(source.clone()); + } + Ok(()) + }, + )?; - std::mem::swap( - &mut self.output_framebuffers, - &mut self.feedback_framebuffers, - ); + for index in 0..passes_len { + if self.feedback_framebuffers.contains(index) { + std::mem::swap(&mut self.output_framebuffers[index], &mut self.feedback_framebuffers[index]); + } + } drop(state_guard); diff --git a/librashader-runtime-d3d12/src/filter_chain.rs b/librashader-runtime-d3d12/src/filter_chain.rs index 99d86b11..efef8b2f 100644 --- a/librashader-runtime-d3d12/src/filter_chain.rs +++ b/librashader-runtime-d3d12/src/filter_chain.rs @@ -53,7 +53,7 @@ use windows::Win32::System::Threading::{CreateEventA, WaitForSingleObject, INFIN use librashader_cache::CachedCompilation; use librashader_presets::context::VideoDriver; use librashader_reflect::reflect::cross::SpirvCross; -use librashader_runtime::framebuffer::FramebufferInit; +use librashader_runtime::framebuffer::{FramebufferInit, FramebufferPool}; use librashader_runtime::render_target::RenderTarget; use librashader_runtime::scaling::ScaleFramebuffer; use rayon::prelude::*; @@ -64,8 +64,8 @@ const MIPMAP_RESERVED_WORKHEAP_DESCRIPTORS: usize = 4096; pub struct FilterChainD3D12 { pub(crate) common: FilterCommon, pub(crate) passes: Vec, - pub(crate) output_framebuffers: Box<[OwnedImage]>, - pub(crate) feedback_framebuffers: Box<[OwnedImage]>, + pub(crate) output_framebuffers: FramebufferPool, + pub(crate) feedback_framebuffers: FramebufferPool, pub(crate) history_framebuffers: VecDeque, pub(crate) staging_heap: D3D12DescriptorHeap, pub(crate) rtv_heap: D3D12DescriptorHeap, @@ -431,8 +431,7 @@ impl FilterChainD3D12 { let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?; // initialize feedback framebuffers - let (feedback_framebuffers, feedback_textures) = - framebuffer_init.init_output_framebuffers()?; + let (feedback_framebuffers, feedback_textures) = framebuffer_init.init_feedback_framebuffers()?; // initialize history let (history_framebuffers, history_textures) = framebuffer_init.init_history()?; @@ -770,20 +769,6 @@ impl FilterChainD3D12 { let filter = passes[0].meta.filter; let wrap_mode = passes[0].meta.wrap_mode; - for ((texture, fbo), pass) in self - .common - .feedback_textures - .iter_mut() - .zip(self.feedback_framebuffers.iter()) - .zip(passes.iter()) - { - *texture = Some(fbo.create_shader_resource_view( - &mut self.staging_heap, - pass.meta.filter, - pass.meta.wrap_mode, - )?); - } - for (texture, fbo) in self .common .history_textures @@ -812,40 +797,32 @@ impl FilterChainD3D12 { let mut source = original.clone(); + let passes_len = passes.len(); + // swap output and feedback **before** recording command buffers - std::mem::swap( - &mut self.output_framebuffers, - &mut self.feedback_framebuffers, - ); + for index in 0..passes_len { + if self.feedback_framebuffers.contains(index) { + std::mem::swap(&mut self.output_framebuffers[index], &mut self.feedback_framebuffers[index]); + } + } - // rescale render buffers to ensure all bindings are valid. - OwnedImage::scale_framebuffers( + // rescale feedback buffers and refresh their bound textures. + OwnedImage::scale_feedback_framebuffers( source.size(), viewport.output.size, original.size(), - &mut self.output_framebuffers, &mut self.feedback_framebuffers, passes, - Some(&mut |index, pass, output, feedback| { - // refresh inputs + |index, pass, feedback| { self.common.feedback_textures[index] = Some(feedback.create_shader_resource_view( &mut self.staging_heap, pass.meta.filter, pass.meta.wrap_mode, )?); - self.common.output_textures[index] = Some(output.create_shader_resource_view( - &mut self.staging_heap, - pass.meta.filter, - pass.meta.wrap_mode, - )?); - Ok(()) - }), + }, )?; - let passes_len = passes.len(); - let (pass, last) = passes.split_at_mut(passes_len - 1); - unsafe { let heaps = [ Some(self.work_heap.clone()), @@ -858,113 +835,155 @@ impl FilterChainD3D12 { self.common.draw_quad.bind_vertices_for_frame(cmd); - for (index, pass) in pass.iter_mut().enumerate() { - source.filter = pass.meta.filter; - source.wrap_mode = pass.meta.wrap_mode; + // rescale output buffers and draw each pass. + OwnedImage::scale_output_framebuffers( + source.size(), + viewport.output.size, + original.size(), + &mut self.output_framebuffers, + passes, + |index, pass, target, size| { + source.filter = pass.meta.filter; + source.wrap_mode = pass.meta.wrap_mode; + + if index != passes_len - 1 { + if !pass.pipeline.has_format(target.format) { + // eprintln!("recompiling final pipeline"); + pass.pipeline.recompile( + target.format, + &self.common.root_signature, + &self.common.d3d12, + )?; + } - let target = &self.output_framebuffers[index]; + util::d3d12_resource_transition::( + cmd, + &target.resource, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_RENDER_TARGET, + ); - if !pass.pipeline.has_format(target.format) { - // eprintln!("recompiling final pipeline"); - pass.pipeline.recompile( - target.format, - &self.common.root_signature, - &self.common.d3d12, - )?; - } + let view = target.create_render_target_view(&mut self.rtv_heap)?; + let out = RenderTarget::identity(&view)?; + + pass.draw( + cmd, + index, + &self.common, + pass.meta.get_frame_count(frame_count), + options, + viewport, + &original, + &source, + &out, + None, + QuadType::Offscreen, + )?; - util::d3d12_resource_transition::( - cmd, - &target.resource, - D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, - D3D12_RESOURCE_STATE_RENDER_TARGET, - ); + util::d3d12_resource_transition::( + cmd, + &target.resource, + D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + ); - let view = target.create_render_target_view(&mut self.rtv_heap)?; - let out = RenderTarget::identity(&view)?; + if target.max_mipmap > 1 && !self.disable_mipmaps { + // barriers don't get disposed because the context is OutlivesFrame + let (residuals, _residual_barriers) = + self.common.mipmap_gen.mipmapping_context( + cmd, + &mut self.mipmap_heap, + |ctx| { + ctx.generate_mipmaps::( + &target.resource, + target.max_mipmap, + target.size, + target.format.into(), + )?; + Ok::<(), FilterChainError>(()) + }, + )?; + + self.residuals.dispose_mipmap_handles(residuals); + } - pass.draw( - cmd, - index, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - &out, - None, - QuadType::Offscreen, - )?; + self.residuals.dispose_output(view.descriptor); + self.common.output_textures[index] = + Some(target.create_shader_resource_view( + &mut self.staging_heap, + pass.meta.filter, + pass.meta.wrap_mode, + )?); + source = self.common.output_textures[index].as_ref().unwrap().clone(); + return Ok(()); + } - util::d3d12_resource_transition::( - cmd, - &target.resource, - D3D12_RESOURCE_STATE_RENDER_TARGET, - D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, - ); - - if target.max_mipmap > 1 && !self.disable_mipmaps { - // barriers don't get disposed because the context is OutlivesFrame - let (residuals, _residual_barriers) = self.common.mipmap_gen.mipmapping_context( - cmd, - &mut self.mipmap_heap, - |ctx| { - ctx.generate_mipmaps::( - &target.resource, - target.max_mipmap, - target.size, - target.format.into(), + // When feedback is enabled, render the last pass to the intermediate + // framebuffer first then render to the viewport with the OutputSize semantic + // overridden to the FB scale. + // + // Shaders need to see the pass's declared scale rather than the viewport size, + // or they won't render correctly for feedback. + let output_size_override = if self.draw_last_pass_feedback { + let feedback_target = target; + + if !pass.pipeline.has_format(feedback_target.format) { + // eprintln!("recompiling final pipeline"); + pass.pipeline.recompile( + feedback_target.format, + &self.common.root_signature, + &self.common.d3d12, )?; - Ok::<(), FilterChainError>(()) - }, - )?; - - self.residuals.dispose_mipmap_handles(residuals); - } + } - self.residuals.dispose_output(view.descriptor); - source = self.common.output_textures[index].as_ref().unwrap().clone() - } + util::d3d12_resource_transition::( + cmd, + &feedback_target.resource, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_RENDER_TARGET, + ); - // try to hint the optimizer - assert_eq!(last.len(), 1); - if let Some(pass) = last.iter_mut().next() { - let index = passes_len - 1; + let view = feedback_target.create_render_target_view(&mut self.rtv_heap)?; + let out = RenderTarget::identity(&view)?; + pass.draw( + cmd, + index, + &self.common, + pass.meta.get_frame_count(frame_count), + options, + viewport, + &original, + &source, + &out, + None, + QuadType::Offscreen, + )?; - source.filter = pass.meta.filter; - source.wrap_mode = pass.meta.wrap_mode; + util::d3d12_resource_transition::( + cmd, + &feedback_target.resource, + D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + ); - // When feedback is enabled, render the last pass to the intermediate - // framebuffer first then render to the viewport with the OutputSize semantic - // overridden to the FB scale. - // - // Shaders need to see the pass's declared scale rather than the viewport size, - // or they won't render correctly for feedback. - let output_size_override = if self.draw_last_pass_feedback { - let feedback_target = &self.output_framebuffers[index]; + Some(size) + } else { + None + }; - if !pass.pipeline.has_format(feedback_target.format) { + if !pass.pipeline.has_format(viewport.output.format) { // eprintln!("recompiling final pipeline"); pass.pipeline.recompile( - feedback_target.format, + viewport.output.format, &self.common.root_signature, &self.common.d3d12, )?; } - util::d3d12_resource_transition::( - cmd, - &feedback_target.resource, - D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, - D3D12_RESOURCE_STATE_RENDER_TARGET, - ); - - let view = feedback_target.create_render_target_view(&mut self.rtv_heap)?; - let out = RenderTarget::identity(&view)?; + let out = RenderTarget::viewport(viewport); pass.draw( cmd, - index, + passes_len - 1, &self.common, pass.meta.get_frame_count(frame_count), options, @@ -972,46 +991,13 @@ impl FilterChainD3D12 { &original, &source, &out, - None, - QuadType::Offscreen, - )?; - - util::d3d12_resource_transition::( - cmd, - &feedback_target.resource, - D3D12_RESOURCE_STATE_RENDER_TARGET, - D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, - ); - - Some(feedback_target.size) - } else { - None - }; - - if !pass.pipeline.has_format(viewport.output.format) { - // eprintln!("recompiling final pipeline"); - pass.pipeline.recompile( - viewport.output.format, - &self.common.root_signature, - &self.common.d3d12, + output_size_override, + QuadType::Final, )?; - } - let out = RenderTarget::viewport(viewport); - pass.draw( - cmd, - passes_len - 1, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - &out, - output_size_override, - QuadType::Final, - )?; - } + Ok(()) + }, + )?; self.push_history(cmd, &original)?; diff --git a/librashader-runtime-d3d12/tests/hello_triangle/mod.rs b/librashader-runtime-d3d12/tests/hello_triangle/mod.rs index 676eb5da..ded9d558 100644 --- a/librashader-runtime-d3d12/tests/hello_triangle/mod.rs +++ b/librashader-runtime-d3d12/tests/hello_triangle/mod.rs @@ -494,7 +494,7 @@ pub mod d3d12_hello_triangle { } fn window_size(&self) -> (i32, i32) { - (800, 600) + (3840 , 2160 ) } fn render(&mut self) { diff --git a/librashader-runtime-d3d12/tests/triangle.rs b/librashader-runtime-d3d12/tests/triangle.rs index 032321c0..39e124bd 100644 --- a/librashader-runtime-d3d12/tests/triangle.rs +++ b/librashader-runtime-d3d12/tests/triangle.rs @@ -46,7 +46,8 @@ fn triangle_d3d12_hdr() { fn triangle_d3d12() { let sample = hello_triangle::d3d12_hello_triangle::Sample::new( // "../test/shaders_slang/sonkun/slot-mask/flat-screen/1080p/test.slangp", - "../test/shaders_slang/crt/crt-royale.slangp", + // "../test/shaders_slang/crt/crt-royale.slangp", + "../test/shaders_slang/edge-smoothing/vectorscale/vectorscale-single-aa.slangp", // "../test/basic.slangp", // "../test/shaders_slang/handheld/console-border/gbc-lcd-grid-v2.slangp", // "../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_GBA_SP/GBA_SP-[ADV]-[LCD-GRID]-[Night].slangp", diff --git a/librashader-runtime-d3d9/src/filter_chain.rs b/librashader-runtime-d3d9/src/filter_chain.rs index 8c2a1d9d..65115436 100644 --- a/librashader-runtime-d3d9/src/filter_chain.rs +++ b/librashader-runtime-d3d9/src/filter_chain.rs @@ -22,7 +22,7 @@ use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtif use librashader_reflect::reflect::semantics::ShaderSemantics; use librashader_reflect::reflect::ReflectShader; use librashader_runtime::binding::{BindingUtil, TextureInput}; -use librashader_runtime::framebuffer::FramebufferInit; +use librashader_runtime::framebuffer::{FramebufferInit, FramebufferPool}; use librashader_runtime::image::{ImageError, LoadedTexture, UVDirection, BGRA8}; use librashader_runtime::quad::QuadType; use librashader_runtime::render_target::RenderTarget; @@ -54,8 +54,8 @@ pub(crate) struct FilterCommon { pub struct FilterChainD3D9 { pub(crate) common: FilterCommon, passes: Vec, - output_framebuffers: Box<[D3D9Texture]>, - feedback_framebuffers: Box<[D3D9Texture]>, + output_framebuffers: FramebufferPool, + feedback_framebuffers: FramebufferPool, history_framebuffers: VecDeque, default_options: FrameOptionsD3D9, draw_last_pass_feedback: bool, @@ -264,8 +264,7 @@ impl FilterChainD3D9 { let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?; // initialize feedback framebuffers - let (feedback_framebuffers, feedback_textures) = - framebuffer_init.init_output_framebuffers()?; + let (feedback_framebuffers, feedback_textures) = framebuffer_init.init_feedback_framebuffers()?; // initialize history let (history_framebuffers, history_textures) = framebuffer_init.init_history()?; @@ -351,81 +350,97 @@ impl FilterChainD3D9 { let mut source = original.clone(); - // rescale render buffers to ensure all bindings are valid. - D3D9Texture::scale_framebuffers( - source.size(), - viewport.output.size()?, - original.size(), - &mut self.output_framebuffers, - &mut self.feedback_framebuffers, - passes, - None, - )?; + let viewport_size = viewport.output.size()?; + let original_size = original.size(); + let source_size = source.size(); + let passes_len = passes.len(); + // rescale feedback render buffers to ensure all bindings are valid. // Refresh inputs for feedback textures. // Don't need to do this for outputs because they are yet to be bound. - for ((texture, fbo), pass) in self - .common - .feedback_textures - .iter_mut() - .zip(self.feedback_framebuffers.iter()) - .zip(passes.iter()) - { - *texture = Some(fbo.as_input(pass.meta.filter, pass.meta.filter, pass.meta.wrap_mode)); - } + D3D9Texture::scale_feedback_framebuffers( + source_size, + viewport_size, + original_size, + &mut self.feedback_framebuffers, + passes, + |index, pass, feedback| { + self.common.feedback_textures[index] = Some(feedback.as_input( + pass.meta.filter, + pass.meta.filter, + pass.meta.wrap_mode, + )); + Ok(()) + }, + )?; - let passes_len = passes.len(); - let (pass, last) = passes.split_at_mut(passes_len - 1); let state_guard = D3D9State::new(&self.common.d3d9)?; - for (index, pass) in pass.iter_mut().enumerate() { - source.filter = pass.meta.filter; - source.wrap = pass.meta.wrap_mode; - source.is_srgb = pass.meta.srgb_framebuffer; - let target = &self.output_framebuffers[index]; - let target_rtv = target.as_output()?; - pass.draw( - &self.common.d3d9, - index, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - RenderTarget::identity(&target_rtv)?, - None, - QuadType::Offscreen, - )?; - - source = D3D9InputTexture { - handle: target.handle.clone(), - filter: pass.meta.filter, - wrap: pass.meta.wrap_mode, - mipmode: pass.meta.filter, - is_srgb: pass.meta.srgb_framebuffer, - }; - self.common.output_textures[index] = Some(source.clone()); - } + D3D9Texture::scale_output_framebuffers( + source_size, + viewport_size, + original_size, + &mut self.output_framebuffers, + passes, + |index, pass, target, size| { + source.filter = pass.meta.filter; + source.wrap = pass.meta.wrap_mode; + source.is_srgb = pass.meta.srgb_framebuffer; + let is_last = index == passes_len - 1; + + if !is_last { + let target_rtv = target.as_output()?; + pass.draw( + &self.common.d3d9, + index, + &self.common, + pass.meta.get_frame_count(frame_count), + options, + viewport, + &original, + &source, + RenderTarget::identity(&target_rtv)?, + None, + QuadType::Offscreen, + )?; + + source = D3D9InputTexture { + handle: target.handle.clone(), + filter: pass.meta.filter, + wrap: pass.meta.wrap_mode, + mipmode: pass.meta.filter, + is_srgb: pass.meta.srgb_framebuffer, + }; + self.common.output_textures[index] = Some(source.clone()); + return Ok(()); + } - // try to hint the optimizer - assert_eq!(last.len(), 1); - if let Some(pass) = last.iter_mut().next() { - let index = passes_len - 1; - source.filter = pass.meta.filter; - source.wrap = pass.meta.wrap_mode; - source.is_srgb = pass.meta.srgb_framebuffer; - - // When feedback is enabled, render the last pass to the intermediate - // framebuffer first then render to the viewport with the OutputSize semantic - // overridden to the FB scale. - // - // Shaders need to see the pass's declared scale rather than the viewport size, - // or they won't render correctly for feedback. - let output_size_override = if self.draw_last_pass_feedback { - let feedback_target = &self.output_framebuffers[index]; - let feedback_target_rtv = feedback_target.as_output()?; - let feedback_size: Size = feedback_target.handle.size()?; + // When feedback is enabled, render the last pass to the intermediate + // framebuffer first then render to the viewport with the OutputSize semantic + // overridden to the FB scale. + // + // Shaders need to see the pass's declared scale rather than the viewport size, + // or they won't render correctly for feedback. + let output_size_override = if self.draw_last_pass_feedback { + let feedback_target_rtv = target.as_output()?; + + pass.draw( + &self.common.d3d9, + index, + &self.common, + pass.meta.get_frame_count(frame_count), + options, + viewport, + &original, + &source, + RenderTarget::viewport_with_output(&feedback_target_rtv, viewport), + None, + QuadType::Final, + )?; + Some(size) + } else { + None + }; pass.draw( &self.common.d3d9, @@ -436,34 +451,19 @@ impl FilterChainD3D9 { viewport, &original, &source, - RenderTarget::viewport_with_output(&feedback_target_rtv, viewport), - None, + RenderTarget::viewport(viewport), + output_size_override, QuadType::Final, )?; - Some(feedback_size) - } else { - None - }; - - pass.draw( - &self.common.d3d9, - index, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - RenderTarget::viewport(viewport), - output_size_override, - QuadType::Final, - )?; - } + Ok(()) + }, + )?; - std::mem::swap( - &mut self.output_framebuffers, - &mut self.feedback_framebuffers, - ); + for index in 0..passes_len { + if self.feedback_framebuffers.contains(index) { + std::mem::swap(&mut self.output_framebuffers[index], &mut self.feedback_framebuffers[index]); + } + } drop(state_guard); diff --git a/librashader-runtime-gl/src/filter_chain/chain.rs b/librashader-runtime-gl/src/filter_chain/chain.rs index 6fd71f87..5fb84e8d 100644 --- a/librashader-runtime-gl/src/filter_chain/chain.rs +++ b/librashader-runtime-gl/src/filter_chain/chain.rs @@ -27,7 +27,7 @@ use librashader_reflect::reflect::cross::SpirvCross; use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact}; use librashader_reflect::reflect::ReflectShader; use librashader_runtime::binding::BindingUtil; -use librashader_runtime::framebuffer::FramebufferInit; +use librashader_runtime::framebuffer::{FramebufferInit, FramebufferPool}; use librashader_runtime::quad::QuadType; use librashader_runtime::render_target::RenderTarget; use librashader_runtime::scaling::ScaleFramebuffer; @@ -39,8 +39,8 @@ pub(crate) struct FilterChainImpl { pub(crate) common: FilterCommon, passes: Box<[FilterPass]>, draw_quad: T::DrawQuad, - output_framebuffers: Box<[GLFramebuffer]>, - feedback_framebuffers: Box<[GLFramebuffer]>, + output_framebuffers: FramebufferPool, + feedback_framebuffers: FramebufferPool, history_framebuffers: VecDeque, render_target: OutputFramebuffer, default_options: FrameOptionsGL, @@ -203,8 +203,7 @@ impl FilterChainImpl { let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?; // initialize feedback framebuffers - let (feedback_framebuffers, feedback_textures) = - framebuffer_init.init_output_framebuffers()?; + let (feedback_framebuffers, feedback_textures) = framebuffer_init.init_feedback_framebuffers()?; // initialize history let (history_framebuffers, history_textures) = framebuffer_init.init_history()?; @@ -212,7 +211,7 @@ impl FilterChainImpl { // create vertex objects let draw_quad = T::DrawQuad::new(&context)?; - let output = OutputFramebuffer::new(&context); + let render_target = OutputFramebuffer::new(&context); Ok(FilterChainImpl { draw_last_pass_feedback: framebuffer_init.uses_final_pass_as_feedback(), @@ -233,7 +232,7 @@ impl FilterChainImpl { context, }, default_options: Default::default(), - render_target: output, + render_target, }) } @@ -365,79 +364,92 @@ impl FilterChainImpl { let mut source = original; - // rescale render buffers to ensure all bindings are valid. - >::scale_framebuffers( + let passes_len = passes.len(); + + // Refresh inputs for feedback textures. + // Don't need to do this for outputs because they are yet to be bound. + // + // Feedback scaling + refresh must complete before drawing any pass, because + // a pass may sample any pass's feedback image. + >::scale_feedback_framebuffers( source.image.size, viewport.output.size, original.image.size, - &mut self.output_framebuffers, &mut self.feedback_framebuffers, passes, - None, + |index, pass, fbo| { + self.common.feedback_textures[index].image = + fbo.as_texture(pass.meta.filter, pass.meta.wrap_mode).image; + Ok(()) + }, )?; - // Refresh inputs for feedback textures. - // Don't need to do this for outputs because they are yet to be bound. - for ((texture, fbo), pass) in self - .common - .feedback_textures - .iter_mut() - .zip(self.feedback_framebuffers.iter()) - .zip(passes.iter()) - { - texture.image = fbo.as_texture(pass.meta.filter, pass.meta.wrap_mode).image; - } - - let passes_len = passes.len(); - let (pass, last) = passes.split_at_mut(passes_len - 1); - self.draw_quad .bind_vertices(&self.common.context, QuadType::Offscreen); - for (index, pass) in pass.iter_mut().enumerate() { - let target = &self.output_framebuffers[index]; - source.filter = pass.meta.filter; - source.mip_filter = pass.meta.filter; - source.wrap_mode = pass.meta.wrap_mode; - - pass.draw( - index, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - RenderTarget::identity(target)?, - None, - )?; - - let target = target.as_texture(pass.meta.filter, pass.meta.wrap_mode); - self.common.output_textures[index] = target; - source = target; - } - self.draw_quad - .bind_vertices(&self.common.context, QuadType::Final); - // try to hint the optimizer - assert_eq!(last.len(), 1); - if let Some(pass) = last.iter_mut().next() { - let index = passes_len - 1; - let final_viewport = self - .render_target - .ensure::(viewport.output)?; - - source.filter = pass.meta.filter; - source.mip_filter = pass.meta.filter; - source.wrap_mode = pass.meta.wrap_mode; - - // When feedback is enabled, render the last pass to the intermediate - // framebuffer first then render to the viewport with the OutputSize semantic - // overridden to the FB scale. - // - // Shaders need to see the pass's declared scale rather than the viewport size, - // or they won't render correctly for feedback. - let output_size_override = if self.draw_last_pass_feedback { - let target = &self.output_framebuffers[index]; + // rescale render buffers to ensure all bindings are valid, then draw each pass. + >::scale_output_framebuffers( + source.image.size, + viewport.output.size, + original.image.size, + &mut self.output_framebuffers, + passes, + |index, pass, target, size| { + source.filter = pass.meta.filter; + source.mip_filter = pass.meta.filter; + source.wrap_mode = pass.meta.wrap_mode; + + let is_last = index == passes_len - 1; + + if !is_last { + pass.draw( + index, + &self.common, + pass.meta.get_frame_count(frame_count), + options, + viewport, + &original, + &source, + RenderTarget::identity(target)?, + None, + )?; + + let target = target.as_texture(pass.meta.filter, pass.meta.wrap_mode); + self.common.output_textures[index] = target; + source = target; + return Ok(()); + } + + self.draw_quad + .bind_vertices(&self.common.context, QuadType::Final); + + let final_viewport = self + .render_target + .ensure::(viewport.output)?; + + // When feedback is enabled, render the last pass to the intermediate + // framebuffer first then render to the viewport with the OutputSize semantic + // overridden to the FB scale. + // + // Shaders need to see the pass's declared scale rather than the viewport size, + // or they won't render correctly for feedback. + let output_size_override = if self.draw_last_pass_feedback { + pass.draw( + index, + &self.common, + pass.meta.get_frame_count(frame_count), + options, + viewport, + &original, + &source, + RenderTarget::viewport_with_output(target, viewport), + None, + )?; + Some(size) + } else { + None + }; + pass.draw( index, &self.common, @@ -446,35 +458,22 @@ impl FilterChainImpl { viewport, &original, &source, - RenderTarget::viewport_with_output(target, viewport), - None, + RenderTarget::viewport_with_output(final_viewport, viewport), + output_size_override, )?; - Some(target.size) - } else { - None - }; - - pass.draw( - index, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - RenderTarget::viewport_with_output(final_viewport, viewport), - output_size_override, - )?; - self.common.output_textures[passes_len - 1] = viewport - .output - .as_texture(pass.meta.filter, pass.meta.wrap_mode); - } + self.common.output_textures[index] = viewport + .output + .as_texture(pass.meta.filter, pass.meta.wrap_mode); + Ok(()) + }, + )?; // swap feedback framebuffers with output - std::mem::swap( - &mut self.output_framebuffers, - &mut self.feedback_framebuffers, - ); + for index in 0..passes_len { + if self.feedback_framebuffers.contains(index) { + std::mem::swap(&mut self.output_framebuffers[index], &mut self.feedback_framebuffers[index]); + } + } self.push_history(input)?; diff --git a/librashader-runtime-mtl/src/filter_chain.rs b/librashader-runtime-mtl/src/filter_chain.rs index a7de1cfb..6719faaa 100644 --- a/librashader-runtime-mtl/src/filter_chain.rs +++ b/librashader-runtime-mtl/src/filter_chain.rs @@ -21,7 +21,7 @@ use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtif use librashader_reflect::reflect::semantics::ShaderSemantics; use librashader_reflect::reflect::ReflectShader; use librashader_runtime::binding::BindingUtil; -use librashader_runtime::framebuffer::FramebufferInit; +use librashader_runtime::framebuffer::{FramebufferInit, FramebufferPool}; use librashader_runtime::image::{ImageError, LoadedTexture, UVDirection, BGRA8}; use librashader_runtime::quad::QuadType; use librashader_runtime::render_target::RenderTarget; @@ -73,8 +73,8 @@ use librashader_runtime::parameters::RuntimeParameters; pub struct FilterChainMetal { pub(crate) common: FilterCommon, passes: Box<[FilterPass]>, - output_framebuffers: Box<[OwnedTexture]>, - feedback_framebuffers: Box<[OwnedTexture]>, + output_framebuffers: FramebufferPool, + feedback_framebuffers: FramebufferPool, history_framebuffers: VecDeque, /// Metal does not allow us to push the input texture to history /// before recording framebuffers, so we double-buffer it. @@ -342,8 +342,7 @@ impl FilterChainMetal { let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?; // // initialize feedback framebuffers - let (feedback_framebuffers, feedback_textures) = - framebuffer_init.init_output_framebuffers()?; + let (feedback_framebuffers, feedback_textures) = framebuffer_init.init_feedback_framebuffers()?; // // initialize history let (history_framebuffers, history_textures) = framebuffer_init.init_history()?; @@ -441,101 +440,120 @@ impl FilterChainMetal { .texture .setLabel(Some(&*NSString::from_str("librashader_sourcetex"))); + let passes_len = passes.len(); + let options = options.unwrap_or(&self.default_options); + // swap output and feedback **before** recording command buffers - std::mem::swap( - &mut self.output_framebuffers, - &mut self.feedback_framebuffers, - ); + for index in 0..passes_len { + if self.feedback_framebuffers.contains(index) { + std::mem::swap(&mut self.output_framebuffers[index], &mut self.feedback_framebuffers[index]); + } + } - // rescale render buffers to ensure all bindings are valid. - OwnedTexture::scale_framebuffers_with_context( - get_texture_size(&source.texture).into(), - get_texture_size(viewport.output), - get_texture_size(&original.texture).into(), - &mut self.output_framebuffers, + let source_size: Size = get_texture_size(&source.texture).into(); + let viewport_size = get_texture_size(viewport.output); + let original_size: Size = get_texture_size(&original.texture).into(); + + // The device acts as the scaling context. Clone it into a local so the + // context borrow stays disjoint from the `&mut self.common` borrows taken + // by the callbacks below. + let scale_context = self.common.device.clone(); + + // rescale feedback buffers and refresh their bound textures. + OwnedTexture::scale_feedback_framebuffers_with_context( + source_size, + viewport_size, + original_size, &mut self.feedback_framebuffers, passes, - &self.common.device, - Some(&mut |index: usize, - pass: &FilterPass, - output: &OwnedTexture, - feedback: &OwnedTexture| { - // refresh inputs + &scale_context, + |index, pass, feedback| { self.common.feedback_textures[index] = Some(feedback.as_input(pass.meta.filter, pass.meta.wrap_mode)?); - self.common.output_textures[index] = - Some(output.as_input(pass.meta.filter, pass.meta.wrap_mode)?); Ok(()) - }), + }, )?; - let passes_len = passes.len(); - let (pass, last) = passes.split_at_mut(passes_len - 1); - let options = options.unwrap_or(&self.default_options); - - for (index, pass) in pass.iter_mut().enumerate() { - let target = &self.output_framebuffers[index]; - source.filter_mode = pass.meta.filter; - source.wrap_mode = pass.meta.wrap_mode; - source.mip_filter = pass.meta.filter; - - let out = RenderTarget::identity(target.texture.as_ref())?; - pass.draw( - &cmd, - index, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - &out, - None, - QuadType::Offscreen, - )?; - - if target.max_miplevels > 1 && !self.disable_mipmaps { - target.generate_mipmaps(&cmd)?; - } + OwnedTexture::scale_output_framebuffers_with_context( + source_size, + viewport_size, + original_size, + &mut self.output_framebuffers, + passes, + &scale_context, + |index, pass, target, size| { + source.filter_mode = pass.meta.filter; + source.wrap_mode = pass.meta.wrap_mode; + source.mip_filter = pass.meta.filter; + + if index != passes_len - 1 { + let out = RenderTarget::identity(target.texture.as_ref())?; + pass.draw( + &cmd, + index, + &self.common, + pass.meta.get_frame_count(frame_count), + options, + viewport, + &original, + &source, + &out, + None, + QuadType::Offscreen, + )?; + + if target.max_miplevels > 1 && !self.disable_mipmaps { + target.generate_mipmaps(&cmd)?; + } - self.common.output_textures[index] = - Some(target.as_input(pass.meta.filter, pass.meta.wrap_mode)?); - source = self.common.output_textures[index] - .as_ref() - .map(InputTexture::try_clone) - .unwrap()?; - } + self.common.output_textures[index] = + Some(target.as_input(pass.meta.filter, pass.meta.wrap_mode)?); + source = self.common.output_textures[index] + .as_ref() + .map(InputTexture::try_clone) + .unwrap()?; + return Ok(()); + } - // try to hint the optimizer - assert_eq!(last.len(), 1); - - if let Some(pass) = last.iter_mut().next() { - if !pass - .graphics_pipeline - .has_format(viewport.output.pixelFormat()) - { - // need to recompile - pass.graphics_pipeline - .recompile(&self.common.device, viewport.output.pixelFormat())?; - } + if !pass + .graphics_pipeline + .has_format(viewport.output.pixelFormat()) + { + // need to recompile + pass.graphics_pipeline + .recompile(&self.common.device, viewport.output.pixelFormat())?; + } - source.filter_mode = pass.meta.filter; - source.wrap_mode = pass.meta.wrap_mode; - source.mip_filter = pass.meta.filter; - let index = passes_len - 1; - - // When feedback is enabled, render the last pass to the intermediate - // framebuffer first then render to the viewport with the OutputSize semantic - // overridden to the FB scale. - // - // Shaders need to see the pass's declared scale rather than the viewport size, - // or they won't render correctly for feedback. - let output_size_override = if self.draw_last_pass_feedback { - let target = &self.output_framebuffers[index]; - let out = RenderTarget::viewport_with_output(target.texture.as_ref(), viewport); + // When feedback is enabled, render the last pass to the intermediate + // framebuffer first then render to the viewport with the OutputSize semantic + // overridden to the FB scale. + // + // Shaders need to see the pass's declared scale rather than the viewport size, + // or they won't render correctly for feedback. + let output_size_override = if self.draw_last_pass_feedback { + let out = RenderTarget::viewport_with_output(target.texture.as_ref(), viewport); + pass.draw( + &cmd, + index, + &self.common, + pass.meta.get_frame_count(frame_count), + options, + viewport, + &original, + &source, + &out, + None, + QuadType::Final, + )?; + Some(size) + } else { + None + }; + + let out = RenderTarget::viewport(viewport); pass.draw( &cmd, - passes_len - 1, + index, &self.common, pass.meta.get_frame_count(frame_count), options, @@ -543,29 +561,12 @@ impl FilterChainMetal { &original, &source, &out, - None, + output_size_override, QuadType::Final, )?; - Some(get_texture_size(target.texture.as_ref())) - } else { - None - }; - - let out = RenderTarget::viewport(viewport); - pass.draw( - &cmd, - index, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - &out, - output_size_override, - QuadType::Final, - )?; - } + Ok(()) + }, + )?; Ok(()) } diff --git a/librashader-runtime-vk/src/filter_chain.rs b/librashader-runtime-vk/src/filter_chain.rs index c7d6271f..2b1ae263 100644 --- a/librashader-runtime-vk/src/filter_chain.rs +++ b/librashader-runtime-vk/src/filter_chain.rs @@ -27,7 +27,7 @@ use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtif use librashader_reflect::reflect::semantics::ShaderSemantics; use librashader_reflect::reflect::ReflectShader; use librashader_runtime::binding::BindingUtil; -use librashader_runtime::framebuffer::FramebufferInit; +use librashader_runtime::framebuffer::{FramebufferInit, FramebufferPool}; use librashader_runtime::image::{ImageError, LoadedTexture, UVDirection, BGRA8}; use librashader_runtime::quad::QuadType; use librashader_runtime::render_target::RenderTarget; @@ -172,8 +172,8 @@ pub struct FilterChainVulkan { pub(crate) common: FilterCommon, passes: Box<[FilterPass]>, vulkan: VulkanObjects, - output_framebuffers: Box<[OwnedImage]>, - feedback_framebuffers: Box<[OwnedImage]>, + output_framebuffers: FramebufferPool, + feedback_framebuffers: FramebufferPool, history_framebuffers: VecDeque, disable_mipmaps: bool, residuals: Box<[FrameResiduals]>, @@ -471,8 +471,7 @@ impl FilterChainVulkan { let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?; // initialize feedback framebuffers - let (feedback_framebuffers, feedback_textures) = - framebuffer_init.init_output_framebuffers()?; + let (feedback_framebuffers, feedback_textures) = framebuffer_init.init_feedback_framebuffers()?; // initialize history let (history_framebuffers, history_textures) = framebuffer_init.init_history()?; @@ -733,159 +732,158 @@ impl FilterChainVulkan { let mut source = original.clone(); + let passes_len = passes.len(); + let options = options.unwrap_or(&self.default_options); + // swap output and feedback **before** recording command buffers - std::mem::swap( - &mut self.output_framebuffers, - &mut self.feedback_framebuffers, - ); + for index in 0..passes_len { + if self.feedback_framebuffers.contains(index) { + std::mem::swap(&mut self.output_framebuffers[index], &mut self.feedback_framebuffers[index]); + } + } + + let scale_context = Some(OwnedImageLayout { + dst_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, + dst_access: vk::AccessFlags::SHADER_READ, + src_stage: vk::PipelineStageFlags::TOP_OF_PIPE, + dst_stage: vk::PipelineStageFlags::FRAGMENT_SHADER, + cmd, + }); - // rescale render buffers to ensure all bindings are valid. - OwnedImage::scale_framebuffers_with_context( + // rescale feedback buffers and refresh their bound textures. + OwnedImage::scale_feedback_framebuffers_with_context( source.image.size, viewport.output.size, original.image.size, - &mut self.output_framebuffers, &mut self.feedback_framebuffers, passes, - &Some(OwnedImageLayout { - dst_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, - dst_access: vk::AccessFlags::SHADER_READ, - src_stage: vk::PipelineStageFlags::TOP_OF_PIPE, - dst_stage: vk::PipelineStageFlags::FRAGMENT_SHADER, - cmd, - }), - Some(&mut |index: usize, - pass: &FilterPass, - output: &OwnedImage, - feedback: &OwnedImage| { - // refresh inputs + &scale_context, + |index, pass, feedback| { self.common.feedback_textures[index] = Some(feedback.as_input(pass.meta.filter, pass.meta.wrap_mode)); - self.common.output_textures[index] = - Some(output.as_input(pass.meta.filter, pass.meta.wrap_mode)); Ok(()) - }), + }, )?; - let passes_len = passes.len(); - let (pass, last) = passes.split_at_mut(passes_len - 1); - - let options = options.unwrap_or(&self.default_options); - self.common .draw_quad .bind_vbo_for_frame(&self.vulkan.device, cmd); - for (index, pass) in pass.iter_mut().enumerate() { - let target = &self.output_framebuffers[index]; - source.filter_mode = pass.meta.filter; - source.wrap_mode = pass.meta.wrap_mode; - source.mip_filter = pass.meta.filter; - - let output_image = OutputImage::new(&self.vulkan.device, target.image.clone())?; - let out = RenderTarget::identity(&output_image)?; - - let residual_fb = pass.draw( - cmd, - target.image.format, - index, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - &out, - None, - QuadType::Offscreen, - false, - )?; - - if target.max_miplevels > 1 && !self.disable_mipmaps { - target.generate_mipmaps_and_end_pass(cmd); - } else { - out.output.end_pass(&self.vulkan.device, cmd); - } - source = self.common.output_textures[index].clone().unwrap(); - intermediates.dispose_outputs(output_image); - intermediates.dispose_framebuffers(residual_fb); - } - - // try to hint the optimizer - assert_eq!(last.len(), 1); - if let Some(pass) = last.iter_mut().next() { - let index = passes_len - 1; - if pass - .graphics_pipeline - .render_passes - .get(&viewport.output.format) - .is_none() - { - // need to recompile - pass.graphics_pipeline.recompile(viewport.output.format)?; - } + OwnedImage::scale_output_framebuffers_with_context( + source.image.size, + viewport.output.size, + original.image.size, + &mut self.output_framebuffers, + passes, + &scale_context, + |index, pass, target, _size| { + source.filter_mode = pass.meta.filter; + source.wrap_mode = pass.meta.wrap_mode; + source.mip_filter = pass.meta.filter; + let frame_count_pass = pass.meta.get_frame_count(frame_count); + + if index != passes_len - 1 { + let output_image = OutputImage::new(&self.vulkan.device, target.image.clone())?; + let out = RenderTarget::identity(&output_image)?; + + let residual_fb = pass.draw( + cmd, + target.image.format, + index, + &self.common, + frame_count_pass, + options, + viewport, + &original, + &source, + &out, + None, + QuadType::Offscreen, + false, + )?; + + if target.max_miplevels > 1 && !self.disable_mipmaps { + target.generate_mipmaps_and_end_pass(cmd); + } else { + out.output.end_pass(&self.vulkan.device, cmd); + } + + self.common.output_textures[index] = + Some(target.as_input(pass.meta.filter, pass.meta.wrap_mode)); + source = self.common.output_textures[index].clone().unwrap(); + intermediates.dispose_outputs(output_image); + intermediates.dispose_framebuffers(residual_fb); + return Ok(()); + } - source.filter_mode = pass.meta.filter; - source.wrap_mode = pass.meta.wrap_mode; - source.mip_filter = pass.meta.filter; + if pass + .graphics_pipeline + .render_passes + .get(&viewport.output.format) + .is_none() + { + // need to recompile + pass.graphics_pipeline.recompile(viewport.output.format)?; + } - // When feedback is enabled, render the last pass to the intermediate - // framebuffer first then render to the viewport with the OutputSize semantic - // overridden to the FB scale. - // - // Shaders need to see the pass's declared scale rather than the viewport size, - // or they won't render correctly for feedback. - let output_size_override = if self.draw_last_pass_feedback { - let target = &self.output_framebuffers[index]; + // When feedback is enabled, render the last pass to the intermediate + // framebuffer first then render to the viewport with the OutputSize semantic + // overridden to the FB scale. + // + // Shaders need to see the pass's declared scale rather than the viewport size, + // or they won't render correctly for feedback. + let output_size_override = if self.draw_last_pass_feedback { + let output_image = OutputImage::new(&self.vulkan.device, target.image.clone())?; + let out = RenderTarget::viewport_with_output(&output_image, viewport); + + let residual_fb = pass.draw( + cmd, + target.image.format, + index, + &self.common, + frame_count_pass, + options, + viewport, + &original, + &source, + &out, + None, + QuadType::Final, + true, + )?; + out.output.end_pass(&self.vulkan.device, cmd); + intermediates.dispose_outputs(output_image); + intermediates.dispose_framebuffers(residual_fb); + Some(target.image.size) + } else { + None + }; - let output_image = OutputImage::new(&self.vulkan.device, target.image.clone())?; + let output_image = + OutputImage::new(&self.vulkan.device, viewport.output.clone())?; let out = RenderTarget::viewport_with_output(&output_image, viewport); let residual_fb = pass.draw( cmd, - target.image.format, + viewport.output.format, index, &self.common, - pass.meta.get_frame_count(frame_count), + frame_count_pass, options, viewport, &original, &source, &out, - None, + output_size_override, QuadType::Final, - true, + false, )?; - out.output.end_pass(&self.vulkan.device, cmd); + intermediates.dispose_outputs(output_image); intermediates.dispose_framebuffers(residual_fb); - Some(target.image.size) - } else { - None - }; - - let output_image = OutputImage::new(&self.vulkan.device, viewport.output.clone())?; - let out = RenderTarget::viewport_with_output(&output_image, viewport); - - let residual_fb = pass.draw( - cmd, - viewport.output.format, - index, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - &out, - output_size_override, - QuadType::Final, - false, - )?; - - intermediates.dispose_outputs(output_image); - intermediates.dispose_framebuffers(residual_fb); - } + Ok(()) + }, + )?; intermediates.dispose_image_view(original_image_view); diff --git a/librashader-runtime-wgpu/src/filter_chain.rs b/librashader-runtime-wgpu/src/filter_chain.rs index 735959b8..466a9bb0 100644 --- a/librashader-runtime-wgpu/src/filter_chain.rs +++ b/librashader-runtime-wgpu/src/filter_chain.rs @@ -22,7 +22,7 @@ use crate::buffer::WgpuStagedBuffer; use crate::draw_quad::DrawQuad; use librashader_common::{FilterMode, Size, Viewport, WrapMode}; use librashader_reflect::reflect::naga::{Naga, NagaLoweringOptions}; -use librashader_runtime::framebuffer::FramebufferInit; +use librashader_runtime::framebuffer::{FramebufferInit, FramebufferPool}; use librashader_runtime::render_target::RenderTarget; use librashader_runtime::scaling::ScaleFramebuffer; @@ -105,8 +105,8 @@ use librashader_runtime::parameters::RuntimeParameters; pub struct FilterChainWgpu { pub(crate) common: FilterCommon, passes: Box<[FilterPass]>, - output_framebuffers: Box<[OwnedImage]>, - feedback_framebuffers: Box<[OwnedImage]>, + output_framebuffers: FramebufferPool, + feedback_framebuffers: FramebufferPool, history_framebuffers: VecDeque, disable_mipmaps: bool, mipmapper: MipmapGen, @@ -259,8 +259,7 @@ impl FilterChainWgpu { let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?; // // initialize feedback framebuffers - let (feedback_framebuffers, feedback_textures) = - framebuffer_init.init_output_framebuffers()?; + let (feedback_framebuffers, feedback_textures) = framebuffer_init.init_feedback_framebuffers()?; // // initialize history let (history_framebuffers, history_textures) = framebuffer_init.init_history()?; @@ -481,134 +480,137 @@ impl FilterChainWgpu { let mut source = original.clone(); + let passes_len = passes.len(); + let options = options.unwrap_or(&self.default_frame_options); + // swap output and feedback **before** recording command buffers - std::mem::swap( - &mut self.output_framebuffers, - &mut self.feedback_framebuffers, - ); + for index in 0..passes_len { + if self.feedback_framebuffers.contains(index) { + std::mem::swap(&mut self.output_framebuffers[index], &mut self.feedback_framebuffers[index]); + } + } + + let scale_context = self.common.device.clone(); - // rescale render buffers to ensure all bindings are valid. - OwnedImage::scale_framebuffers_with_context( + // rescale feedback buffers and refresh their bound textures. + OwnedImage::scale_feedback_framebuffers_with_context( source.image.size().into(), viewport.output.size, original.image.size().into(), - &mut self.output_framebuffers, &mut self.feedback_framebuffers, passes, - &self.common.device, - Some(&mut |index: usize, - pass: &FilterPass, - output: &OwnedImage, - feedback: &OwnedImage| { - // refresh inputs + &scale_context, + |index, pass, feedback| { self.common.feedback_textures[index] = Some(feedback.as_input(pass.meta.filter, pass.meta.wrap_mode)); - self.common.output_textures[index] = - Some(output.as_input(pass.meta.filter, pass.meta.wrap_mode)); Ok(()) - }), + }, )?; - let passes_len = passes.len(); - let (pass, last) = passes.split_at_mut(passes_len - 1); - - let options = options.unwrap_or(&self.default_frame_options); - - for (index, pass) in pass.iter_mut().enumerate() { - source.filter_mode = pass.meta.filter; - source.wrap_mode = pass.meta.wrap_mode; - source.mip_filter = pass.meta.filter; - - let target = &self.output_framebuffers[index]; - let output_image = WgpuOutputView::from(target); - let out = RenderTarget::identity(&output_image)?; - - pass.draw( - cmd, - index, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - &out, - None, - QuadType::Offscreen, - )?; - - if target.max_miplevels > 1 && !self.disable_mipmaps { - let sampler = self.common.samplers.get( - WrapMode::ClampToEdge, - FilterMode::Linear, - FilterMode::Nearest, - ); - - target.generate_mipmaps(&self.common.device, cmd, &mut self.mipmapper, &sampler); - } - - source = self.common.output_textures[index].clone().unwrap(); - } - - // try to hint the optimizer - assert_eq!(last.len(), 1); - - if let Some(pass) = last.iter_mut().next() { - let index = passes_len - 1; - if !pass.graphics_pipeline.has_format(viewport.output.format) { - // need to recompile - pass.graphics_pipeline - .recompile(&self.common.device, viewport.output.format); - } + OwnedImage::scale_output_framebuffers_with_context( + source.image.size().into(), + viewport.output.size, + original.image.size().into(), + &mut self.output_framebuffers, + passes, + &scale_context, + |index, pass, target, size| { + source.filter_mode = pass.meta.filter; + source.wrap_mode = pass.meta.wrap_mode; + source.mip_filter = pass.meta.filter; + let frame_count_pass = pass.meta.get_frame_count(frame_count); + + if index != passes_len - 1 { + let output_image = WgpuOutputView::from(&*target); + let out = RenderTarget::identity(&output_image)?; + + pass.draw( + cmd, + index, + &self.common, + frame_count_pass, + options, + viewport, + &original, + &source, + &out, + None, + QuadType::Offscreen, + )?; + + if target.max_miplevels > 1 && !self.disable_mipmaps { + let sampler = self.common.samplers.get( + WrapMode::ClampToEdge, + FilterMode::Linear, + FilterMode::Nearest, + ); + + target.generate_mipmaps( + &self.common.device, + cmd, + &mut self.mipmapper, + &sampler, + ); + } + + self.common.output_textures[index] = + Some(target.as_input(pass.meta.filter, pass.meta.wrap_mode)); + source = self.common.output_textures[index].clone().unwrap(); + return Ok(()); + } - source.filter_mode = pass.meta.filter; - source.wrap_mode = pass.meta.wrap_mode; - source.mip_filter = pass.meta.filter; - - // When feedback is enabled, render the last pass to the intermediate - // framebuffer first then render to the viewport with the OutputSize semantic - // overridden to the FB scale. - // - // Shaders need to see the pass's declared scale rather than the viewport size, - // or they won't render correctly for feedback. - let output_size_override = if self.draw_last_pass_feedback { - let target = &self.output_framebuffers[index]; - let output_image = WgpuOutputView::from(target); - let out = RenderTarget::viewport_with_output(&output_image, viewport); + if !pass.graphics_pipeline.has_format(viewport.output.format) { + // need to recompile + pass.graphics_pipeline + .recompile(&self.common.device, viewport.output.format); + } + // When feedback is enabled, render the last pass to the intermediate + // framebuffer first then render to the viewport with the OutputSize semantic + // overridden to the FB scale. + // + // Shaders need to see the pass's declared scale rather than the viewport size, + // or they won't render correctly for feedback. + let output_size_override = if self.draw_last_pass_feedback { + let output_image = WgpuOutputView::from(&*target); + let out = RenderTarget::viewport_with_output(&output_image, viewport); + + pass.draw( + cmd, + index, + &self.common, + frame_count_pass, + options, + viewport, + &original, + &source, + &out, + None, + QuadType::Final, + )?; + Some(size) + } else { + None + }; + + let out = RenderTarget::viewport(viewport); pass.draw( cmd, index, &self.common, - pass.meta.get_frame_count(frame_count), + frame_count_pass, options, viewport, &original, &source, &out, - None, + output_size_override, QuadType::Final, )?; - Some(target.size) - } else { - None - }; - - let out = RenderTarget::viewport(viewport); - pass.draw( - cmd, - index, - &self.common, - pass.meta.get_frame_count(frame_count), - options, - viewport, - &original, - &source, - &out, - output_size_override, - QuadType::Final, - )?; - } + + Ok(()) + }, + )?; self.push_history(&input, cmd); Ok(()) diff --git a/librashader-runtime/Cargo.toml b/librashader-runtime/Cargo.toml index d7627c6f..8e63b952 100644 --- a/librashader-runtime/Cargo.toml +++ b/librashader-runtime/Cargo.toml @@ -21,6 +21,7 @@ bytemuck = { version = "1.12.3", features = ["derive"] } num-traits = "0.2.15" array-concat = "0.5.2" arc-swap = "1.7.1" +bit-set = "0.8" image = { workspace = true } diff --git a/librashader-runtime/src/binding.rs b/librashader-runtime/src/binding.rs index 7e610204..17c3ca0a 100644 --- a/librashader-runtime/src/binding.rs +++ b/librashader-runtime/src/binding.rs @@ -1,5 +1,6 @@ use crate::parameters::RuntimeParameters; use crate::uniforms::{BindUniform, NoUniformBinder, UniformStorage}; +use bit_set::BitSet; use librashader_common::map::{FastHashMap, ShortString}; use librashader_common::Size; use librashader_preprocess::ShaderParameter; @@ -496,8 +497,14 @@ where #[derive(Debug)] pub struct BindingRequirements { + /// The number of `OriginalHistory` frames that must be kept. pub(crate) required_history: usize, + /// Whether the final pass's output must be retained for feedback. pub(crate) uses_final_pass_as_feedback: bool, + /// Pass indices whose output is referenced as `PassFeedback`. + pub(crate) feedback_mask: BitSet, + /// For each pass, the index of the last pass that reads its output this frame. + pub(crate) last_use: Box<[usize]>, } /// Trait for objects that can be used to create a binding map. @@ -539,60 +546,85 @@ impl BindingUtil for BindingMeta { where Self: 'a, { - let mut required_images = 0; + let passes: Vec<&BindingMeta> = pass_meta.collect(); + let len = passes.len(); - let mut len: i64 = 0; + let mut required_images = 0; let mut latest_feedback_pass: i64 = -1; + let mut feedback_mask = BitSet::new(); - for pass in pass_meta { - len += 1; + // Each pass output is read at least by its immediate successor (as `Source`), so its + // liveness extends to `index + 1`. The final pass has no successor. + let mut last_use = vec![0usize; len]; + for index in 0..len { + last_use[index] = if index + 1 < len { index + 1 } else { index }; + } + for (pass_index, pass) in passes.iter().enumerate() { // If a shader uses history size, but not history, we still need to keep the texture. let history_texture_max_index = pass .texture_meta - .iter() - .filter(|(semantics, _)| semantics.semantics == TextureSemantics::OriginalHistory) - .map(|(semantic, _)| semantic.index) + .keys() + .filter(|semantics| semantics.semantics == TextureSemantics::OriginalHistory) + .map(|semantic| semantic.index) .fold(0, std::cmp::max); let history_texture_size_max_index = pass .texture_size_meta - .iter() - .filter(|(semantics, _)| semantics.semantics == TextureSemantics::OriginalHistory) - .map(|(semantic, _)| semantic.index) + .keys() + .filter(|semantics| semantics.semantics == TextureSemantics::OriginalHistory) + .map(|semantic| semantic.index) .fold(0, std::cmp::max); - let feedback_max_index = pass + // A pass output referenced by a future pass as PassFeedback needs its + // previous-frame output preserved. + for semantic in pass .texture_meta - .iter() - .filter(|(semantics, _)| semantics.semantics == TextureSemantics::PassFeedback) - .map(|(semantic, _)| semantic.index as i64) - .fold(-1, std::cmp::max); - let feedback_max_size_index = pass - .texture_size_meta - .iter() - .filter(|(semantics, _)| semantics.semantics == TextureSemantics::PassFeedback) - .map(|(semantic, _)| semantic.index as i64) - .fold(-1, std::cmp::max); + .keys() + .chain(pass.texture_size_meta.keys()) + .filter(|semantics| semantics.semantics == TextureSemantics::PassFeedback) + { + feedback_mask.insert(semantic.index); + latest_feedback_pass = std::cmp::max(latest_feedback_pass, semantic.index as i64); + } - latest_feedback_pass = std::cmp::max(latest_feedback_pass, feedback_max_index); - latest_feedback_pass = std::cmp::max(latest_feedback_pass, feedback_max_size_index); + // A pass output sampled or sized as PassOutput is live until this consumer. + for semantic in pass + .texture_meta + .keys() + .chain(pass.texture_size_meta.keys()) + .filter(|semantics| semantics.semantics == TextureSemantics::PassOutput) + { + if semantic.index < len { + last_use[semantic.index] = std::cmp::max(last_use[semantic.index], pass_index); + } + } required_images = std::cmp::max(required_images, history_texture_max_index); required_images = std::cmp::max(required_images, history_texture_size_max_index); } + // Feedback passes must survive the whole frame — their output is swapped into the + // feedback buffer at frame end — so pin their liveness to the final pass. + for index in feedback_mask.iter() { + if index < len { + last_use[index] = len.saturating_sub(1); + } + } + let uses_feedback = if latest_feedback_pass.is_negative() { false } else { // Technically = but we can be permissive here // account for off by 1 - latest_feedback_pass + 1 >= len + latest_feedback_pass + 1 >= len as i64 }; BindingRequirements { required_history: required_images, uses_final_pass_as_feedback: uses_feedback, + feedback_mask, + last_use: last_use.into_boxed_slice(), } } } diff --git a/librashader-runtime/src/framebuffer.rs b/librashader-runtime/src/framebuffer.rs index 611074ae..876eec9a 100644 --- a/librashader-runtime/src/framebuffer.rs +++ b/librashader-runtime/src/framebuffer.rs @@ -1,6 +1,100 @@ use crate::binding::{BindingRequirements, BindingUtil}; +use bit_set::BitSet; +use librashader_common::map::FastHashMap; +use librashader_common::Size; use librashader_reflect::reflect::semantics::BindingMeta; use std::collections::VecDeque; +use std::ops::{Index, IndexMut}; +use num_traits::AsPrimitive; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub(crate) struct Slot(i32); + +impl Slot { + pub const NONE: Slot = Slot(-1); + + const fn new(index: usize) -> Slot { + Slot(index as i32) + } + + /// The pool index, or `None` if this is [`Slot::NONE`]. + const fn get(self) -> Option { + if self.0 < 0 { + None + } else { + Some(self.0 as usize) + } + } +} + +/// A pool of framebuffers with internal liveness analysis, indexed by pass number. +pub struct FramebufferPool { + pool: Box<[F]>, + slots: Box<[Slot]>, + + // At pass i, store the last pass that reads i (the liveness period). Empty when init for feedback. + last_use: Box<[usize]>, +} + +impl FramebufferPool { + /// Calculate liveness assignments based on pass sizes. + pub(crate) fn prepare(&mut self, sizes: &[Size]) { + struct Event { + slot: usize, + size: u64, + next_slot: usize, + } + + let n = sizes.len(); + + // `free[size]` holds slots currently free and of that size. + let mut free: FastHashMap> = FastHashMap::default(); + + let mut freed_at = vec![usize::MAX; n + 1]; + let mut events: Vec = Vec::with_capacity(n); + let mut slot_count = 0; + + for pass in 0..n { + let mut event = freed_at[pass]; + while event != usize::MAX { + let Event { slot, size, next_slot } = events[event]; + free.entry(size).or_default().push(slot); + event = next_slot; + } + + let size = sizes[pass].as_(); + let slot = free.get_mut(&size).and_then(Vec::pop).unwrap_or_else(|| { + let slot = slot_count; + slot_count += 1; + slot + }); + + self.slots[pass] = Slot::new(slot); + let free_at = self.last_use[pass] + 1; + events.push(Event { slot, size, next_slot: freed_at[free_at] }); + freed_at[free_at] = events.len() - 1; + } + } + + /// Returns whether the given pass maps to a buffer. + pub fn contains(&self, pass: usize) -> bool { + self.slots.get(pass).and_then(|s| s.get()).is_some() + } +} + +impl Index for FramebufferPool { + type Output = F; + + fn index(&self, pass: usize) -> &F { + &self.pool[self.slots[pass].get().expect("pass has no framebuffer")] + } +} + +impl IndexMut for FramebufferPool { + fn index_mut(&mut self, pass: usize) -> &mut F { + &mut self.pool[self.slots[pass].get().expect("pass has no framebuffer")] + } +} /// Helper to initialize framebuffers in a graphics API agnostic way. pub struct FramebufferInit<'a, F, I, E> { @@ -38,10 +132,22 @@ impl<'a, F, I, E> FramebufferInit<'a, F, I, E> { ) } - /// Initialize output framebuffers and views. - pub fn init_output_framebuffers(&self) -> Result<(Box<[F]>, Box<[I]>), E> { + /// Initialize output framebuffers pooled by pass-output liveness. + pub fn init_output_framebuffers(&self) -> Result<(FramebufferPool, Box<[I]>), E> { init_output_framebuffers( self.filters_count, + &self.requirements.last_use, + self.owned_generator, + self.input_generator, + ) + } + + /// Initialize sparse feedback framebuffers, allocating a previous-frame copy only for + /// passes referenced as `PassFeedback`. + pub fn init_feedback_framebuffers(&self) -> Result<(FramebufferPool, Box<[I]>), E> { + init_feedback_framebuffers( + self.filters_count, + &self.requirements.feedback_mask, self.owned_generator, self.input_generator, ) @@ -79,23 +185,132 @@ fn init_history<'a, F, I, E>( } fn init_output_framebuffers( - len: usize, + filters_count: usize, + last_use: &[usize], owned_generator: impl Fn() -> Result, input_generator: impl Fn() -> I, -) -> Result<(Box<[F]>, Box<[I]>), E> { - let mut output_framebuffers = Vec::new(); - output_framebuffers.resize_with(len, owned_generator); +) -> Result<(FramebufferPool, Box<[I]>), E> { + let mut pool = Vec::with_capacity(filters_count); + pool.resize_with(filters_count, &owned_generator); + let pool = pool + .into_iter() + .collect::, E>>()? + .into_boxed_slice(); + + let mut textures = Vec::new(); + textures.resize_with(filters_count, input_generator); + + Ok(( + FramebufferPool { + pool, + last_use: last_use.to_vec().into_boxed_slice(), + slots: (0..filters_count).map(Slot::new).collect(), + }, + textures.into_boxed_slice(), + )) +} + +fn init_feedback_framebuffers( + filters_count: usize, + feedback_mask: &BitSet, + owned_generator: impl Fn() -> Result, + input_generator: impl Fn() -> I, +) -> Result<(FramebufferPool, Box<[I]>), E> { + + // assign feedback slots according to the usage mask + fn assign_slots(mask: &BitSet, filters_count: usize) -> (usize, Box<[Slot]>) { + let mut slot_of_pass = vec![Slot::NONE; filters_count]; + let mut count = 0; + for pass in mask.iter() { + if pass < filters_count { + slot_of_pass[pass] = Slot::new(count); + count += 1; + } + } + (count, slot_of_pass.into_boxed_slice()) + } - // resolve all results - let output_framebuffers = output_framebuffers + let (count, slots) = assign_slots(feedback_mask, filters_count); + + let mut pool = Vec::with_capacity(count); + pool.resize_with(count, owned_generator); + let pool = pool .into_iter() - .collect::, E>>()?; + .collect::, E>>()? + .into_boxed_slice(); - let mut output_textures = Vec::new(); - output_textures.resize_with(len, input_generator); + let mut textures = Vec::new(); + textures.resize_with(filters_count, input_generator); Ok(( - output_framebuffers.into_boxed_slice(), - output_textures.into_boxed_slice(), + FramebufferPool { + pool, + last_use: Box::new([]), + slots, + }, + textures.into_boxed_slice(), )) } + +#[cfg(test)] +mod tests { + use super::{FramebufferPool, Slot}; + use librashader_common::Size; + use std::collections::HashSet; + + fn fb(last_use: Vec) -> FramebufferPool<()> { + let n = last_use.len(); + FramebufferPool { + pool: vec![(); n].into_boxed_slice(), + last_use: last_use.into_boxed_slice(), + slots: vec![Slot::new(0); n].into_boxed_slice(), + } + } + + fn distinct(slots: &[Slot]) -> usize { + slots.iter().copied().collect::>().len() + } + + #[test] + fn liveness_pools_chain() { + let size = |w, h| Size::::new(w, h); + let last_use = vec![1, 2, 10, 4, 5, 6, 7, 8, 9, 10, 10]; + let sizes = vec![ + size(1280, 960), // p0 + size(1280, 960), // p1 + size(1280, 3360), // p2 (retained, read by p3..p10) + size(1280, 1680), // p3 + size(1280, 1680), // p4 + size(1280, 1680), // p5 + size(1280, 1680), // p6 + size(1280, 1680), // p7 + size(1280, 1680), // p8 + size(3840, 1680), // p9 + size(1280, 720), // p10 (final / viewport) + ]; + + let mut output = fb(last_use); + output.prepare(&sizes); + + // p3..=p8 (six same-size passes, each live only into the next) collapse onto two + // ping-pong buffers. + assert_eq!(distinct(&output.slots[3..=8]), 2); + + // The whole chain needs 7 physical buffers instead of 11, and no buffer is ever + // assigned two different sizes (the invariant that keeps it deferred-safe). + assert_eq!(distinct(&output.slots), 7); + let mut seen: std::collections::HashMap> = Default::default(); + for (pass, &slot) in output.slots.iter().enumerate() { + assert_eq!(*seen.entry(slot).or_insert(sizes[pass]), sizes[pass]); + } + } + + // A uniform-size linear chain (the common case) collapses to a 2-buffer ping-pong. + #[test] + fn liveness_pools_uniform_chain() { + let sizes = vec![Size::::new(1920, 1080); 8]; + let mut output = fb(vec![1, 2, 3, 4, 5, 6, 7, 7]); + output.prepare(&sizes); + assert_eq!(distinct(&output.slots), 2); + } +} diff --git a/librashader-runtime/src/image.rs b/librashader-runtime/src/image.rs index 4c735b7b..d56147bb 100644 --- a/librashader-runtime/src/image.rs +++ b/librashader-runtime/src/image.rs @@ -187,38 +187,34 @@ mod test { #[test] pub fn generate_normal_swizzle() { let swizzle = generate_swizzle::<32>([0, 1, 2, 3]); - assert_eq!( - swizzle, - #[rustfmt::skip] - [ - 0, 1, 2, 3, - 4, 5, 6, 7, - 8, 9, 10, 11, - 12, 13, 14, 15, - 16, 17, 18, 19, - 20, 21, 22, 23, - 24, 25, 26, 27, - 28, 29, 30, 31 - ] - ) + #[rustfmt::skip] + let expected = [ + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 12, 13, 14, 15, + 16, 17, 18, 19, + 20, 21, 22, 23, + 24, 25, 26, 27, + 28, 29, 30, 31 + ]; + assert_eq!(swizzle, expected) } #[test] pub fn generate_argb_swizzle() { let swizzle = generate_swizzle::<32>([3, 0, 1, 2]); - assert_eq!( - swizzle, - #[rustfmt::skip] - [ - 3, 0, 1, 2, - 7, 4, 5, 6, - 11, 8, 9, 10, - 15, 12, 13, 14, - 19, 16, 17, 18, - 23, 20, 21, 22, - 27, 24, 25, 26, - 31, 28, 29, 30 - ] - ) + #[rustfmt::skip] + let expected = [ + 3, 0, 1, 2, + 7, 4, 5, 6, + 11, 8, 9, 10, + 15, 12, 13, 14, + 19, 16, 17, 18, + 23, 20, 21, 22, + 27, 24, 25, 26, + 31, 28, 29, 30 + ]; + assert_eq!(swizzle, expected) } } diff --git a/librashader-runtime/src/scaling.rs b/librashader-runtime/src/scaling.rs index 51db5343..00ecf612 100644 --- a/librashader-runtime/src/scaling.rs +++ b/librashader-runtime/src/scaling.rs @@ -1,4 +1,5 @@ use crate::filter_pass::FilterPassMeta; +use crate::framebuffer::FramebufferPool; use crate::scaling; use librashader_common::{ImageFormat, Size}; use librashader_presets::{Scale2D, ScaleFactor, ScaleType, Scaling}; @@ -151,27 +152,26 @@ pub trait ScaleFramebuffer { context: &Self::Context, ) -> Result, Self::Error>; - /// Scale framebuffers with default context. + /// Scale the sparse feedback framebuffers, invoking `callback` for each pass that is + /// referenced as feedback so the runtime can refresh its bound feedback texture. #[inline(always)] - fn scale_framebuffers

( + fn scale_feedback_framebuffers

( source_size: Size, viewport_size: Size, original_size: Size, - output: &mut [Self], - feedback: &mut [Self], + feedback: &mut FramebufferPool, passes: &[P], - callback: Option<&mut dyn FnMut(usize, &P, &Self, &Self) -> Result<(), Self::Error>>, + callback: impl FnMut(usize, &P, &Self) -> Result<(), Self::Error>, ) -> Result<(), Self::Error> where Self: Sized, Self::Context: Default, P: FilterPassMeta, { - scale_framebuffers_with_context_callback::( + Self::scale_feedback_framebuffers_with_context( source_size, viewport_size, original_size, - output, feedback, passes, &Self::Context::default(), @@ -179,53 +179,101 @@ pub trait ScaleFramebuffer { ) } - /// Scale framebuffers with user provided context. + /// Scale the sparse feedback framebuffers with a user provided context. #[inline(always)] - fn scale_framebuffers_with_context

( + fn scale_feedback_framebuffers_with_context

( source_size: Size, viewport_size: Size, original_size: Size, - output: &mut [Self], - feedback: &mut [Self], + feedback: &mut FramebufferPool, passes: &[P], context: &Self::Context, - callback: Option<&mut dyn FnMut(usize, &P, &Self, &Self) -> Result<(), Self::Error>>, + callback: impl FnMut(usize, &P, &Self) -> Result<(), Self::Error>, ) -> Result<(), Self::Error> where Self: Sized, P: FilterPassMeta, { - scale_framebuffers_with_context_callback::( + scale_feedback_framebuffers_callback::( source_size, viewport_size, original_size, - output, feedback, passes, context, callback, ) } + + /// Scale the pooled output framebuffers, invoking `callback` for each pass with its + /// routed render target and scaled output size so the runtime can draw it. + #[inline(always)] + fn scale_output_framebuffers

( + source_size: Size, + viewport_size: Size, + original_size: Size, + output: &mut FramebufferPool, + passes: &mut [P], + callback: impl FnMut(usize, &mut P, &Self, Size) -> Result<(), Self::Error>, + ) -> Result<(), Self::Error> + where + Self: Sized, + Self::Context: Default, + P: FilterPassMeta, + { + Self::scale_output_framebuffers_with_context( + source_size, + viewport_size, + original_size, + output, + passes, + &Self::Context::default(), + callback, + ) + } + + /// Scale the pooled output framebuffers with a user provided context. + #[inline(always)] + fn scale_output_framebuffers_with_context

( + source_size: Size, + viewport_size: Size, + original_size: Size, + output: &mut FramebufferPool, + passes: &mut [P], + context: &Self::Context, + callback: impl FnMut(usize, &mut P, &Self, Size) -> Result<(), Self::Error>, + ) -> Result<(), Self::Error> + where + Self: Sized, + P: FilterPassMeta, + { + scale_output_framebuffers_callback::( + source_size, + viewport_size, + original_size, + output, + passes, + context, + callback, + ) + } } -/// Scale framebuffers according to the pass configs, source and viewport size -/// passing a context into the scale function and a callback for each framebuffer rescale. #[inline(always)] -fn scale_framebuffers_with_context_callback( +fn scale_feedback_framebuffers_callback( source_size: Size, viewport_size: Size, original_size: Size, - output: &mut [F], - feedback: &mut [F], + feedback: &mut FramebufferPool, passes: &[P], context: &C, - mut callback: Option<&mut dyn FnMut(usize, &P, &F, &F) -> Result<(), E>>, + mut callback: CB, ) -> Result<(), E> where F: ScaleFramebuffer, P: FilterPassMeta, + CB: FnMut(usize, &P, &F) -> Result<(), E>, { - assert_eq!(output.len(), feedback.len()); let mut iterator = passes.iter().enumerate().peekable(); let mut target_size = source_size; while let Some((index, pass)) = iterator.next() { @@ -233,31 +281,88 @@ where .peek() .map_or(false, |(_, p)| p.meta().mipmap_input); - let next_size = output[index].scale( + let next_size = target_size.scale_viewport( pass.meta().scaling.clone(), - pass.get_format(), - &viewport_size, - &target_size, - &original_size, - should_mipmap, - context, - )?; + viewport_size, + original_size, + None, + ); - feedback[index].scale( + if feedback.contains(index) { + feedback[index].scale( + pass.meta().scaling.clone(), + pass.get_format(), + &viewport_size, + &target_size, + &original_size, + should_mipmap, + context, + )?; + callback(index, pass, &feedback[index])?; + } + + target_size = next_size; + } + + Ok(()) +} + +#[inline(always)] +fn scale_output_framebuffers_callback( + source_size: Size, + viewport_size: Size, + original_size: Size, + output: &mut FramebufferPool, + passes: &mut [P], + context: &C, + mut callback: CB, +) -> Result<(), E> +where + F: ScaleFramebuffer, + P: FilterPassMeta, + CB: FnMut(usize, &mut P, &F, Size) -> Result<(), E>, +{ + let len = passes.len(); + + // Compute every pass's output size up front so the pool can be colored by liveness + // before any buffer is touched. + let mut sizes = Vec::with_capacity(len); + let mut target_size = source_size; + for pass in passes.iter() { + target_size = target_size.scale_viewport( pass.meta().scaling.clone(), - pass.get_format(), + viewport_size, + original_size, + None, + ); + sizes.push(target_size); + } + + output.prepare(&sizes); + + for index in 0..len { + let scaling = passes[index].meta().scaling.clone(); + let format = passes[index].get_format(); + let should_mipmap = passes + .get(index + 1) + .map_or(false, |p| p.meta().mipmap_input); + let prev = if index == 0 { + source_size + } else { + sizes[index - 1] + }; + + let size = output[index].scale( + scaling, + format, &viewport_size, - &target_size, + &prev, &original_size, should_mipmap, context, )?; - target_size = next_size; - - if let Some(callback) = callback.as_mut() { - callback(index, pass, &output[index], &feedback[index])?; - } + callback(index, &mut passes[index], &output[index], size)?; } Ok(())