OpenGLContext Flat Rendering

This document describes OpenGLContext's "flat" rendering process.  This process uses client-side (not GL-side) calculations to produce composed transformation matrices which are directly loaded before rendering geometry.  The rendering process is considerably less involved than the original design, in which a set of separate "RenderPass" objects each traversed the whole scenegraph; that system has since been removed.

The flat pass carries two implementations: a legacy fixed-function path (the compatibility profile) and a shader path (the core profile). For how the shader path works -- its passes, shader programs and the VRML97 lighting shaders -- read Core-Profile Rendering; for the physically based renderer built on it, read Physically Based Rendering.

Observables and Tree Updates

PyVRML97 allows for watching updates to properties of nodes.  OpenGLContext uses this to watch for all updates to node fields within a scenegraph.  For each path to each node, it records a NodePath object which can calculate (and cache) the combined transform matrices for the path.

With this data structure (essentially a list of matrices and Render nodes), the scenegraph can be rendered with a number of simple iterations, rather than with a complex traversal mechanism (which traditionally was a significant factor of OpenGLContext run-time).

The default flat render pass also includes "colour select" rendering.  That is, it can do a selection rendering pass which can be queried to process incoming mouse events to find the object under the mouse.  This avoids the use of the legacy select render mode.

Triggering the RenderPass

So let's take a look at how the rendering process is triggered, from the moment the GUI library sends the "OnPaint" or equivalent event to the Context through to the calling of an individual RenderPass.

  1. event handler for the Context object, such as wxOnPaint for the wxPython Context sub-classes calls self.triggerRedraw(1) to force a redraw of the Context
  2. Context.triggerRedraw sets the "alreadyDrawn" flag to false, which tells the context that it needs to be redrawn at the next available opportunity, if not able to immediately draw, sets the redrawRequest event.
    1. at the next available opportunity (which may be within the triggerRedraw method, depending on the threading status and/or whether or not we are currently in the middle of rendering), the context's OnDraw method will be called
  3. Context.OnDraw
    1. performs an event cascade (calls the DoEventCascade customization point while the scenegraph lock is held (by default this does nothing))
    2. sets this Context instance as the current context
      1. acquires the OpenGLContext contextLock
      2. does the appropriate GUI library set current call
    3. clears the redrawRequest event
    4. calls the Context's renderPasses attribute receiving a flag specifying whether there was a visible change (flat *always* returns True here)
      1. if there was a change, swaps buffers
    5. finally, un-sets the current context
  4. defaultRenderPasses.__call__
    1. picks the FlatPass class for the Context's profile and renderer -- the compatibility pass, the core pass, or the PBR pass -- and caches it, rebuilding only when the scenegraph itself is replaced
    2. for the core profile, binds the scene's active Viewpoint into the view platform (the compatibility path does this inside its own traversal)
    3. returns the result of calling that FlatPass with the Context
  5. FlatPass.Render
    1. walks the paths it has observed on the scenegraph, sorting them into background, opaque, transparent and (when there are pick events) selection work
    2. draws each group in turn, then swaps buffers

Earlier versions instead built an OverallPass holding a set of sub-passes, each of which traversed the whole scenegraph in turn. That system was removed once the flat pass replaced it.