Within the top-level OpenGLContext package are the objects implementing the Context interface. Each supported GUI library defines a derived Context class which overrides various methods to support the Context API (these derived classes are named GLUTContext, PyGameContext, wxContext, etceteras). Each supported GUI will also provide a sub-class which uses the mix in InteractiveContext, which, through the events package (see below), allows for keyboard and mouse event processing. Each GUI library will then provide a module Xtestingcontext.py (e.g. gluttestingcontext.py) which provides a context class factory function, and a "mainloop" function. The testingcontext.py module then provides an interface for finding the appropriate testing context for the specified GUI library.
Context classes are registered via SetupTools entry_points in the setup.py script. You can register context classes for your GUI library and produce a .egg file which will plug into OpenGLContext.
GLFW, GLUT, Pygame, wxPython and Qt/PySide are all first-class
targets. Breaking one of them is a regression, not a cleanup, and none
of them is a candidate for removal. GLFW is what the test suite and the
visual-regression captures run against, and it is the recommended backend for
core-profile and PBR rendering; wxPython is the one that embeds a GL canvas
inside a larger application's window rather than owning the whole window (see
tests/wx_with_controls.py); Qt/PySide is supported through the
separate OpenGLContext_qt project,
which OpenGLContext imports opportunistically. All of them can request a core
profile, and all of them can create a compatibility-profile context for the
fixed-function tutorials and for the display-list-backed font providers.
Code that targets one platform is supported on that platform whether or not
the machine you are reading this on can run it -- for instance
scenegraph/text/wglfont.py is Windows font support, and is expected
to work on Windows.
Rendering runs through the passes package described in Rendering Passes below: a single "flat" pass that observes the scenegraph's structure and draws it in a fixed sequence (background, opaque, transparent, selection, overlay). See Flat Rendering for the overview.
Earlier versions instead gave each Context a list of
RenderMode objects (Timer, Opaque, Transparent, Select) driven by a
visitor-pattern traversal. That system was removed once the flat pass replaced
it; Context.renderPasses now names the flat-pass dispatcher.
OpenGLContext supports both legacy (compatibility) and modern (core profile) OpenGL rendering. To enable core profile rendering, set the environment variable:
OPENGLCONTEXT_PROFILE=core python your_script.py
Core profile uses GLSL shaders instead of the deprecated fixed-function pipeline, making it compatible with modern OpenGL 3.3+ contexts and platforms like macOS that no longer support the compatibility profile.
Important: wxPython on GTK3 uses EGL (not GLX) for OpenGL context creation. When using core profile rendering with wxPython, you must configure PyOpenGL to use EGL for proper context tracking:
PYOPENGL_PLATFORM=egl OPENGLCONTEXT_PROFILE=core python your_script.py
The wxcontext module attempts to set this automatically, but if OpenGL is imported before wxcontext, you may need to set it manually. Symptoms of missing EGL configuration include errors like "Attempt to retrieve context when no valid context" during shader rendering. Legacy (fixed-function) rendering typically works without this setting.
The Qt backend lives in the separate OpenGLContext-qt
distribution and registers itself under the name qt, so installing
it is all that is needed for OPENGLCONTEXT_BACKEND=qt to select it.
It is built on QWindow with a QOpenGLContext of its
own rather than on QOpenGLWidget, so framebuffer 0 is the screen
and every render path -- the bloom composite, the selection buffer's blit, the
back-buffer read behind a screenshot -- behaves as it does under GLUT and GLFW.
Both profiles are supported, and every ContextDefinition field that
describes the window is mapped to the Qt surface format.
OPENGLCONTEXT_BACKEND=qt python your_script.py
Which Qt platform plugin you get matters. Qt asks the platform integration for the context, and some of them hand back one that is current on no drawable surface at all: every GL call succeeds, nothing raises, and every frame is black. The backend detects that at start-up and says so, naming the plugin. Selecting another one usually fixes it:
QT_QPA_PLATFORM=xcb OPENGLCONTEXT_BACKEND=qt python your_script.py
Two limits are worth knowing. accumulationBuffer does not exist
in Qt 6 and is reported rather than silently dropped, and
OPENGLCONTEXT_HIDDEN is not supported -- Qt's offscreen surfaces
have no default framebuffer on the common EGL platforms, so headless capture
should use the glfw backend.
The ViewPlatform and ViewPlatformMixin classes provide a simple navigation interface using the keyboard arrow keys (walk/fly) and the ALT-arrow keys (pan/slide). These classes make use of the Trackball, ExamineManager and DragWatcher classes as well.
An application wanting more than that declares movement modes
on its ContextDefinition: walking, flying, swimming and first-person
mouse-look as scenegraph nodes, each with its own speeds and key bindings, driven
from sampled input rather than from events so several inputs act in one frame.
See Movement Modes & Navigation. A context that
declares none keeps the older navigation untouched.
An application that needs a screen over the running world — rendering
settings, key rebinding, a licence notice, a console — mixes in
ui.overlay.OverlayMixin, which adds a stack of panels, routes input
to the topmost one and draws it after the frame. While a modal panel is up the
world hears nothing at all, including the input sampler — and a press the
overlay took takes its release with it, so the keystroke that closes the last
panel does not also reach the world. Every switchable rendering feature is a
field on the ContextDefinition, read through
renderoptions, so the settings screen is generated from those fields
rather than hand-written. The window's height picks the font size and everything
else is measured against it, so the interface is the same size in the eye at
1080p and at 4K. See Overlay UI.
Finally, at the top-level, we have a number of utility functions and modules, including drawcube (a testing function), Quaternion,utilities, vector utilities, and triangle utilities.
The passes package holds the current rendering system. A
single FlatPass observes the scenegraph and, each frame, draws it in a
fixed sequence -- background, opaque, transmissive, transparent, selection and
overlay -- rather than traversing it once per rendering mode. The base class
(passes/_flat.py) carries two code paths, chosen by one flag:
flatcompat.py -- the compatibility-profile pass, using the
fixed-function pipeline (glLight*, glMaterial*).flatcore.py -- the core-profile pass, using GLSL shaders. See
Core-Profile Rendering.Shader programs are managed by VRML97ShaderProgram
(passes/shaderpass.py), which compiles the lit, unlit, vertex-colour,
point, line and shadow-depth programs and assembles them from the GLSL sources in
the shaders/ directory (the .vert/.frag
files plus shared _*.glsl include files, spliced together at compile
time). Built on top of this are:
pbrpass.py -- the physically based (metallic/roughness)
renderer, a Cook-Torrance uber-shader. See
Physically Based Rendering and the
shader walkthrough.ibl.py -- image-based (environment) lighting: the precomputed
irradiance/prefilter/BRDF-LUT probe.shadowmap.py, shadowmixin.py,
shadowcaps.py, shadowmath.py -- the shared shadow
subsystem used by both the VRML97 and PBR lit shaders. See
Shadows.transmission.py -- the backdrop capture for glass
(KHR_materials_transmission).selection.py -- colour/object-id picking.renderpass.py -- picks which FlatPass subclass
renders a context (profile + renderer) and caches it across frames;
rendervisitor.py binds the scene's active Viewpoint into the
view platform for the core-profile path.The loaders package reads external model formats into the scenegraph:
The viewer package is the reusable viewer itself —
loading a scene without freezing the window, default lighting, auto-framing,
cameras and animations, the caption, screenshots, a library of samples to open,
and rendering one settled frame to a file. An application embeds
viewer.sceneviewer.ViewerContext and configures it with a
ViewerOptions, which is the same object the command line fills in
(see Embedding the viewer). Its parts
— adapters, asyncscene, framing,
environment, caption, capture,
library, menu — are usable individually by a
context that is not a viewer.
What format a source is in is decided by an adapter
registered under plugins.Adapter, alongside the loader, context and
node registries, so the dispatch is data rather than a chain of tests and a
third party adds a format without touching the viewer
(Adding a format).
The bin package provides console commands, registered as
scripts when the package is installed: oglc-view
(the viewer plus a command line, for every format),
oglc-terrain, oglc-gltf-demo,
oglc-gltf-regression, oglc-ui, oglc-test,
oglc-lorentz and oglc-profile. oglc-gltf,
oglc-vrml and oglc-tiles are deprecated aliases for
oglc-view.
The scenegraph and scenegraph.text packages provide a set of Python classes which render certain common types of geometry, materials, textures and grouping nodes (modeled loosely after VRML 97 nodes). This allows you to create "retained mode" scenes for rendering in your contexts (note that these classes are largely divorced from the internals of the context). You'll find Transform (including integer "names" reported during selection), Shape,Material,ImageTexture, and Light nodes which are similar to their VRML 97 namesakes. The ArrayGeometry class handles the rendering of all of IndexedFaceSet, IndexedLineSet and PointSet, with the modules of those names simply instantiating ArrayGeometry instances with the appropriate parameters. The text package provides basic text rendering in 3D or 2D forms using TTFQuery or one of the GUI library engines.
Alongside the VRML97-style nodes, the scenegraph includes
PBRMaterial and a PBRMesh geometry
(scenegraph/pbrmaterial.py), the metallic/roughness material and mesh
produced by the glTF loader and rendered by the
PBR pass. Legacy Material nodes are converted
to the same model when the PBR renderer is active, so both kinds of content share
one pipeline.
The events package implements a simple cross GUI-library event generation and handling system. Each GUI library defines subclasses of the major event and event handler classes. These subclasses translate from native events to OpenGLContext events. The event handler classes (which are mixin classes) are then included in the GUI library's Context class to provide the event handling interfaces.
Mouse events reach the application through the pick queue: a backend adds
one with Context.addPickEvent and the selection pass dispatches it
once it has resolved what the pick point is over. The queue is a mapping keyed
by Event.getPickKey, so events that are the same news twice within
one frame cost one dispatch. The wheel is the exception: a
notch arrives as a press and release of button 3 or 4
(mouseevents.WHEEL_UP and WHEEL_DOWN, the X11
numbering), it is an increment rather than a state, and its pick key is
distinct per notch so none is dropped. GLFW reports scrolling on a callback of
its own in offsets and translates to those buttons; see
the overlay UI documentation.
One, and it is the last registered: a key is identified by
(name, state, modifiers) and registering a second handler for the
same triple replaces the first. So the two places a context binds keys run in a
deliberate order, and Context.__init__ runs them that way:
setupDefaultEventCallbacks — what a key does when
nobody has said otherwise: Escape, the arrow-key navigation,
right-drag to examine, PageDown to cycle viewpoints.setupCallbacks — what a key does in this
context. It lands on top, so an application that wants a key the
framework also binds simply binds it.Modifiers are a three-tuple in the order
(shift, control, alt). A binding that asks for the wrong slot is
not an error — it registers, and the key silently never fires.
Handlers are held by weak reference, so the caller must keep the callback alive; a bound method of a live object is the normal choice, and one with no other reference is collected the moment the registration returns, leaving the key silently dead.