gl46 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gl46-0.1.0/.gitignore +33 -0
- gl46-0.1.0/LICENSE +21 -0
- gl46-0.1.0/PKG-INFO +276 -0
- gl46-0.1.0/README.md +240 -0
- gl46-0.1.0/examples/compute_sobel.py +208 -0
- gl46-0.1.0/examples/data/meshes/monkey.ibo +0 -0
- gl46-0.1.0/examples/data/meshes/sphere.ibo +0 -0
- gl46-0.1.0/examples/data/meshes/teapot.ibo +0 -0
- gl46-0.1.0/examples/data/textures/acknowledgments.txt +2 -0
- gl46-0.1.0/examples/data/textures/cubemap_back.bmp +0 -0
- gl46-0.1.0/examples/data/textures/cubemap_bottom.bmp +0 -0
- gl46-0.1.0/examples/data/textures/cubemap_front.bmp +0 -0
- gl46-0.1.0/examples/data/textures/cubemap_left.bmp +0 -0
- gl46-0.1.0/examples/data/textures/cubemap_right.bmp +0 -0
- gl46-0.1.0/examples/data/textures/cubemap_top.bmp +0 -0
- gl46-0.1.0/examples/data/textures/texture_1.bmp +0 -0
- gl46-0.1.0/examples/data/textures/texture_2.bmp +0 -0
- gl46-0.1.0/examples/data/textures/texture_3.bmp +0 -0
- gl46-0.1.0/examples/imgui_scene.py +148 -0
- gl46-0.1.0/examples/instancing_mrt.py +440 -0
- gl46-0.1.0/examples/offscreen_render.py +195 -0
- gl46-0.1.0/examples/qt_scene.py +130 -0
- gl46-0.1.0/examples/query_timing.py +131 -0
- gl46-0.1.0/examples/quickstart.py +15 -0
- gl46-0.1.0/examples/shaders/cubemap.frag +20 -0
- gl46-0.1.0/examples/shaders/cubemap.vert +55 -0
- gl46-0.1.0/examples/shaders/mesh.frag +53 -0
- gl46-0.1.0/examples/shaders/mesh.vert +54 -0
- gl46-0.1.0/examples/shaders/quad.frag +38 -0
- gl46-0.1.0/examples/shaders/quad.vert +30 -0
- gl46-0.1.0/examples/triangle.py +120 -0
- gl46-0.1.0/pyproject.toml +94 -0
- gl46-0.1.0/src/gl46/__init__.py +99 -0
- gl46-0.1.0/src/gl46/barrier.py +82 -0
- gl46-0.1.0/src/gl46/base.py +53 -0
- gl46-0.1.0/src/gl46/buffer.py +351 -0
- gl46-0.1.0/src/gl46/camera.py +146 -0
- gl46-0.1.0/src/gl46/constants.py +361 -0
- gl46-0.1.0/src/gl46/debug.py +113 -0
- gl46-0.1.0/src/gl46/errors.py +45 -0
- gl46-0.1.0/src/gl46/framebuffer.py +233 -0
- gl46-0.1.0/src/gl46/gl.py +1342 -0
- gl46-0.1.0/src/gl46/material.py +84 -0
- gl46-0.1.0/src/gl46/pipeline.py +53 -0
- gl46-0.1.0/src/gl46/program.py +208 -0
- gl46-0.1.0/src/gl46/py.typed +0 -0
- gl46-0.1.0/src/gl46/query.py +88 -0
- gl46-0.1.0/src/gl46/renderbuffer.py +78 -0
- gl46-0.1.0/src/gl46/sampler.py +58 -0
- gl46-0.1.0/src/gl46/shader.py +103 -0
- gl46-0.1.0/src/gl46/texture.py +456 -0
- gl46-0.1.0/src/gl46/transformfeedback.py +75 -0
- gl46-0.1.0/src/gl46/utils.py +143 -0
- gl46-0.1.0/src/gl46/version.py +39 -0
- gl46-0.1.0/src/gl46/vertexarray.py +80 -0
- gl46-0.1.0/src/gl46/window.py +189 -0
- gl46-0.1.0/src/gl46/window_imgui.py +126 -0
- gl46-0.1.0/src/gl46/window_qt.py +121 -0
- gl46-0.1.0/tests/__init__.py +0 -0
- gl46-0.1.0/tests/conftest.py +20 -0
- gl46-0.1.0/tests/fake_gl.py +850 -0
- gl46-0.1.0/tests/test_barrier.py +99 -0
- gl46-0.1.0/tests/test_base.py +78 -0
- gl46-0.1.0/tests/test_buffer.py +257 -0
- gl46-0.1.0/tests/test_buffer_extra.py +193 -0
- gl46-0.1.0/tests/test_camera.py +98 -0
- gl46-0.1.0/tests/test_constants.py +21 -0
- gl46-0.1.0/tests/test_debug.py +110 -0
- gl46-0.1.0/tests/test_errors.py +55 -0
- gl46-0.1.0/tests/test_fake_gl.py +99 -0
- gl46-0.1.0/tests/test_framebuffer.py +274 -0
- gl46-0.1.0/tests/test_gl.py +211 -0
- gl46-0.1.0/tests/test_material.py +50 -0
- gl46-0.1.0/tests/test_pipeline.py +83 -0
- gl46-0.1.0/tests/test_program.py +231 -0
- gl46-0.1.0/tests/test_query.py +101 -0
- gl46-0.1.0/tests/test_renderbuffer.py +47 -0
- gl46-0.1.0/tests/test_sampler.py +69 -0
- gl46-0.1.0/tests/test_shader.py +75 -0
- gl46-0.1.0/tests/test_texture.py +211 -0
- gl46-0.1.0/tests/test_texture_extra.py +132 -0
- gl46-0.1.0/tests/test_transformfeedback.py +80 -0
- gl46-0.1.0/tests/test_utils.py +167 -0
- gl46-0.1.0/tests/test_version.py +35 -0
- gl46-0.1.0/tests/test_vertexarray.py +79 -0
- gl46-0.1.0/tests/test_window.py +166 -0
- gl46-0.1.0/tests/test_window_imgui.py +145 -0
- gl46-0.1.0/tests/test_window_qt.py +131 -0
- gl46-0.1.0/uv.lock +1012 -0
gl46-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Byte-compiled / cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
.mypy_cache/
|
|
6
|
+
.ruff_cache/
|
|
7
|
+
|
|
8
|
+
# SDD / plan scratch
|
|
9
|
+
.superpowers/
|
|
10
|
+
|
|
11
|
+
# Repair backup + downloaded research artifacts
|
|
12
|
+
dialog.jsonl.bak
|
|
13
|
+
68747470733a2f2f692e696d6775722e636f6d2f6451456d3833772e676966.gif
|
|
14
|
+
|
|
15
|
+
# Virtual environments
|
|
16
|
+
.venv/
|
|
17
|
+
venv/
|
|
18
|
+
|
|
19
|
+
# Build artifacts
|
|
20
|
+
build/
|
|
21
|
+
dist/
|
|
22
|
+
*.egg-info/
|
|
23
|
+
|
|
24
|
+
# Environment
|
|
25
|
+
.env
|
|
26
|
+
|
|
27
|
+
# Example screenshot artifacts
|
|
28
|
+
*_frame.png
|
|
29
|
+
*_close.png
|
|
30
|
+
triangle.png
|
|
31
|
+
imgui_scene.png
|
|
32
|
+
qt_scene.png
|
|
33
|
+
qt_scene_r.png
|
gl46-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yiguo Tang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gl46-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: gl46
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python OpenGL 4.6 Direct State Access (DSA) utility library.
|
|
5
|
+
Author-email: Yiguo Tang <yiguo.tang@outlook.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: compute,dsa,gpu,graphics,opengl
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
17
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: glfw>=2.10.2
|
|
21
|
+
Requires-Dist: numpy
|
|
22
|
+
Requires-Dist: pyopengl
|
|
23
|
+
Requires-Dist: pyopengl-accelerate>=3.1.10
|
|
24
|
+
Provides-Extra: examples
|
|
25
|
+
Requires-Dist: pillow>=12.3.0; extra == 'examples'
|
|
26
|
+
Provides-Extra: imgui
|
|
27
|
+
Requires-Dist: imgui-bundle>=1.92.900; extra == 'imgui'
|
|
28
|
+
Provides-Extra: qt
|
|
29
|
+
Requires-Dist: pyside6>=6.5; extra == 'qt'
|
|
30
|
+
Provides-Extra: test
|
|
31
|
+
Requires-Dist: mypy; extra == 'test'
|
|
32
|
+
Requires-Dist: pytest; extra == 'test'
|
|
33
|
+
Requires-Dist: pytest-cov; extra == 'test'
|
|
34
|
+
Requires-Dist: ruff; extra == 'test'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# gl46
|
|
38
|
+
|
|
39
|
+
Python OpenGL 4.6 Direct State Access (DSA) utility library focused on compute and data-oriented workflows, wrapping PyOpenGL through an explicit GL protocol.
|
|
40
|
+
|
|
41
|
+
## Overview
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
Key classes, grouped by concern:
|
|
46
|
+
|
|
47
|
+
- **Buffers**: `Buffer`, `EBO`, `IndirectDrawBuffer` (aliased as `IBO`), `ShaderStorage`
|
|
48
|
+
- **Textures**: `Texture2D`, `Texture2DArray`, `TextureCubeMapArray`, `TextureMultisample`, `TextureBuffer`
|
|
49
|
+
- **Shaders and programs**: `Shader`, `ShaderStage`, `Program`, `ProgramPipeline`, `Sampler`
|
|
50
|
+
- **Geometry and framebuffer state**: `VertexArray`, `VertexAttrib`, `Framebuffer`, `Renderbuffer`, `RenderbufferMultisample`
|
|
51
|
+
- **Scene helpers**: `Material`, `Camera`, `Window`
|
|
52
|
+
- **Compute, queries, and debug**: `Barrier` (aliased as `MemoryBarrier`), `Query`, `TransformFeedback`, `DebugOutput`
|
|
53
|
+
|
|
54
|
+
**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.
|
|
55
|
+
|
|
56
|
+
gl46 is not a ModernGL replacement: it is a minimal, DSA-first, fully testable substrate for developers assembling their own rendering framework.
|
|
57
|
+
|
|
58
|
+
## Requirements
|
|
59
|
+
|
|
60
|
+
- Python 3.11+
|
|
61
|
+
- [uv](https://docs.astral.sh/uv/) as the package manager
|
|
62
|
+
- `glfw>=2.10.2`
|
|
63
|
+
- `PyOpenGL` (plus `pyopengl-accelerate`) for the real GL backend
|
|
64
|
+
- An OpenGL 4.5+ driver; Window requests a 4.6 core context by default.
|
|
65
|
+
|
|
66
|
+
The core package has no windowing, GUI, or image-library dependency. `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]"`.
|
|
67
|
+
|
|
68
|
+
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.
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
From PyPI (any project):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install gl46
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Optional extras:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install "gl46[imgui]" # ImGui backend (gl46.window_imgui)
|
|
82
|
+
pip install "gl46[qt]" # PySide6 backend (gl46.window_qt)
|
|
83
|
+
pip install "gl46[examples]" # Pillow, for examples/instancing_mrt.py
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
If you use [uv](https://docs.astral.sh/uv/):
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
uv add gl46
|
|
90
|
+
uv add "gl46[imgui]" # etc.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Development install inside this repository:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
uv sync --all-extras
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Quick Start
|
|
100
|
+
|
|
101
|
+
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.
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
import numpy as np
|
|
105
|
+
from gl46 import Buffer, Window
|
|
106
|
+
from gl46.constants import GL_MAP_READ_BIT, GL_MAP_WRITE_BIT
|
|
107
|
+
|
|
108
|
+
with Window(320, 240, visible=False): # creates a GL context
|
|
109
|
+
buf = Buffer(
|
|
110
|
+
16 * 4,
|
|
111
|
+
flags=GL_MAP_READ_BIT | GL_MAP_WRITE_BIT,
|
|
112
|
+
initial_data=np.arange(16, dtype=np.float32),
|
|
113
|
+
)
|
|
114
|
+
with buf.map_range(0, buf.size_bytes, GL_MAP_READ_BIT) as mapped:
|
|
115
|
+
out = mapped.as_numpy(np.float32, (16,))
|
|
116
|
+
print(out) # [ 0. 1. 2. ... 15.]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
For a full rendering example with a visible window, see `examples/triangle.py`.
|
|
120
|
+
|
|
121
|
+
## Examples
|
|
122
|
+
|
|
123
|
+
Run any example with `uv run python examples/<name>.py` from the repo root. Each opens a visible window; press **SPACE** to capture the current frame as `<name>_frame.png` on the spot, and closing the window writes `<name>_close.png`.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
uv run python examples/quickstart.py # Quick Start Demo
|
|
127
|
+
uv run python examples/query_timing.py # DSA timer-query GPU timing
|
|
128
|
+
uv run python examples/triangle.py # VAO/VBO + a rotating triangle
|
|
129
|
+
uv run python examples/offscreen_render.py # offscreen FBO + fullscreen present
|
|
130
|
+
uv run python examples/compute_sobel.py # compute-shader Sobel + timing
|
|
131
|
+
uv run python examples/instancing_mrt.py # instancing & MRT scene
|
|
132
|
+
uv run python examples/imgui_scene.py # gl46 + ImGui overlay (imgui_bundle/immapp)
|
|
133
|
+
uv run python examples/qt_scene.py # gl46 hosted in a QOpenGLWidget (PySide6)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Two examples need extra dependencies beyond the core package:
|
|
137
|
+
|
|
138
|
+
- `imgui_scene.py` needs `imgui-bundle>=1.92.900`. Its `SMOKE` callback raises `RuntimeError` if the `RunnerParams.app_shall_exit` hook added in that version is missing.
|
|
139
|
+
- `instancing_mrt.py` uses `Pillow` to load its assets (the `examples` extra); its mesh and texture files live under `examples/data/`, shaders under `examples/shaders/`.
|
|
140
|
+
|
|
141
|
+
`imgui_scene.py` captures `imgui_scene.png` (scene + overlay) on its second frame, since the ImGui overlay renders after the first frame; `qt_scene.py` captures `qt_scene.png` via `grabFramebuffer()` on first draw.
|
|
142
|
+
|
|
143
|
+
PySide6 is optional: `uv sync --extra qt` installs it (needed only for `examples/qt_scene.py` / `gl46.window_qt`).
|
|
144
|
+
|
|
145
|
+
## Building your own framework
|
|
146
|
+
|
|
147
|
+
gl46 leaves the application shell to you, but ships two thin window backends.
|
|
148
|
+
|
|
149
|
+
### GLFW + ImGui (imgui_bundle)
|
|
150
|
+
|
|
151
|
+
The loop belongs to `immapp.run`; you supply callbacks. The scene draws in `custom_background` (behind the ImGui layer), GUI widgets in `show_gui`, one-time setup in `post_init`. Input for your scene is read through the ImGui API (`imgui.is_key_pressed` / `imgui.get_io()`).
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from gl46.window_imgui import run_imgui_app
|
|
155
|
+
|
|
156
|
+
def scene_render() -> None: ... # draw with gl46 objects
|
|
157
|
+
def gui() -> None: ... # imgui widgets
|
|
158
|
+
|
|
159
|
+
run_imgui_app(scene_render=scene_render, gui=gui,
|
|
160
|
+
width=800, height=600, title="app")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### PySide6 (Qt)
|
|
164
|
+
|
|
165
|
+
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).
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from gl46.window_qt import GL46Widget, run_qt_app
|
|
169
|
+
|
|
170
|
+
class AppWidget(GL46Widget):
|
|
171
|
+
def initialize_gl(self) -> None: ... # create gl46 objects
|
|
172
|
+
def resize_gl(self, w: int, h: int) -> None: ... # framebuffer pixels
|
|
173
|
+
def paint_gl(self) -> None: ... # draw the frame
|
|
174
|
+
|
|
175
|
+
run_qt_app(AppWidget, title="app")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Testing
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
uv run pytest -q
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The suite runs against the `FakeGL` backend (no GPU required).
|
|
185
|
+
|
|
186
|
+
## Quality Gates
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
uv sync --all-extras
|
|
190
|
+
uv run ruff check src tests
|
|
191
|
+
uv run ruff format --check src tests
|
|
192
|
+
uv run mypy src
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Project Structure
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
gl46/
|
|
199
|
+
|-- src/
|
|
200
|
+
| `-- gl46/
|
|
201
|
+
| |-- __init__.py
|
|
202
|
+
| |-- py.typed
|
|
203
|
+
| |-- barrier.py
|
|
204
|
+
| |-- base.py
|
|
205
|
+
| |-- buffer.py
|
|
206
|
+
| |-- camera.py
|
|
207
|
+
| |-- constants.py
|
|
208
|
+
| |-- debug.py
|
|
209
|
+
| |-- errors.py
|
|
210
|
+
| |-- framebuffer.py
|
|
211
|
+
| |-- gl.py
|
|
212
|
+
| |-- material.py
|
|
213
|
+
| |-- pipeline.py
|
|
214
|
+
| |-- program.py
|
|
215
|
+
| |-- query.py
|
|
216
|
+
| |-- renderbuffer.py
|
|
217
|
+
| |-- sampler.py
|
|
218
|
+
| |-- shader.py
|
|
219
|
+
| |-- texture.py
|
|
220
|
+
| |-- transformfeedback.py
|
|
221
|
+
| |-- version.py
|
|
222
|
+
| |-- vertexarray.py
|
|
223
|
+
| |-- window.py
|
|
224
|
+
| |-- window_imgui.py
|
|
225
|
+
| `-- window_qt.py
|
|
226
|
+
|-- tests/
|
|
227
|
+
| |-- __init__.py
|
|
228
|
+
| |-- conftest.py
|
|
229
|
+
| |-- fake_gl.py
|
|
230
|
+
| |-- test_barrier.py
|
|
231
|
+
| |-- test_base.py
|
|
232
|
+
| |-- test_buffer.py
|
|
233
|
+
| |-- test_buffer_extra.py
|
|
234
|
+
| |-- test_camera.py
|
|
235
|
+
| |-- test_constants.py
|
|
236
|
+
| |-- test_debug.py
|
|
237
|
+
| |-- test_errors.py
|
|
238
|
+
| |-- test_fake_gl.py
|
|
239
|
+
| |-- test_framebuffer.py
|
|
240
|
+
| |-- test_gl.py
|
|
241
|
+
| |-- test_material.py
|
|
242
|
+
| |-- test_pipeline.py
|
|
243
|
+
| |-- test_program.py
|
|
244
|
+
| |-- test_query.py
|
|
245
|
+
| |-- test_renderbuffer.py
|
|
246
|
+
| |-- test_sampler.py
|
|
247
|
+
| |-- test_shader.py
|
|
248
|
+
| |-- test_texture.py
|
|
249
|
+
| |-- test_texture_extra.py
|
|
250
|
+
| |-- test_transformfeedback.py
|
|
251
|
+
| |-- test_utils.py
|
|
252
|
+
| |-- test_vertexarray.py
|
|
253
|
+
| |-- test_version.py
|
|
254
|
+
| |-- test_window.py
|
|
255
|
+
| |-- test_window_imgui.py
|
|
256
|
+
| `-- test_window_qt.py
|
|
257
|
+
|-- examples/
|
|
258
|
+
| |-- data/
|
|
259
|
+
| |-- shaders/
|
|
260
|
+
| |-- compute_sobel.py
|
|
261
|
+
| |-- imgui_scene.py
|
|
262
|
+
| |-- instancing_mrt.py
|
|
263
|
+
| |-- offscreen_render.py
|
|
264
|
+
| |-- qt_scene.py
|
|
265
|
+
| |-- query_timing.py
|
|
266
|
+
| |-- quickstart.py
|
|
267
|
+
| `-- triangle.py
|
|
268
|
+
|-- pyproject.toml
|
|
269
|
+
|-- uv.lock
|
|
270
|
+
|-- README.md
|
|
271
|
+
`-- LICENSE
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MIT
|
gl46-0.1.0/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# gl46
|
|
2
|
+
|
|
3
|
+
Python OpenGL 4.6 Direct State Access (DSA) utility library focused on compute and data-oriented workflows, wrapping PyOpenGL through an explicit GL protocol.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
Key classes, grouped by concern:
|
|
10
|
+
|
|
11
|
+
- **Buffers**: `Buffer`, `EBO`, `IndirectDrawBuffer` (aliased as `IBO`), `ShaderStorage`
|
|
12
|
+
- **Textures**: `Texture2D`, `Texture2DArray`, `TextureCubeMapArray`, `TextureMultisample`, `TextureBuffer`
|
|
13
|
+
- **Shaders and programs**: `Shader`, `ShaderStage`, `Program`, `ProgramPipeline`, `Sampler`
|
|
14
|
+
- **Geometry and framebuffer state**: `VertexArray`, `VertexAttrib`, `Framebuffer`, `Renderbuffer`, `RenderbufferMultisample`
|
|
15
|
+
- **Scene helpers**: `Material`, `Camera`, `Window`
|
|
16
|
+
- **Compute, queries, and debug**: `Barrier` (aliased as `MemoryBarrier`), `Query`, `TransformFeedback`, `DebugOutput`
|
|
17
|
+
|
|
18
|
+
**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.
|
|
19
|
+
|
|
20
|
+
gl46 is not a ModernGL replacement: it is a minimal, DSA-first, fully testable substrate for developers assembling their own rendering framework.
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
23
|
+
|
|
24
|
+
- Python 3.11+
|
|
25
|
+
- [uv](https://docs.astral.sh/uv/) as the package manager
|
|
26
|
+
- `glfw>=2.10.2`
|
|
27
|
+
- `PyOpenGL` (plus `pyopengl-accelerate`) for the real GL backend
|
|
28
|
+
- An OpenGL 4.5+ driver; Window requests a 4.6 core context by default.
|
|
29
|
+
|
|
30
|
+
The core package has no windowing, GUI, or image-library dependency. `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]"`.
|
|
31
|
+
|
|
32
|
+
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.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
From PyPI (any project):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install gl46
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Optional extras:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install "gl46[imgui]" # ImGui backend (gl46.window_imgui)
|
|
46
|
+
pip install "gl46[qt]" # PySide6 backend (gl46.window_qt)
|
|
47
|
+
pip install "gl46[examples]" # Pillow, for examples/instancing_mrt.py
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If you use [uv](https://docs.astral.sh/uv/):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uv add gl46
|
|
54
|
+
uv add "gl46[imgui]" # etc.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Development install inside this repository:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv sync --all-extras
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
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.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import numpy as np
|
|
69
|
+
from gl46 import Buffer, Window
|
|
70
|
+
from gl46.constants import GL_MAP_READ_BIT, GL_MAP_WRITE_BIT
|
|
71
|
+
|
|
72
|
+
with Window(320, 240, visible=False): # creates a GL context
|
|
73
|
+
buf = Buffer(
|
|
74
|
+
16 * 4,
|
|
75
|
+
flags=GL_MAP_READ_BIT | GL_MAP_WRITE_BIT,
|
|
76
|
+
initial_data=np.arange(16, dtype=np.float32),
|
|
77
|
+
)
|
|
78
|
+
with buf.map_range(0, buf.size_bytes, GL_MAP_READ_BIT) as mapped:
|
|
79
|
+
out = mapped.as_numpy(np.float32, (16,))
|
|
80
|
+
print(out) # [ 0. 1. 2. ... 15.]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For a full rendering example with a visible window, see `examples/triangle.py`.
|
|
84
|
+
|
|
85
|
+
## Examples
|
|
86
|
+
|
|
87
|
+
Run any example with `uv run python examples/<name>.py` from the repo root. Each opens a visible window; press **SPACE** to capture the current frame as `<name>_frame.png` on the spot, and closing the window writes `<name>_close.png`.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
uv run python examples/quickstart.py # Quick Start Demo
|
|
91
|
+
uv run python examples/query_timing.py # DSA timer-query GPU timing
|
|
92
|
+
uv run python examples/triangle.py # VAO/VBO + a rotating triangle
|
|
93
|
+
uv run python examples/offscreen_render.py # offscreen FBO + fullscreen present
|
|
94
|
+
uv run python examples/compute_sobel.py # compute-shader Sobel + timing
|
|
95
|
+
uv run python examples/instancing_mrt.py # instancing & MRT scene
|
|
96
|
+
uv run python examples/imgui_scene.py # gl46 + ImGui overlay (imgui_bundle/immapp)
|
|
97
|
+
uv run python examples/qt_scene.py # gl46 hosted in a QOpenGLWidget (PySide6)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Two examples need extra dependencies beyond the core package:
|
|
101
|
+
|
|
102
|
+
- `imgui_scene.py` needs `imgui-bundle>=1.92.900`. Its `SMOKE` callback raises `RuntimeError` if the `RunnerParams.app_shall_exit` hook added in that version is missing.
|
|
103
|
+
- `instancing_mrt.py` uses `Pillow` to load its assets (the `examples` extra); its mesh and texture files live under `examples/data/`, shaders under `examples/shaders/`.
|
|
104
|
+
|
|
105
|
+
`imgui_scene.py` captures `imgui_scene.png` (scene + overlay) on its second frame, since the ImGui overlay renders after the first frame; `qt_scene.py` captures `qt_scene.png` via `grabFramebuffer()` on first draw.
|
|
106
|
+
|
|
107
|
+
PySide6 is optional: `uv sync --extra qt` installs it (needed only for `examples/qt_scene.py` / `gl46.window_qt`).
|
|
108
|
+
|
|
109
|
+
## Building your own framework
|
|
110
|
+
|
|
111
|
+
gl46 leaves the application shell to you, but ships two thin window backends.
|
|
112
|
+
|
|
113
|
+
### GLFW + ImGui (imgui_bundle)
|
|
114
|
+
|
|
115
|
+
The loop belongs to `immapp.run`; you supply callbacks. The scene draws in `custom_background` (behind the ImGui layer), GUI widgets in `show_gui`, one-time setup in `post_init`. Input for your scene is read through the ImGui API (`imgui.is_key_pressed` / `imgui.get_io()`).
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from gl46.window_imgui import run_imgui_app
|
|
119
|
+
|
|
120
|
+
def scene_render() -> None: ... # draw with gl46 objects
|
|
121
|
+
def gui() -> None: ... # imgui widgets
|
|
122
|
+
|
|
123
|
+
run_imgui_app(scene_render=scene_render, gui=gui,
|
|
124
|
+
width=800, height=600, title="app")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### PySide6 (Qt)
|
|
128
|
+
|
|
129
|
+
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).
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from gl46.window_qt import GL46Widget, run_qt_app
|
|
133
|
+
|
|
134
|
+
class AppWidget(GL46Widget):
|
|
135
|
+
def initialize_gl(self) -> None: ... # create gl46 objects
|
|
136
|
+
def resize_gl(self, w: int, h: int) -> None: ... # framebuffer pixels
|
|
137
|
+
def paint_gl(self) -> None: ... # draw the frame
|
|
138
|
+
|
|
139
|
+
run_qt_app(AppWidget, title="app")
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Testing
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
uv run pytest -q
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The suite runs against the `FakeGL` backend (no GPU required).
|
|
149
|
+
|
|
150
|
+
## Quality Gates
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
uv sync --all-extras
|
|
154
|
+
uv run ruff check src tests
|
|
155
|
+
uv run ruff format --check src tests
|
|
156
|
+
uv run mypy src
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Project Structure
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
gl46/
|
|
163
|
+
|-- src/
|
|
164
|
+
| `-- gl46/
|
|
165
|
+
| |-- __init__.py
|
|
166
|
+
| |-- py.typed
|
|
167
|
+
| |-- barrier.py
|
|
168
|
+
| |-- base.py
|
|
169
|
+
| |-- buffer.py
|
|
170
|
+
| |-- camera.py
|
|
171
|
+
| |-- constants.py
|
|
172
|
+
| |-- debug.py
|
|
173
|
+
| |-- errors.py
|
|
174
|
+
| |-- framebuffer.py
|
|
175
|
+
| |-- gl.py
|
|
176
|
+
| |-- material.py
|
|
177
|
+
| |-- pipeline.py
|
|
178
|
+
| |-- program.py
|
|
179
|
+
| |-- query.py
|
|
180
|
+
| |-- renderbuffer.py
|
|
181
|
+
| |-- sampler.py
|
|
182
|
+
| |-- shader.py
|
|
183
|
+
| |-- texture.py
|
|
184
|
+
| |-- transformfeedback.py
|
|
185
|
+
| |-- version.py
|
|
186
|
+
| |-- vertexarray.py
|
|
187
|
+
| |-- window.py
|
|
188
|
+
| |-- window_imgui.py
|
|
189
|
+
| `-- window_qt.py
|
|
190
|
+
|-- tests/
|
|
191
|
+
| |-- __init__.py
|
|
192
|
+
| |-- conftest.py
|
|
193
|
+
| |-- fake_gl.py
|
|
194
|
+
| |-- test_barrier.py
|
|
195
|
+
| |-- test_base.py
|
|
196
|
+
| |-- test_buffer.py
|
|
197
|
+
| |-- test_buffer_extra.py
|
|
198
|
+
| |-- test_camera.py
|
|
199
|
+
| |-- test_constants.py
|
|
200
|
+
| |-- test_debug.py
|
|
201
|
+
| |-- test_errors.py
|
|
202
|
+
| |-- test_fake_gl.py
|
|
203
|
+
| |-- test_framebuffer.py
|
|
204
|
+
| |-- test_gl.py
|
|
205
|
+
| |-- test_material.py
|
|
206
|
+
| |-- test_pipeline.py
|
|
207
|
+
| |-- test_program.py
|
|
208
|
+
| |-- test_query.py
|
|
209
|
+
| |-- test_renderbuffer.py
|
|
210
|
+
| |-- test_sampler.py
|
|
211
|
+
| |-- test_shader.py
|
|
212
|
+
| |-- test_texture.py
|
|
213
|
+
| |-- test_texture_extra.py
|
|
214
|
+
| |-- test_transformfeedback.py
|
|
215
|
+
| |-- test_utils.py
|
|
216
|
+
| |-- test_vertexarray.py
|
|
217
|
+
| |-- test_version.py
|
|
218
|
+
| |-- test_window.py
|
|
219
|
+
| |-- test_window_imgui.py
|
|
220
|
+
| `-- test_window_qt.py
|
|
221
|
+
|-- examples/
|
|
222
|
+
| |-- data/
|
|
223
|
+
| |-- shaders/
|
|
224
|
+
| |-- compute_sobel.py
|
|
225
|
+
| |-- imgui_scene.py
|
|
226
|
+
| |-- instancing_mrt.py
|
|
227
|
+
| |-- offscreen_render.py
|
|
228
|
+
| |-- qt_scene.py
|
|
229
|
+
| |-- query_timing.py
|
|
230
|
+
| |-- quickstart.py
|
|
231
|
+
| `-- triangle.py
|
|
232
|
+
|-- pyproject.toml
|
|
233
|
+
|-- uv.lock
|
|
234
|
+
|-- README.md
|
|
235
|
+
`-- LICENSE
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT
|