I've been writing shaders for a while now, and i keep running into the same couple of issues. They aren't deal-breakers, but they are annoying enough that I started wondering if there was a better way to handle them.
The biggest one is Coordinate Spaces.
In standard GLSL, a vec4 is just a vec4. The compiler doesn't care if that vector represents a position in World Space, Model Space, or Clip Space. If you accidentally multiply a View-Space-Vector by a Model-Matrix, the compiler won't stop you. You just get a black screen or weird lighting, and then you spend 20 minutes debugging only to realize you mixed up your spaces.
So, I decided to start a side project called Cast.
It's a small language that compiles down to GLSL, written in C# using ANTLR4. My goal wasn't to replace GLSL, but to create a "wrapper" that enforces some safety rules before generating the final code.
The Main Idea:
I wanted the coordinate system to be a part of the type itself. Instead of just writing vec4, in Cast you write vec4<World> or vec4<Model>.
This allows the compiler to check the math.
let modelPos : vec4<Model> = ...;
let matrix: mat4<Model, World> = ...;
// This works because of the types match
let worldPos = matrix * modelPos;
// This would throw a compiler error
let wrong = projectionMatrix * modelPos;
It's basically just using a strong type system to prevent logical errors.
Cleaning up the Syntax
The other thing that bothered me was reading nested math functions. Shader code often ends up looking like this: max(pow(dot(N, L), 32.0), 0.0)
You have to read it from inside out. My idea was to read it from left to right, instead of inside out. The outcome would be N.dot(L).pow(32.0).max(0.0)
Adding new features
While this is just syntactic sugar, i wanted to implement something that i remember from the language Go. It's called a Receiver Type and it goes like this:
type SomeStruct struct {
width,
height int
}
func (r *SomeStruct) SomeFunction() {...}
It's basically the same in Cast.
struct SomeStruct { x: float, y: float }
fn (SomeStruct) SomeFunction() {...}
Additionally, to keep the file a bit more structured i added in, out and uniform groups.
uniform {
...
}
in {
...
}
out {
...
}
Cast is currently in the "Proof of Concept" stage. It is not production-ready. Many standard features are still missing, and the compiler might crash if you look at it the wrong way.
I am sharing this now because i think the concepts (explicit coordinates and so on) are worth discussing, even if the implementation is still young.
I'd love to hear your feedback on the syntax and the architecture! Feel free to check out the source code, open an issue, or just tell me why my approach to matrix multiplication is wrong.
You can find it here Cast.
Top comments (0)