Sunday, October 25, 2015

luminance, episode 0.6: UBO, SSBO, Stackage.

Up to now, luminance has been lacking two cool features: UBO and SSBO. Both are buffer-backed uniform techniques. That is, a way to pass uniforms to shader stages through buffers.

The latest version of luminance has one of the two features. UBO were added and SSBO will follow for the next version, I guess.

What is UBO?

UBO stands for Uniform Bbuffer Object. Basically, it enables you to create uniform blocks in GLSL in feed them with buffers. Instead of passing values directly to the uniform interface, you just write whatever values you want to to buffers, and then pass the buffer as a source for the uniform block.

Such a technique has a lot of advantages. Among them, you can pass a lot of values. It’s also cool when you want to pass values instances of a structure (in the GLSL source code). You can also use them to share uniforms between several shader programs as well as quickly change all the uniforms to use.

In luminance, you need several things. First thing first, you need… a buffer! More specifically, you need a buffer Region to store values in. However, you cannot use any kind of region. You have to use a region that can hold values that will be fetched from shaders. This is done with a type called UB a. A buffer of UB a can be used as UBO.

Let’s say you want to store colors in a buffer, so that you can use them in your fragment shader. We’ll want three colors to shade a triangle. We need to create the buffer and get the region:

colorBuffer :: Region RW (UB (V3 Float)) <- createBuffer (newRegion 3)

The explicit type is there so that GHC can infer the correct types for the Region. As you can see, nothing fancy, except that we just don’t want a Region RW (V3 Float but Region RW (UB (V3 Float)). Why RW?

Then, we’ll want to store colors in the buffer. Easy peasy:

writeWhole colorBuffer (map UB colors)

colors :: [V3 Float]
colors = [V3 1 0 0,V3 0 1 0,V3 0 0 1] -- red, green, blue

At this point, colorBuffer represents a GPU buffer that holds three colors: red, green and blue. The next part is to get the uniform interface. That part is experimental in terms of exposed interface, but the core idea will remain the same. You’re given a function to build UBO uniforms as you also have a function to build simple and plain uniforms in createProgram:

createProgram shaderList $ \uni uniBlock -> {- … -}

Don’t spend too much time reading the signature of that function. You just have to know that uni is a function that takes Either String Natural – either a uniform’s name or its integral semantic – and gives you mapped U in return and that uniBlock does the same thing, but for uniform blocks instead.

Here’s our vertex shader:

in vec2 co;
out vec4 vertexColor;

// This is the uniform block, called "Colors" and storing three colors
// as an array of three vec3 (RGB).
uniform Colors {
  vec3 colors[3];
};

void main() {
  gl_Position = vec4(co, 0., 1.);
  vertexColor = vec4(colors[gl_VertexID], 1.);
}"

So we want to get a U a mapped to that "Colors" uniform block. Easy!

(program,colorsU) <- createProgram shaderStages $ \_ uniBlock -> uniBlock "Colors"

And that’s all! The type of colorsU is U (Region rw (UB (V3 Float))). You can then gather colorBuffer and colorsU in a uniform interface to send colorBuffer to colorsU!

You can find the complete sample here.

Finally, you can augment the type you can use UB with by implementing the UniformBlock typeclass. You can derive the Generic typeclass and then use a default instance:

data MyType = {- … -} deriving (Generic)

instance UniformBlock MyTpe -- we’re good to go with buffer of MyType!

luminance, luminance-samples and Stackage

I added luminance and luminance-samples into Stackage. You can then find them in the nightly snapshots and the future LTS ones.

What’s next?

I plan to add stencil support for the framebuffer, because it’s missing and people might like it included. I will of course add support for *SSBO** as soon as I can. I also need to work on cheddar but that project is complex and I’m still stuck with design decisions.

Thanks for reading my and for your feedback. Have you great week!

Sunday, October 18, 2015

luminance-0.5.1 and wavefront-0.4.0.1

It’s been a few days I haven’t talked about luminance. I’ve been working on it a lot those days along with wavefront. In order that you keep up to date, I’ll describe the changes I made in those packages you have a talk about the future directions of those packages.

I’ll also give a snippet you can use to load geometries with wavefront and adapt them to embed into luminance so that you can actually render them! A package might come up from that kind of snippet – luminance-wavefront? We’ll see that!

wavefront

This package has received several changes among two major increments and several fixes. In the first place, I removed some code from the interface that was useless and used only for test purposes. I removed the Ctxt object – it’s a type used by the internal lexer anyways, so you don’t have to know about it – and exposed a type called WavefrontOBJ. That type reprents the parsed Wavefront data and is the main type used by the library in the interface.

Then, I also removed most of the modules, because they’re re-exported by the main module – Codec.Wavefront. I think the documentation is pretty straight-forward, but you think something is missing, please shoot a PM or an email! ;)

On the bugs level, I fixed a few things. Among them, there was a nasty bug in the implementation of an internal recursive parser that caused the last wavefront statement to be silently ignored.

I’d also like to point out that I performed some benchmarks – I will provide the data later on with a heap profile and graphs – and I’m pretty astonished with the results! The parser/lexer is insanely fast! It only takes a few milliseconds (between 7ms and 8ms) to load 50k faces (a 2MB .obj file). The code is not yet optimized, so I guess the package could go even faster!

You can find the changelog here.

luminance

I made a lot of work on luminance lately. First, the V type – used to represent vertex components – is not anymore defined by luminance but by linear. You can find the type here. You’ll need the DataKinds extension to write types like V 3 Float.

That change is due to the fact linear is a mature library with a lot of interesting functions and types everyone might use when doing graphics. Its V type has several interesting instances – Storable, Ord, etc. – that are required in luminance. Because it’s not simple to build such V, luminance provides you with three functions to build the 1D, 2D and 3D versions – vec2, vec3 and vec4. Currently, that type is the only one you can use to build vertex components. I might add V2, V3 and V4 as well later.

An interesting change: the Uniform typeclass has a lot of new instances! Basically, all vector types from linear, their array version and the 4x4 floating matrix – M44 Float. You can find the list of all instances here.

A new function was added to the Graphics.Lumimance.Geometry module called nubDirect. That function performs in linear logarithmic time and is used to turn a direct representation of vertices into a pair of data used to represent indexed vertices. The new list of vertices stores only unique vertices and the list of integral values stores the indices. You can then use both the information to build indexed geometries – see createGeometry for further details.

The interface to transfer texels to textures has changed. It doesn’t depend on Foldable anymore but on Data.Vector.Storable.Vector. That change is due to the fact that the Foldable solution uses toList behind the hood, which causes bad performance for the simple reason that we send the list to the GPU through the FFI. It’s then more efficient to use a Storable version. Furthermore, th most known package for textures loading – JuicyPixels – already uses that type of Vector. So you just have to enjoy the new performance boost! ;)

About bugs… I fixed a few ones. First, the implementation of the Storable instance for (:.) had an error for sizeOf. The implementation must be lazy in its argument, and the old one was not, causing undefined crashes when using that type. The strictness was removed and now everything works just fine!

Two bugs that were also fixed: the indexed render and the render of geometries with several vertex components. Those bugs were easy to fix and now you won’t experience those issues anymore.

Interfacing luminance with wavefront to render geometries from artists!

I thought it would be a hard task but I’m pretty proud of how easy it was to interface both the packages! The idea was to provide a function that would turn a WavefrontOBJ into a direct representation of luminance vertices. Here’s the function that implements such a conversion:

type Vtx = V 3 Float :. V 3 Float -- location :. normal

objToDirect :: WavefrontOBJ -> Maybe [Vtx]
objToDirect obj = traverse faceToVtx (toList faces)
  where
    locations = objLocations obj
    normals = objNormals obj
    faces = objFaces obj
    faceToVtx face = do
      let face' = elValue face
      vni <- faceNorIndex face'
      v <- locations !? (faceLocIndex face' - 1)
      vn <- normals !? (vni - 1)
      let loc = vec3 (locX v) (locY v) (locZ v)
          nor = vec3 (norX vn) (norY vn) (norZ vn)
      pure (loc :. nor)

As you can see, that function is pure and will eventually turn a WavefrontOBJ into a list of Vtx. Vtx is our own vertex type, encoding the location and the normal of the vertex. You can add texture coordinates if you want to. The function fails if a face’s index has no normal associated with or if an index is out-of-bound.

And… and that’s all! You can already have your Geometry with that – direct one:

  x <- fmap (fmap objToDirect) (fromFile "./ubercool-mesh.obj")
  case x of
    Right (Just vertices) -> createGeometry vertices Nothing Triangle
    _ -> throwError {- whatever you need as error there -}

You want an indexed version? Well, you already have everything to do that:

  x <- fmap (fmap (nubDirect . objToDirect) (fromFile "./ubercool-mesh.obj")
  case x of
    Right (Just (vertices,indices)) -> createGeometry vertices (Just indices) Triangle
    _ -> throwError {- whatever you need as error there -}

Even though the nubDirect performs in a pretty good complexity, it takes time. Don’t be surprised to see the “loading” time longer then.

I might package those snippets and helpers around them into a luminance-wavefront package, but that’s not trivial as the vertex format should be free.

Future directions and thank you

I received a lot of warm feedback from people about what I do in the Haskell community, and I’m just amazed. I’d like to thank each and everyone of you for your support – I even got support from non-Haskellers!

What’s next then… Well, I need to add a few more textures to luminance – texture arrays are not supported yet, and the framebuffers have to be altered to support all kind of textures. I will also try to write a cheddar interpreter directly into luminance to dump the String type of shader stages and replace it with cheddar’s whatever will be. For the long terms, I’ll add UBO and SSBO to luminance, and… compatibility with older OpenGL versions.

Once again, thank you, and keep the vibe!

Sunday, October 11, 2015

Load geometries with wavefront-0.1!

I’ve been away from luminance for a few days because I wanted to enhance the graphics world of Haskell. luminance might be interesting, if you can’t use the art works of your artists, you won’t go any further for a real-world application. I decided that I to write a parser/lexer to load 3D geometries from files. The Wavefront OBJ is an old yet simple and efficient way of encoding such objects. It supports materials, surfaces and a lot of other cool stuff – I don’t cover them yet, though.

There’s a package out there to do that, but it hasn’t been updated since 2008 and has a lot of dependencies I don’t like (InfixApplicative, OpenGL, OpenGLCheck, graphicsFormats, Codec-Image-Devil, and so on…). I like to keep things ultra simple and lightweight. So here we go. wavefront.

Currently, my package only builds up a pure value you can do whatever you want with. Upload it to the GPU, modify it, pretty print it, perform some physics on it. Whatever you want. The interface is not frozen yet and I need to perform some benchmarks to see if I have to improve the performance – the lexer is very simple and naive, I’d be amazed if the performance were that good yet.

As always, feel free to contribute, and keep in mind that the package will move quickly along the performance axis.

Tuesday, October 06, 2015

luminance-0.3 – Adding more texture kinds to the equation…

Unleashing the power of textures!

From luminance-0.1 to luminance-0.2 included, it was not possible to use any texture types different than two-dimensional textures. This blog entry tags the new release, luminance-0.3, which adds support for several kinds of texture.

A bit more dimensions

Texture1D, Texture2D and Texture3D are all part of the new release. The interface has changed – hence the breaking changes yield a major version increment – and I’ll explain how it has.

Basically, textures are now fully polymorphic and are constrained by a typeclass: Texture. That typeclass enables ad hoc polymorphism. It is then possible to add more texture types without having to change the interface, which is cool. Like everything else in luminance, you just have to ask the typesystem which kind of texture you want, and everything will be taken care of for you.

Basically, you have three functions to know:

  • createTexture, which is used to create a new texture ;
  • uploadSub, used to upload texels to a subpart of the texture ;
  • fillSub, used to fill – clear – a subpart of the texture with a given value.

All those functions work on (Texture t) => t, so it will work with all kinds of texture.

Cubemaps

Cubemaps are also included. They work like other textures but add the concept of faces. Feel free to dig in the documentation for further details.

What’s next?

I need to find a way to wrap texture arrays, which are very nice and useful for layered rendering. After that, I’ll try to expose the change to the framebuffers so that we can create framebuffers with cubemaps or that kind of cool feature.

In the waiting, have a good a week!