1use std::fmt::Write as _;
6
7use alpha2_gpu::CapabilityTier;
8
9use crate::{EffectDef, EffectKind, ParamSpec, ParamType, ParamUnit, ParamValue};
10
11pub const GENERATOR_EFFECT_ID: &str = "alpha2.internal.generator";
13
14pub const MAX_GENERATOR_STOPS: usize = 8;
16
17pub const PAINT_KINDS: [&str; 5] = ["none", "solid", "linear", "radial", "conic"];
19
20pub const PAINT_SPREADS: [&str; 3] = ["pad", "repeat", "reflect"];
22
23#[derive(Copy, Clone, Debug, Eq, PartialEq)]
26pub struct PaintParams {
27 pub kind: &'static str,
29 pub a: &'static str,
31 pub b: &'static str,
33 pub spread: &'static str,
35 pub count: &'static str,
37 pub offsets0: &'static str,
39 pub offsets1: &'static str,
41 pub colors: [&'static str; MAX_GENERATOR_STOPS],
43}
44
45macro_rules! paint_params {
46 ($paint:literal) => {
47 PaintParams {
48 kind: concat!($paint, "_kind"),
49 a: concat!($paint, "_a"),
50 b: concat!($paint, "_b"),
51 spread: concat!($paint, "_spread"),
52 count: concat!($paint, "_count"),
53 offsets0: concat!($paint, "_offsets0"),
54 offsets1: concat!($paint, "_offsets1"),
55 colors: [
56 concat!($paint, "_c0"),
57 concat!($paint, "_c1"),
58 concat!($paint, "_c2"),
59 concat!($paint, "_c3"),
60 concat!($paint, "_c4"),
61 concat!($paint, "_c5"),
62 concat!($paint, "_c6"),
63 concat!($paint, "_c7"),
64 ],
65 }
66 };
67}
68
69pub const FILL_PAINT_PARAMS: PaintParams = paint_params!("fill");
72
73fn param(name: String, param_type: ParamType, default: ParamValue) -> ParamSpec {
74 ParamSpec {
75 name,
76 param_type,
77 default,
78 min: None,
79 max: None,
80 step: None,
81 unit: ParamUnit::Normalized,
82 enum_values: Vec::new(),
83 }
84}
85
86fn enum_param(name: String, values: &[&str]) -> ParamSpec {
87 ParamSpec {
88 unit: ParamUnit::Enum,
89 enum_values: values.iter().map(|value| (*value).to_owned()).collect(),
90 ..param(name, ParamType::Enum, ParamValue::Enum(0))
91 }
92}
93
94#[must_use]
104pub fn generator_definition() -> EffectDef {
105 let names = FILL_PAINT_PARAMS;
106 let mut params = vec![
107 enum_param(names.kind.into(), &PAINT_KINDS),
108 param(
109 names.a.into(),
110 ParamType::Vec2,
111 ParamValue::Vec2([0.0, 0.0]),
112 ),
113 param(
114 names.b.into(),
115 ParamType::Vec2,
116 ParamValue::Vec2([1.0, 0.0]),
117 ),
118 enum_param(names.spread.into(), &PAINT_SPREADS),
119 param(names.count.into(), ParamType::Float, ParamValue::Float(0.0)),
120 param(
121 names.offsets0.into(),
122 ParamType::Vec4,
123 ParamValue::Vec4([0.0; 4]),
124 ),
125 param(
126 names.offsets1.into(),
127 ParamType::Vec4,
128 ParamValue::Vec4([0.0; 4]),
129 ),
130 ];
131 params.extend(names.colors.map(|name| ParamSpec {
132 unit: ParamUnit::Color,
133 ..param(name.into(), ParamType::Color, ParamValue::Color([0.0; 4]))
134 }));
135 EffectDef {
136 id: GENERATOR_EFFECT_ID.into(),
137 version: 1,
138 kind: EffectKind::Generator,
139 inputs: Vec::new(),
140 params,
141 resources: Vec::new(),
142 wgsl_body: generator_body(),
143 tier_min: CapabilityTier::TierB,
144 license: Some(crate::builtins::ALPHA2_EFFECT_LICENSE.into()),
145 }
146}
147
148fn paint_constructor(paint: &str) -> String {
149 let mut colors = String::new();
150 for stop in 0..MAX_GENERATOR_STOPS {
151 let separator = if stop + 1 == MAX_GENERATOR_STOPS {
152 ""
153 } else {
154 ", "
155 };
156 write!(colors, "params.{paint}_c{stop}{separator}")
157 .expect("writing to a String cannot fail");
158 }
159 format!(
160 "fn builtin_{paint}_paint() -> AlphaPaint {{
161 let o0 = params.{paint}_offsets0;
162 let o1 = params.{paint}_offsets1;
163 return AlphaPaint(
164 params.{paint}_kind,
165 params.{paint}_a,
166 params.{paint}_b,
167 params.{paint}_spread,
168 u32(clamp(params.{paint}_count, 0.0, {MAX_GENERATOR_STOPS}.0)),
169 array<f32, {MAX_GENERATOR_STOPS}>(o0.x, o0.y, o0.z, o0.w, o1.x, o1.y, o1.z, o1.w),
170 array<vec4f, {MAX_GENERATOR_STOPS}>({colors}),
171 );
172}}
173"
174 )
175}
176
177fn generator_body() -> String {
178 let paint = paint_constructor("fill");
179 format!(
180 r"{paint_wgsl}
181{paint}
182fn generate(uv: vec2f) -> vec4f {{
183 return builtin_generator_store(builtin_paint(builtin_fill_paint(), uv, resolution()));
184}}
185",
186 paint_wgsl = crate::paint_wgsl::paint_wgsl(),
187 )
188}
189
190#[cfg(test)]
191mod tests {
192 use super::{FILL_PAINT_PARAMS, generator_definition};
193
194 #[test]
195 fn the_paints_static_names_are_the_ones_the_pass_declares() {
196 let declared = generator_definition()
197 .params
198 .into_iter()
199 .map(|spec| spec.name)
200 .collect::<Vec<_>>();
201 let names = FILL_PAINT_PARAMS;
202 let mut expected = Vec::new();
203 let fields = [
204 ("kind", names.kind),
205 ("a", names.a),
206 ("b", names.b),
207 ("spread", names.spread),
208 ("count", names.count),
209 ("offsets0", names.offsets0),
210 ("offsets1", names.offsets1),
211 ];
212 let stops = names
213 .colors
214 .iter()
215 .enumerate()
216 .map(|(stop, name)| (format!("c{stop}"), *name));
217 for (field, name) in fields
218 .into_iter()
219 .map(|(field, name)| (field.to_owned(), name))
220 .chain(stops)
221 {
222 assert_eq!(name, format!("fill_{field}"));
223 expected.push(name.to_owned());
224 }
225 assert_eq!(declared, expected);
226 }
227}