summaryrefslogtreecommitdiff
path: root/rust/crates/effect-defs/src
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-07-28 19:41:16 +0200
committercashmere <cashmere@autistici.org>2026-07-28 19:41:16 +0200
commitf5a82152394fde4d8f740f664ab7737b82861618 (patch)
tree5a51b181abead1ce03d2dbfc12b77918d3be0e1b /rust/crates/effect-defs/src
parente837e7504379e100ff3b56282577ec67bb88bec9 (diff)
downloadopencut-f5a82152394fde4d8f740f664ab7737b82861618.tar.gz
feat: GPUI desktop port foundation
- scene crate: full project/scene/element model, serde round-trip proven against the web SerializedProject shape - transfer crate: .ocp zip container, cross-compat with web fflate archives proven in both directions - storage crate: filesystem project store replacing IndexedDB, .ocp import/export - media crate: ffmpeg-next 8 decoder (frames, audio, thumbnails, waveform summaries) - playback crate: wall-clock anchored, frame-rounded playback clock - gpu crate: native texture readback; compositor renders headless offscreen on Vulkan (spike test) - flake devshell: rust toolchain, GPUI native deps, ffmpeg dev, bindgen clang args; GPUI hello-world builds - port plan in notes/gpui-port.md
Diffstat (limited to 'rust/crates/effect-defs/src')
-rw-r--r--rust/crates/effect-defs/src/lib.rs64
-rw-r--r--rust/crates/effect-defs/src/math.rs57
-rw-r--r--rust/crates/effect-defs/src/params.rs165
-rw-r--r--rust/crates/effect-defs/src/registry.rs35
-rw-r--r--rust/crates/effect-defs/src/types.rs30
5 files changed, 351 insertions, 0 deletions
diff --git a/rust/crates/effect-defs/src/lib.rs b/rust/crates/effect-defs/src/lib.rs
new file mode 100644
index 00000000..e326a1a9
--- /dev/null
+++ b/rust/crates/effect-defs/src/lib.rs
@@ -0,0 +1,64 @@
+mod definitions;
+mod math;
+mod params;
+mod registry;
+mod types;
+
+use effects::EffectPass;
+use scene::ParamValues;
+use thiserror::Error;
+
+pub use definitions::blur::{
+ GAUSSIAN_BLUR_SHADER, INTENSITY_TO_SIGMA_DIVISOR, blur_effect_definition,
+ build_gaussian_blur_passes, intensity_to_sigma, parse_intensity,
+};
+pub use definitions::register_default_effects;
+pub use params::{
+ BooleanParamDefinition, ColorParamDefinition, FontParamDefinition, NumberParamDefinition,
+ ParamDefinition, ParamDependency, SelectOption, SelectParamDefinition, TextParamDefinition,
+ build_default_param_values,
+};
+pub use registry::EffectsRegistry;
+pub use types::{EffectDefinition, EffectPassTemplate, EffectRendererConfig, PassContext};
+
+#[derive(Debug, Error, PartialEq)]
+pub enum EffectDefsError {
+ #[error("Unknown effect: {effect_type}")]
+ UnknownEffect { effect_type: String },
+}
+
+pub fn resolve_effect_passes(
+ definition: &EffectDefinition,
+ effect_params: &ParamValues,
+ width: u32,
+ height: u32,
+) -> Vec<EffectPass> {
+ let context = PassContext {
+ effect_params,
+ width,
+ height,
+ };
+ if let Some(build_passes) = definition.renderer.build_passes {
+ return build_passes(&context);
+ }
+ definition
+ .renderer
+ .passes
+ .iter()
+ .map(|pass| EffectPass {
+ shader: pass.shader.to_string(),
+ uniforms: (pass.uniforms)(&context),
+ })
+ .collect()
+}
+
+pub fn params_with_defaults(
+ definition: &EffectDefinition,
+ effect_params: &ParamValues,
+) -> ParamValues {
+ let mut values = build_default_param_values(&definition.params);
+ for (key, value) in effect_params {
+ values.insert(key.clone(), value.clone());
+ }
+ values
+}
diff --git a/rust/crates/effect-defs/src/math.rs b/rust/crates/effect-defs/src/math.rs
new file mode 100644
index 00000000..dafc281e
--- /dev/null
+++ b/rust/crates/effect-defs/src/math.rs
@@ -0,0 +1,57 @@
+pub(crate) fn js_max(a: f64, b: f64) -> f64 {
+ if a.is_nan() || b.is_nan() { f64::NAN } else { a.max(b) }
+}
+
+pub(crate) fn js_min(a: f64, b: f64) -> f64 {
+ if a.is_nan() || b.is_nan() { f64::NAN } else { a.min(b) }
+}
+
+pub(crate) fn js_parse_float(value: &str) -> f64 {
+ let trimmed = value.trim_start();
+ let (sign, rest) = match trimmed.strip_prefix('-') {
+ Some(rest) => (-1.0, rest),
+ None => (1.0, trimmed.strip_prefix('+').unwrap_or(trimmed)),
+ };
+ if rest.starts_with("Infinity") {
+ return sign * f64::INFINITY;
+ }
+ let bytes = rest.as_bytes();
+ let mut end = 0;
+ let mut seen_digit = false;
+ let mut seen_dot = false;
+ while end < bytes.len() {
+ match bytes[end] {
+ b'0'..=b'9' => {
+ seen_digit = true;
+ end += 1;
+ }
+ b'.' if !seen_dot => {
+ seen_dot = true;
+ end += 1;
+ }
+ _ => break,
+ }
+ }
+ if !seen_digit {
+ return f64::NAN;
+ }
+ let mantissa_end = end;
+ if end < bytes.len() && (bytes[end] == b'e' || bytes[end] == b'E') {
+ let mut exponent_end = end + 1;
+ if exponent_end < bytes.len()
+ && (bytes[exponent_end] == b'+' || bytes[exponent_end] == b'-')
+ {
+ exponent_end += 1;
+ }
+ let digits_start = exponent_end;
+ while exponent_end < bytes.len() && bytes[exponent_end].is_ascii_digit() {
+ exponent_end += 1;
+ }
+ if exponent_end > digits_start {
+ end = exponent_end;
+ } else {
+ end = mantissa_end;
+ }
+ }
+ sign * rest[..end].parse::<f64>().unwrap_or(f64::NAN)
+}
diff --git a/rust/crates/effect-defs/src/params.rs b/rust/crates/effect-defs/src/params.rs
new file mode 100644
index 00000000..cc7c43dc
--- /dev/null
+++ b/rust/crates/effect-defs/src/params.rs
@@ -0,0 +1,165 @@
+use scene::params::{ParamValue, ParamValues};
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct ParamDependency {
+ pub param: String,
+ pub equals: ParamValue,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct SelectOption {
+ pub value: String,
+ pub label: String,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct NumberParamDefinition {
+ pub key: String,
+ pub label: String,
+ pub default_value: f64,
+ pub min: f64,
+ pub max: Option<f64>,
+ pub step: f64,
+ pub group: Option<String>,
+ pub keyframable: Option<bool>,
+ pub dependencies: Vec<ParamDependency>,
+ pub display_multiplier: Option<f64>,
+ pub unit: Option<String>,
+ pub short_label: Option<String>,
+}
+
+impl NumberParamDefinition {
+ pub fn new(
+ key: &str,
+ label: &str,
+ default_value: f64,
+ min: f64,
+ max: Option<f64>,
+ step: f64,
+ ) -> Self {
+ Self {
+ key: key.to_string(),
+ label: label.to_string(),
+ default_value,
+ min,
+ max,
+ step,
+ group: None,
+ keyframable: None,
+ dependencies: Vec::new(),
+ display_multiplier: None,
+ unit: None,
+ short_label: None,
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct BooleanParamDefinition {
+ pub key: String,
+ pub label: String,
+ pub default_value: bool,
+ pub group: Option<String>,
+ pub keyframable: Option<bool>,
+ pub dependencies: Vec<ParamDependency>,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct ColorParamDefinition {
+ pub key: String,
+ pub label: String,
+ pub default_value: String,
+ pub group: Option<String>,
+ pub keyframable: Option<bool>,
+ pub dependencies: Vec<ParamDependency>,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct SelectParamDefinition {
+ pub key: String,
+ pub label: String,
+ pub default_value: String,
+ pub options: Vec<SelectOption>,
+ pub group: Option<String>,
+ pub keyframable: Option<bool>,
+ pub dependencies: Vec<ParamDependency>,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct TextParamDefinition {
+ pub key: String,
+ pub label: String,
+ pub default_value: String,
+ pub group: Option<String>,
+ pub keyframable: Option<bool>,
+ pub dependencies: Vec<ParamDependency>,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct FontParamDefinition {
+ pub key: String,
+ pub label: String,
+ pub default_value: String,
+ pub group: Option<String>,
+ pub keyframable: Option<bool>,
+ pub dependencies: Vec<ParamDependency>,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum ParamDefinition {
+ Number(NumberParamDefinition),
+ Boolean(BooleanParamDefinition),
+ Color(ColorParamDefinition),
+ Select(SelectParamDefinition),
+ Text(TextParamDefinition),
+ Font(FontParamDefinition),
+}
+
+impl ParamDefinition {
+ pub fn key(&self) -> &str {
+ match self {
+ Self::Number(param) => &param.key,
+ Self::Boolean(param) => &param.key,
+ Self::Color(param) => &param.key,
+ Self::Select(param) => &param.key,
+ Self::Text(param) => &param.key,
+ Self::Font(param) => &param.key,
+ }
+ }
+
+ pub fn label(&self) -> &str {
+ match self {
+ Self::Number(param) => &param.label,
+ Self::Boolean(param) => &param.label,
+ Self::Color(param) => &param.label,
+ Self::Select(param) => &param.label,
+ Self::Text(param) => &param.label,
+ Self::Font(param) => &param.label,
+ }
+ }
+
+ pub fn default_value(&self) -> ParamValue {
+ match self {
+ Self::Number(param) => ParamValue::Number(param.default_value),
+ Self::Boolean(param) => ParamValue::Bool(param.default_value),
+ Self::Color(param) => ParamValue::String(param.default_value.clone()),
+ Self::Select(param) => ParamValue::String(param.default_value.clone()),
+ Self::Text(param) => ParamValue::String(param.default_value.clone()),
+ Self::Font(param) => ParamValue::String(param.default_value.clone()),
+ }
+ }
+
+ pub fn numeric_range(&self) -> Option<(f64, Option<f64>, f64)> {
+ match self {
+ Self::Number(param) => Some((param.min, param.max, param.step)),
+ _ => None,
+ }
+ }
+}
+
+pub fn build_default_param_values(params: &[ParamDefinition]) -> ParamValues {
+ params
+ .iter()
+ .map(|param| (param.key().to_string(), param.default_value()))
+ .collect()
+}
diff --git a/rust/crates/effect-defs/src/registry.rs b/rust/crates/effect-defs/src/registry.rs
new file mode 100644
index 00000000..340f9b9b
--- /dev/null
+++ b/rust/crates/effect-defs/src/registry.rs
@@ -0,0 +1,35 @@
+use std::collections::HashMap;
+
+use crate::{EffectDefsError, types::EffectDefinition};
+
+#[derive(Default)]
+pub struct EffectsRegistry {
+ definitions: HashMap<String, EffectDefinition>,
+}
+
+impl EffectsRegistry {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub fn register(&mut self, definition: EffectDefinition) {
+ self.definitions
+ .insert(definition.effect_type.to_string(), definition);
+ }
+
+ pub fn has(&self, key: &str) -> bool {
+ self.definitions.contains_key(key)
+ }
+
+ pub fn get(&self, key: &str) -> Result<&EffectDefinition, EffectDefsError> {
+ self.definitions
+ .get(key)
+ .ok_or_else(|| EffectDefsError::UnknownEffect {
+ effect_type: key.to_string(),
+ })
+ }
+
+ pub fn get_all(&self) -> Vec<&EffectDefinition> {
+ self.definitions.values().collect()
+ }
+}
diff --git a/rust/crates/effect-defs/src/types.rs b/rust/crates/effect-defs/src/types.rs
new file mode 100644
index 00000000..c9815fd7
--- /dev/null
+++ b/rust/crates/effect-defs/src/types.rs
@@ -0,0 +1,30 @@
+use std::collections::HashMap;
+
+use effects::{EffectPass, UniformValue};
+use scene::params::ParamValues;
+
+use crate::params::ParamDefinition;
+
+pub struct PassContext<'a> {
+ pub effect_params: &'a ParamValues,
+ pub width: u32,
+ pub height: u32,
+}
+
+pub struct EffectPassTemplate {
+ pub shader: &'static str,
+ pub uniforms: fn(&PassContext) -> HashMap<String, UniformValue>,
+}
+
+pub struct EffectRendererConfig {
+ pub passes: Vec<EffectPassTemplate>,
+ pub build_passes: Option<fn(&PassContext) -> Vec<EffectPass>>,
+}
+
+pub struct EffectDefinition {
+ pub effect_type: &'static str,
+ pub name: &'static str,
+ pub keywords: &'static [&'static str],
+ pub params: Vec<ParamDefinition>,
+ pub renderer: EffectRendererConfig,
+}