Showing posts with label shading. Show all posts
Showing posts with label shading. Show all posts

Sunday, July 30, 2017

Rust GLSL crate

It’s been almost two months that I’ve been working on my glsl crate. This crate exposes a GLSL450 compiler that enables you to parse GLSL-formatted sources into memory in the form of an AST. Currently, that AST is everything you get from the parsing process – you’re free to do whatever you want with it. In the next days, I’ll write a GLSL writer (so that I can check that I can parse GLSL to GLSL…). I’d love to see contributions from Vulkan people to write a SPIR-V backend, though!

Just for the record, the initial goal I had in mind was to parse a subset of GLSL for my spectra demoscene framework. I’ve been planning to write my own GLSL-based shading language (with modules, composability, etc. etc.) and hence I need to be able to parse GLSL sources. Because I wanted to share my effort, I decided to create a dedicated project and here we are with a GLSL crate.

Currently, you can successfully parse a GLSL450-formatted source (or part of it, I expose all the intermediary parsers as well as it’s required by my needs to create another shading language over GLSL). See for instance this shading snippet parsed to an AST.

However, because the project is still very young, there a lot of features that are missing:

  • I followed the official GLSL450 specifications, which is great, but the types are not very intuitive – some refactoring must be done here;
  • I use nom as a parser library. It’s not perfect but it does its job very well. However, error reporting is pretty absent right now (if you have an error in your source, you’re basically left with Error(SomeVariant) flag, which is completely unusable;
  • I wrote about 110 unit tests to ensure the parsing process is correct. However, there’s for now zero semantic check. Those are lacking.
  • About the semantic checks, the crate is also missing a semantic analysis. I don’t really know what to do here, because it’s a lot of work (like, ensure that the assigned value to a float value is a real float and not a boolean or ensure that a function that must returns a vec3 returns a vec3 and not something else, etc.). This is not a trivial task and because this is already done by the OpenGL drivers, I won’t provide this feature yet. However, to me, it’d be a great value.

If you’re curious, you can start using the crate with glsl = "0.2.1" in your Cargo.toml. You probably want to use the translation_unit parser, which is the most external parser (it parses an actual shader). If you’re looking for something similar to my need and want to parse subparts of GLSL, feel free to dig in the documentation, and especially the parsers module, that exports all the parsers available to parse GLSL’s parts.

Either way, you have to pass a bytes slice. If your source’s type is String or &str, you can use the str::as_bytes() function to feed the input of the parsers.

Not all parsers are exported, only the most important ones. You will not find an octal parser, for instance, while it’s defined and used in the crate internally.

Call to contribution

If you think it’s worth it, I’m looking for people who would like to:

  • write a GLSL to SPIR-V writer: it’s an easy task, because you just have to write a function of the form AST -> Result<SPIRV, _>, for being pragmatic;
  • test the crate and provide feedback about any error you’d find! Please do not open an issue if you have an error in your source and find “the error from the parsers is not useful”, because it’s already well established this is a problem and I’m doing my best to solve it;
  • any kind of contributions you think could be interesting for the crate.

Happy coding, and keep the vibe!

Monday, November 17, 2014

Abstracting Over Shader – Environment

In the previous episode…

This blog entry directly follows the one in which I introduced Ash, a shading language embedded in Haskell. Feel free to read it here before going on.

Controlling behavior

A shader is commonly a function. However, it’s a bit more than a simple function. If you’re a haskeller, you might already know the MonadReader typeclass or simply Reader (or its transformer version ReaderT). Well, a shader is kind of a function in a reader monad.

So… that implies a shader runs in… an environment?

Yeah, exactly! And you define that environment. The environment is guaranteed not to change between two invocations of a shader for the same render (e.g. between two vertices in the same render). This is interesting, because it enables you to use nice variables, such as time, screen resolution, matrices and whatever your imagination brings on ;)

The environment, however, can be changed between two renders, so that you can update the time value passed to the shader, the new resolution if the window resizes, the updated matrices since your camera’s moved, and so on and so forth.

Let’s see a few example in GLSL first.

Shader environment in GLSL

To control the environment of a shader in GLSL, we use uniform variables. Those are special, global variables and shared between all stages of a shader chain1.

Let’s see how to introduce a few uniforms in our shader:

uniform float time;       // time of the host application
uniform vec2 resolution;  // (w,h)
uniform vec2 iresolution; // (1 / w, 1 / h), really useful in a lot of cases ;)
uniform mat4 proj;        // projection matrix
uniform int seed;         // a seed for whatever purpose (perlin noise?)
uniform ivec2 gridSize;   // size of a perlin noise grid!

You got it. Nothing fancy. Those uniforms are shared between all stages so that we can use time in all our shaders, which is pretty cool. You use them as any kind of other variables.

Ok, let’s write an expression that takes a time, a bias value, and multiply them between each other:

uniform float time;
uniform float bias;

// this is not a valid shader, just the expression using it
time * bias;

Shader environment in HLSL

HLSL uses the term constant buffers to introduce the concept of environment. I don’t have any examples right now, sorry for the inconvenience.

Shader environment in Ash

In Ash, environment variables are not called uniforms nor constant buffers. They’re called… CPU variables. That might be weird at first, but let’s think of it. Those values are handled through your application, which lives CPU-side. The environment is like a bridge between the CPU world and the GPU one. A CPU variable refers to a constant value GPU-side, but varying CPU-side.

Create a CPU variable is pretty straight-forward. You have to use a function called cpu. That function is a monadic function working in the EDSL monad. I won’t describe that type since it’s still a work in progress, but it’s a monad for sure.

Note: If you’ve read the previous blog entry, you might have come across the Ash type, describing a HOAST. That type is no more a HOAST. The “new Ash” – the type describing the HOAST – is now Expr.

This is cpu:

cpu :: (CPU a) => Chain (Expr a)

CPU is a typeclass that enables a type to be injected in the environment of a shader chain. The instances are provided by Ash and you can’t make your own – do you really want to make instance CPU String, or instance (CPU a) => CPU (Maybe a)? Don’t think so ;)

Let’s implement the same time–bias example as the GLSL one:

foo :: Chain (Expr Float)
foo = liftA2 (*) cpu cpu

That example is ridiculous, since in normal code, you’d actually want to pass the CPU variables to nested expressions, in shaders. So you could do that:

foo :: Chain ()
foo = do
  time <- cpu
  bias <- cpu

  -- do whatever you want with time and bias
  return ()

You said Chain?

Chain is a new type I introduce in this paper. The idea came up from a discussion I had with Edward Kmett when I discovered that the EDSL needed a way to bind the CPU variables. I spotted two ways to go:

  • using a name, like String, passed to cpu; that would result in writing the name in every shader using it, so that’s not ideal;
  • introducing the environment and providing a monad instance so that we could bind the CPU variables and use them in shaders inside the monad.

The latter also provides a nice hidden feature. A chain of shaders might imply varying2 values. Those varying values have information attached. If you mistake them, that results in catastrophic situations. Using a higher monadic type to capture that information – along with the environment – is in my opinion pretty great because it can prevent you from going into the wall ;).

To sum up, Chain provides a clear way to describe the relation between shaders.

What’s next?

I’m still building and enhancing Ash. In the next post, I’ll try to detail the interface to build functions, but I still need to find how to represent them the best possible way.


  1. You can imagine a shader chain as an explicit composition of functions (i.e. shaders). For instance, a vertex shader followed by geometry shader, itself followed by a fragment shader.

  2. Varying values are values that travel between shaders. When a shader outputs a value, it can go to the input of another shader. That is call a varying value.

Friday, November 14, 2014

Abstracting shader – introducing the ash Haskell library

Foreword

Abstracting what?

Shaders are very common units in the world of graphics. Even though we’re used to using them for shading1 purposes, they’re not limited to that. Vulgarisation has ripped off the meaning up and down so much that nowadays, a shader might have nothing related to shading. If you’re already doing some graphics, you may know OpenGL and its compute shaders. They have nothing to do with shading.

You might also already know shader toy. That’s a great place to host cool and fancy OpenGL shaders2. You write your shaders in GLSL3 then a GLSL compiler is invoked, and your shader is running on the GPU.

The problem with source based shaders

So you write your shader as a source code in a host language, for instance in C/C++, Java, Haskell, whatever, and you end up with a shader running on GPU.

There’re two nasty issues with that way of doing though:

  • the shader is compiled at runtime, so if it contains error, you’ll know that after your application starts ;
  • you have to learn a new language for each target shader compilers.

They’re both serious issues I’m going to explain further.

Issue n°1: compiled at runtime

This is problematic for a good reason: a lot of shaders are application dependent. Shadertoy is a nice exception, just like modeling tools or material editors, but seriously, in most applications, end users are not asked the shaders to run with. In a game for instance, you write all the shaders while writing the game, and then release the whole package.

Yeah… What’s about additional content? Per-map shaders, or that kind of stuff?

Those shaders are like resources. That doesn’t imply using them as is though. We could use dynamic relocatable objects (.so or .dll) for instance.

What compile-time compilation gives you?

It gives you something hyper cool: host language features. If you have a strongly-typed language, you’ll benefit from that. And that’s a huge benefit you can’t get away from. If you’re writing an incorrectly typed shader, your application / library won’t compile, so that the application won’t react in weird way at run-time. That’s pretty badass.

Issue n°2: languages, languages…

This issue is not as important as the first one, but still. If you’re working on a project and you target several platforms (among ones using OpenGL, OpenGL ES, DirectX and a soft renderer), you’ll have to learn several shading languages as well (GLSL, HLSL4).

In order to solve that, there’re two ways to go:

  • a DSL5 ;
  • an EDSL6.

A DSL is appealing. You have a standalone language for writing shaders, and backends for a compiler/language. However, that sounds a bit overwhelming for such an aim.

An EDSL is pretty cool as well. Take a host language (we’ll be using Haskell) and provide structure and construction idioms borrowed from such a language to create a small embedded one. That is the solution I’m going to introduce.

Ash

Ash stands for Abstract Shader. It’s a Haskell package I’ve been working on for a few weeks now. The main idea is:

  • to provide a typesafe shading language compiled at compile-time;
  • to provide backends;
  • to provide a nice and friendly haskellish interface.

I guessed it’d be a good idea to share my thoughts about the whole concept, since I reckon several people will be interested in such a topic. However, keep in mind that Ash is still a big work in progress. I’m gonna use several blog entries to write down my thoughts, share it with you, possibly enhance Ash, and finally release a decent and powerful library.

If you’re curious, you can find Ash here.

Basics

Ash is a library that provides useful tools to build up shaders in Haskell. In Ash, a shader is commonly function. For instance, a vertex shader is a function that folds vertex components down to other ones – possibly maps, but it could add/remove components as well – and yields extra values for the next stages to work with.

You write a shader with the Ash EDSL then you pass it along to a backend compiler.

Here are two examples. In order for you to understand how Ash works, I’ll first write the GLSL (330 core) shader, then the Ash one.

First example: a simple vertex shader

Let’s write a vertex shader that takes a position and a color, and projects the vertex using a perspective matrix, a view matrix and the object matrix of the object currently being rendered and passes the color to the next stage:

#version 330 core

in vec3 pos;
in vec4 col;

out vec4 vcol;

uniform mat4 projViewModel;

void main() {
  vcol = col; 
  gl_Position = projViewModel * vec4(pos, 1.);
}

And now, the Ash one:

vertexShader :: Ash (M44 Float -> V3 Float :. V4 Float -> V4 Float :. V4 Float)
vertexShader = lam $ \proj -> lam $ \v ->
  let pos :. col = v
  in proj #* v3v4 pos 1 :. col

Ash is the type used to lift the shading expression up to Haskell. You use it to use the EDSL. It actually represents some kind of HOAST7.

Then, you can find M44, V3, V4 and (:.).

M44 is the type of 4x4 matrices. Since projection matrix, view matrix and model matrix are all 4x4 floating matrix, M44 Float makes sense.

V3 and V4 represents 3D and 4D vectors, respectively. V3 Int is three ints packed in a vector as well as V4 Float is four floats packed in a vector. You’ll also meet V2, which is… the 2D version.

(:.) is a type operator used to build tuples. You can see (:.) as a generalized (,) – the default Haskell pair type – but (:.) is more power full since it can flatten expressions:

a :. (b :. c) = a :. b :. c

The (:.) has a lot of uses in Ash. In our cases, a chain of (:.) represents a vertex’ components.

So our vertexShader value is just a function that takes a matrix and a vertex (two components) and outputs two values: the new position of the shader, and the color. Let’s see the body of the function.

lam $ \proj -> lam $ \v ->

This is a pretty weird expression, but I haven’t found – yet? – a better way to go. lam is a combinator used to introduce lambdas in the EDSL. This expression then introduces a lambda that takes two values: proj and v. You can read that as:

\proj v ->

Next:

let pos :. col = v

This is the tricky part. That let expression extracts the components out of the vertex and binds them to pos and col for later use.

in proj #* v3v4 pos 1 :. col

(#*) is a cool operator used to multiply a matrix by a vector, yielding a new vector.

(v3v4) is a shortcut used to to build a V4 using a V3 by providing the missing value – here, 1. You’ll find similar functions, like v2v3 and v2v4, to respectively build a V3 from a V2 by providing the missing value and build a V4 from a V2 by providing the two missing values.

We finally wrap the result in a tuple (:.), and we’re done.

Features

Ash embeds regular linear expressions (vectors, matrix), textures manipulation, tuples creation, let-bindings, lambda functions (they represent shader stages up to now), and a lot of other features.

Each feature is supposed to have an implementation in a given backend. For instance, in the GLSL backend, a lambda function is often turned into the well done main function. Its parameters are expanded to as in values, and control parameters are uniform variables.

Each backend is supposed to export a compile function – the name may varies though. However, each backend is free to compiles to whatever smart they think is. For instance, compiling an Ash shader to GLuint (shader stage) is not very smart since it would use IO and handles error a specific way we don’t want it to do. So the GLSL compiler is a function like glslCompile :: Ash … -> Either CompilationError String, and the String can be used as a regular GLSL source code string you’ll pass to whatever implementation of shader you’ve written.

What’s next?

I need to finish the implentation of the EDSL, and write the whole GLSL 330 compiler. If it’s a success, I’ll accept pull-requests for other famous compilers (other GLSL version compilers, HLSL, and so on and so forth).

Once that done, I’ll write a few other blog entries with example as a proof-of-concept :)


  1. Shading is the process in which primitives (sets of vertices) are turned into colors (i.e fragments, a.k.a. pixels or texels).

  2. Actually, they’re fragment shaders.

  3. OpenGL Shading Language.

  4. High Level Shading Language.

  5. Domain-Specific Language.

  6. Embedded Specific Language.

  7. High-Order Abstract Syntax tree; for the purpose of this paper, you don’t have to fully understand them to get your feet wet with Ash (which is cool, right? :) ).