librashader_test/render/
mod.rs

1#[cfg(all(windows, feature = "d3d11"))]
2pub mod d3d11;
3
4#[cfg(all(windows, feature = "d3d12"))]
5pub mod d3d12;
6
7#[cfg(all(windows, feature = "d3d9"))]
8pub mod d3d9;
9
10#[cfg(feature = "opengl")]
11pub mod gl;
12
13#[cfg(feature = "vulkan")]
14pub mod vk;
15
16#[cfg(feature = "wgpu")]
17pub mod wgpu;
18
19#[cfg(all(target_vendor = "apple", feature = "metal"))]
20pub mod mtl;
21
22use librashader::presets::{ShaderFeatures, ShaderPreset};
23use librashader::runtime::Size;
24use librashader_runtime::impl_default_frame_options;
25use librashader_runtime::parameters::RuntimeParameters;
26use std::path::Path;
27
28/// Test harness to set up a device, render a triangle, and apply a shader
29pub trait RenderTest {
30    /// Create a new instance of the test harness.
31    fn new(path: &Path) -> anyhow::Result<Self>
32    where
33        Self: Sized;
34
35    /// Get the size of the image loaded.
36    fn image_size(&self) -> Size<u32>;
37
38    /// Render a shader onto an image buffer, applying the provided shader.
39    ///
40    /// The test should render in linear colour space for proper comparison against
41    /// backends.
42    ///
43    /// For testing purposes, it is often that a single image will be reused with multiple
44    /// shader presets, so the actual image that a shader will be applied to
45    /// will often be part of the test harness object.
46    fn render(
47        &mut self,
48        path: &Path,
49        flags: ShaderFeatures,
50        frame_count: usize,
51        output_size: Option<Size<u32>>,
52    ) -> anyhow::Result<image::RgbaImage> {
53        let preset = ShaderPreset::try_parse(path, flags)?;
54        self.render_with_preset(preset, frame_count, output_size)
55    }
56
57    /// Render a shader onto an image buffer, applying the provided shader.
58    ///
59    /// The test should render in linear colour space for proper comparison against
60    /// backends.
61    ///
62    /// For testing purposes, it is often that a single image will be reused with multiple
63    /// shader presets, so the actual image that a shader will be applied to
64    /// will often be part of the test harness object.
65    fn render_with_preset(
66        &mut self,
67        preset: ShaderPreset,
68        frame_count: usize,
69        output_size: Option<Size<u32>>,
70    ) -> anyhow::Result<image::RgbaImage> {
71        self.render_with_preset_and_params(preset, frame_count, output_size, None, None)
72    }
73
74    /// Render a shader onto an image buffer, applying the provided shader.
75    ///
76    /// The test should render in linear colour space for proper comparison against
77    /// backends.
78    ///
79    /// For testing purposes, it is often that a single image will be reused with multiple
80    /// shader presets, so the actual image that a shader will be applied to
81    /// will often be part of the test harness object.
82    fn render_with_preset_and_params(
83        &mut self,
84        preset: ShaderPreset,
85        frame_count: usize,
86        output_size: Option<Size<u32>>,
87        param_setter: Option<&dyn Fn(&RuntimeParameters)>,
88        frame_options: Option<CommonFrameOptions>,
89    ) -> anyhow::Result<image::RgbaImage>;
90}
91
92impl_default_frame_options!(CommonFrameOptions);
93
94#[cfg(test)]
95mod test {
96
97    use crate::render::RenderTest;
98    use image::codecs::png::PngEncoder;
99    use librashader::presets::ShaderFeatures;
100    use std::fs::File;
101
102    const IMAGE_PATH: &str = "../triangle.png";
103    const FILTER_PATH: &str = "../test/shaders_slang/crt/crt-royale.slangp";
104
105    // const FILTER_PATH: &str =
106    //     "../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp";
107
108    fn do_test<T: RenderTest>() -> anyhow::Result<()> {
109        let mut test = T::new(IMAGE_PATH.as_ref())?;
110        let image = test.render(FILTER_PATH.as_ref(), ShaderFeatures::NONE, 100)?;
111
112        let out = File::create("out.png")?;
113        image.write_with_encoder(PngEncoder::new(out))?;
114        Ok(())
115    }
116
117    #[test]
118    #[cfg(all(windows, feature = "d3d11"))]
119    pub fn test_d3d11() -> anyhow::Result<()> {
120        do_test::<crate::render::d3d11::Direct3D11>()
121    }
122
123    #[test]
124    #[cfg(feature = "wgpu")]
125    pub fn test_wgpu() -> anyhow::Result<()> {
126        do_test::<crate::render::wgpu::Wgpu>()
127    }
128
129    #[test]
130    #[cfg(feature = "vulkan")]
131    pub fn test_vk() -> anyhow::Result<()> {
132        do_test::<crate::render::vk::Vulkan>()
133    }
134
135    #[test]
136    #[cfg(feature = "opengl")]
137    pub fn test_gl3() -> anyhow::Result<()> {
138        do_test::<crate::render::gl::OpenGl3>()
139    }
140
141    #[test]
142    #[cfg(feature = "opengl")]
143    pub fn test_gl4() -> anyhow::Result<()> {
144        do_test::<crate::render::gl::OpenGl4>()
145    }
146
147    #[test]
148    #[cfg(all(target_vendor = "apple", feature = "metal"))]
149    pub fn test_metal() -> anyhow::Result<()> {
150        do_test::<crate::render::mtl::Metal>()
151    }
152
153    #[test]
154    #[cfg(all(windows, feature = "d3d9"))]
155    pub fn test_d3d9() -> anyhow::Result<()> {
156        do_test::<crate::render::d3d9::Direct3D9>()
157    }
158
159    #[test]
160    #[cfg(all(windows, feature = "d3d12"))]
161    pub fn test_d3d12() -> anyhow::Result<()> {
162        do_test::<crate::render::d3d12::Direct3D12>()
163    }
164
165    pub fn compare<A: RenderTest, B: RenderTest>() -> anyhow::Result<()> {
166        let mut a = A::new(IMAGE_PATH.as_ref())?;
167        let mut b = B::new(IMAGE_PATH.as_ref())?;
168
169        let a_image = a.render(FILTER_PATH.as_ref(), ShaderFeatures::NONE, 100)?;
170        let b_image = b.render(FILTER_PATH.as_ref(), ShaderFeatures::NONE, 100)?;
171
172        let similarity = image_compare::rgba_hybrid_compare(&a_image, &b_image)?;
173        assert!(similarity.score > 0.95);
174        Ok(())
175    }
176}