Things to know about inlining

Ultraviolet allows you to share / reuse code, as long as it is inlined following Scala 3's standard inlining rules. However there are things to know about how this will affect your GLSL!

Here, 'external' means 'not inside the body of your shader'.

Using Ultraviolet with Scala > 3.3.0

Ultraviolet (up to 0.1.1) was built against Scala 3.2.x, but does work with Scala 3.3.0 with one minor caveat: The thing to know is that calling external inlined functions doesn't work the way it used to anymore, and will produce errors that complain about illegal forward references.

Being able to call external inlined functions is important, because it's one of the main ways the Ultraviolet achieves code reuse.

Here is a simple example. Given an object called Importable like this:

object Importable:
  inline def addOne = (i: Int) => i + 1

In Scala 3.2.x

This used to work:

import Importable.*

inline def fragment: Shader[FragEnv, Int] =
  Shader { _ =>
    val value = 10
    addOne(value)
  }

Scala 3.3.x

Unfortunately the way inlines are represented in the AST has now changed, and the old method does not work anymore. Luckily there is a simple workaround. All we have to do is create a local proxy with the same signature that delegates to the original function:

import Importable.*

inline def fragment: Shader[FragEnv, Int] =
  Shader { _ =>
    val proxy: Int => Int = addOne
    val value = 10
    proxy(value)
  }

With this small change, we can continue to reuse code between shaders as before. Indeed the compiled output will be identical to how it was before.