saft/
codegen.rs

1use super::Program;
2use std::rc::Rc;
3
4#[derive(PartialEq, Eq)]
5#[allow(clippy::upper_case_acronyms)]
6pub enum Backend {
7    GLSL,
8}
9
10#[derive(Copy, Clone)]
11pub enum OutputType {
12    DistanceOnly,
13    DistanceWithRgb,
14}
15
16pub struct CodeGenContext<'a> {
17    function_name: &'a str,
18    dynamic_constants: bool,
19    variable_index: usize,
20    constant_index: usize,
21
22    variable_stack: Vec<Rc<str>>,
23    position_variable_stack: Vec<Rc<str>>,
24    current_position: Rc<str>,
25}
26
27impl<'a> CodeGenContext<'a> {
28    pub fn new(
29        initial_position_variable: &str,
30        function_name: &'a str,
31        dynamic_constants: bool,
32    ) -> Self {
33        let position: Rc<str> = Rc::from(initial_position_variable);
34
35        Self {
36            function_name,
37            dynamic_constants,
38            variable_index: 0,
39            constant_index: 0,
40            variable_stack: Vec::new(),
41            position_variable_stack: vec![position.clone()],
42            current_position: position,
43        }
44    }
45
46    pub fn push_variable(&mut self) -> Rc<str> {
47        let name = format!("sdf{}", self.variable_index);
48        self.variable_index += 1;
49
50        let name: Rc<str> = Rc::from(name.as_str());
51        self.variable_stack.push(name.clone());
52        name
53    }
54
55    pub fn uint32(&mut self) -> String {
56        let components = &[".x", ".y", ".z", ".w"];
57
58        let s = format!(
59            "{}_constants[{}_constants_offset + {}]{}",
60            self.function_name,
61            self.function_name,
62            if self.dynamic_constants {
63                self.constant_index >> 2
64            } else {
65                self.constant_index
66            },
67            if self.dynamic_constants {
68                components[self.constant_index & 0x3]
69            } else {
70                ""
71            }
72        );
73        self.constant_index += 1;
74        s
75    }
76
77    pub fn float32(&mut self) -> String {
78        format!("uintBitsToFloat({})", self.uint32())
79    }
80
81    pub fn vec2(&mut self) -> String {
82        format!("vec2({}, {})", self.float32(), self.float32())
83    }
84
85    pub fn vec3(&mut self) -> String {
86        format!(
87            "vec3({}, {}, {})",
88            self.float32(),
89            self.float32(),
90            self.float32()
91        )
92    }
93
94    pub fn vec4(&mut self) -> String {
95        format!(
96            "vec4({}, {}, {}, {})",
97            self.float32(),
98            self.float32(),
99            self.float32(),
100            self.float32()
101        )
102    }
103
104    pub fn quat(&mut self) -> String {
105        self.vec4()
106    }
107
108    pub fn material(&mut self) -> String {
109        self.vec3()
110    }
111
112    pub fn pop_variable(&mut self) -> Option<Rc<str>> {
113        self.variable_stack.pop()
114    }
115
116    pub fn pop_transform(&mut self) {
117        self.current_position = self.position_variable_stack.pop().unwrap();
118    }
119
120    pub fn current_position(&self) -> Rc<str> {
121        self.current_position.clone()
122    }
123
124    // Returns new_position, old_position variable names.
125    pub fn push_transform(&mut self) -> (Rc<str>, Rc<str>) {
126        let s = format!("transform{}", self.variable_index);
127        let s: Rc<str> = Rc::from(s.as_str());
128        self.variable_index += 1;
129
130        self.position_variable_stack
131            .push(self.current_position.clone());
132        let old_position = self.current_position.clone();
133        self.current_position = s.clone();
134        (s, old_position)
135    }
136}
137
138pub struct CodeGen {
139    backend: Backend,
140}
141
142impl CodeGen {
143    pub fn glsl() -> Self {
144        Self {
145            backend: Backend::GLSL,
146        }
147    }
148
149    // Generates code that is used by the translated program
150    pub fn get_library_code(&self) -> &'static str {
151        match self.backend {
152            Backend::GLSL => saft_sdf::get_glsl_sdf_library_code(),
153        }
154    }
155
156    fn build_glsl_code(
157        program: &Program,
158        function_name: &str,
159        output_type: OutputType,
160        dynamic_constants: bool,
161    ) -> String {
162        use super::Opcode::*;
163        use std::fmt::Write;
164
165        let mut code = String::new();
166        code.push_str("// !!! START OF GENERATED CODE !!!\n");
167
168        let output_glsl_type = match output_type {
169            OutputType::DistanceOnly => "float",
170            OutputType::DistanceWithRgb => "vec4",
171        };
172
173        if !dynamic_constants {
174            let _ = write!(
175                &mut code,
176                "\tconst uint {}_constants[{}] = uint[](",
177                function_name,
178                program.constants.len()
179            );
180
181            for (i, c) in program.constants.iter().enumerate() {
182                if i != 0 {
183                    code.push(',');
184                }
185
186                let _ = write!(&mut code, "{}", c.to_bits());
187            }
188
189            code.push_str(");\n");
190        }
191
192        let mut ctx = CodeGenContext::new("pos", function_name, dynamic_constants);
193
194        let _ = writeln!(
195            &mut code,
196            "{} {}_base(vec3 pos) {{",
197            output_glsl_type, function_name
198        );
199
200        let prefix = match output_type {
201            OutputType::DistanceOnly => "sd",
202            OutputType::DistanceWithRgb => "sdrgb",
203        };
204
205        for opcode in &program.opcodes {
206            match opcode {
207                Plane => {
208                    let variable_name = ctx.push_variable();
209                    let plane = ctx.vec4();
210                    let _ = writeln!(
211                        &mut code,
212                        "\t{} {} = {}_plane({}, {});",
213                        output_glsl_type,
214                        variable_name,
215                        prefix,
216                        ctx.current_position(),
217                        plane,
218                    );
219                }
220                Sphere => {
221                    let variable_name = ctx.push_variable();
222                    let center = ctx.vec3();
223                    let radius = ctx.float32();
224                    let _ = writeln!(
225                        &mut code,
226                        "\t{} {} = {}_sphere({}, {}, {});",
227                        output_glsl_type,
228                        variable_name,
229                        prefix,
230                        ctx.current_position(),
231                        center,
232                        radius,
233                    );
234                }
235                Capsule => {
236                    let variable_name = ctx.push_variable();
237                    let points = [ctx.vec3(), ctx.vec3()];
238                    let radius = ctx.float32();
239                    let _ = writeln!(
240                        &mut code,
241                        "\t{} {} = {}_capsule({}, {}, {}, {});",
242                        output_glsl_type,
243                        variable_name,
244                        prefix,
245                        ctx.current_position(),
246                        points[0],
247                        points[1],
248                        radius,
249                    );
250                }
251                RoundedCylinder => {
252                    let variable_name = ctx.push_variable();
253                    let cylinder_radius = ctx.float32();
254                    let half_height = ctx.float32();
255                    let rounding_radius = ctx.float32();
256                    let _ = writeln!(
257                        &mut code,
258                        "\t{} {} = {}_rounded_cylinder({}, {}, {}, {});",
259                        output_glsl_type,
260                        variable_name,
261                        prefix,
262                        ctx.current_position(),
263                        cylinder_radius,
264                        half_height,
265                        rounding_radius,
266                    );
267                }
268                TaperedCapsule => {
269                    let variable_name = ctx.push_variable();
270                    let p0 = ctx.vec3();
271                    let r0 = ctx.float32();
272                    let p1 = ctx.vec3();
273                    let r1 = ctx.float32();
274                    let _ = writeln!(
275                        &mut code,
276                        "\t{} {} = {}_tapered_capsule({}, {}, {}, {}, {});",
277                        output_glsl_type,
278                        variable_name,
279                        prefix,
280                        ctx.current_position(),
281                        p0,
282                        p1,
283                        r0,
284                        r1,
285                    );
286                }
287                Cone => {
288                    let variable_name = ctx.push_variable();
289                    let r = ctx.float32();
290                    let h = ctx.float32();
291                    let _ = writeln!(
292                        &mut code,
293                        "\t{} {} = {}_cone({}, {}, {});",
294                        output_glsl_type,
295                        variable_name,
296                        prefix,
297                        ctx.current_position(),
298                        r,
299                        h,
300                    );
301                }
302                RoundedBox => {
303                    let variable_name = ctx.push_variable();
304                    let half_size = ctx.vec3();
305                    let radius = ctx.float32();
306                    let _ = writeln!(
307                        &mut code,
308                        "\t{} {} = {}_rounded_box({}, {}, {});",
309                        output_glsl_type,
310                        variable_name,
311                        prefix,
312                        ctx.current_position(),
313                        half_size,
314                        radius,
315                    );
316                }
317                Torus => {
318                    // big_r, small_r
319                    let _ = writeln!(
320                        &mut code,
321                        "\t{} {} = {}_torus({}, {}, {});",
322                        output_glsl_type,
323                        ctx.push_variable(),
324                        prefix,
325                        ctx.current_position(),
326                        ctx.float32(),
327                        ctx.float32(),
328                    );
329                }
330                TorusSector => {
331                    // big_r, small_r, sin_cos_half_angle
332                    let _ = writeln!(
333                        &mut code,
334                        "\t{} {} = {}_torus_sector({}, {}, {}, {});",
335                        output_glsl_type,
336                        ctx.push_variable(),
337                        prefix,
338                        ctx.current_position(),
339                        ctx.float32(),
340                        ctx.float32(),
341                        ctx.vec2(),
342                    );
343                }
344                BiconvexLens => {
345                    let variable_name = ctx.push_variable();
346                    let lower_sagitta = ctx.float32();
347                    let upper_sagitta = ctx.float32();
348                    let chord = ctx.float32();
349                    let _ = writeln!(
350                        &mut code,
351                        "\t{} {} = {}_biconvex_lens({}, {}, {}, {});",
352                        output_glsl_type,
353                        variable_name,
354                        prefix,
355                        ctx.current_position(),
356                        lower_sagitta,
357                        upper_sagitta,
358                        chord,
359                    );
360                }
361                Material => {
362                    let sd = ctx.pop_variable().unwrap();
363                    let material = ctx.material();
364                    let variable_name = ctx.push_variable();
365                    let _ = writeln!(
366                        &mut code,
367                        "\t{} {} = {}_material({}, {});",
368                        output_glsl_type, variable_name, prefix, sd, material
369                    );
370                }
371                Union => {
372                    let sd1 = ctx.pop_variable().unwrap();
373                    let sd2 = ctx.pop_variable().unwrap();
374                    let variable_name = ctx.push_variable();
375                    let _ = writeln!(
376                        &mut code,
377                        "\t{} {} = {}_op_union({}, {});",
378                        output_glsl_type, variable_name, prefix, sd1, sd2
379                    );
380                }
381                UnionSmooth => {
382                    let sd1 = ctx.pop_variable().unwrap();
383                    let sd2 = ctx.pop_variable().unwrap();
384                    let variable_name = ctx.push_variable();
385                    let size = ctx.float32();
386                    let _ = writeln!(
387                        &mut code,
388                        "\t{} {} = {}_op_union_smooth({}, {}, {});",
389                        output_glsl_type, variable_name, prefix, sd1, sd2, size
390                    );
391                }
392                Subtract => {
393                    let sd1 = ctx.pop_variable().unwrap();
394                    let sd2 = ctx.pop_variable().unwrap();
395                    let variable_name = ctx.push_variable();
396                    let _ = writeln!(
397                        &mut code,
398                        "\t{} {} = {}_op_subtract({}, {});",
399                        output_glsl_type, variable_name, prefix, sd1, sd2
400                    );
401                }
402                SubtractSmooth => {
403                    let sd1 = ctx.pop_variable().unwrap();
404                    let sd2 = ctx.pop_variable().unwrap();
405                    let variable_name = ctx.push_variable();
406                    let size = ctx.float32();
407                    let _ = writeln!(
408                        &mut code,
409                        "\t{} {} = {}_op_subtract_smooth({}, {}, {});",
410                        output_glsl_type, variable_name, prefix, sd1, sd2, size
411                    );
412                }
413                Intersect => {
414                    let sd1 = ctx.pop_variable().unwrap();
415                    let sd2 = ctx.pop_variable().unwrap();
416                    let variable_name = ctx.push_variable();
417                    let _ = writeln!(
418                        &mut code,
419                        "\t{} {} = {}_op_intersect({}, {});",
420                        output_glsl_type, variable_name, prefix, sd1, sd2
421                    );
422                }
423                IntersectSmooth => {
424                    let sd1 = ctx.pop_variable().unwrap();
425                    let sd2 = ctx.pop_variable().unwrap();
426                    let variable_name = ctx.push_variable();
427                    let size = ctx.float32();
428                    let _ = writeln!(
429                        &mut code,
430                        "\t{} {} = {}_op_intersect_smooth({}, {}, {});",
431                        output_glsl_type, variable_name, prefix, sd1, sd2, size
432                    );
433                }
434                PushTranslation => {
435                    let translation = ctx.vec3();
436                    let (new_position, old_position) = ctx.push_transform();
437                    let _ = writeln!(
438                        &mut code,
439                        "\tvec3 {} = {} + {};",
440                        new_position, old_position, translation
441                    );
442                }
443                PopTransform => {
444                    ctx.pop_transform();
445                }
446                PushRotation => {
447                    let rotation = ctx.quat();
448                    let (new_position, old_position) = ctx.push_transform();
449                    let _ = writeln!(
450                        &mut code,
451                        "\tvec3 {} = mul_quat({}, {});",
452                        new_position, rotation, old_position
453                    );
454                }
455                PushScale => {
456                    let scale = ctx.float32();
457                    let (new_position, old_position) = ctx.push_transform();
458                    let _ = writeln!(
459                        &mut code,
460                        "\tvec3 {} = {} * {};",
461                        new_position, old_position, scale
462                    );
463                }
464                PopScale => {
465                    ctx.pop_transform();
466                    let inv_scale = ctx.float32();
467                    let sd = ctx.pop_variable().unwrap();
468                    let variable_name = ctx.push_variable();
469                    let _ = writeln!(
470                        &mut code,
471                        "\t{} {} = {}_op_scale_distance({}, {});",
472                        output_glsl_type, variable_name, prefix, sd, inv_scale
473                    );
474                }
475                End => {
476                    break;
477                }
478            }
479        }
480
481        let ret = ctx.pop_variable().unwrap();
482        let _ = writeln!(&mut code, "\treturn {};\n}}", ret);
483
484        let _ = writeln!(
485            &mut code,
486            "float {}(vec3 pos) {{ return {}_base(pos){}; }}",
487            function_name,
488            function_name,
489            match output_type {
490                OutputType::DistanceOnly => "",
491                OutputType::DistanceWithRgb => ".w",
492            }
493        );
494
495        match output_type {
496            OutputType::DistanceWithRgb => {
497                let _ = writeln!(
498                    &mut code,
499                    "vec3 {}_color(vec3 pos) {{ return {}_base(pos).rgb; }}",
500                    function_name, function_name,
501                );
502            }
503            OutputType::DistanceOnly => {
504                let _ = writeln!(
505                    &mut code,
506                    "vec3 {}_color(vec3 /*pos*/) {{ return vec3(1.0, 1.0, 1.0); }}",
507                    function_name,
508                );
509            }
510        }
511
512        code.push_str("// !!! END OF GENERATED CODE !!!\n");
513        code
514    }
515
516    // Recompiles an 'Program' into the target.
517    pub fn to_code(
518        &self,
519        program: &Program,
520        function_name: &str,
521        output_type: OutputType,
522        dynamic_constants: bool,
523    ) -> String {
524        match self.backend {
525            Backend::GLSL => {
526                Self::build_glsl_code(program, function_name, output_type, dynamic_constants)
527            }
528        }
529    }
530}