OpenGLContext Flat Rendering

This document describes OpenGLContext's new "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 set of separate "RenderPass" objects which would traverse the scenegraph for each pass.

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. PassSet.__call__
    1. instantiates a new OverallPass object with pointers to the three sub-passes as an argument
    2. returns the result of calling the new OverallPass with the passed Context object
  5. OverallPass.__init__
    1. stores various matrices
    2. sets up global structures (see above)
    3. instantiates each sub-pass (with a pointer to the OverallPass, and the passCount for the given sub-pass)
  6. OverallPass.__call__
    1. iterates through the list of sub-pass instances calling each one in turn and tracking whether it reports a visible change
    2. returns the sum of all the "changed" values (which is in turn returned by the PassSet to the Context's OnDraw method)
  7. (Visiting)RenderPass.__call__
    1. generally checks whether the pass should be rendered or not (shouldDraw method)
    2. if not, returns false (no visible change)
    3. otherwise performs the pass-specific rendering and returns it's "visible" flag

So, at this point, the RenderPass has been called, we are within the Context thread (with the lock held), and the OverallPass is initialised.  Each RenderPass defines it's own __call__ method, but the ones likely of the most interest are the ones derived from VisitingRenderPass.