gl46
Python OpenGL 4.6 Direct State Access (DSA) utility library focused on compute and data-oriented workflows, wrapping PyOpenGL through an explicit GL protocol.
Overview
gl46 provides type-annotated, DSA-first GPU objects for OpenGL 4.6 core. Every GL call goes through a GL protocol (gl46.gl.GL), with a real PyOpenGLGL backend for the driver and a FakeGL recording backend for headless tests, so the same script runs against both.
Key classes, grouped by concern:
- Buffers:
Buffer,EBO,IndirectDrawBuffer(aliased asIBO),ShaderStorage - Textures:
Texture2D,Texture2DArray,TextureCubeMapArray,TextureMultisample,TextureBuffer - Shaders and programs:
Shader,ShaderStage,Program,ProgramPipeline,Sampler - Geometry and framebuffer state:
VertexArray,VertexAttrib,Framebuffer,Renderbuffer,RenderbufferMultisample - Scene helpers:
Material,Camera,Window - Compute, queries, and debug:
Barrier(aliased asMemoryBarrier),Query,TransformFeedback,DebugOutput
Not included. Windowing and input are not part of the core API — thin optional backends (gl46.window_imgui, gl46.window_qt) provide windows, render loops, and input. There is no scene graph, no math types (use numpy / pyglm), and no bindless-texture or multi-draw-indirect layer yet.
gl46 is not a ModernGL replacement: it is a minimal, DSA-first, fully testable substrate for developers assembling their own rendering framework.
Requirements
- Python 3.11+
glfw>=2.10.2PyOpenGLandpyopengl-acceleratefor the real GL backend- An OpenGL 4.5+ driver;
Windowrequests a 4.6 core context by default.
Development inside this repository uses uv; see Install below.
The core package has no GUI-framework or image-library dependency — glfw is the only windowing dependency, and it is required only by gl46.window.Window. imgui_bundle and PySide6 are optional extras, needed only by examples/imgui_scene.py and examples/qt_scene.py (uv sync --extra imgui or --extra qt; equivalent pip form: pip install "gl46[imgui]" or pip install "gl46[qt]"). Pillow is a separate optional extra for examples/instancing_mrt.py: pip install "gl46[examples]".
The committed uv.lock is resolved against a China mirror (Huawei Cloud); international contributors may want to override the index locally. This has no effect on users installing gl46 from PyPI.
Install
From PyPI (any project):
pip install gl46
Optional extras:
pip install "gl46[imgui]" # ImGui backend (gl46.window_imgui)
pip install "gl46[qt]" # PySide6 backend (gl46.window_qt)
pip install "gl46[examples]" # Pillow, for examples/instancing_mrt.py
If you use uv:
uv add gl46
uv add "gl46[imgui]" # etc.
Development install inside this repository:
uv sync --all-extras
Quick Start
Every gl46 call needs a current OpenGL 4.5+ context. The built-in Window creates one (pass visible=False for an offscreen context); you can also point gl46 at any other context you already own.
import numpy as np
from gl46 import Buffer, Window
from gl46.constants import GL_MAP_READ_BIT, GL_MAP_WRITE_BIT
with Window(320, 240, visible=False): # creates a GL context
buf = Buffer(
16 * 4,
flags=GL_MAP_READ_BIT | GL_MAP_WRITE_BIT,
initial_data=np.arange(16, dtype=np.float32),
)
with buf.map_range(0, buf.size_bytes, GL_MAP_READ_BIT) as mapped:
out = mapped.as_numpy(np.float32, (16,))
print(out) # [ 0. 1. 2. ... 15.]
For a full rendering example with a visible window, see examples/triangle.py.
Examples
Run any example with uv run python examples/<name>.py from the repo root. Except for quickstart.py (a headless snippet), each opens a visible window. Most respond to SPACE by writing <name>_frame.png; closing the window writes <name>_close.png. Three behave differently:
quickstart.pycreates an offscreen context (visible=False) and prints a numpy array — no window, no capture.imgui_scene.pywritesimgui_scene.png(scene + ImGui overlay) automatically on its second frame.qt_scene.pywritesqt_scene.pngon first draw, andqt_scene_r.pngwhen you press R.
uv run python examples/quickstart.py # Quick Start demo
uv run python examples/query_timing.py # DSA timer-query GPU timing
uv run python examples/triangle.py # VAO/VBO + a rotating triangle
uv run python examples/offscreen_render.py # offscreen FBO + fullscreen present
uv run python examples/compute_sobel.py # compute-shader Sobel + timing
uv run python examples/instancing_mrt.py # instancing & MRT scene
uv run python examples/imgui_scene.py # gl46 + ImGui overlay (imgui_bundle/immapp)
uv run python examples/qt_scene.py # gl46 hosted in a QOpenGLWidget (PySide6)
Three examples need extra dependencies beyond the core package:
imgui_scene.pyneedsimgui-bundle>=1.92.900. ItsSMOKEcallback raisesRuntimeErrorif theRunnerParams.app_shall_exithook added in that version is missing.qt_scene.pyneedsPySide6(theqtextra).instancing_mrt.pyusesPillowto load its assets (theexamplesextra); its mesh and texture files live underexamples/data/, shaders underexamples/shaders/.
imgui_scene.py needs the second frame because the ImGui overlay is not yet present in the framebuffer on the first one.
Building your own framework
gl46 leaves the application shell to you, but ships two thin window backends.
GLFW + ImGui (imgui_bundle)
The loop belongs to immapp.run; you supply three callbacks. post_init
runs once after the ImGui context is created but before the first frame —
create all gl46 objects there, since that is the earliest point where a GL
context is guaranteed. scene_render draws the 3D scene each frame (behind
the ImGui layer), and gui draws the control panel. Input for your scene
is read through the ImGui API (imgui.is_key_pressed / imgui.get_io()).
from gl46.window_imgui import run_imgui_app
class MyScene:
def __init__(self) -> None:
# Create gl46 objects here (Buffer, Program, VertexArray, ...).
...
def render(self) -> None:
# Draw the frame. Resize the viewport with the current
# framebuffer size, e.g.
# w, h = glfw.get_framebuffer_size(glfw.get_current_context())
# gl.viewport(0, 0, w, h)
...
scene: MyScene | None = None
def post_init() -> None:
global scene
scene = MyScene()
def scene_render() -> None:
if scene is not None:
scene.render()
def gui() -> None:
# Draw ImGui widgets. `scene` is available here for controls.
...
run_imgui_app(
post_init=post_init,
scene_render=scene_render,
gui=gui,
width=800,
height=600,
title="app",
)
For a complete runnable version — with an animated triangle, a tint slider,
and a composed imgui_scene.png screenshot written on the second frame —
see examples/imgui_scene.py.
run_imgui_app accepts two optional screenshot parameters: capture_path
writes one PNG, and capture_composed=True waits until the ImGui overlay
has rendered before capturing, so the file contains scene + UI. The default
capture_composed=False captures only the pure scene.
PySide6 (Qt)
Subclass GL46Widget, override the three hooks, then host it with run_qt_app. The loop is Qt's event loop; repaint via a QTimer (default).
from gl46.window_qt import GL46Widget, run_qt_app
class AppWidget(GL46Widget):
def initialize_gl(self) -> None: ... # create gl46 objects
def resize_gl(self, w: int, h: int) -> None: ... # framebuffer pixels
def paint_gl(self) -> None: ... # draw the frame
run_qt_app(AppWidget, title="app")
Testing
uv run pytest -q
The suite runs against the FakeGL backend (no GPU required).
For a real-GL smoke check of the ImGui and Qt examples, set GL46_SMOKE=1:
the app will close itself after a few frames.
GL46_SMOKE=1 uv run python examples/imgui_scene.py
GL46_SMOKE=1 uv run python examples/qt_scene.py
On Windows: set GL46_SMOKE=1 before running.
Quality Gates
uv sync --all-extras
uv run ruff check src tests
uv run ruff format --check src tests
uv run mypy src
Project Structure
gl46/
|-- src/
| `-- gl46/
| |-- __init__.py
| |-- py.typed
| |-- barrier.py
| |-- base.py
| |-- buffer.py
| |-- camera.py
| |-- constants.py
| |-- debug.py
| |-- errors.py
| |-- framebuffer.py
| |-- gl.py
| |-- material.py
| |-- pipeline.py
| |-- program.py
| |-- query.py
| |-- renderbuffer.py
| |-- sampler.py
| |-- shader.py
| |-- texture.py
| |-- transformfeedback.py
| |-- version.py
| |-- vertexarray.py
| |-- window.py
| |-- window_imgui.py
| `-- window_qt.py
|-- tests/
| |-- __init__.py
| |-- conftest.py
| |-- fake_gl.py
| |-- test_barrier.py
| |-- test_base.py
| |-- test_buffer.py
| |-- test_buffer_extra.py
| |-- test_camera.py
| |-- test_constants.py
| |-- test_debug.py
| |-- test_errors.py
| |-- test_fake_gl.py
| |-- test_framebuffer.py
| |-- test_gl.py
| |-- test_material.py
| |-- test_pipeline.py
| |-- test_program.py
| |-- test_query.py
| |-- test_renderbuffer.py
| |-- test_sampler.py
| |-- test_shader.py
| |-- test_texture.py
| |-- test_texture_extra.py
| |-- test_transformfeedback.py
| |-- test_utils.py
| |-- test_vertexarray.py
| |-- test_version.py
| |-- test_window.py
| |-- test_window_imgui.py
| `-- test_window_qt.py
|-- examples/
| |-- data/
| |-- shaders/
| |-- compute_sobel.py
| |-- imgui_scene.py
| |-- instancing_mrt.py
| |-- offscreen_render.py
| |-- qt_scene.py
| |-- query_timing.py
| |-- quickstart.py
| `-- triangle.py
|-- pyproject.toml
|-- uv.lock
|-- README.md
`-- LICENSE
License
MIT
Metadata
Release files for gl46 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| gl46-0.1.2.tar.gz | 3.7 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| gl46-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 3.7 MB
Release files / gl46-0.1.2.tar.gz
| Download URL | gl46-0.1.2.tar.gz |
|---|---|
| Size | 3.7 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cd2f1c38ae27ccc705e4e9f413dfe9828e326589086c8a2543918a5c71b6ce68
|
|
BLAKE2b-256 checksum How to use checksums |
7eda9304052faed8161e5ba102824a460df484af7e8ebbc3872ab9564dee5460
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / gl46-0.1.2-py3-none-any.whl
| Download URL | gl46-0.1.2-py3-none-any.whl |
|---|---|
| Size | 49.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
79b7038f47d7aba6332a694b43f342d10396a77ac46e9094f1078679518e7f10
|
|
BLAKE2b-256 checksum How to use checksums |
4854a739a00a4b210b3e7aed115197f16b689103a45abb01255e5a263eb05e4f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|