cli(render): add ability to specify params and passes enabled

This commit is contained in:
chyyran 2024-09-26 21:08:39 -04:00 committed by Ronny Chan
parent 3993f57271
commit 91f8089277
11 changed files with 297 additions and 36 deletions

29
CLI.md
View file

@ -46,6 +46,19 @@ Options:
-p, --preset <PRESET>
The path to the shader preset to load
-w, --wildcards <WILDCARDS>...
Additional wildcard options, comma separated with equals signs. The PRESET and PRESET_DIR wildcards are always added to the preset parsing context.
For example, CONTENT-DIR=MyVerticalGames,GAME=mspacman
--params <PARAMS>...
Parameters to pass to the shader preset, comma separated with equals signs.
For example, crt_gamma=2.5,halation_weight=0.001
--passes-enabled <PASSES_ENABLED>
Set the number of passes enabled for the preset
-i, --image <IMAGE>
The path to the input image
@ -57,10 +70,11 @@ Options:
-r, --runtime <RUNTIME>
The runtime to use to render the shader preset
[possible values: opengl3, opengl4, vulkan, wgpu, d3d9, d3d11, d3d12, metal]
[possible values: opengl3, opengl4, vulkan, wgpu, d3d9, d3d11, d3d12]
-h, --help
Print help (see a summary with '-h')
```
The `render` command can be used to apply a shader preset to an image. The available runtimes will depend on the platform
@ -93,6 +107,19 @@ Options:
-p, --preset <PRESET>
The path to the shader preset to load
-w, --wildcards <WILDCARDS>...
Additional wildcard options, comma separated with equals signs. The PRESET and PRESET_DIR wildcards are always added to the preset parsing context.
For example, CONTENT-DIR=MyVerticalGames,GAME=mspacman
--params <PARAMS>...
Parameters to pass to the shader preset, comma separated with equals signs.
For example, crt_gamma=2.5,halation_weight=0.001
--passes-enabled <PASSES_ENABLED>
Set the number of passes enabled for the preset
-i, --image <IMAGE>
The path to the input image

View file

@ -7,6 +7,8 @@ use librashader::reflect::cross::{GlslVersion, HlslShaderModel, MslVersion, Spir
use librashader::reflect::naga::{Naga, NagaLoweringOptions};
use librashader::reflect::semantics::ShaderSemantics;
use librashader::reflect::{CompileShader, FromCompilation, ReflectShader, SpirvCompilation};
use librashader::{FastHashMap, ShortString};
use librashader_runtime::parameters::RuntimeParameters;
use librashader_test::render::RenderTest;
use std::path::{Path, PathBuf};
@ -28,6 +30,21 @@ enum Commands {
/// The path to the shader preset to load.
#[arg(short, long)]
preset: PathBuf,
/// Additional wildcard options, comma separated with equals signs. The PRESET and PRESET_DIR
/// wildcards are always added to the preset parsing context.
///
/// For example, CONTENT-DIR=MyVerticalGames,GAME=mspacman
#[arg(short, long, value_delimiter = ',', num_args = 1..)]
wildcards: Option<Vec<String>>,
/// Parameters to pass to the shader preset, comma separated with equals signs.
///
/// For example, crt_gamma=2.5,halation_weight=0.001
#[arg(long, value_delimiter = ',', num_args = 1..)]
params: Option<Vec<String>>,
/// Set the number of passes enabled for the preset.
#[arg(long)]
passes_enabled: Option<usize>,
/// The path to the input image.
#[arg(short, long)]
image: PathBuf,
@ -49,6 +66,20 @@ enum Commands {
/// The path to the shader preset to load.
#[arg(short, long)]
preset: PathBuf,
/// Additional wildcard options, comma separated with equals signs. The PRESET and PRESET_DIR
/// wildcards are always added to the preset parsing context.
///
/// For example, CONTENT-DIR=MyVerticalGames,GAME=mspacman
#[arg(short, long, value_delimiter = ',', num_args = 1..)]
wildcards: Option<Vec<String>>,
/// Parameters to pass to the shader preset, comma separated with equals signs.
///
/// For example, crt_gamma=2.5,halation_weight=0.001
#[arg(long, value_delimiter = ',', num_args = 1..)]
params: Option<Vec<String>>,
/// Set the number of passes enabled for the preset.
#[arg(long)]
passes_enabled: Option<usize>,
/// The path to the input image.
#[arg(short, long)]
image: PathBuf,
@ -241,12 +272,22 @@ pub fn main() -> Result<(), anyhow::Error> {
Commands::Render {
frame,
preset,
wildcards,
params,
passes_enabled,
image,
out,
runtime,
} => {
let test: &mut dyn RenderTest = get_runtime!(runtime, image);
let image = test.render(preset.as_path(), frame)?;
let preset = get_shader_preset(preset, wildcards)?;
let params = parse_params(params)?;
let image = test.render_with_preset_and_params(
preset,
frame,
Some(&|rp| set_params(rp, &params, passes_enabled)),
)?;
if out.as_path() == Path::new("-") {
let out = std::io::stdout();
@ -258,6 +299,9 @@ pub fn main() -> Result<(), anyhow::Error> {
Commands::Compare {
frame,
preset,
wildcards,
params,
passes_enabled,
image,
left,
right,
@ -265,9 +309,22 @@ pub fn main() -> Result<(), anyhow::Error> {
} => {
let left: &mut dyn RenderTest = get_runtime!(left, image);
let right: &mut dyn RenderTest = get_runtime!(right, image);
let params = parse_params(params)?;
let left_preset = get_shader_preset(preset.clone(), wildcards.clone())?;
let left_image = left.render_with_preset_and_params(
left_preset,
frame,
Some(&|rp| set_params(rp, &params, passes_enabled)),
)?;
let right_preset = get_shader_preset(preset.clone(), wildcards.clone())?;
let right_image = right.render_with_preset_and_params(
right_preset,
frame,
Some(&|rp| set_params(rp, &params, passes_enabled)),
)?;
let left_image = left.render(preset.as_path(), frame)?;
let right_image = right.render(preset.as_path(), frame)?;
let similarity = image_compare::rgba_hybrid_compare(&left_image, &right_image)?;
print!("{}", similarity.score);
@ -312,7 +369,8 @@ pub fn main() -> Result<(), anyhow::Error> {
librashader::reflect::targets::GLSL::from_compilation(compilation)?;
compilation.validate()?;
let version = version.map(|s| parse_glsl_version(&s))
let version = version
.map(|s| parse_glsl_version(&s))
.unwrap_or(Ok(GlslVersion::Glsl330))?;
let output = compilation.compile(version)?;
@ -326,7 +384,8 @@ pub fn main() -> Result<(), anyhow::Error> {
librashader::reflect::targets::HLSL::from_compilation(compilation)?;
compilation.validate()?;
let shader_model = version.map(|s| parse_hlsl_version(&s))
let shader_model = version
.map(|s| parse_hlsl_version(&s))
.unwrap_or(Ok(HlslShaderModel::ShaderModel5_0))?;
let output = compilation.compile(Some(shader_model))?;
@ -356,7 +415,8 @@ pub fn main() -> Result<(), anyhow::Error> {
>>::from_compilation(compilation)?;
compilation.validate()?;
let version = version.map(|s| parse_msl_version(&s))
let version = version
.map(|s| parse_msl_version(&s))
.unwrap_or(Ok(MslVersion::new(1, 2, 0)))?;
let output = compilation.compile(Some(version))?;
@ -459,6 +519,49 @@ fn get_shader_preset(
Ok(preset)
}
fn parse_params(
assignments: Option<Vec<String>>,
) -> anyhow::Result<Option<FastHashMap<ShortString, f32>>> {
let Some(assignments) = assignments else {
return Ok(None);
};
let mut map = FastHashMap::default();
for string in assignments {
let Some((left, right)) = string.split_once("=") else {
return Err(anyhow!("Encountered invalid parameter string {string}"));
};
let value = right
.parse::<f32>()
.map_err(|_| anyhow!("Encountered invalid parameter value: {right}"))?;
map.insert(ShortString::from(left), value);
}
Ok(Some(map))
}
fn set_params(
params: &RuntimeParameters,
assignments: &Option<FastHashMap<ShortString, f32>>,
passes_enabled: Option<usize>,
) {
if let Some(passes_enabled) = passes_enabled {
params.set_passes_enabled(passes_enabled)
};
let Some(assignments) = assignments else {
return;
};
params.update_parameters(|params| {
for (key, param) in assignments {
params.insert(key.clone(), *param);
}
});
}
fn spirv_to_dis(spirv: Vec<u32>) -> anyhow::Result<String> {
let binary = spq_spvasm::SpirvBinary::from(spirv);
spq_spvasm::Disassembler::new()
@ -473,7 +576,7 @@ fn spirv_to_dis(spirv: Vec<u32>) -> anyhow::Result<String> {
fn parse_glsl_version(version_str: &str) -> anyhow::Result<GlslVersion> {
if version_str.contains("es") {
let Some(version) = version_str.strip_suffix("es").map(|s| s.trim()) else {
return Err(anyhow!("Unknown GLSL version"))
return Err(anyhow!("Unknown GLSL version"));
};
Ok(match version {
@ -516,7 +619,9 @@ fn version_to_usize(version_str: &str) -> anyhow::Result<usize> {
version_str
};
let version = version.parse::<usize>().map_err(|_| anyhow!("Invalid version string"))?;
let version = version
.parse::<usize>()
.map_err(|_| anyhow!("Invalid version string"))?;
Ok(version)
}
@ -562,4 +667,4 @@ fn parse_msl_version(version_str: &str) -> anyhow::Result<MslVersion> {
}
_ => return Err(anyhow!("Unknown MSL version")),
})
}
}

View file

@ -2,6 +2,7 @@ use crate::render::RenderTest;
use anyhow::anyhow;
use image::RgbaImage;
use librashader::runtime::d3d11::*;
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader::runtime::{Size, Viewport};
use std::path::Path;
@ -13,18 +14,28 @@ impl RenderTest for Direct3D11 {
Direct3D11::new(path)
}
fn render(&mut self, path: &Path, frame_count: usize) -> anyhow::Result<RgbaImage> {
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
) -> anyhow::Result<image::RgbaImage> {
let (renderbuffer, rtv) = self.create_renderbuffer(self.image_bytes.size)?;
unsafe {
let mut filter_chain = FilterChain::load_from_path(
path,
let mut filter_chain = FilterChain::load_from_preset(
preset,
&self.device,
Some(&FilterChainOptions {
force_no_mipmaps: false,
disable_cache: false,
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
filter_chain.frame(
None,
&self.image_srv,
@ -78,6 +89,7 @@ impl RenderTest for Direct3D11 {
}
}
use librashader::presets::ShaderPreset;
use librashader_runtime::image::{Image, UVDirection};
use windows::{
Win32::Foundation::*, Win32::Graphics::Direct3D::*, Win32::Graphics::Direct3D11::*,

View file

@ -6,10 +6,12 @@ use crate::render::RenderTest;
use anyhow::anyhow;
use d3d12_descriptor_heap::{D3D12DescriptorHeap, D3D12DescriptorHeapSlot};
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::d3d12::{
D3D12InputImage, D3D12OutputView, FilterChain, FilterChainOptions,
};
use librashader::runtime::Viewport;
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader_runtime::image::{Image, PixelFormat, UVDirection, BGRA8};
use std::path::Path;
use windows::core::Interface;
@ -58,7 +60,12 @@ impl RenderTest for Direct3D12 {
Direct3D12::new(path)
}
fn render(&mut self, path: &Path, frame_count: usize) -> anyhow::Result<RgbaImage> {
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
) -> anyhow::Result<image::RgbaImage> {
unsafe {
let descriptor = self.rtv_heap.allocate_descriptor()?;
@ -72,8 +79,8 @@ impl RenderTest for Direct3D12 {
let fence_event = CreateEventA(None, false, false, None)?;
let fence: ID3D12Fence = self.device.CreateFence(0, D3D12_FENCE_FLAG_NONE)?;
let mut filter_chain = FilterChain::load_from_path(
path,
let mut filter_chain = FilterChain::load_from_preset(
preset,
&self.device,
Some(&FilterChainOptions {
force_hlsl_pipeline: false,
@ -82,6 +89,10 @@ impl RenderTest for Direct3D12 {
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let mut output_texture = None;
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,

View file

@ -1,8 +1,10 @@
use crate::render::RenderTest;
use anyhow::anyhow;
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::d3d9::{FilterChain, FilterChainOptions};
use librashader::runtime::Viewport;
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader_runtime::image::{Image, PixelFormat, UVDirection, BGRA8};
use std::path::Path;
use windows::Win32::Foundation::{HWND, TRUE};
@ -29,10 +31,15 @@ impl RenderTest for Direct3D9 {
Direct3D9::new(path)
}
fn render(&mut self, path: &Path, frame_count: usize) -> anyhow::Result<RgbaImage> {
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
) -> anyhow::Result<image::RgbaImage> {
unsafe {
let mut filter_chain = FilterChain::load_from_path(
path,
let mut filter_chain = FilterChain::load_from_preset(
preset,
&self.device,
Some(&FilterChainOptions {
force_no_mipmaps: false,
@ -40,6 +47,10 @@ impl RenderTest for Direct3D9 {
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let mut render_texture = None;
self.device.CreateTexture(

View file

@ -5,8 +5,10 @@ use crate::render::RenderTest;
use anyhow::anyhow;
use glow::{HasContext, PixelPackData, PixelUnpackData};
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::gl::{FilterChain, FilterChainOptions, GLImage};
use librashader::runtime::Viewport;
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader_runtime::image::{Image, UVDirection, RGBA8};
use std::path::Path;
use std::sync::Arc;
@ -28,10 +30,15 @@ impl RenderTest for OpenGl3 {
OpenGl3::new(path)
}
fn render(&mut self, path: &Path, frame_count: usize) -> anyhow::Result<RgbaImage> {
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
) -> anyhow::Result<image::RgbaImage> {
let mut filter_chain = unsafe {
FilterChain::load_from_path(
path,
FilterChain::load_from_preset(
preset,
Arc::clone(&self.0.context.gl),
Some(&FilterChainOptions {
glsl_version: 330,
@ -42,6 +49,10 @@ impl RenderTest for OpenGl3 {
)
}?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
Ok(self.0.render(&mut filter_chain, frame_count)?)
}
}
@ -54,10 +65,15 @@ impl RenderTest for OpenGl4 {
OpenGl4::new(path)
}
fn render(&mut self, path: &Path, frame_count: usize) -> anyhow::Result<RgbaImage> {
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
) -> anyhow::Result<image::RgbaImage> {
let mut filter_chain = unsafe {
FilterChain::load_from_path(
path,
FilterChain::load_from_preset(
preset,
Arc::clone(&self.0.context.gl),
Some(&FilterChainOptions {
glsl_version: 460,
@ -68,6 +84,10 @@ impl RenderTest for OpenGl4 {
)
}?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
Ok(self.0.render(&mut filter_chain, frame_count)?)
}
}

View file

@ -19,6 +19,8 @@ pub mod wgpu;
#[cfg(all(target_vendor = "apple", feature = "metal"))]
pub mod mtl;
use librashader::presets::ShaderPreset;
use librashader_runtime::parameters::RuntimeParameters;
use std::path::Path;
/// Test harness to set up a device, render a triangle, and apply a shader
@ -36,7 +38,41 @@ pub trait RenderTest {
/// For testing purposes, it is often that a single image will be reused with multiple
/// shader presets, so the actual image that a shader will be applied to
/// will often be part of the test harness object.
fn render(&mut self, path: &Path, frame_count: usize) -> anyhow::Result<image::RgbaImage>;
fn render(&mut self, path: &Path, frame_count: usize) -> anyhow::Result<image::RgbaImage> {
let preset = ShaderPreset::try_parse(path)?;
self.render_with_preset(preset, frame_count)
}
/// Render a shader onto an image buffer, applying the provided shader.
///
/// The test should render in linear colour space for proper comparison against
/// backends.
///
/// For testing purposes, it is often that a single image will be reused with multiple
/// shader presets, so the actual image that a shader will be applied to
/// will often be part of the test harness object.
fn render_with_preset(
&mut self,
preset: ShaderPreset,
frame_count: usize,
) -> anyhow::Result<image::RgbaImage> {
self.render_with_preset_and_params(preset, frame_count, None)
}
/// Render a shader onto an image buffer, applying the provided shader.
///
/// The test should render in linear colour space for proper comparison against
/// backends.
///
/// For testing purposes, it is often that a single image will be reused with multiple
/// shader presets, so the actual image that a shader will be applied to
/// will often be part of the test harness object.
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
) -> anyhow::Result<image::RgbaImage>;
}
#[cfg(test)]

View file

@ -1,8 +1,10 @@
use crate::render::RenderTest;
use anyhow::anyhow;
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::mtl::{FilterChain, FilterChainOptions};
use librashader::runtime::Viewport;
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader_runtime::image::{Image, PixelFormat, UVDirection, BGRA8, RGBA8};
use objc2::ffi::NSUInteger;
use objc2::rc::Retained;
@ -29,7 +31,12 @@ impl RenderTest for Metal {
Metal::new(path)
}
fn render(&mut self, path: &Path, frame_count: usize) -> anyhow::Result<RgbaImage> {
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
) -> anyhow::Result<image::RgbaImage> {
let queue = self
.device
.newCommandQueue()
@ -39,14 +46,18 @@ impl RenderTest for Metal {
.commandBuffer()
.ok_or_else(|| anyhow!("Unable to create command buffer"))?;
let mut filter_chain = FilterChain::load_from_path(
path,
let mut filter_chain = FilterChain::load_from_preset(
preset,
&queue,
Some(&FilterChainOptions {
force_no_mipmaps: false,
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let render_texture = unsafe {
let texture_descriptor =
MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped(

View file

@ -5,8 +5,10 @@ use anyhow::anyhow;
use ash::vk;
use gpu_allocator::MemoryLocation;
use image::RgbaImage;
use librashader::presets::ShaderPreset;
use librashader::runtime::vk::{FilterChain, FilterChainOptions, VulkanImage};
use librashader::runtime::Viewport;
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use librashader_runtime::image::{Image, UVDirection, BGRA8};
use std::path::Path;
@ -30,10 +32,15 @@ impl RenderTest for Vulkan {
Vulkan::new(path)
}
fn render(&mut self, path: &Path, frame_count: usize) -> anyhow::Result<RgbaImage> {
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
) -> anyhow::Result<image::RgbaImage> {
unsafe {
let mut filter_chain = FilterChain::load_from_path(
path,
let mut filter_chain = FilterChain::load_from_preset(
preset,
&self.vk,
Some(&FilterChainOptions {
frames_in_flight: 3,
@ -43,6 +50,10 @@ impl RenderTest for Vulkan {
}),
)?;
if let Some(setter) = param_setter {
setter(filter_chain.parameters());
}
let image_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::B8G8R8A8_UNORM)

View file

@ -14,6 +14,8 @@ use wgpu_types::{
ImageDataLayout, Maintain, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
};
use librashader::presets::ShaderPreset;
use librashader::runtime::{FilterChainParameters, RuntimeParameters};
use parking_lot::Mutex;
pub struct Wgpu {
@ -54,9 +56,14 @@ impl RenderTest for Wgpu {
Wgpu::new(path)
}
fn render(&mut self, path: &Path, frame_count: usize) -> anyhow::Result<RgbaImage> {
let mut chain = FilterChain::load_from_path(
path,
fn render_with_preset_and_params(
&mut self,
preset: ShaderPreset,
frame_count: usize,
param_setter: Option<&dyn Fn(&RuntimeParameters)>,
) -> anyhow::Result<image::RgbaImage> {
let mut chain = FilterChain::load_from_preset(
preset,
Arc::clone(&self.device),
Arc::clone(&self.queue),
Some(&FilterChainOptions {
@ -65,7 +72,9 @@ impl RenderTest for Wgpu {
adapter_info: None,
}),
)?;
if let Some(setter) = param_setter {
setter(&chain.parameters());
}
let mut cmd = self
.device
.create_command_encoder(&CommandEncoderDescriptor { label: None });

View file

@ -42,6 +42,7 @@ impl RuntimeParameters {
/// Set a runtime parameter.
///
/// This is a relatively slow operation as it will be synchronized across threads.
/// If updating multiple parameters, see [`RuntimeParameters::update_parameters`].
pub fn set_parameter_value(&self, name: &str, new_value: f32) -> Option<f32> {
let mut updated_map = FastHashMap::clone(&self.parameters.load());
@ -57,6 +58,13 @@ impl RuntimeParameters {
}
}
/// Update multiple runtime parameters atomically through a function.
pub fn update_parameters(&self, updater: impl FnOnce(&mut FastHashMap<ShortString, f32>)) {
let mut updated_map = FastHashMap::clone(&self.parameters.load());
updater(&mut updated_map);
self.parameters.store(Arc::new(updated_map));
}
/// Get a reference to the runtime parameters.
pub fn parameters(&self) -> Arc<FastHashMap<ShortString, f32>> {
self.parameters.load_full()