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