diff --git a/Cargo.lock b/Cargo.lock
index d140377..d63f2f1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -123,6 +123,12 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
 
+[[package]]
+name = "arc-swap"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+
 [[package]]
 name = "arg_enum_proc_macro"
 version = "0.3.4"
@@ -1749,6 +1755,7 @@ dependencies = [
 name = "librashader-runtime"
 version = "0.3.3"
 dependencies = [
+ "arc-swap",
  "array-concat",
  "bytemuck",
  "image",
diff --git a/librashader-capi/src/runtime/d3d11/filter_chain.rs b/librashader-capi/src/runtime/d3d11/filter_chain.rs
index 8a5aaec..1345616 100644
--- a/librashader-capi/src/runtime/d3d11/filter_chain.rs
+++ b/librashader-capi/src/runtime/d3d11/filter_chain.rs
@@ -260,14 +260,14 @@ extern_fn! {
         chain: *mut libra_d3d11_filter_chain_t,
         param_name: *const c_char,
         value: f32
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         assert_non_null!(param_name);
         unsafe {
             let name = CStr::from_ptr(param_name);
             let name = name.to_str()?;
 
-            if chain.set_parameter(name, value).is_none() {
+            if chain.parameters().set_parameter(name, value).is_none() {
                 return LibrashaderError::UnknownShaderParameter(param_name).export()
             }
         }
@@ -282,17 +282,17 @@ extern_fn! {
     /// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d11_filter_chain_t`.
     /// - `param_name` must be either null or a null terminated string.
     fn libra_d3d11_filter_chain_get_param(
-        chain: *mut libra_d3d11_filter_chain_t,
+        chain: *const libra_d3d11_filter_chain_t,
         param_name: *const c_char,
         out: *mut MaybeUninit<f32>
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         assert_non_null!(param_name);
         unsafe {
             let name = CStr::from_ptr(param_name);
             let name = name.to_str()?;
 
-            let Some(value) = chain.get_parameter(name) else {
+            let Some(value) = chain.parameters().get_parameter(name) else {
                 return LibrashaderError::UnknownShaderParameter(param_name).export()
             };
 
@@ -309,9 +309,9 @@ extern_fn! {
     fn libra_d3d11_filter_chain_set_active_pass_count(
         chain: *mut libra_d3d11_filter_chain_t,
         value: u32
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
-        chain.set_enabled_pass_count(value as usize);
+    ) |chain| {
+        assert_some_ptr!(chain);
+        chain.parameters().set_passes_enabled(value as usize);
     }
 }
 
@@ -321,12 +321,12 @@ extern_fn! {
     /// ## Safety
     /// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d11_filter_chain_t`.
     fn libra_d3d11_filter_chain_get_active_pass_count(
-        chain: *mut libra_d3d11_filter_chain_t,
+        chain: *const libra_d3d11_filter_chain_t,
         out: *mut MaybeUninit<u32>
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         unsafe {
-            let value = chain.get_enabled_pass_count();
+            let value = chain.parameters().passes_enabled();
             out.write(MaybeUninit::new(value as u32))
         }
     }
diff --git a/librashader-capi/src/runtime/d3d12/filter_chain.rs b/librashader-capi/src/runtime/d3d12/filter_chain.rs
index ec00429..8a115d5 100644
--- a/librashader-capi/src/runtime/d3d12/filter_chain.rs
+++ b/librashader-capi/src/runtime/d3d12/filter_chain.rs
@@ -280,14 +280,14 @@ extern_fn! {
         chain: *mut libra_d3d12_filter_chain_t,
         param_name: *const c_char,
         value: f32
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         assert_non_null!(param_name);
         unsafe {
             let name = CStr::from_ptr(param_name);
             let name = name.to_str()?;
 
-            if chain.set_parameter(name, value).is_none() {
+            if chain.parameters().set_parameter(name, value).is_none() {
                 return LibrashaderError::UnknownShaderParameter(param_name).export()
             }
         }
@@ -302,17 +302,17 @@ extern_fn! {
     /// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d12_filter_chain_t`.
     /// - `param_name` must be either null or a null terminated string.
     fn libra_d3d12_filter_chain_get_param(
-        chain: *mut libra_d3d12_filter_chain_t,
+        chain: *const libra_d3d12_filter_chain_t,
         param_name: *const c_char,
         out: *mut MaybeUninit<f32>
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         assert_non_null!(param_name);
         unsafe {
             let name = CStr::from_ptr(param_name);
             let name = name.to_str()?;
 
-            let Some(value) = chain.get_parameter(name) else {
+            let Some(value) = chain.parameters().get_parameter(name) else {
                 return LibrashaderError::UnknownShaderParameter(param_name).export()
             };
 
@@ -329,9 +329,9 @@ extern_fn! {
     fn libra_d3d12_filter_chain_set_active_pass_count(
         chain: *mut libra_d3d12_filter_chain_t,
         value: u32
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
-        chain.set_enabled_pass_count(value as usize);
+    ) |chain| {
+        assert_some_ptr!(chain);
+        chain.parameters().set_passes_enabled(value as usize);
     }
 }
 
@@ -341,12 +341,12 @@ extern_fn! {
     /// ## Safety
     /// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d12_filter_chain_t`.
     fn libra_d3d12_filter_chain_get_active_pass_count(
-        chain: *mut libra_d3d12_filter_chain_t,
+        chain: *const libra_d3d12_filter_chain_t,
         out: *mut MaybeUninit<u32>
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         unsafe {
-            let value = chain.get_enabled_pass_count();
+            let value = chain.parameters().passes_enabled();
             out.write(MaybeUninit::new(value as u32))
         }
     }
diff --git a/librashader-capi/src/runtime/d3d9/filter_chain.rs b/librashader-capi/src/runtime/d3d9/filter_chain.rs
index 8750e7d..e9770b3 100644
--- a/librashader-capi/src/runtime/d3d9/filter_chain.rs
+++ b/librashader-capi/src/runtime/d3d9/filter_chain.rs
@@ -170,14 +170,14 @@ extern_fn! {
         chain: *mut libra_d3d9_filter_chain_t,
         param_name: *const c_char,
         value: f32
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         assert_non_null!(param_name);
         unsafe {
             let name = CStr::from_ptr(param_name);
             let name = name.to_str()?;
 
-            if chain.set_parameter(name, value).is_none() {
+            if chain.parameters().set_parameter(name, value).is_none() {
                 return LibrashaderError::UnknownShaderParameter(param_name).export()
             }
         }
@@ -192,17 +192,17 @@ extern_fn! {
     /// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
     /// - `param_name` must be either null or a null terminated string.
     fn libra_d3d9_filter_chain_get_param(
-        chain: *mut libra_d3d9_filter_chain_t,
+        chain: *const libra_d3d9_filter_chain_t,
         param_name: *const c_char,
         out: *mut MaybeUninit<f32>
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         assert_non_null!(param_name);
         unsafe {
             let name = CStr::from_ptr(param_name);
             let name = name.to_str()?;
 
-            let Some(value) = chain.get_parameter(name) else {
+            let Some(value) = chain.parameters().get_parameter(name) else {
                 return LibrashaderError::UnknownShaderParameter(param_name).export()
             };
 
@@ -219,9 +219,9 @@ extern_fn! {
     fn libra_d3d9_filter_chain_set_active_pass_count(
         chain: *mut libra_d3d9_filter_chain_t,
         value: u32
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
-        chain.set_enabled_pass_count(value as usize);
+    ) |chain| {
+        assert_some_ptr!(chain);
+        chain.parameters().set_passes_enabled(value as usize);
     }
 }
 
@@ -231,12 +231,12 @@ extern_fn! {
     /// ## Safety
     /// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
     fn libra_d3d9_filter_chain_get_active_pass_count(
-        chain: *mut libra_d3d9_filter_chain_t,
+        chain: *const libra_d3d9_filter_chain_t,
         out: *mut MaybeUninit<u32>
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         unsafe {
-            let value = chain.get_enabled_pass_count();
+            let value = chain.parameters().passes_enabled();
             out.write(MaybeUninit::new(value as u32))
         }
     }
diff --git a/librashader-capi/src/runtime/gl/filter_chain.rs b/librashader-capi/src/runtime/gl/filter_chain.rs
index e23c06d..f42ba47 100644
--- a/librashader-capi/src/runtime/gl/filter_chain.rs
+++ b/librashader-capi/src/runtime/gl/filter_chain.rs
@@ -227,14 +227,14 @@ extern_fn! {
         chain: *mut libra_gl_filter_chain_t,
         param_name: *const c_char,
         value: f32
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         assert_non_null!(param_name);
         unsafe {
             let name = CStr::from_ptr(param_name);
             let name = name.to_str()?;
 
-            if chain.set_parameter(name, value).is_none() {
+            if chain.parameters().set_parameter(name, value).is_none() {
                 return LibrashaderError::UnknownShaderParameter(param_name).export()
             }
         }
@@ -249,17 +249,17 @@ extern_fn! {
     /// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_gl_filter_chain_t`.
     /// - `param_name` must be either null or a null terminated string.
     fn libra_gl_filter_chain_get_param(
-        chain: *mut libra_gl_filter_chain_t,
+        chain: *const libra_gl_filter_chain_t,
         param_name: *const c_char,
         out: *mut MaybeUninit<f32>
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         assert_non_null!(param_name);
         unsafe {
             let name = CStr::from_ptr(param_name);
             let name = name.to_str()?;
 
-            let Some(value) = chain.get_parameter(name) else {
+            let Some(value) = chain.parameters().get_parameter(name) else {
                 return LibrashaderError::UnknownShaderParameter(param_name).export()
             };
 
@@ -276,9 +276,9 @@ extern_fn! {
     fn libra_gl_filter_chain_set_active_pass_count(
         chain: *mut libra_gl_filter_chain_t,
         value: u32
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
-        chain.set_enabled_pass_count(value as usize);
+    ) |chain| {
+        assert_some_ptr!(chain);
+        chain.parameters().set_passes_enabled(value as usize);
     }
 }
 
@@ -292,7 +292,7 @@ extern_fn! {
         out: *mut MaybeUninit<u32>
     ) mut |chain| {
         assert_some_ptr!(mut chain);
-        let value = chain.get_enabled_pass_count();
+        let value = chain.parameters().passes_enabled();
         unsafe {
             out.write(MaybeUninit::new(value as u32))
         }
diff --git a/librashader-capi/src/runtime/mtl/filter_chain.rs b/librashader-capi/src/runtime/mtl/filter_chain.rs
index d885f92..5821e89 100644
--- a/librashader-capi/src/runtime/mtl/filter_chain.rs
+++ b/librashader-capi/src/runtime/mtl/filter_chain.rs
@@ -229,13 +229,13 @@ extern_fn! {
         chain: *mut libra_mtl_filter_chain_t,
         param_name: *const c_char,
         value: f32
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         unsafe {
             let name = CStr::from_ptr(param_name);
             let name = name.to_str()?;
 
-            if chain.set_parameter(name, value).is_none() {
+            if chain.parameters().set_parameter(name, value).is_none() {
                 return LibrashaderError::UnknownShaderParameter(param_name).export()
             }
         }
@@ -250,17 +250,17 @@ extern_fn! {
     /// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
     /// - `param_name` must be either null or a null terminated string.
     fn libra_mtl_filter_chain_get_param(
-        chain: *mut libra_mtl_filter_chain_t,
+        chain: *const libra_mtl_filter_chain_t,
         param_name: *const c_char,
         out: *mut MaybeUninit<f32>
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
 
         unsafe {
             let name = CStr::from_ptr(param_name);
             let name = name.to_str()?;
 
-            let Some(value) = chain.get_parameter(name) else {
+            let Some(value) = chain.parameters().get_parameter(name) else {
                 return LibrashaderError::UnknownShaderParameter(param_name).export()
             };
 
@@ -277,9 +277,9 @@ extern_fn! {
     fn libra_mtl_filter_chain_set_active_pass_count(
         chain: *mut libra_mtl_filter_chain_t,
         value: u32
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
-        chain.set_enabled_pass_count(value as usize);
+    ) |chain| {
+        assert_some_ptr!(chain);
+        chain.parameters().set_passes_enabled(value as usize);
     }
 }
 
@@ -289,11 +289,11 @@ extern_fn! {
     /// ## Safety
     /// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
     fn libra_mtl_filter_chain_get_active_pass_count(
-        chain: *mut libra_mtl_filter_chain_t,
+        chain: *const libra_mtl_filter_chain_t,
         out: *mut MaybeUninit<u32>
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
-        let value = chain.get_enabled_pass_count();
+    ) |chain| {
+        assert_some_ptr!(chain);
+        let value = chain.parameters().passes_enabled();
         unsafe {
             out.write(MaybeUninit::new(value as u32))
         }
diff --git a/librashader-capi/src/runtime/vk/filter_chain.rs b/librashader-capi/src/runtime/vk/filter_chain.rs
index 0e9fe50..20040d3 100644
--- a/librashader-capi/src/runtime/vk/filter_chain.rs
+++ b/librashader-capi/src/runtime/vk/filter_chain.rs
@@ -306,14 +306,14 @@ extern_fn! {
         chain: *mut libra_vk_filter_chain_t,
         param_name: *const c_char,
         value: f32
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         assert_non_null!(param_name);
         unsafe {
             let name = CStr::from_ptr(param_name);
             let name = name.to_str()?;
 
-            if chain.set_parameter(name, value).is_none() {
+            if chain.parameters().set_parameter(name, value).is_none() {
                 return LibrashaderError::UnknownShaderParameter(param_name).export()
             }
         }
@@ -328,17 +328,17 @@ extern_fn! {
     /// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_vk_filter_chain_t`.
     /// - `param_name` must be either null or a null terminated string.
     fn libra_vk_filter_chain_get_param(
-        chain: *mut libra_vk_filter_chain_t,
+        chain: *const libra_vk_filter_chain_t,
         param_name: *const c_char,
         out: *mut MaybeUninit<f32>
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
+    ) |chain| {
+        assert_some_ptr!(chain);
         assert_non_null!(param_name);
         unsafe {
             let name = CStr::from_ptr(param_name);
             let name = name.to_str()?;
 
-            let Some(value) = chain.get_parameter(name) else {
+            let Some(value) = chain.parameters().get_parameter(name) else {
                 return LibrashaderError::UnknownShaderParameter(param_name).export()
             };
 
@@ -355,9 +355,9 @@ extern_fn! {
     fn libra_vk_filter_chain_set_active_pass_count(
         chain: *mut libra_vk_filter_chain_t,
         value: u32
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
-        chain.set_enabled_pass_count(value as usize);
+    ) |chain| {
+        assert_some_ptr!(chain);
+        chain.parameters().set_passes_enabled(value as usize);
     }
 }
 
@@ -367,11 +367,11 @@ extern_fn! {
     /// ## Safety
     /// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_vk_filter_chain_t`.
     fn libra_vk_filter_chain_get_active_pass_count(
-        chain: *mut libra_vk_filter_chain_t,
+        chain: *const libra_vk_filter_chain_t,
         out: *mut MaybeUninit<u32>
-    ) mut |chain| {
-        assert_some_ptr!(mut chain);
-        let value = chain.get_enabled_pass_count();
+    ) |chain| {
+        assert_some_ptr!(chain);
+        let value = chain.parameters().passes_enabled();
         unsafe {
             out.write(MaybeUninit::new(value as u32))
         }
diff --git a/librashader-runtime-d3d11/src/filter_chain.rs b/librashader-runtime-d3d11/src/filter_chain.rs
index 41fe0a7..169bf9a 100644
--- a/librashader-runtime-d3d11/src/filter_chain.rs
+++ b/librashader-runtime-d3d11/src/filter_chain.rs
@@ -43,11 +43,6 @@ use windows::Win32::Graphics::Direct3D11::{
 };
 use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM;
 
-pub struct FilterMutable {
-    pub(crate) passes_enabled: usize,
-    pub(crate) parameters: FastHashMap<String, f32>,
-}
-
 /// A Direct3D 11 filter chain.
 pub struct FilterChainD3D11 {
     pub(crate) common: FilterCommon,
@@ -71,7 +66,7 @@ pub(crate) struct FilterCommon {
     pub output_textures: Box<[Option<InputTexture>]>,
     pub feedback_textures: Box<[Option<InputTexture>]>,
     pub history_textures: Box<[Option<InputTexture>]>,
-    pub config: FilterMutable,
+    pub config: RuntimeParameters,
     pub disable_mipmaps: bool,
     pub(crate) draw_quad: DrawQuad,
 }
@@ -103,6 +98,7 @@ mod compile {
 }
 
 use compile::{compile_passes, ShaderPassMeta};
+use librashader_runtime::parameters::RuntimeParameters;
 
 impl FilterChainD3D11 {
     /// Load the shader preset at the given path into a filter chain.
@@ -193,14 +189,7 @@ impl FilterChainD3D11 {
                     _device: device.clone(),
                     immediate_context,
                 },
-                config: FilterMutable {
-                    passes_enabled: preset.shader_count as usize,
-                    parameters: preset
-                        .parameters
-                        .into_iter()
-                        .map(|param| (param.name, param.value))
-                        .collect(),
-                },
+                config: RuntimeParameters::new(preset.shader_count as usize, preset.parameters),
                 disable_mipmaps: options.map_or(false, |o| o.force_no_mipmaps),
                 luts,
                 samplers,
@@ -405,7 +394,7 @@ impl FilterChainD3D11 {
         frame_count: usize,
         options: Option<&FrameOptionsD3D11>,
     ) -> error::Result<()> {
-        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled);
+        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled());
 
         // Need to clone this because pushing history needs a mutable borrow.
         let immediate_context = &self.common.d3d11.immediate_context.clone();
diff --git a/librashader-runtime-d3d11/src/filter_pass.rs b/librashader-runtime-d3d11/src/filter_pass.rs
index 935872c..0c2451e 100644
--- a/librashader-runtime-d3d11/src/filter_pass.rs
+++ b/librashader-runtime-d3d11/src/filter_pass.rs
@@ -139,7 +139,7 @@ impl FilterPass {
             parent.history_textures.iter().map(|o| o.as_ref()),
             parent.luts.iter().map(|(u, i)| (*u, i.as_ref())),
             &self.source.parameters,
-            &parent.config.parameters,
+            &parent.config,
         );
     }
 
diff --git a/librashader-runtime-d3d12/src/filter_chain.rs b/librashader-runtime-d3d12/src/filter_chain.rs
index 5530621..61e3a44 100644
--- a/librashader-runtime-d3d12/src/filter_chain.rs
+++ b/librashader-runtime-d3d12/src/filter_chain.rs
@@ -60,11 +60,6 @@ use rayon::prelude::*;
 
 const MIPMAP_RESERVED_WORKHEAP_DESCRIPTORS: usize = 4096;
 
-pub struct FilterMutable {
-    pub(crate) passes_enabled: usize,
-    pub(crate) parameters: FastHashMap<String, f32>,
-}
-
 /// A Direct3D 12 filter chain.
 pub struct FilterChainD3D12 {
     pub(crate) common: FilterCommon,
@@ -92,7 +87,7 @@ pub(crate) struct FilterCommon {
     pub output_textures: Box<[Option<InputTexture>]>,
     pub feedback_textures: Box<[Option<InputTexture>]>,
     pub history_textures: Box<[Option<InputTexture>]>,
-    pub config: FilterMutable,
+    pub config: RuntimeParameters,
     // pub disable_mipmaps: bool,
     pub luts: FastHashMap<usize, LutTexture>,
     pub mipmap_gen: D3D12MipmapGen,
@@ -222,6 +217,7 @@ mod compile {
 }
 
 use compile::{compile_passes_dxil, compile_passes_hlsl, DxilShaderPassMeta, HlslShaderPassMeta};
+use librashader_runtime::parameters::RuntimeParameters;
 
 impl FilterChainD3D12 {
     /// Load the shader preset at the given path into a filter chain.
@@ -387,14 +383,7 @@ impl FilterChainD3D12 {
                 mipmap_gen,
                 root_signature,
                 draw_quad,
-                config: FilterMutable {
-                    passes_enabled: preset.shader_count as usize,
-                    parameters: preset
-                        .parameters
-                        .into_iter()
-                        .map(|param| (param.name, param.value))
-                        .collect(),
-                },
+                config: RuntimeParameters::new(preset.shader_count as usize, preset.parameters),
                 history_textures,
             },
             staging_heap,
@@ -669,6 +658,13 @@ impl FilterChainD3D12 {
     ) -> error::Result<()> {
         self.residuals.dispose();
 
+        // limit number of passes to those enabled.
+        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled());
+        let passes = &mut self.passes[0..max];
+        if passes.is_empty() {
+            return Ok(());
+        }
+
         if let Some(options) = options {
             if options.clear_history {
                 for framebuffer in &mut self.history_framebuffers {
@@ -677,22 +673,8 @@ impl FilterChainD3D12 {
             }
         }
 
-        // limit number of passes to those enabled.
-        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled);
-        let passes = &mut self.passes[0..max];
-
-        if passes.is_empty() {
-            return Ok(());
-        }
-
         let options = options.unwrap_or(&self.default_options);
 
-        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled);
-        let passes = &mut self.passes[0..max];
-        if passes.is_empty() {
-            return Ok(());
-        }
-
         let filter = passes[0].config.filter;
         let wrap_mode = passes[0].config.wrap_mode;
 
diff --git a/librashader-runtime-d3d12/src/filter_pass.rs b/librashader-runtime-d3d12/src/filter_pass.rs
index abdaa92..9552d61 100644
--- a/librashader-runtime-d3d12/src/filter_pass.rs
+++ b/librashader-runtime-d3d12/src/filter_pass.rs
@@ -122,7 +122,7 @@ impl FilterPass {
             parent.history_textures.iter().map(|o| o.as_ref()),
             parent.luts.iter().map(|(u, i)| (*u, i.as_ref())),
             &self.source.parameters,
-            &parent.config.parameters,
+            &parent.config,
         );
     }
 
diff --git a/librashader-runtime-d3d9/src/filter_chain.rs b/librashader-runtime-d3d9/src/filter_chain.rs
index fcbff44..cdaa61b 100644
--- a/librashader-runtime-d3d9/src/filter_chain.rs
+++ b/librashader-runtime-d3d9/src/filter_chain.rs
@@ -36,11 +36,6 @@ use crate::util::GetSize;
 
 use windows::Win32::Graphics::Direct3D9::{IDirect3DDevice9, IDirect3DSurface9, IDirect3DTexture9};
 
-pub struct FilterMutable {
-    pub(crate) passes_enabled: usize,
-    pub(crate) parameters: FastHashMap<String, f32>,
-}
-
 pub(crate) struct FilterCommon {
     pub(crate) d3d9: IDirect3DDevice9,
     pub(crate) luts: FastHashMap<usize, LutTexture>,
@@ -48,7 +43,7 @@ pub(crate) struct FilterCommon {
     pub output_textures: Box<[Option<D3D9InputTexture>]>,
     pub feedback_textures: Box<[Option<D3D9InputTexture>]>,
     pub history_textures: Box<[Option<D3D9InputTexture>]>,
-    pub config: FilterMutable,
+    pub config: RuntimeParameters,
     pub disable_mipmaps: bool,
     pub(crate) draw_quad: DrawQuad,
 }
@@ -90,6 +85,7 @@ mod compile {
 }
 
 use compile::{compile_passes, ShaderPassMeta};
+use librashader_runtime::parameters::RuntimeParameters;
 
 impl FilterChainD3D9 {
     fn init_passes(
@@ -255,14 +251,7 @@ impl FilterChainD3D9 {
             history_framebuffers,
             common: FilterCommon {
                 d3d9: device.clone(),
-                config: FilterMutable {
-                    passes_enabled: preset.shader_count as usize,
-                    parameters: preset
-                        .parameters
-                        .into_iter()
-                        .map(|param| (param.name, param.value))
-                        .collect(),
-                },
+                config: RuntimeParameters::new(preset.shader_count as usize, preset.parameters),
                 disable_mipmaps: options.map_or(false, |o| o.force_no_mipmaps),
                 luts,
                 samplers,
@@ -295,7 +284,8 @@ impl FilterChainD3D9 {
         frame_count: usize,
         options: Option<&FrameOptionsD3D9>,
     ) -> error::Result<()> {
-        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled);
+        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled());
+
         let passes = &mut self.passes[0..max];
         if let Some(options) = options {
             if options.clear_history {
diff --git a/librashader-runtime-d3d9/src/filter_pass.rs b/librashader-runtime-d3d9/src/filter_pass.rs
index b141cae..594401f 100644
--- a/librashader-runtime-d3d9/src/filter_pass.rs
+++ b/librashader-runtime-d3d9/src/filter_pass.rs
@@ -133,7 +133,7 @@ impl FilterPass {
             parent.history_textures.iter().map(|o| o.as_ref()),
             parent.luts.iter().map(|(u, i)| (*u, i.as_ref())),
             &self.source.parameters,
-            &parent.config.parameters,
+            &parent.config,
         );
     }
 
diff --git a/librashader-runtime-gl/src/filter_chain/filter_impl.rs b/librashader-runtime-gl/src/filter_chain/filter_impl.rs
index 3da0d51..aea776f 100644
--- a/librashader-runtime-gl/src/filter_chain/filter_impl.rs
+++ b/librashader-runtime-gl/src/filter_chain/filter_impl.rs
@@ -43,7 +43,7 @@ pub(crate) struct FilterChainImpl<T: GLInterface> {
 
 pub(crate) struct FilterCommon {
     // semantics: ReflectSemantics,
-    pub config: FilterMutable,
+    pub config: RuntimeParameters,
     pub luts: FastHashMap<usize, InputTexture>,
     pub samplers: SamplerSet,
     pub output_textures: Box<[InputTexture]>,
@@ -52,11 +52,6 @@ pub(crate) struct FilterCommon {
     pub disable_mipmaps: bool,
 }
 
-pub struct FilterMutable {
-    pub(crate) passes_enabled: usize,
-    pub(crate) parameters: FastHashMap<String, f32>,
-}
-
 impl<T: GLInterface> FilterChainImpl<T> {
     fn reflect_uniform_location(pipeline: GLuint, meta: &dyn UniformMeta) -> VariableLocation {
         let mut location = VariableLocation {
@@ -119,6 +114,7 @@ mod compile {
 }
 
 use compile::{compile_passes, ShaderPassMeta};
+use librashader_runtime::parameters::RuntimeParameters;
 
 impl<T: GLInterface> FilterChainImpl<T> {
     /// Load a filter chain from a pre-parsed `ShaderPreset`.
@@ -178,14 +174,7 @@ impl<T: GLInterface> FilterChainImpl<T> {
             history_framebuffers,
             draw_quad,
             common: FilterCommon {
-                config: FilterMutable {
-                    passes_enabled: preset.shader_count as usize,
-                    parameters: preset
-                        .parameters
-                        .into_iter()
-                        .map(|param| (param.name, param.value))
-                        .collect(),
-                },
+                config: RuntimeParameters::new(preset.shader_count as usize, preset.parameters),
                 disable_mipmaps: options.map_or(false, |o| o.force_no_mipmaps),
                 luts,
                 samplers,
@@ -274,7 +263,7 @@ impl<T: GLInterface> FilterChainImpl<T> {
         options: Option<&FrameOptionsGL>,
     ) -> error::Result<()> {
         // limit number of passes to those enabled.
-        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled);
+        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled());
         let passes = &mut self.passes[0..max];
 
         if let Some(options) = options {
diff --git a/librashader-runtime-gl/src/filter_chain/parameters.rs b/librashader-runtime-gl/src/filter_chain/parameters.rs
index cc69243..e6eb793 100644
--- a/librashader-runtime-gl/src/filter_chain/parameters.rs
+++ b/librashader-runtime-gl/src/filter_chain/parameters.rs
@@ -2,7 +2,7 @@ use crate::filter_chain::filter_impl::FilterChainImpl;
 use crate::filter_chain::inner::FilterChainDispatch;
 use crate::gl::GLInterface;
 use crate::FilterChainGL;
-use librashader_runtime::parameters::FilterChainParameters;
+use librashader_runtime::parameters::{FilterChainParameters, RuntimeParameters};
 
 impl AsRef<dyn FilterChainParameters + 'static> for FilterChainDispatch {
     fn as_ref<'a>(&'a self) -> &'a (dyn FilterChainParameters + 'static) {
@@ -23,60 +23,13 @@ impl AsMut<dyn FilterChainParameters + 'static> for FilterChainDispatch {
 }
 
 impl FilterChainParameters for FilterChainGL {
-    fn get_enabled_pass_count(&self) -> usize {
-        self.filter.as_ref().get_enabled_pass_count()
-    }
-
-    fn set_enabled_pass_count(&mut self, count: usize) {
-        self.filter.as_mut().set_enabled_pass_count(count)
-    }
-
-    fn enumerate_parameters(&self) -> ::librashader_common::map::halfbrown::Iter<String, f32> {
-        self.filter.as_ref().enumerate_parameters()
-    }
-
-    fn get_parameter(&self, parameter: &str) -> Option<f32> {
-        self.filter.as_ref().get_parameter(parameter)
-    }
-
-    fn set_parameter(&mut self, parameter: &str, new_value: f32) -> Option<f32> {
-        self.filter.as_mut().set_parameter(parameter, new_value)
+    fn parameters(&self) -> &RuntimeParameters {
+        self.filter.as_ref().parameters()
     }
 }
 
 impl<T: GLInterface> FilterChainParameters for FilterChainImpl<T> {
-    fn get_enabled_pass_count(&self) -> usize {
-        self.common.config.passes_enabled
-    }
-
-    fn set_enabled_pass_count(&mut self, count: usize) {
-        self.common.config.passes_enabled = count
-    }
-
-    fn enumerate_parameters(&self) -> ::librashader_common::map::halfbrown::Iter<String, f32> {
-        self.common.config.parameters.iter()
-    }
-
-    fn get_parameter(&self, parameter: &str) -> Option<f32> {
-        self.common
-            .config
-            .parameters
-            .get::<str>(parameter.as_ref())
-            .copied()
-    }
-
-    fn set_parameter(&mut self, parameter: &str, new_value: f32) -> Option<f32> {
-        if let Some(value) = self
-            .common
-            .config
-            .parameters
-            .get_mut::<str>(parameter.as_ref())
-        {
-            let old = *value;
-            *value = new_value;
-            Some(old)
-        } else {
-            None
-        }
+    fn parameters(&self) -> &RuntimeParameters {
+        &self.common.config
     }
 }
diff --git a/librashader-runtime-gl/src/filter_pass.rs b/librashader-runtime-gl/src/filter_pass.rs
index 42591bd..4c5d487 100644
--- a/librashader-runtime-gl/src/filter_pass.rs
+++ b/librashader-runtime-gl/src/filter_pass.rs
@@ -196,7 +196,7 @@ impl<T: GLInterface> FilterPass<T> {
             parent.history_textures.iter().map(|o| o.bound()),
             parent.luts.iter().map(|(u, i)| (*u, i)),
             &self.source.parameters,
-            &parent.config.parameters,
+            &parent.config,
         );
     }
 }
diff --git a/librashader-runtime-mtl/src/filter_chain.rs b/librashader-runtime-mtl/src/filter_chain.rs
index f135970..d7a944d 100644
--- a/librashader-runtime-mtl/src/filter_chain.rs
+++ b/librashader-runtime-mtl/src/filter_chain.rs
@@ -57,6 +57,7 @@ mod compile {
 }
 
 use compile::{compile_passes, ShaderPassMeta};
+use librashader_runtime::parameters::RuntimeParameters;
 
 /// A Metal filter chain.
 pub struct FilterChainMetal {
@@ -75,18 +76,13 @@ impl Debug for FilterChainMetal {
     }
 }
 
-pub struct FilterMutable {
-    pub passes_enabled: usize,
-    pub(crate) parameters: FastHashMap<String, f32>,
-}
-
 pub(crate) struct FilterCommon {
     pub output_textures: Box<[Option<InputTexture>]>,
     pub feedback_textures: Box<[Option<InputTexture>]>,
     pub history_textures: Box<[Option<InputTexture>]>,
     pub luts: FastHashMap<usize, LutTexture>,
     pub samplers: SamplerSet,
-    pub config: FilterMutable,
+    pub config: RuntimeParameters,
     pub internal_frame_count: i32,
     pub(crate) draw_quad: DrawQuad,
     device: Id<ProtocolObject<dyn MTLDevice>>,
@@ -301,14 +297,7 @@ impl FilterChainMetal {
             common: FilterCommon {
                 luts,
                 samplers,
-                config: FilterMutable {
-                    passes_enabled: preset.shader_count as usize,
-                    parameters: preset
-                        .parameters
-                        .into_iter()
-                        .map(|param| (param.name, param.value))
-                        .collect(),
-                },
+                config: RuntimeParameters::new(preset.shader_count as usize, preset.parameters),
                 draw_quad,
                 device,
                 output_textures,
@@ -336,7 +325,7 @@ impl FilterChainMetal {
         frame_count: usize,
         options: Option<&FrameOptionsMetal>,
     ) -> error::Result<()> {
-        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled);
+        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled());
         let passes = &mut self.passes[0..max];
         if let Some(options) = &options {
             let desc = unsafe {
diff --git a/librashader-runtime-mtl/src/filter_pass.rs b/librashader-runtime-mtl/src/filter_pass.rs
index d2e0590..f6d852b 100644
--- a/librashader-runtime-mtl/src/filter_pass.rs
+++ b/librashader-runtime-mtl/src/filter_pass.rs
@@ -163,7 +163,7 @@ impl FilterPass {
             parent.history_textures.iter().map(|o| o.as_ref()),
             parent.luts.iter().map(|(u, i)| (*u, i.as_ref())),
             &self.source.parameters,
-            &parent.config.parameters,
+            &parent.config,
         );
 
         // flush to buffers
diff --git a/librashader-runtime-vk/src/filter_chain.rs b/librashader-runtime-vk/src/filter_chain.rs
index ca13c1f..669d609 100644
--- a/librashader-runtime-vk/src/filter_chain.rs
+++ b/librashader-runtime-vk/src/filter_chain.rs
@@ -128,11 +128,6 @@ pub struct FilterChainVulkan {
     default_options: FrameOptionsVulkan,
 }
 
-pub struct FilterMutable {
-    pub(crate) passes_enabled: usize,
-    pub(crate) parameters: FastHashMap<String, f32>,
-}
-
 pub(crate) struct FilterCommon {
     pub(crate) luts: FastHashMap<usize, LutTexture>,
     pub samplers: SamplerSet,
@@ -140,7 +135,7 @@ pub(crate) struct FilterCommon {
     pub output_textures: Box<[Option<InputImage>]>,
     pub feedback_textures: Box<[Option<InputImage>]>,
     pub history_textures: Box<[Option<InputImage>]>,
-    pub config: FilterMutable,
+    pub config: RuntimeParameters,
     pub device: Arc<ash::Device>,
     pub(crate) internal_frame_count: usize,
 }
@@ -236,6 +231,7 @@ mod compile {
 }
 
 use compile::{compile_passes, ShaderPassMeta};
+use librashader_runtime::parameters::RuntimeParameters;
 
 impl FilterChainVulkan {
     /// Load the shader preset at the given path into a filter chain.
@@ -386,14 +382,7 @@ impl FilterChainVulkan {
             common: FilterCommon {
                 luts,
                 samplers,
-                config: FilterMutable {
-                    passes_enabled: preset.shader_count as usize,
-                    parameters: preset
-                        .parameters
-                        .into_iter()
-                        .map(|param| (param.name, param.value))
-                        .collect(),
-                },
+                config: RuntimeParameters::new(preset.shader_count as usize, preset.parameters),
                 draw_quad: DrawQuad::new(&device.device, &device.alloc)?,
                 device: device.device.clone(),
                 output_textures,
@@ -576,7 +565,7 @@ impl FilterChainVulkan {
         intermediates.dispose();
 
         // limit number of passes to those enabled.
-        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled);
+        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled());
         let passes = &mut self.passes[0..max];
 
         if let Some(options) = &options {
diff --git a/librashader-runtime-vk/src/filter_pass.rs b/librashader-runtime-vk/src/filter_pass.rs
index 83463db..b6499d6 100644
--- a/librashader-runtime-vk/src/filter_pass.rs
+++ b/librashader-runtime-vk/src/filter_pass.rs
@@ -219,7 +219,7 @@ impl FilterPass {
             parent.history_textures.iter().map(|o| o.as_ref()),
             parent.luts.iter().map(|(u, i)| (*u, i.as_ref())),
             &self.source.parameters,
-            &parent.config.parameters,
+            &parent.config,
         );
     }
 }
diff --git a/librashader-runtime-wgpu/src/filter_chain.rs b/librashader-runtime-wgpu/src/filter_chain.rs
index 0d2baca..adaadb0 100644
--- a/librashader-runtime-wgpu/src/filter_chain.rs
+++ b/librashader-runtime-wgpu/src/filter_chain.rs
@@ -56,6 +56,7 @@ mod compile {
 }
 
 use compile::{compile_passes, ShaderPassMeta};
+use librashader_runtime::parameters::RuntimeParameters;
 
 /// A wgpu filter chain.
 pub struct FilterChainWgpu {
@@ -69,19 +70,13 @@ pub struct FilterChainWgpu {
     default_frame_options: FrameOptionsWgpu,
 }
 
-pub struct FilterMutable {
-    pub passes_enabled: usize,
-    pub(crate) parameters: FastHashMap<String, f32>,
-}
-
 pub(crate) struct FilterCommon {
     pub output_textures: Box<[Option<InputImage>]>,
     pub feedback_textures: Box<[Option<InputImage>]>,
     pub history_textures: Box<[Option<InputImage>]>,
     pub luts: FastHashMap<usize, LutTexture>,
     pub samplers: SamplerSet,
-    pub config: FilterMutable,
-    pub internal_frame_count: i32,
+    pub config: RuntimeParameters,
     pub(crate) draw_quad: DrawQuad,
     device: Arc<Device>,
     pub(crate) queue: Arc<wgpu::Queue>,
@@ -199,21 +194,13 @@ impl FilterChainWgpu {
             common: FilterCommon {
                 luts,
                 samplers,
-                config: FilterMutable {
-                    passes_enabled: preset.shader_count as usize,
-                    parameters: preset
-                        .parameters
-                        .into_iter()
-                        .map(|param| (param.name, param.value))
-                        .collect(),
-                },
+                config: RuntimeParameters::new(preset.shader_count as usize, preset.parameters),
                 draw_quad,
                 device,
                 queue,
                 output_textures,
                 feedback_textures,
                 history_textures,
-                internal_frame_count: 0,
             },
             passes: filters,
             output_framebuffers,
@@ -377,7 +364,7 @@ impl FilterChainWgpu {
         frame_count: usize,
         options: Option<&FrameOptionsWgpu>,
     ) -> error::Result<()> {
-        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled);
+        let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled());
         let passes = &mut self.passes[0..max];
 
         if let Some(options) = &options {
@@ -513,7 +500,6 @@ impl FilterChainWgpu {
         }
 
         self.push_history(&input, cmd);
-        self.common.internal_frame_count = self.common.internal_frame_count.wrapping_add(1);
         Ok(())
     }
 }
diff --git a/librashader-runtime-wgpu/src/filter_pass.rs b/librashader-runtime-wgpu/src/filter_pass.rs
index 9778973..b04a0f4 100644
--- a/librashader-runtime-wgpu/src/filter_pass.rs
+++ b/librashader-runtime-wgpu/src/filter_pass.rs
@@ -238,7 +238,7 @@ impl FilterPass {
             parent.history_textures.iter().map(|o| o.as_ref()),
             parent.luts.iter().map(|(u, i)| (*u, i.as_ref())),
             &self.source.parameters,
-            &parent.config.parameters,
+            &parent.config,
         );
 
         // flush to buffers
diff --git a/librashader-runtime/Cargo.toml b/librashader-runtime/Cargo.toml
index 167478f..b4e4d73 100644
--- a/librashader-runtime/Cargo.toml
+++ b/librashader-runtime/Cargo.toml
@@ -19,6 +19,7 @@ librashader-reflect = { path = "../librashader-reflect", version = "0.3.3" }
 bytemuck = {  version = "1.12.3", features = ["derive"] }
 num-traits = "0.2.15"
 array-concat = "0.5.2"
+arc-swap = "1.7.1"
 
 tinymap = "0.4.0"
 
diff --git a/librashader-runtime/src/binding.rs b/librashader-runtime/src/binding.rs
index ee31e32..8848a99 100644
--- a/librashader-runtime/src/binding.rs
+++ b/librashader-runtime/src/binding.rs
@@ -1,3 +1,4 @@
+use crate::parameters::RuntimeParameters;
 use crate::uniforms::{BindUniform, NoUniformBinder, UniformStorage};
 use librashader_common::map::FastHashMap;
 use librashader_common::Size;
@@ -120,8 +121,9 @@ where
         original_history: impl Iterator<Item = Option<impl AsRef<Self::InputTexture>>>,
         lookup_textures: impl Iterator<Item = (usize, impl AsRef<Self::InputTexture>)>,
         parameter_defaults: &FastHashMap<String, ShaderParameter>,
-        runtime_parameters: &FastHashMap<String, f32>,
+        runtime_parameters: &RuntimeParameters,
     ) {
+        let runtime_parameters = runtime_parameters.parameters.load();
         // Bind MVP
         if let Some(offset) = uniform_bindings.get(&UniqueSemantics::MVP.into()) {
             uniform_storage.bind_mat4(
diff --git a/librashader-runtime/src/parameters.rs b/librashader-runtime/src/parameters.rs
index 1ba9add..81c9638 100644
--- a/librashader-runtime/src/parameters.rs
+++ b/librashader-runtime/src/parameters.rs
@@ -1,64 +1,92 @@
+use std::ops::Deref;
+use arc_swap::ArcSwap;
+use librashader_common::map::FastHashMap;
+use librashader_presets::ParameterConfig;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Arc;
+
 /// Trait for filter chains that allow runtime reflection of shader parameters.
 pub trait FilterChainParameters {
-    /// Gets the number of shader passes enabled at runtime.
-    fn get_enabled_pass_count(&self) -> usize;
+    /// Get the runtime parameters for this filter chain.
+    fn parameters(&self) -> &RuntimeParameters;
+}
 
-    /// Sets the number of shader passes enabled at runtime.
-    fn set_enabled_pass_count(&mut self, count: usize);
+/// Runtime reflection of shader parameters for filter chains.
+///
+/// All operations on runtime parameters are atomic and can be done on
+/// any thread.
+pub struct RuntimeParameters {
+    passes_enabled: AtomicUsize,
+    pub(crate) parameters: ArcSwap<FastHashMap<String, f32>>,
+}
 
-    /// Enumerates the active parameters as well as their values in the current filter chain.
-    fn enumerate_parameters<'a>(
-        &'a self,
-    ) -> ::librashader_common::map::halfbrown::Iter<String, f32>;
+impl RuntimeParameters {
+    /// Create a new instance of runtime parameters from a `Vec` of
+    /// shader parameters from a [`ShaderPreset`](librashader_presets::ShaderPreset).
+    pub fn new(passes_enabled: usize, parameters: Vec<ParameterConfig>) -> Self {
+        RuntimeParameters {
+            passes_enabled: AtomicUsize::new(passes_enabled),
+            parameters: ArcSwap::new(Arc::new(
+                parameters
+                    .into_iter()
+                    .map(|param| (param.name, param.value))
+                    .collect(),
+            )),
+        }
+    }
 
-    /// Get the value of the given parameter if present.
-    fn get_parameter(&self, parameter: &str) -> Option<f32>;
+    /// Get the value of a runtime parameter
+    pub fn get_parameter(&self, name: &str) -> Option<f32> {
+        self.parameters.load().get::<str>(name.as_ref()).copied()
+    }
 
-    /// Set the value of the given parameter if present.
+    /// Set a runtime parameter.
     ///
-    /// Returns `None` if the parameter did not exist, or the old value if successful.
-    fn set_parameter(&mut self, parameter: &str, new_value: f32) -> Option<f32>;
+    /// This is a relatively slow operation as it will be synchronized across threads.
+    pub fn set_parameter(&self, name: &str, new_value: f32) -> Option<f32> {
+        let mut updated_map = FastHashMap::clone(&self.parameters.load());
+
+        if let Some(value) = updated_map.get_mut::<str>(name.as_ref()) {
+            let old = *value;
+            *value = new_value;
+
+            self.parameters.store(Arc::new(updated_map));
+
+            Some(old)
+        } else {
+            None
+        }
+    }
+
+    /// Get a reference to the runtime parameters.
+    pub fn parameters(&self) -> Arc<FastHashMap<String, f32>> {
+        self.parameters.load_full()
+    }
+
+    /// Get the number of passes enabled.
+    ///
+    /// If set from [`RuntimeParameters::set_passes_enabled`] from a different thread,
+    /// it is not guaranteed to be immediately visible.
+    #[inline(always)]
+    pub fn passes_enabled(&self) -> usize {
+        self.passes_enabled.load(Ordering::Relaxed)
+    }
+
+    /// Set the number of passes enabled.
+    ///
+    /// This is an atomic operation and is thread-safe.
+    #[inline(always)]
+    pub fn set_passes_enabled(&self, count: usize) {
+        self.passes_enabled.store(count, Ordering::Relaxed);
+    }
 }
 
 #[macro_export]
 macro_rules! impl_filter_chain_parameters {
     ($ty:ty) => {
         impl ::librashader_runtime::parameters::FilterChainParameters for $ty {
-            fn get_enabled_pass_count(&self) -> usize {
-                self.common.config.passes_enabled
-            }
-
-            fn set_enabled_pass_count(&mut self, count: usize) {
-                self.common.config.passes_enabled = count
-            }
-
-            fn enumerate_parameters<'a>(
-                &'a self,
-            ) -> ::librashader_common::map::halfbrown::Iter<String, f32> {
-                self.common.config.parameters.iter()
-            }
-
-            fn get_parameter(&self, parameter: &str) -> Option<f32> {
-                self.common
-                    .config
-                    .parameters
-                    .get::<str>(parameter.as_ref())
-                    .copied()
-            }
-
-            fn set_parameter(&mut self, parameter: &str, new_value: f32) -> Option<f32> {
-                if let Some(value) = self
-                    .common
-                    .config
-                    .parameters
-                    .get_mut::<str>(parameter.as_ref())
-                {
-                    let old = *value;
-                    *value = new_value;
-                    Some(old)
-                } else {
-                    None
-                }
+            fn parameters(&self) -> &::librashader_runtime::parameters::RuntimeParameters {
+                &self.common.config
             }
         }
     };
diff --git a/librashader/src/lib.rs b/librashader/src/lib.rs
index 0dd8469..1f7c227 100644
--- a/librashader/src/lib.rs
+++ b/librashader/src/lib.rs
@@ -236,6 +236,7 @@ pub mod reflect {
 pub mod runtime {
     pub use librashader_common::{Size, Viewport};
     pub use librashader_runtime::parameters::FilterChainParameters;
+    pub use librashader_runtime::parameters::RuntimeParameters;
 
     #[cfg(feature = "runtime-gl")]
     #[doc(cfg(feature = "runtime-gl"))]