Using Imports & Shared Code
Imports and shared code are not entirely straight forward to use with Ultraviolet.
Ultraviolet would never have come into being without Scala 3's powerful inline macro system, but this is where you really start to see the limits of the current setup, and it's all down to how inlining works. No doubt all these problems are solvable, we only await someone with the time and inclination to come and fix them!
In any case, while sharing code in Ultraviolet is undoubtedly particular and peculiar to use, it is still a massive improvement over building shaders with strings as you would in JavaScript.
Please see the main docs on inlining for more information.
Example Links
How to import shared code
Shared code
First we'll declare some contrived code that we're pretending we'd like to use in multiple shaders.
There are some important things to note about this small section of code.
-
We've imported
ultraviolet.syntax.*
. Recall that, unfortunately, both Indigo and Ultraviolet both have types called things likevec2
,vec4
, etc. This import is to ensure that we're using the Ultraviolet types. In this specific example the import is done inside the object because indigo is imported at the top, but if the code was in a separate class then you could just put the import at the top as usual. -
All the fields and functions are declared as
inline
. This is because Ultraviolet is implemented with inline macros, and if the code isn't inlined, then Ultraviolet can't see it. -
redAmount
looks like it should be aval
... and indeed normally it would be. However,val
declarations do not work, so we have to useinline def
instead.
object SharedCode:
import ultraviolet.syntax.*
inline def redAmount: Float = 0.5
inline def grabX(uv: vec2): Float = uv.x
Within our custom shader object, we can import the shared code and use it in our shader, but
we can't do things like SharedCode.redAmount
at the present time.
import SharedCode.* // This is okay.
// SharedCode.redAmount // This does not work.
Another unfortunate limitation is that we cannot use our grabX function directly. The code will compile if you try to use it, but your shader will be black, and if you check your browser console you'll see a mysterious error, like this:
[Indigo] ERROR: 0:72: 'constructor' : too many arguments
In order to use the grabX function, we need to assign it to a proxy/delegate function within the scope of the shader.
val proxyGrabX: vec2 => Float = uv => grabX(uv)
Finally, we can see the shared code in use.
def fragment(color: vec4): vec4 =
vec4(redAmount, proxyGrabX(env.UV), 0.0f, 1.0f)