rt: fix history framebuffer requirements and unify framebuffer initialization logic
This commit is contained in:
parent
115382d8f0
commit
3c15a3a523
|
@ -5,7 +5,7 @@ use librashader_presets::{ShaderPreset, TextureConfig};
|
|||
use librashader_reflect::back::targets::HLSL;
|
||||
use librashader_reflect::back::{CompileReflectShader, CompileShader};
|
||||
use librashader_reflect::front::GlslangCompilation;
|
||||
use librashader_reflect::reflect::semantics::{BindingMeta, ShaderSemantics};
|
||||
use librashader_reflect::reflect::semantics::ShaderSemantics;
|
||||
use librashader_reflect::reflect::ReflectShader;
|
||||
use librashader_runtime::image::{Image, UVDirection};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
@ -16,7 +16,7 @@ use std::path::Path;
|
|||
use crate::draw_quad::DrawQuad;
|
||||
use crate::error::{assume_d3d11_init, FilterChainError};
|
||||
use crate::filter_pass::{ConstantBufferBinding, FilterPass};
|
||||
use crate::framebuffer::OwnedFramebuffer;
|
||||
use crate::framebuffer::OwnedImage;
|
||||
use crate::graphics_pipeline::D3D11State;
|
||||
use crate::options::{FilterChainOptionsD3D11, FrameOptionsD3D11};
|
||||
use crate::samplers::SamplerSet;
|
||||
|
@ -24,6 +24,7 @@ use crate::util::d3d11_compile_bound_shader;
|
|||
use crate::{error, util, D3D11OutputView};
|
||||
use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact};
|
||||
use librashader_runtime::binding::{BindingUtil, TextureInput};
|
||||
use librashader_runtime::framebuffer::FramebufferInit;
|
||||
use librashader_runtime::quad::QuadType;
|
||||
use librashader_runtime::render_target::RenderTarget;
|
||||
use librashader_runtime::scaling::ScaleFramebuffer;
|
||||
|
@ -49,9 +50,9 @@ type ShaderPassMeta =
|
|||
pub struct FilterChainD3D11 {
|
||||
pub(crate) common: FilterCommon,
|
||||
passes: Vec<FilterPass>,
|
||||
output_framebuffers: Box<[OwnedFramebuffer]>,
|
||||
feedback_framebuffers: Box<[OwnedFramebuffer]>,
|
||||
history_framebuffers: VecDeque<OwnedFramebuffer>,
|
||||
output_framebuffers: Box<[OwnedImage]>,
|
||||
feedback_framebuffers: Box<[OwnedImage]>,
|
||||
history_framebuffers: VecDeque<OwnedImage>,
|
||||
state: D3D11State,
|
||||
}
|
||||
|
||||
|
@ -102,45 +103,34 @@ impl FilterChainD3D11 {
|
|||
|
||||
let immediate_context = unsafe { device.GetImmediateContext()? };
|
||||
|
||||
// initialize output framebuffers
|
||||
let mut output_framebuffers = Vec::new();
|
||||
output_framebuffers.resize_with(filters.len(), || {
|
||||
OwnedFramebuffer::new(device, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, false)
|
||||
});
|
||||
|
||||
// resolve all results
|
||||
let output_framebuffers = output_framebuffers
|
||||
.into_iter()
|
||||
.collect::<error::Result<Vec<OwnedFramebuffer>>>()?;
|
||||
|
||||
let mut output_textures = Vec::new();
|
||||
output_textures.resize_with(filters.len(), || None);
|
||||
//
|
||||
// // initialize feedback framebuffers
|
||||
let mut feedback_framebuffers = Vec::new();
|
||||
feedback_framebuffers.resize_with(filters.len(), || {
|
||||
OwnedFramebuffer::new(device, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, false)
|
||||
});
|
||||
// resolve all results
|
||||
let feedback_framebuffers = feedback_framebuffers
|
||||
.into_iter()
|
||||
.collect::<error::Result<Vec<OwnedFramebuffer>>>()?;
|
||||
|
||||
let mut feedback_textures = Vec::new();
|
||||
feedback_textures.resize_with(filters.len(), || None);
|
||||
|
||||
// load luts
|
||||
let luts = FilterChainD3D11::load_luts(device, &immediate_context, &preset.textures)?;
|
||||
|
||||
let (history_framebuffers, history_textures) =
|
||||
FilterChainD3D11::init_history(device, &filters)?;
|
||||
let framebuffer_gen =
|
||||
|| OwnedImage::new(device, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, false);
|
||||
let input_gen = || None;
|
||||
let framebuffer_init = FramebufferInit::new(
|
||||
filters.iter().map(|f| &f.reflection.meta),
|
||||
&framebuffer_gen,
|
||||
&input_gen,
|
||||
);
|
||||
|
||||
// initialize output framebuffers
|
||||
let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?;
|
||||
|
||||
// initialize feedback framebuffers
|
||||
let (feedback_framebuffers, feedback_textures) =
|
||||
framebuffer_init.init_output_framebuffers()?;
|
||||
|
||||
// initialize history
|
||||
let (history_framebuffers, history_textures) = framebuffer_init.init_history()?;
|
||||
|
||||
let draw_quad = DrawQuad::new(device)?;
|
||||
let state = D3D11State::new(device)?;
|
||||
Ok(FilterChainD3D11 {
|
||||
passes: filters,
|
||||
output_framebuffers: output_framebuffers.into_boxed_slice(),
|
||||
feedback_framebuffers: feedback_framebuffers.into_boxed_slice(),
|
||||
output_framebuffers,
|
||||
feedback_framebuffers,
|
||||
history_framebuffers,
|
||||
common: FilterCommon {
|
||||
d3d11: Direct3D11 {
|
||||
|
@ -158,8 +148,8 @@ impl FilterChainD3D11 {
|
|||
disable_mipmaps: options.map_or(false, |o| o.force_no_mipmaps),
|
||||
luts,
|
||||
samplers,
|
||||
output_textures: output_textures.into_boxed_slice(),
|
||||
feedback_textures: feedback_textures.into_boxed_slice(),
|
||||
output_textures,
|
||||
feedback_textures,
|
||||
history_textures,
|
||||
draw_quad,
|
||||
},
|
||||
|
@ -283,37 +273,6 @@ impl FilterChainD3D11 {
|
|||
Ok(filters)
|
||||
}
|
||||
|
||||
fn init_history(
|
||||
device: &ID3D11Device,
|
||||
filters: &Vec<FilterPass>,
|
||||
) -> error::Result<(VecDeque<OwnedFramebuffer>, Box<[Option<InputTexture>]>)> {
|
||||
let required_images =
|
||||
BindingMeta::calculate_required_history(filters.iter().map(|f| &f.reflection.meta));
|
||||
|
||||
// not using frame history;
|
||||
if required_images <= 1 {
|
||||
// println!("[history] not using frame history");
|
||||
return Ok((VecDeque::new(), Box::new([])));
|
||||
}
|
||||
|
||||
// history0 is aliased with the original
|
||||
|
||||
// eprintln!("[history] using frame history with {required_images} images");
|
||||
let mut framebuffers = VecDeque::with_capacity(required_images);
|
||||
framebuffers.resize_with(required_images, || {
|
||||
OwnedFramebuffer::new(device, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, false)
|
||||
});
|
||||
|
||||
let framebuffers = framebuffers
|
||||
.into_iter()
|
||||
.collect::<error::Result<VecDeque<OwnedFramebuffer>>>()?;
|
||||
|
||||
let mut history_textures = Vec::new();
|
||||
history_textures.resize_with(required_images, || None);
|
||||
|
||||
Ok((framebuffers, history_textures.into_boxed_slice()))
|
||||
}
|
||||
|
||||
fn push_history(
|
||||
&mut self,
|
||||
ctx: &ID3D11DeviceContext,
|
||||
|
@ -394,20 +353,6 @@ impl FilterChainD3D11 {
|
|||
let filter = passes[0].config.filter;
|
||||
let wrap_mode = passes[0].config.wrap_mode;
|
||||
|
||||
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.config.wrap_mode,
|
||||
pass.config.filter,
|
||||
)?);
|
||||
}
|
||||
|
||||
for (texture, fbo) in self
|
||||
.common
|
||||
.history_textures
|
||||
|
@ -426,7 +371,7 @@ impl FilterChainD3D11 {
|
|||
let mut source = original.clone();
|
||||
|
||||
// rescale render buffers to ensure all bindings are valid.
|
||||
OwnedFramebuffer::scale_framebuffers(
|
||||
OwnedImage::scale_framebuffers(
|
||||
source.size(),
|
||||
viewport.output.size,
|
||||
&mut self.output_framebuffers,
|
||||
|
@ -435,6 +380,22 @@ impl FilterChainD3D11 {
|
|||
None,
|
||||
)?;
|
||||
|
||||
// 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.config.wrap_mode,
|
||||
pass.config.filter,
|
||||
)?);
|
||||
}
|
||||
|
||||
let passes_len = passes.len();
|
||||
let (pass, last) = passes.split_at_mut(passes_len - 1);
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT, DXGI_SAMPLE_DESC};
|
|||
static CLEAR: &[f32; 4] = &[0.0f32, 0.0, 0.0, 0.0];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct OwnedFramebuffer {
|
||||
pub(crate) struct OwnedImage {
|
||||
render: ID3D11Texture2D,
|
||||
pub(crate) size: Size<u32>,
|
||||
format: DXGI_FORMAT,
|
||||
|
@ -29,13 +29,13 @@ pub(crate) struct OwnedFramebuffer {
|
|||
max_mipmap: u32,
|
||||
}
|
||||
|
||||
impl OwnedFramebuffer {
|
||||
impl OwnedImage {
|
||||
pub fn new(
|
||||
device: &ID3D11Device,
|
||||
size: Size<u32>,
|
||||
format: ImageFormat,
|
||||
mipmap: bool,
|
||||
) -> error::Result<OwnedFramebuffer> {
|
||||
) -> error::Result<OwnedImage> {
|
||||
unsafe {
|
||||
let format = d3d11_get_closest_format(
|
||||
device,
|
||||
|
@ -49,7 +49,7 @@ impl OwnedFramebuffer {
|
|||
device.CreateTexture2D(&desc, None, Some(&mut render))?;
|
||||
assume_d3d11_init!(render, "CreateTexture2D");
|
||||
|
||||
Ok(OwnedFramebuffer {
|
||||
Ok(OwnedImage {
|
||||
render,
|
||||
size,
|
||||
format,
|
||||
|
@ -245,7 +245,7 @@ fn default_desc(size: Size<u32>, format: DXGI_FORMAT, mip_levels: u32) -> D3D11_
|
|||
}
|
||||
}
|
||||
|
||||
impl ScaleFramebuffer for OwnedFramebuffer {
|
||||
impl ScaleFramebuffer for OwnedImage {
|
||||
type Error = FilterChainError;
|
||||
type Context = ();
|
||||
|
||||
|
|
|
@ -568,7 +568,7 @@ pub mod d3d11_hello_triangle {
|
|||
// eprintln!("w: {} h: {}", backbuffer_desc.Width, backbuffer_desc.Height);
|
||||
self.filter
|
||||
.frame(
|
||||
Some(&resources.deferred_context),
|
||||
None,
|
||||
D3D11InputView {
|
||||
handle: srv,
|
||||
size: Size {
|
||||
|
@ -593,12 +593,12 @@ pub mod d3d11_hello_triangle {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let mut command_list = None;
|
||||
resources
|
||||
.deferred_context
|
||||
.FinishCommandList(false, Some(&mut command_list))?;
|
||||
let command_list = command_list.unwrap();
|
||||
self.context.ExecuteCommandList(&command_list, false);
|
||||
// let mut command_list = None;
|
||||
// resources
|
||||
// .deferred_context
|
||||
// .FinishCommandList(false, Some(&mut command_list))?;
|
||||
// let command_list = command_list.unwrap();
|
||||
// self.context.ExecuteCommandList(&command_list, false);
|
||||
// self.context.CopyResource(&resources.backbuffer, &backup);
|
||||
}
|
||||
|
||||
|
|
|
@ -38,8 +38,10 @@ mod tests {
|
|||
// const FILTER_PATH: &str =
|
||||
// "../test/slang-shaders/handheld/console-border/gbc-lcd-grid-v2.slangp";
|
||||
// "../test/null.slangp",
|
||||
const FILTER_PATH: &str =
|
||||
"../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV-GLASS.slangp";
|
||||
// const FILTER_PATH: &str = "../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV-GLASS.slangp";
|
||||
|
||||
// const FILTER_PATH: &str = "../test/slang-shaders/test/history.slangp";
|
||||
const FILTER_PATH: &str = "../test/slang-shaders/test/feedback.slangp";
|
||||
|
||||
// const FILTER_PATH: &str = "../test/slang-shaders/crt/crt-royale.slangp";
|
||||
const IMAGE_PATH: &str = "../triangle.png";
|
||||
|
|
|
@ -13,7 +13,7 @@ use windows::Win32::Graphics::Direct3D11::{
|
|||
use windows::Win32::Graphics::Dxgi::Common::DXGI_SAMPLE_DESC;
|
||||
|
||||
use crate::error::{assume_d3d11_init, Result};
|
||||
use crate::framebuffer::OwnedFramebuffer;
|
||||
use crate::framebuffer::OwnedImage;
|
||||
|
||||
/// An image view for use as a shader resource.
|
||||
///
|
||||
|
@ -46,7 +46,7 @@ pub struct InputTexture {
|
|||
|
||||
impl InputTexture {
|
||||
pub(crate) fn from_framebuffer(
|
||||
fbo: &OwnedFramebuffer,
|
||||
fbo: &OwnedImage,
|
||||
wrap_mode: WrapMode,
|
||||
filter: FilterMode,
|
||||
) -> Result<Self> {
|
||||
|
|
|
@ -20,7 +20,7 @@ use librashader_reflect::back::targets::{DXIL, HLSL};
|
|||
use librashader_reflect::back::{CompileReflectShader, CompileShader};
|
||||
use librashader_reflect::front::GlslangCompilation;
|
||||
use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact};
|
||||
use librashader_reflect::reflect::semantics::{BindingMeta, ShaderSemantics, MAX_BINDINGS_COUNT};
|
||||
use librashader_reflect::reflect::semantics::{ShaderSemantics, MAX_BINDINGS_COUNT};
|
||||
use librashader_reflect::reflect::ReflectShader;
|
||||
use librashader_runtime::binding::{BindingUtil, TextureInput};
|
||||
use librashader_runtime::image::{Image, UVDirection};
|
||||
|
@ -46,6 +46,7 @@ use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_UNKNOWN;
|
|||
use windows::Win32::System::Threading::{CreateEventA, ResetEvent, WaitForSingleObject};
|
||||
use windows::Win32::System::WindowsProgramming::INFINITE;
|
||||
|
||||
use librashader_runtime::framebuffer::FramebufferInit;
|
||||
use librashader_runtime::render_target::RenderTarget;
|
||||
use librashader_runtime::scaling::ScaleFramebuffer;
|
||||
use rayon::prelude::*;
|
||||
|
@ -190,44 +191,31 @@ impl FilterChainD3D12 {
|
|||
options.map_or(false, |o| o.force_hlsl_pipeline),
|
||||
)?;
|
||||
|
||||
let framebuffer_gen =
|
||||
|| OwnedImage::new(device, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, false);
|
||||
let input_gen = || None;
|
||||
let framebuffer_init = FramebufferInit::new(
|
||||
filters.iter().map(|f| &f.reflection.meta),
|
||||
&framebuffer_gen,
|
||||
&input_gen,
|
||||
);
|
||||
|
||||
// initialize output framebuffers
|
||||
let mut output_framebuffers = Vec::new();
|
||||
output_framebuffers.resize_with(filters.len(), || {
|
||||
OwnedImage::new(device, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, false)
|
||||
});
|
||||
let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?;
|
||||
|
||||
// resolve all results
|
||||
let output_framebuffers = output_framebuffers
|
||||
.into_iter()
|
||||
.collect::<error::Result<Vec<OwnedImage>>>()?;
|
||||
let mut output_textures = Vec::new();
|
||||
output_textures.resize_with(filters.len(), || None);
|
||||
// initialize feedback framebuffers
|
||||
let (feedback_framebuffers, feedback_textures) =
|
||||
framebuffer_init.init_output_framebuffers()?;
|
||||
|
||||
// let mut output_textures = Vec::new();
|
||||
// output_textures.resize_with(filters.len(), || None);
|
||||
//
|
||||
// // initialize feedback framebuffers
|
||||
let mut feedback_framebuffers = Vec::new();
|
||||
feedback_framebuffers.resize_with(filters.len(), || {
|
||||
OwnedImage::new(device, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, false)
|
||||
});
|
||||
|
||||
// resolve all results
|
||||
let feedback_framebuffers = feedback_framebuffers
|
||||
.into_iter()
|
||||
.collect::<error::Result<Vec<OwnedImage>>>()?;
|
||||
let mut feedback_textures = Vec::new();
|
||||
feedback_textures.resize_with(filters.len(), || None);
|
||||
|
||||
let (history_framebuffers, history_textures) =
|
||||
FilterChainD3D12::init_history(device, &filters)?;
|
||||
// initialize history
|
||||
let (history_framebuffers, history_textures) = framebuffer_init.init_history()?;
|
||||
|
||||
Ok(FilterChainD3D12 {
|
||||
common: FilterCommon {
|
||||
d3d12: device.clone(),
|
||||
samplers,
|
||||
output_textures: output_textures.into_boxed_slice(),
|
||||
feedback_textures: feedback_textures.into_boxed_slice(),
|
||||
output_textures,
|
||||
feedback_textures,
|
||||
luts,
|
||||
mipmap_gen,
|
||||
root_signature,
|
||||
|
@ -245,8 +233,8 @@ impl FilterChainD3D12 {
|
|||
staging_heap,
|
||||
rtv_heap,
|
||||
passes: filters,
|
||||
output_framebuffers: output_framebuffers.into_boxed_slice(),
|
||||
feedback_framebuffers: feedback_framebuffers.into_boxed_slice(),
|
||||
output_framebuffers,
|
||||
feedback_framebuffers,
|
||||
history_framebuffers,
|
||||
work_heap: texture_heap,
|
||||
sampler_heap,
|
||||
|
@ -256,37 +244,6 @@ impl FilterChainD3D12 {
|
|||
})
|
||||
}
|
||||
|
||||
fn init_history(
|
||||
device: &ID3D12Device,
|
||||
filters: &Vec<FilterPass>,
|
||||
) -> error::Result<(VecDeque<OwnedImage>, Box<[Option<InputTexture>]>)> {
|
||||
let required_images =
|
||||
BindingMeta::calculate_required_history(filters.iter().map(|f| &f.reflection.meta));
|
||||
|
||||
// not using frame history;
|
||||
if required_images <= 1 {
|
||||
// println!("[history] not using frame history");
|
||||
return Ok((VecDeque::new(), Box::new([])));
|
||||
}
|
||||
|
||||
// history0 is aliased with the original
|
||||
|
||||
// eprintln!("[history] using frame history with {required_images} images");
|
||||
let mut framebuffers = VecDeque::with_capacity(required_images);
|
||||
framebuffers.resize_with(required_images, || {
|
||||
OwnedImage::new(device, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, false)
|
||||
});
|
||||
|
||||
let framebuffers = framebuffers
|
||||
.into_iter()
|
||||
.collect::<error::Result<VecDeque<OwnedImage>>>()?;
|
||||
|
||||
let mut history_textures = Vec::new();
|
||||
history_textures.resize_with(required_images, || None);
|
||||
|
||||
Ok((framebuffers, history_textures.into_boxed_slice()))
|
||||
}
|
||||
|
||||
fn load_luts(
|
||||
device: &ID3D12Device,
|
||||
heap: &mut D3D12DescriptorHeap<CpuStagingHeap>,
|
||||
|
|
|
@ -35,7 +35,9 @@ mod tests {
|
|||
let sample = hello_triangle::d3d12_hello_triangle::Sample::new(
|
||||
// "../test/slang-shaders/crt/crt-lottes.slangp",
|
||||
// "../test/basic.slangp",
|
||||
"../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV-GLASS.slangp",
|
||||
// "../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV-GLASS.slangp",
|
||||
"../test/slang-shaders/test/feedback.slangp",
|
||||
// "../test/slang-shaders/test/history.slangp",
|
||||
// "../test/slang-shaders/crt/crt-royale.slangp",
|
||||
// "../test/slang-shaders/vhs/VHSPro.slangp",
|
||||
&SampleCommandLine {
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
use crate::binding::{GlUniformStorage, UniformLocation, VariableLocation};
|
||||
use crate::error::FilterChainError;
|
||||
use crate::filter_pass::{FilterPass, UniformOffset};
|
||||
use crate::gl::{DrawQuad, Framebuffer, FramebufferInterface, GLInterface, LoadLut, UboRing};
|
||||
use crate::gl::{DrawQuad, FramebufferInterface, GLFramebuffer, GLInterface, LoadLut, UboRing};
|
||||
use crate::options::{FilterChainOptionsGL, FrameOptionsGL};
|
||||
use crate::samplers::SamplerSet;
|
||||
use crate::texture::InputTexture;
|
||||
use crate::util::{gl_get_version, gl_u16_to_version};
|
||||
use crate::{error, util, GLImage};
|
||||
use gl::types::{GLint, GLuint};
|
||||
use librashader_common::{FilterMode, Viewport, WrapMode};
|
||||
use librashader_common::Viewport;
|
||||
|
||||
use librashader_presets::ShaderPreset;
|
||||
use librashader_reflect::back::cross::GlslVersion;
|
||||
use librashader_reflect::back::targets::GLSL;
|
||||
use librashader_reflect::back::{CompileReflectShader, CompileShader};
|
||||
use librashader_reflect::front::GlslangCompilation;
|
||||
use librashader_reflect::reflect::semantics::{BindingMeta, ShaderSemantics, UniformMeta};
|
||||
use librashader_reflect::reflect::semantics::{ShaderSemantics, UniformMeta};
|
||||
|
||||
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::render_target::RenderTarget;
|
||||
use librashader_runtime::scaling::ScaleFramebuffer;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
@ -38,9 +39,9 @@ pub(crate) struct FilterChainImpl<T: GLInterface> {
|
|||
pub(crate) common: FilterCommon,
|
||||
passes: Box<[FilterPass<T>]>,
|
||||
draw_quad: T::DrawQuad,
|
||||
output_framebuffers: Box<[Framebuffer]>,
|
||||
feedback_framebuffers: Box<[Framebuffer]>,
|
||||
history_framebuffers: VecDeque<Framebuffer>,
|
||||
output_framebuffers: Box<[GLFramebuffer]>,
|
||||
feedback_framebuffers: Box<[GLFramebuffer]>,
|
||||
history_framebuffers: VecDeque<GLFramebuffer>,
|
||||
}
|
||||
|
||||
pub(crate) struct FilterCommon {
|
||||
|
@ -120,31 +121,40 @@ impl<T: GLInterface> FilterChainImpl<T> {
|
|||
|
||||
let samplers = SamplerSet::new();
|
||||
|
||||
// initialize output framebuffers
|
||||
let mut output_framebuffers = Vec::new();
|
||||
output_framebuffers.resize_with(filters.len(), || T::FramebufferInterface::new(1));
|
||||
let mut output_textures = Vec::new();
|
||||
output_textures.resize_with(filters.len(), InputTexture::default);
|
||||
|
||||
// initialize feedback framebuffers
|
||||
let mut feedback_framebuffers = Vec::new();
|
||||
feedback_framebuffers.resize_with(filters.len(), || T::FramebufferInterface::new(1));
|
||||
let mut feedback_textures = Vec::new();
|
||||
feedback_textures.resize_with(filters.len(), InputTexture::default);
|
||||
|
||||
// load luts
|
||||
let luts = T::LoadLut::load_luts(&preset.textures)?;
|
||||
|
||||
let (history_framebuffers, history_textures) =
|
||||
FilterChainImpl::init_history(&filters, default_filter, default_wrap);
|
||||
let framebuffer_gen = || Ok::<_, FilterChainError>(T::FramebufferInterface::new(1));
|
||||
let input_gen = || InputTexture {
|
||||
image: Default::default(),
|
||||
filter: default_filter,
|
||||
mip_filter: default_filter,
|
||||
wrap_mode: default_wrap,
|
||||
};
|
||||
|
||||
let framebuffer_init = FramebufferInit::new(
|
||||
filters.iter().map(|f| &f.reflection.meta),
|
||||
&framebuffer_gen,
|
||||
&input_gen,
|
||||
);
|
||||
|
||||
// initialize output framebuffers
|
||||
let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?;
|
||||
|
||||
// initialize feedback framebuffers
|
||||
let (feedback_framebuffers, feedback_textures) =
|
||||
framebuffer_init.init_output_framebuffers()?;
|
||||
|
||||
// initialize history
|
||||
let (history_framebuffers, history_textures) = framebuffer_init.init_history()?;
|
||||
|
||||
// create vertex objects
|
||||
let draw_quad = T::DrawQuad::new();
|
||||
|
||||
Ok(FilterChainImpl {
|
||||
passes: filters,
|
||||
output_framebuffers: output_framebuffers.into_boxed_slice(),
|
||||
feedback_framebuffers: feedback_framebuffers.into_boxed_slice(),
|
||||
output_framebuffers,
|
||||
feedback_framebuffers,
|
||||
history_framebuffers,
|
||||
draw_quad,
|
||||
common: FilterCommon {
|
||||
|
@ -159,8 +169,8 @@ impl<T: GLInterface> FilterChainImpl<T> {
|
|||
disable_mipmaps: options.map_or(false, |o| o.force_no_mipmaps),
|
||||
luts,
|
||||
samplers,
|
||||
output_textures: output_textures.into_boxed_slice(),
|
||||
feedback_textures: feedback_textures.into_boxed_slice(),
|
||||
output_textures,
|
||||
feedback_textures,
|
||||
history_textures,
|
||||
},
|
||||
})
|
||||
|
@ -274,37 +284,6 @@ impl<T: GLInterface> FilterChainImpl<T> {
|
|||
Ok(filters.into_boxed_slice())
|
||||
}
|
||||
|
||||
fn init_history(
|
||||
filters: &[FilterPass<T>],
|
||||
filter: FilterMode,
|
||||
wrap_mode: WrapMode,
|
||||
) -> (VecDeque<Framebuffer>, Box<[InputTexture]>) {
|
||||
let required_images =
|
||||
BindingMeta::calculate_required_history(filters.iter().map(|f| &f.reflection.meta));
|
||||
|
||||
// not using frame history;
|
||||
if required_images <= 1 {
|
||||
// println!("[history] not using frame history");
|
||||
return (VecDeque::new(), Box::new([]));
|
||||
}
|
||||
|
||||
// history0 is aliased with the original
|
||||
|
||||
// eprintln!("[history] using frame history with {required_images} images");
|
||||
let mut framebuffers = VecDeque::with_capacity(required_images);
|
||||
framebuffers.resize_with(required_images, || T::FramebufferInterface::new(1));
|
||||
|
||||
let mut history_textures = Vec::new();
|
||||
history_textures.resize_with(required_images, || InputTexture {
|
||||
image: Default::default(),
|
||||
filter,
|
||||
mip_filter: filter,
|
||||
wrap_mode,
|
||||
});
|
||||
|
||||
(framebuffers, history_textures.into_boxed_slice())
|
||||
}
|
||||
|
||||
fn push_history(&mut self, input: &GLImage) -> error::Result<()> {
|
||||
if let Some(mut back) = self.history_framebuffers.pop_back() {
|
||||
if back.size != input.size || (input.format != 0 && input.format != back.format) {
|
||||
|
@ -313,7 +292,6 @@ impl<T: GLInterface> FilterChainImpl<T> {
|
|||
}
|
||||
|
||||
back.copy_from::<T::FramebufferInterface>(input)?;
|
||||
|
||||
self.history_framebuffers.push_front(back)
|
||||
}
|
||||
|
||||
|
@ -326,7 +304,7 @@ impl<T: GLInterface> FilterChainImpl<T> {
|
|||
pub fn frame(
|
||||
&mut self,
|
||||
frame_count: usize,
|
||||
viewport: &Viewport<&Framebuffer>,
|
||||
viewport: &Viewport<&GLFramebuffer>,
|
||||
input: &GLImage,
|
||||
options: Option<&FrameOptionsGL>,
|
||||
) -> error::Result<()> {
|
||||
|
@ -364,18 +342,6 @@ impl<T: GLInterface> FilterChainImpl<T> {
|
|||
texture.image = fbo.as_texture(filter, wrap_mode).image;
|
||||
}
|
||||
|
||||
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.config.filter, pass.config.wrap_mode)
|
||||
.image;
|
||||
}
|
||||
|
||||
// shader_gl3: 2067
|
||||
let original = InputTexture {
|
||||
image: *input,
|
||||
|
@ -387,7 +353,7 @@ impl<T: GLInterface> FilterChainImpl<T> {
|
|||
let mut source = original;
|
||||
|
||||
// rescale render buffers to ensure all bindings are valid.
|
||||
<Framebuffer as ScaleFramebuffer<T::FramebufferInterface>>::scale_framebuffers(
|
||||
<GLFramebuffer as ScaleFramebuffer<T::FramebufferInterface>>::scale_framebuffers(
|
||||
source.image.size,
|
||||
viewport.output.size,
|
||||
&mut self.output_framebuffers,
|
||||
|
@ -396,6 +362,20 @@ impl<T: GLInterface> FilterChainImpl<T> {
|
|||
None,
|
||||
)?;
|
||||
|
||||
// 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.config.filter, pass.config.wrap_mode)
|
||||
.image;
|
||||
}
|
||||
|
||||
let passes_len = passes.len();
|
||||
let (pass, last) = passes.split_at_mut(passes_len - 1);
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::error::{FilterChainError, Result};
|
|||
use crate::filter_chain::filter_impl::FilterChainImpl;
|
||||
use crate::filter_chain::inner::FilterChainDispatch;
|
||||
use crate::options::{FilterChainOptionsGL, FrameOptionsGL};
|
||||
use crate::{Framebuffer, GLImage};
|
||||
use crate::{GLFramebuffer, GLImage};
|
||||
use librashader_presets::ShaderPreset;
|
||||
|
||||
mod filter_impl;
|
||||
|
@ -61,7 +61,7 @@ impl FilterChainGL {
|
|||
pub fn frame(
|
||||
&mut self,
|
||||
input: &GLImage,
|
||||
viewport: &Viewport<&Framebuffer>,
|
||||
viewport: &Viewport<&GLFramebuffer>,
|
||||
frame_count: usize,
|
||||
options: Option<&FrameOptionsGL>,
|
||||
) -> Result<()> {
|
||||
|
|
|
@ -14,7 +14,7 @@ use crate::binding::{GlUniformBinder, GlUniformStorage, UniformLocation, Variabl
|
|||
use crate::filter_chain::FilterCommon;
|
||||
use crate::gl::{BindTexture, GLInterface, UboRing};
|
||||
use crate::samplers::SamplerSet;
|
||||
use crate::Framebuffer;
|
||||
use crate::GLFramebuffer;
|
||||
|
||||
use crate::texture::InputTexture;
|
||||
|
||||
|
@ -29,7 +29,7 @@ impl UniformOffset {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct FilterPass<T: GLInterface> {
|
||||
pub(crate) struct FilterPass<T: GLInterface> {
|
||||
pub reflection: ShaderReflection,
|
||||
pub program: GLuint,
|
||||
pub ubo_location: UniformLocation<GLuint>,
|
||||
|
@ -81,10 +81,10 @@ impl<T: GLInterface> FilterPass<T> {
|
|||
parent: &FilterCommon,
|
||||
frame_count: u32,
|
||||
frame_direction: i32,
|
||||
viewport: &Viewport<&Framebuffer>,
|
||||
viewport: &Viewport<&GLFramebuffer>,
|
||||
original: &InputTexture,
|
||||
source: &InputTexture,
|
||||
output: RenderTarget<Framebuffer, GLint>,
|
||||
output: RenderTarget<GLFramebuffer, GLint>,
|
||||
) {
|
||||
let framebuffer = output.output;
|
||||
|
||||
|
@ -93,7 +93,7 @@ impl<T: GLInterface> FilterPass<T> {
|
|||
}
|
||||
|
||||
unsafe {
|
||||
gl::BindFramebuffer(gl::FRAMEBUFFER, framebuffer.handle);
|
||||
gl::BindFramebuffer(gl::FRAMEBUFFER, framebuffer.fbo);
|
||||
gl::UseProgram(self.program);
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ impl<T: GLInterface> FilterPass<T> {
|
|||
frame_count: u32,
|
||||
frame_direction: i32,
|
||||
fb_size: Size<u32>,
|
||||
viewport: &Viewport<&Framebuffer>,
|
||||
viewport: &Viewport<&GLFramebuffer>,
|
||||
original: &InputTexture,
|
||||
source: &InputTexture,
|
||||
) {
|
||||
|
|
|
@ -11,9 +11,9 @@ use librashader_runtime::scaling::ScaleFramebuffer;
|
|||
///
|
||||
/// Generally for use as render targets.
|
||||
#[derive(Debug)]
|
||||
pub struct Framebuffer {
|
||||
pub struct GLFramebuffer {
|
||||
pub(crate) image: GLuint,
|
||||
pub(crate) handle: GLuint,
|
||||
pub(crate) fbo: GLuint,
|
||||
pub(crate) size: Size<u32>,
|
||||
pub(crate) format: GLenum,
|
||||
pub(crate) max_levels: u32,
|
||||
|
@ -21,7 +21,7 @@ pub struct Framebuffer {
|
|||
pub(crate) is_raw: bool,
|
||||
}
|
||||
|
||||
impl Framebuffer {
|
||||
impl GLFramebuffer {
|
||||
/// Create a framebuffer from an already initialized texture and framebuffer.
|
||||
///
|
||||
/// The framebuffer will not be deleted when this struct is dropped.
|
||||
|
@ -31,14 +31,14 @@ impl Framebuffer {
|
|||
format: GLenum,
|
||||
size: Size<u32>,
|
||||
miplevels: u32,
|
||||
) -> Framebuffer {
|
||||
Framebuffer {
|
||||
) -> GLFramebuffer {
|
||||
GLFramebuffer {
|
||||
image: texture,
|
||||
size,
|
||||
format,
|
||||
max_levels: miplevels,
|
||||
mip_levels: miplevels,
|
||||
handle: fbo,
|
||||
fbo: fbo,
|
||||
is_raw: true,
|
||||
}
|
||||
}
|
||||
|
@ -76,15 +76,15 @@ impl Framebuffer {
|
|||
}
|
||||
}
|
||||
|
||||
impl Drop for Framebuffer {
|
||||
impl Drop for GLFramebuffer {
|
||||
fn drop(&mut self) {
|
||||
if self.is_raw {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
if self.handle != 0 {
|
||||
gl::DeleteFramebuffers(1, &self.handle);
|
||||
if self.fbo != 0 {
|
||||
gl::DeleteFramebuffers(1, &self.fbo);
|
||||
}
|
||||
if self.image != 0 {
|
||||
gl::DeleteTextures(1, &self.image);
|
||||
|
@ -93,7 +93,7 @@ impl Drop for Framebuffer {
|
|||
}
|
||||
}
|
||||
//
|
||||
impl<T: FramebufferInterface> ScaleFramebuffer<T> for Framebuffer {
|
||||
impl<T: FramebufferInterface> ScaleFramebuffer<T> for GLFramebuffer {
|
||||
type Error = FilterChainError;
|
||||
type Context = ();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::error::{FilterChainError, Result};
|
||||
use crate::framebuffer::GLImage;
|
||||
use crate::gl::framebuffer::Framebuffer;
|
||||
use crate::gl::framebuffer::GLFramebuffer;
|
||||
use crate::gl::FramebufferInterface;
|
||||
use gl::types::{GLenum, GLint, GLsizei};
|
||||
use librashader_common::{ImageFormat, Size};
|
||||
|
@ -11,7 +11,7 @@ use librashader_runtime::scaling::{MipmapSize, ViewportSize};
|
|||
pub struct Gl3Framebuffer;
|
||||
|
||||
impl FramebufferInterface for Gl3Framebuffer {
|
||||
fn new(max_levels: u32) -> Framebuffer {
|
||||
fn new(max_levels: u32) -> GLFramebuffer {
|
||||
let mut framebuffer = 0;
|
||||
unsafe {
|
||||
gl::GenFramebuffers(1, &mut framebuffer);
|
||||
|
@ -19,7 +19,7 @@ impl FramebufferInterface for Gl3Framebuffer {
|
|||
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
Framebuffer {
|
||||
GLFramebuffer {
|
||||
image: 0,
|
||||
size: Size {
|
||||
width: 1,
|
||||
|
@ -28,13 +28,13 @@ impl FramebufferInterface for Gl3Framebuffer {
|
|||
format: 0,
|
||||
max_levels,
|
||||
mip_levels: 0,
|
||||
handle: framebuffer,
|
||||
fbo: framebuffer,
|
||||
is_raw: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn scale(
|
||||
fb: &mut Framebuffer,
|
||||
fb: &mut GLFramebuffer,
|
||||
scaling: Scale2D,
|
||||
format: ImageFormat,
|
||||
viewport_size: &Size<u32>,
|
||||
|
@ -66,10 +66,10 @@ impl FramebufferInterface for Gl3Framebuffer {
|
|||
}
|
||||
Ok(size)
|
||||
}
|
||||
fn clear<const REBIND: bool>(fb: &Framebuffer) {
|
||||
fn clear<const REBIND: bool>(fb: &GLFramebuffer) {
|
||||
unsafe {
|
||||
if REBIND {
|
||||
gl::BindFramebuffer(gl::FRAMEBUFFER, fb.handle);
|
||||
gl::BindFramebuffer(gl::FRAMEBUFFER, fb.fbo);
|
||||
}
|
||||
gl::ColorMask(gl::TRUE, gl::TRUE, gl::TRUE, gl::TRUE);
|
||||
gl::ClearColor(0.0, 0.0, 0.0, 0.0);
|
||||
|
@ -79,14 +79,14 @@ impl FramebufferInterface for Gl3Framebuffer {
|
|||
}
|
||||
}
|
||||
}
|
||||
fn copy_from(fb: &mut Framebuffer, image: &GLImage) -> Result<()> {
|
||||
fn copy_from(fb: &mut GLFramebuffer, image: &GLImage) -> Result<()> {
|
||||
// todo: may want to use a shader and draw a quad to be faster.
|
||||
if image.size != fb.size || image.format != fb.format {
|
||||
Self::init(fb, image.size, image.format)?;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
gl::BindFramebuffer(gl::FRAMEBUFFER, fb.handle);
|
||||
gl::BindFramebuffer(gl::FRAMEBUFFER, fb.fbo);
|
||||
|
||||
gl::FramebufferTexture2D(
|
||||
gl::READ_FRAMEBUFFER,
|
||||
|
@ -149,7 +149,7 @@ impl FramebufferInterface for Gl3Framebuffer {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
fn init(fb: &mut Framebuffer, mut size: Size<u32>, format: impl Into<GLenum>) -> Result<()> {
|
||||
fn init(fb: &mut GLFramebuffer, mut size: Size<u32>, format: impl Into<GLenum>) -> Result<()> {
|
||||
if fb.is_raw {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ impl FramebufferInterface for Gl3Framebuffer {
|
|||
fb.size = size;
|
||||
|
||||
unsafe {
|
||||
gl::BindFramebuffer(gl::FRAMEBUFFER, fb.handle);
|
||||
gl::BindFramebuffer(gl::FRAMEBUFFER, fb.fbo);
|
||||
|
||||
// reset the framebuffer image
|
||||
if fb.image != 0 {
|
||||
|
|
|
@ -10,7 +10,7 @@ use librashader_common::{Size, Viewport};
|
|||
use crate::filter_chain::FilterChainGL;
|
||||
use crate::framebuffer::GLImage;
|
||||
|
||||
use crate::Framebuffer;
|
||||
use crate::GLFramebuffer;
|
||||
|
||||
const WIDTH: u32 = 800;
|
||||
const HEIGHT: u32 = 600;
|
||||
|
@ -464,7 +464,7 @@ void main()
|
|||
let (fb_width, fb_height) = window.get_framebuffer_size();
|
||||
let (vp_width, vp_height) = window.get_size();
|
||||
|
||||
let output = Framebuffer::new_from_raw(
|
||||
let output = GLFramebuffer::new_from_raw(
|
||||
output_texture,
|
||||
output_framebuffer_handle,
|
||||
gl::RGBA8,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::error::{FilterChainError, Result};
|
||||
use crate::framebuffer::GLImage;
|
||||
use crate::gl::framebuffer::Framebuffer;
|
||||
use crate::gl::framebuffer::GLFramebuffer;
|
||||
use crate::gl::FramebufferInterface;
|
||||
use gl::types::{GLenum, GLint, GLsizei};
|
||||
use librashader_common::{ImageFormat, Size};
|
||||
|
@ -11,13 +11,13 @@ use librashader_runtime::scaling::{MipmapSize, ViewportSize};
|
|||
pub struct Gl46Framebuffer;
|
||||
|
||||
impl FramebufferInterface for Gl46Framebuffer {
|
||||
fn new(max_levels: u32) -> Framebuffer {
|
||||
fn new(max_levels: u32) -> GLFramebuffer {
|
||||
let mut framebuffer = 0;
|
||||
unsafe {
|
||||
gl::CreateFramebuffers(1, &mut framebuffer);
|
||||
}
|
||||
|
||||
Framebuffer {
|
||||
GLFramebuffer {
|
||||
image: 0,
|
||||
size: Size {
|
||||
width: 1,
|
||||
|
@ -26,13 +26,13 @@ impl FramebufferInterface for Gl46Framebuffer {
|
|||
format: 0,
|
||||
max_levels,
|
||||
mip_levels: 0,
|
||||
handle: framebuffer,
|
||||
fbo: framebuffer,
|
||||
is_raw: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn scale(
|
||||
fb: &mut Framebuffer,
|
||||
fb: &mut GLFramebuffer,
|
||||
scaling: Scale2D,
|
||||
format: ImageFormat,
|
||||
viewport_size: &Size<u32>,
|
||||
|
@ -66,17 +66,17 @@ impl FramebufferInterface for Gl46Framebuffer {
|
|||
}
|
||||
Ok(size)
|
||||
}
|
||||
fn clear<const REBIND: bool>(fb: &Framebuffer) {
|
||||
fn clear<const REBIND: bool>(fb: &GLFramebuffer) {
|
||||
unsafe {
|
||||
gl::ClearNamedFramebufferfv(
|
||||
fb.handle,
|
||||
fb.fbo,
|
||||
gl::COLOR,
|
||||
0,
|
||||
[0.0f32, 0.0, 0.0, 0.0].as_ptr().cast(),
|
||||
);
|
||||
}
|
||||
}
|
||||
fn copy_from(fb: &mut Framebuffer, image: &GLImage) -> Result<()> {
|
||||
fn copy_from(fb: &mut GLFramebuffer, image: &GLImage) -> Result<()> {
|
||||
// todo: confirm this behaviour for unbound image.
|
||||
if image.handle == 0 {
|
||||
return Ok(());
|
||||
|
@ -90,11 +90,11 @@ impl FramebufferInterface for Gl46Framebuffer {
|
|||
unsafe {
|
||||
// gl::NamedFramebufferDrawBuffer(fb.handle, gl::COLOR_ATTACHMENT1);
|
||||
gl::NamedFramebufferReadBuffer(image.handle, gl::COLOR_ATTACHMENT0);
|
||||
gl::NamedFramebufferDrawBuffer(fb.handle, gl::COLOR_ATTACHMENT0);
|
||||
gl::NamedFramebufferDrawBuffer(fb.fbo, gl::COLOR_ATTACHMENT0);
|
||||
|
||||
gl::BlitNamedFramebuffer(
|
||||
image.handle,
|
||||
fb.handle,
|
||||
fb.fbo,
|
||||
0,
|
||||
0,
|
||||
image.size.width as GLint,
|
||||
|
@ -110,7 +110,7 @@ impl FramebufferInterface for Gl46Framebuffer {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
fn init(fb: &mut Framebuffer, mut size: Size<u32>, format: impl Into<GLenum>) -> Result<()> {
|
||||
fn init(fb: &mut GLFramebuffer, mut size: Size<u32>, format: impl Into<GLenum>) -> Result<()> {
|
||||
if fb.is_raw {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ impl FramebufferInterface for Gl46Framebuffer {
|
|||
unsafe {
|
||||
// reset the framebuffer image
|
||||
if fb.image != 0 {
|
||||
gl::NamedFramebufferTexture(fb.handle, gl::COLOR_ATTACHMENT0, 0, 0);
|
||||
gl::NamedFramebufferTexture(fb.fbo, gl::COLOR_ATTACHMENT0, 0, 0);
|
||||
gl::DeleteTextures(1, &fb.image);
|
||||
}
|
||||
|
||||
|
@ -149,13 +149,13 @@ impl FramebufferInterface for Gl46Framebuffer {
|
|||
size.height as GLsizei,
|
||||
);
|
||||
|
||||
gl::NamedFramebufferTexture(fb.handle, gl::COLOR_ATTACHMENT0, fb.image, 0);
|
||||
gl::NamedFramebufferTexture(fb.fbo, gl::COLOR_ATTACHMENT0, fb.image, 0);
|
||||
|
||||
let status = gl::CheckFramebufferStatus(gl::FRAMEBUFFER);
|
||||
if status != gl::FRAMEBUFFER_COMPLETE {
|
||||
match status {
|
||||
gl::FRAMEBUFFER_UNSUPPORTED => {
|
||||
gl::NamedFramebufferTexture(fb.handle, gl::COLOR_ATTACHMENT0, 0, 0);
|
||||
gl::NamedFramebufferTexture(fb.fbo, gl::COLOR_ATTACHMENT0, 0, 0);
|
||||
gl::DeleteTextures(1, &fb.image);
|
||||
gl::CreateTextures(gl::TEXTURE_2D, 1, &mut fb.image);
|
||||
|
||||
|
@ -174,7 +174,7 @@ impl FramebufferInterface for Gl46Framebuffer {
|
|||
size.width as GLsizei,
|
||||
size.height as GLsizei,
|
||||
);
|
||||
gl::NamedFramebufferTexture(fb.handle, gl::COLOR_ATTACHMENT0, fb.image, 0);
|
||||
gl::NamedFramebufferTexture(fb.fbo, gl::COLOR_ATTACHMENT0, fb.image, 0);
|
||||
// fb.init =
|
||||
// gl::CheckFramebufferStatus(gl::FRAMEBUFFER) == gl::FRAMEBUFFER_COMPLETE;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use librashader_common::{Size, Viewport};
|
|||
use crate::filter_chain::FilterChainGL;
|
||||
use crate::framebuffer::GLImage;
|
||||
|
||||
use crate::Framebuffer;
|
||||
use crate::GLFramebuffer;
|
||||
|
||||
const WIDTH: u32 = 800;
|
||||
const HEIGHT: u32 = 600;
|
||||
|
@ -455,7 +455,7 @@ void main()
|
|||
let (fb_width, fb_height) = window.get_framebuffer_size();
|
||||
let (vp_width, vp_height) = window.get_size();
|
||||
|
||||
let output = Framebuffer::new_from_raw(
|
||||
let output = GLFramebuffer::new_from_raw(
|
||||
output_texture,
|
||||
output_framebuffer_handle,
|
||||
gl::RGBA8,
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::error::Result;
|
|||
use crate::framebuffer::GLImage;
|
||||
use crate::samplers::SamplerSet;
|
||||
use crate::texture::InputTexture;
|
||||
pub use framebuffer::Framebuffer;
|
||||
pub use framebuffer::GLFramebuffer;
|
||||
use gl::types::{GLenum, GLuint};
|
||||
use librashader_common::{ImageFormat, Size};
|
||||
use librashader_presets::{Scale2D, TextureConfig};
|
||||
|
@ -15,17 +15,17 @@ use librashader_reflect::reflect::semantics::{TextureBinding, UboReflection};
|
|||
use librashader_runtime::uniforms::UniformStorageAccess;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
pub trait LoadLut {
|
||||
pub(crate) trait LoadLut {
|
||||
fn load_luts(textures: &[TextureConfig]) -> Result<FxHashMap<usize, InputTexture>>;
|
||||
}
|
||||
|
||||
pub trait DrawQuad {
|
||||
pub(crate) trait DrawQuad {
|
||||
fn new() -> Self;
|
||||
fn bind_vertices(&self);
|
||||
fn unbind_vertices(&self);
|
||||
}
|
||||
|
||||
pub trait UboRing<const SIZE: usize> {
|
||||
pub(crate) trait UboRing<const SIZE: usize> {
|
||||
fn new(buffer_size: u32) -> Self;
|
||||
fn bind_for_frame(
|
||||
&mut self,
|
||||
|
@ -35,27 +35,27 @@ pub trait UboRing<const SIZE: usize> {
|
|||
);
|
||||
}
|
||||
|
||||
pub trait FramebufferInterface {
|
||||
fn new(max_levels: u32) -> Framebuffer;
|
||||
pub(crate) trait FramebufferInterface {
|
||||
fn new(max_levels: u32) -> GLFramebuffer;
|
||||
fn scale(
|
||||
fb: &mut Framebuffer,
|
||||
fb: &mut GLFramebuffer,
|
||||
scaling: Scale2D,
|
||||
format: ImageFormat,
|
||||
viewport_size: &Size<u32>,
|
||||
source_size: &Size<u32>,
|
||||
mipmap: bool,
|
||||
) -> Result<Size<u32>>;
|
||||
fn clear<const REBIND: bool>(fb: &Framebuffer);
|
||||
fn copy_from(fb: &mut Framebuffer, image: &GLImage) -> Result<()>;
|
||||
fn init(fb: &mut Framebuffer, size: Size<u32>, format: impl Into<GLenum>) -> Result<()>;
|
||||
fn clear<const REBIND: bool>(fb: &GLFramebuffer);
|
||||
fn copy_from(fb: &mut GLFramebuffer, image: &GLImage) -> Result<()>;
|
||||
fn init(fb: &mut GLFramebuffer, size: Size<u32>, format: impl Into<GLenum>) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait BindTexture {
|
||||
pub(crate) trait BindTexture {
|
||||
fn bind_texture(samplers: &SamplerSet, binding: &TextureBinding, texture: &InputTexture);
|
||||
fn gen_mipmaps(texture: &InputTexture);
|
||||
}
|
||||
|
||||
pub trait GLInterface {
|
||||
pub(crate) trait GLInterface {
|
||||
type FramebufferInterface: FramebufferInterface;
|
||||
type UboRing: UboRing<16>;
|
||||
type DrawQuad: DrawQuad;
|
||||
|
|
|
@ -21,7 +21,7 @@ mod texture;
|
|||
pub mod error;
|
||||
pub mod options;
|
||||
|
||||
pub use crate::gl::Framebuffer;
|
||||
pub use crate::gl::GLFramebuffer;
|
||||
pub use filter_chain::FilterChainGL;
|
||||
pub use framebuffer::GLImage;
|
||||
|
||||
|
@ -52,7 +52,9 @@ mod tests {
|
|||
let (glfw, window, events, shader, vao) = gl::gl46::hello_triangle::setup();
|
||||
let mut filter = FilterChainGL::load_from_path(
|
||||
// "../test/slang-shaders/vhs/VHSPro.slangp",
|
||||
"../test/slang-shaders/crt/crt-royale.slangp",
|
||||
// "../test/slang-shaders/test/history.slangp",
|
||||
// "../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp",
|
||||
"../test/slang-shaders/test/feedback.slangp",
|
||||
Some(&FilterChainOptionsGL {
|
||||
glsl_version: 0,
|
||||
use_dsa: true,
|
||||
|
|
|
@ -3,13 +3,14 @@ use crate::framebuffer::GLImage;
|
|||
use librashader_common::{FilterMode, WrapMode};
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone)]
|
||||
pub struct InputTexture {
|
||||
pub(crate) struct InputTexture {
|
||||
pub image: GLImage,
|
||||
pub filter: FilterMode,
|
||||
pub mip_filter: FilterMode,
|
||||
pub wrap_mode: WrapMode,
|
||||
}
|
||||
|
||||
/// An OpenGL texture bound as a shader resource.
|
||||
impl InputTexture {
|
||||
pub fn is_bound(&self) -> bool {
|
||||
self.image.handle != 0
|
||||
|
|
|
@ -19,7 +19,7 @@ use librashader_reflect::back::targets::SPIRV;
|
|||
use librashader_reflect::back::{CompileReflectShader, CompileShader};
|
||||
use librashader_reflect::front::GlslangCompilation;
|
||||
use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact};
|
||||
use librashader_reflect::reflect::semantics::{BindingMeta, ShaderSemantics};
|
||||
use librashader_reflect::reflect::semantics::ShaderSemantics;
|
||||
use librashader_reflect::reflect::ReflectShader;
|
||||
use librashader_runtime::binding::BindingUtil;
|
||||
use librashader_runtime::image::{Image, UVDirection};
|
||||
|
@ -31,6 +31,7 @@ use std::collections::VecDeque;
|
|||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use librashader_runtime::framebuffer::FramebufferInit;
|
||||
use librashader_runtime::render_target::RenderTarget;
|
||||
use librashader_runtime::scaling::ScaleFramebuffer;
|
||||
use rayon::prelude::*;
|
||||
|
@ -145,8 +146,8 @@ pub(crate) struct FilterCommon {
|
|||
pub(crate) luts: FxHashMap<usize, LutTexture>,
|
||||
pub samplers: SamplerSet,
|
||||
pub(crate) draw_quad: DrawQuad,
|
||||
pub output_inputs: Box<[Option<InputImage>]>,
|
||||
pub feedback_inputs: Box<[Option<InputImage>]>,
|
||||
pub output_textures: Box<[Option<InputImage>]>,
|
||||
pub feedback_textures: Box<[Option<InputImage>]>,
|
||||
pub history_textures: Box<[Option<InputImage>]>,
|
||||
pub config: FilterMutable,
|
||||
pub device: Arc<ash::Device>,
|
||||
|
@ -256,28 +257,24 @@ impl FilterChainVulkan {
|
|||
let luts = FilterChainVulkan::load_luts(&device, &preset.textures)?;
|
||||
let samplers = SamplerSet::new(&device.device)?;
|
||||
|
||||
let (history_framebuffers, history_textures) =
|
||||
FilterChainVulkan::init_history(&device, &filters)?;
|
||||
let framebuffer_gen =
|
||||
|| OwnedImage::new(&device, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, 1);
|
||||
let input_gen = || None;
|
||||
let framebuffer_init = FramebufferInit::new(
|
||||
filters.iter().map(|f| &f.reflection.meta),
|
||||
&framebuffer_gen,
|
||||
&input_gen,
|
||||
);
|
||||
|
||||
let mut output_framebuffers = Vec::new();
|
||||
output_framebuffers.resize_with(filters.len(), || {
|
||||
OwnedImage::new(&device, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, 1)
|
||||
});
|
||||
// initialize output framebuffers
|
||||
let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?;
|
||||
|
||||
let mut feedback_framebuffers = Vec::new();
|
||||
feedback_framebuffers.resize_with(filters.len(), || {
|
||||
OwnedImage::new(&device, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, 1)
|
||||
});
|
||||
// initialize feedback framebuffers
|
||||
let (feedback_framebuffers, feedback_textures) =
|
||||
framebuffer_init.init_output_framebuffers()?;
|
||||
|
||||
let output_framebuffers: error::Result<Vec<OwnedImage>> =
|
||||
output_framebuffers.into_iter().collect();
|
||||
let mut output_textures = Vec::new();
|
||||
output_textures.resize_with(filters.len(), || None);
|
||||
|
||||
let feedback_framebuffers: error::Result<Vec<OwnedImage>> =
|
||||
feedback_framebuffers.into_iter().collect();
|
||||
let mut feedback_textures = Vec::new();
|
||||
feedback_textures.resize_with(filters.len(), || None);
|
||||
// initialize history
|
||||
let (history_framebuffers, history_textures) = framebuffer_init.init_history()?;
|
||||
|
||||
let mut intermediates = Vec::new();
|
||||
intermediates.resize_with(frames_in_flight as usize, || {
|
||||
|
@ -298,14 +295,14 @@ impl FilterChainVulkan {
|
|||
},
|
||||
draw_quad: DrawQuad::new(&device.device, &device.alloc)?,
|
||||
device: device.device.clone(),
|
||||
output_inputs: output_textures.into_boxed_slice(),
|
||||
feedback_inputs: feedback_textures.into_boxed_slice(),
|
||||
output_textures,
|
||||
feedback_textures,
|
||||
history_textures,
|
||||
},
|
||||
passes: filters,
|
||||
vulkan: device,
|
||||
output_framebuffers: output_framebuffers?.into_boxed_slice(),
|
||||
feedback_framebuffers: feedback_framebuffers?.into_boxed_slice(),
|
||||
output_framebuffers,
|
||||
feedback_framebuffers,
|
||||
history_framebuffers,
|
||||
residuals: intermediates.into_boxed_slice(),
|
||||
disable_mipmaps: options.map_or(false, |o| o.force_no_mipmaps),
|
||||
|
@ -374,6 +371,7 @@ impl FilterChainVulkan {
|
|||
graphics_pipeline,
|
||||
// ubo_ring,
|
||||
frames_in_flight,
|
||||
internal_frame_count: 0,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
@ -443,36 +441,6 @@ impl FilterChainVulkan {
|
|||
Ok(luts)
|
||||
}
|
||||
|
||||
fn init_history(
|
||||
vulkan: &VulkanObjects,
|
||||
filters: &[FilterPass],
|
||||
) -> error::Result<(VecDeque<OwnedImage>, Box<[Option<InputImage>]>)> {
|
||||
let required_images =
|
||||
BindingMeta::calculate_required_history(filters.iter().map(|f| &f.reflection.meta));
|
||||
|
||||
// not using frame history;
|
||||
if required_images <= 1 {
|
||||
// println!("[history] not using frame history");
|
||||
return Ok((VecDeque::new(), Box::new([])));
|
||||
}
|
||||
|
||||
// history0 is aliased with the original
|
||||
|
||||
// eprintln!("[history] using frame history with {required_images} images");
|
||||
let mut images = Vec::with_capacity(required_images);
|
||||
images.resize_with(required_images, || {
|
||||
OwnedImage::new(vulkan, Size::new(1, 1), ImageFormat::R8G8B8A8Unorm, 1)
|
||||
});
|
||||
|
||||
let images: error::Result<Vec<OwnedImage>> = images.into_iter().collect();
|
||||
let images = VecDeque::from(images?);
|
||||
|
||||
let mut image_views = Vec::new();
|
||||
image_views.resize_with(required_images, || None);
|
||||
|
||||
Ok((images, image_views.into_boxed_slice()))
|
||||
}
|
||||
|
||||
// image must be in SHADER_READ_OPTIMAL
|
||||
pub fn push_history(
|
||||
&mut self,
|
||||
|
@ -640,9 +608,9 @@ impl FilterChainVulkan {
|
|||
output: &OwnedImage,
|
||||
feedback: &OwnedImage| {
|
||||
// refresh inputs
|
||||
self.common.feedback_inputs[index] =
|
||||
self.common.feedback_textures[index] =
|
||||
Some(feedback.as_input(pass.config.filter, pass.config.wrap_mode));
|
||||
self.common.output_inputs[index] =
|
||||
self.common.output_textures[index] =
|
||||
Some(output.as_input(pass.config.filter, pass.config.wrap_mode));
|
||||
Ok(())
|
||||
}),
|
||||
|
@ -682,7 +650,7 @@ impl FilterChainVulkan {
|
|||
out.output.end_pass(cmd);
|
||||
}
|
||||
|
||||
source = self.common.output_inputs[index].clone().unwrap();
|
||||
source = self.common.output_textures[index].clone().unwrap();
|
||||
intermediates.dispose_outputs(output_image);
|
||||
intermediates.dispose_framebuffers(residual_fb);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ pub struct FilterPass {
|
|||
pub graphics_pipeline: VulkanGraphicsPipeline,
|
||||
// pub ubo_ring: VkUboRing,
|
||||
pub frames_in_flight: u32,
|
||||
pub internal_frame_count: usize,
|
||||
}
|
||||
|
||||
impl TextureInput for InputImage {
|
||||
|
@ -100,7 +101,7 @@ impl FilterPass {
|
|||
vbo_type: QuadType,
|
||||
) -> error::Result<Option<vk::Framebuffer>> {
|
||||
let mut descriptor = self.graphics_pipeline.layout.descriptor_sets
|
||||
[(frame_count % self.frames_in_flight) as usize];
|
||||
[self.internal_frame_count % self.frames_in_flight as usize];
|
||||
|
||||
self.build_semantics(
|
||||
pass_index,
|
||||
|
@ -193,6 +194,7 @@ impl FilterPass {
|
|||
parent.draw_quad.draw_quad(cmd, vbo_type);
|
||||
self.graphics_pipeline.end_rendering(&parent.device, cmd);
|
||||
}
|
||||
self.internal_frame_count += 1;
|
||||
Ok(residual)
|
||||
}
|
||||
|
||||
|
@ -223,10 +225,10 @@ impl FilterPass {
|
|||
source,
|
||||
&self.uniform_bindings,
|
||||
&self.reflection.meta.texture_meta,
|
||||
parent.output_inputs[0..pass_index]
|
||||
parent.output_textures[0..pass_index]
|
||||
.iter()
|
||||
.map(|o| o.as_ref()),
|
||||
parent.feedback_inputs.iter().map(|o| o.as_ref()),
|
||||
parent.feedback_textures.iter().map(|o| o.as_ref()),
|
||||
parent.history_textures.iter().map(|o| o.as_ref()),
|
||||
parent.luts.iter().map(|(u, i)| (*u, i.as_ref())),
|
||||
&self.source.parameters,
|
||||
|
|
|
@ -44,7 +44,8 @@ mod tests {
|
|||
let filter = FilterChainVulkan::load_from_path(
|
||||
&base,
|
||||
// "../test/slang-shaders/crt/crt-royale.slangp",
|
||||
"../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__2__ADV-NO-REFLECT.slangp",
|
||||
// "../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV-GLASS.slangp",
|
||||
"../test/slang-shaders/test/feedback.slangp",
|
||||
// "../test/basic.slangp",
|
||||
Some(&FilterChainOptionsVulkan {
|
||||
frames_in_flight: 3,
|
||||
|
|
|
@ -310,19 +310,21 @@ impl BindingUtil for BindingMeta {
|
|||
|
||||
for pass in pass_meta {
|
||||
// If a shader uses history size, but not history, we still need to keep the texture.
|
||||
let texture_count = pass
|
||||
let texture_max_index = pass
|
||||
.texture_meta
|
||||
.iter()
|
||||
.filter(|(semantics, _)| semantics.semantics == TextureSemantics::OriginalHistory)
|
||||
.count();
|
||||
let texture_size_count = pass
|
||||
.map(|(semantic, _)| semantic.index)
|
||||
.fold(0, std::cmp::max);
|
||||
let texture_size_max_index = pass
|
||||
.texture_size_meta
|
||||
.iter()
|
||||
.filter(|(semantics, _)| semantics.semantics == TextureSemantics::OriginalHistory)
|
||||
.count();
|
||||
.map(|(semantic, _)| semantic.index)
|
||||
.fold(0, std::cmp::max);
|
||||
|
||||
required_images = std::cmp::max(required_images, texture_count);
|
||||
required_images = std::cmp::max(required_images, texture_size_count);
|
||||
required_images = std::cmp::max(required_images, texture_max_index);
|
||||
required_images = std::cmp::max(required_images, texture_size_max_index);
|
||||
}
|
||||
|
||||
required_images
|
||||
|
|
92
librashader-runtime/src/framebuffer.rs
Normal file
92
librashader-runtime/src/framebuffer.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
use crate::binding::BindingUtil;
|
||||
use librashader_reflect::reflect::semantics::BindingMeta;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Helper to initialize framebuffers in a graphics API agnostic way.
|
||||
pub struct FramebufferInit<'a, F, I, E> {
|
||||
owned_generator: &'a dyn Fn() -> Result<F, E>,
|
||||
input_generator: &'a dyn Fn() -> I,
|
||||
required_history: usize,
|
||||
filters_count: usize,
|
||||
}
|
||||
|
||||
impl<'a, F, I, E> FramebufferInit<'a, F, I, E> {
|
||||
/// Create a new framebuffer initializer with the given
|
||||
/// closures to create owned framebuffers and image views.
|
||||
pub fn new(
|
||||
filters: impl Iterator<Item = &'a BindingMeta> + ExactSizeIterator,
|
||||
owned_generator: &'a dyn Fn() -> Result<F, E>,
|
||||
input_generator: &'a dyn Fn() -> I,
|
||||
) -> Self {
|
||||
let filters_count = filters.len();
|
||||
let required_history = BindingMeta::calculate_required_history(filters);
|
||||
Self {
|
||||
owned_generator,
|
||||
input_generator,
|
||||
filters_count,
|
||||
required_history,
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize history framebuffers and views.
|
||||
pub fn init_history(&self) -> Result<(VecDeque<F>, Box<[I]>), E> {
|
||||
init_history(
|
||||
self.required_history,
|
||||
self.owned_generator,
|
||||
self.input_generator,
|
||||
)
|
||||
}
|
||||
|
||||
/// Initialize output framebuffers and views.
|
||||
pub fn init_output_framebuffers(&self) -> Result<(Box<[F]>, Box<[I]>), E> {
|
||||
init_output_framebuffers(
|
||||
self.filters_count,
|
||||
self.owned_generator,
|
||||
self.input_generator,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn init_history<'a, F, I, E>(
|
||||
required_images: usize,
|
||||
owned_generator: impl Fn() -> Result<F, E>,
|
||||
input_generator: impl Fn() -> I,
|
||||
) -> Result<(VecDeque<F>, Box<[I]>), E> {
|
||||
if required_images <= 1 {
|
||||
return Ok((VecDeque::new(), Box::new([])));
|
||||
}
|
||||
|
||||
let mut framebuffers = VecDeque::with_capacity(required_images);
|
||||
framebuffers.resize_with(required_images, owned_generator);
|
||||
|
||||
let framebuffers = framebuffers
|
||||
.into_iter()
|
||||
.collect::<Result<VecDeque<F>, E>>()?;
|
||||
|
||||
let mut history_textures = Vec::new();
|
||||
history_textures.resize_with(required_images, input_generator);
|
||||
|
||||
Ok((framebuffers, history_textures.into_boxed_slice()))
|
||||
}
|
||||
|
||||
fn init_output_framebuffers<F, I, E>(
|
||||
len: usize,
|
||||
owned_generator: impl Fn() -> Result<F, E>,
|
||||
input_generator: impl Fn() -> I,
|
||||
) -> Result<(Box<[F]>, Box<[I]>), E> {
|
||||
let mut output_framebuffers = Vec::new();
|
||||
output_framebuffers.resize_with(len, owned_generator);
|
||||
|
||||
// resolve all results
|
||||
let output_framebuffers = output_framebuffers
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<F>, E>>()?;
|
||||
|
||||
let mut output_textures = Vec::new();
|
||||
output_textures.resize_with(len, input_generator);
|
||||
|
||||
Ok((
|
||||
output_framebuffers.into_boxed_slice(),
|
||||
output_textures.into_boxed_slice(),
|
||||
))
|
||||
}
|
|
@ -34,3 +34,6 @@ pub mod filter_pass;
|
|||
|
||||
/// Common types for render targets.
|
||||
pub mod render_target;
|
||||
|
||||
/// Helpers for handling framebuffers.
|
||||
pub mod framebuffer;
|
||||
|
|
|
@ -214,7 +214,7 @@ pub mod runtime {
|
|||
pub use librashader_runtime_gl::{
|
||||
error,
|
||||
options::{FilterChainOptionsGL as FilterChainOptions, FrameOptionsGL as FrameOptions},
|
||||
FilterChainGL as FilterChain, Framebuffer, GLImage,
|
||||
FilterChainGL as FilterChain, GLFramebuffer, GLImage,
|
||||
};
|
||||
|
||||
#[doc(hidden)]
|
||||
|
|
Loading…
Reference in a new issue