Swing and a miss

by baggers

Ok so last week I thought I was nearly done with documentation...and I was

I had 2 more macros to document when I started realizing that the defpipeline macro, the one that lets you compose gpu-function into pipelines, was too complicated.

This was a real pain, but once I had the feeling that it could be simpler I could ignore it and started whiteboarding.

So defpipeline can be used in 2 ways, to compose gpu-functions into pipelines, and to compose pipelines in multipass pipelines.

It was that second nature that was too complicated, but there were reasons I made it in the first place, it had the following features.

  • automatic uniform arguments (you dont have to add the arguments to the signature as the compiler knows them)
  • very fast blending parameters (It does some preprocessing on the params to make sending them to gl fast)
  • local fbos (you can define fbos that belong to the pipeline)
  • all the with-fbo-bound stuff is written for you.

So it had something going for it, but it turned out that with one exception all of this could be done in a regular function.

I wanted to turn this:

(defpipeline bloom (stream &uniform tx)
    (g-> (c0 (blit stream :tex tx))
         (h0 (smooth stream :tex (attachment c0 0) :offset (v! (/ 1.2 512) 0)))
         (nil (combine stream
                       :t0 (attachment c0 0) :t1 (attachment h0 0))))
  (c0 '(:c :dimensions (512 512) :magnify-filter :linear))
  (h0 '(:c :dimensions (512 512))))

into this

(let ((c0 '(make-fbo '(:c :dimensions (512 512) :magnify-filter :linear)))
      (h0 '(make-fbo '(:c :dimensions (512 512)))))
  (defun bloom (stream tx)
    (map-g-into c0 #'blit stream :tex tx)
    (map-g-into h0 #'smooth stream :tex (attachment c0 0) :offset (v! (/ 1.2 512) 0))
    (map-g #'combine stream :t0 (attachment c0 0) :t1 (attachment h0 0))))

map-g-into is a function that let's you specify which fbo you are writing into, this removes a little boilerplate around that task and handles one of the features defpipeline had. (map-g by constract draws into whatever fbo is currently bound)

The :fbo declarations in the defpipeline version are replaced by the lexical closure in version 2. This is all nice syntactically but has one big problem. Because this is a top-level function, the values in those variables are going to initialzied when the program starts, but we dont have a GL context at that point, so making an FBO will fail. That sucked for a while, but what I have done now it to have the constructors check if the GL context exists when creating the object; if the context exists they carry on as normal, if it doesnt then they return an 'uninitialized' object, and add the initialized function a callback that runs when the context is created.

This takes care of another defpipeline feature.

'Very fast blending parameters' was another interesting one. Because defpipeline is a macro, we can generate all kinds of helper code behind the scenes to make things faster. One thing we did was to make a foreign array with gl parameters ready to go so we could avoid any lisp->C conversion costs. To keep this feature I'm going to move the 'foreign array' trick to the FBO struct, I should be able to get very similar performance that way.

And lastly there is the exception, 'automatic uniform arguments'. This feature meant you didnt need to write long signatures by hand as they would be written for you, however you could override the defaults if you wished. Looking at this I realized the feature added more confusion that it solved, the overriding was not obvious or easy to explain. What I'm going to do instead is make this a function that can be called by tools to aid in writing functions. This means your editor could query the argumnents from CEPL and inject them into your function definition if you ask it to.

I am just about finished with the bulk of the changes. I also refactored how my samplers worked to make things simpler but I will leave that for another week.