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.
Files changed (89) hide show
  1. gl46-0.1.0/.gitignore +33 -0
  2. gl46-0.1.0/LICENSE +21 -0
  3. gl46-0.1.0/PKG-INFO +276 -0
  4. gl46-0.1.0/README.md +240 -0
  5. gl46-0.1.0/examples/compute_sobel.py +208 -0
  6. gl46-0.1.0/examples/data/meshes/monkey.ibo +0 -0
  7. gl46-0.1.0/examples/data/meshes/sphere.ibo +0 -0
  8. gl46-0.1.0/examples/data/meshes/teapot.ibo +0 -0
  9. gl46-0.1.0/examples/data/textures/acknowledgments.txt +2 -0
  10. gl46-0.1.0/examples/data/textures/cubemap_back.bmp +0 -0
  11. gl46-0.1.0/examples/data/textures/cubemap_bottom.bmp +0 -0
  12. gl46-0.1.0/examples/data/textures/cubemap_front.bmp +0 -0
  13. gl46-0.1.0/examples/data/textures/cubemap_left.bmp +0 -0
  14. gl46-0.1.0/examples/data/textures/cubemap_right.bmp +0 -0
  15. gl46-0.1.0/examples/data/textures/cubemap_top.bmp +0 -0
  16. gl46-0.1.0/examples/data/textures/texture_1.bmp +0 -0
  17. gl46-0.1.0/examples/data/textures/texture_2.bmp +0 -0
  18. gl46-0.1.0/examples/data/textures/texture_3.bmp +0 -0
  19. gl46-0.1.0/examples/imgui_scene.py +148 -0
  20. gl46-0.1.0/examples/instancing_mrt.py +440 -0
  21. gl46-0.1.0/examples/offscreen_render.py +195 -0
  22. gl46-0.1.0/examples/qt_scene.py +130 -0
  23. gl46-0.1.0/examples/query_timing.py +131 -0
  24. gl46-0.1.0/examples/quickstart.py +15 -0
  25. gl46-0.1.0/examples/shaders/cubemap.frag +20 -0
  26. gl46-0.1.0/examples/shaders/cubemap.vert +55 -0
  27. gl46-0.1.0/examples/shaders/mesh.frag +53 -0
  28. gl46-0.1.0/examples/shaders/mesh.vert +54 -0
  29. gl46-0.1.0/examples/shaders/quad.frag +38 -0
  30. gl46-0.1.0/examples/shaders/quad.vert +30 -0
  31. gl46-0.1.0/examples/triangle.py +120 -0
  32. gl46-0.1.0/pyproject.toml +94 -0
  33. gl46-0.1.0/src/gl46/__init__.py +99 -0
  34. gl46-0.1.0/src/gl46/barrier.py +82 -0
  35. gl46-0.1.0/src/gl46/base.py +53 -0
  36. gl46-0.1.0/src/gl46/buffer.py +351 -0
  37. gl46-0.1.0/src/gl46/camera.py +146 -0
  38. gl46-0.1.0/src/gl46/constants.py +361 -0
  39. gl46-0.1.0/src/gl46/debug.py +113 -0
  40. gl46-0.1.0/src/gl46/errors.py +45 -0
  41. gl46-0.1.0/src/gl46/framebuffer.py +233 -0
  42. gl46-0.1.0/src/gl46/gl.py +1342 -0
  43. gl46-0.1.0/src/gl46/material.py +84 -0
  44. gl46-0.1.0/src/gl46/pipeline.py +53 -0
  45. gl46-0.1.0/src/gl46/program.py +208 -0
  46. gl46-0.1.0/src/gl46/py.typed +0 -0
  47. gl46-0.1.0/src/gl46/query.py +88 -0
  48. gl46-0.1.0/src/gl46/renderbuffer.py +78 -0
  49. gl46-0.1.0/src/gl46/sampler.py +58 -0
  50. gl46-0.1.0/src/gl46/shader.py +103 -0
  51. gl46-0.1.0/src/gl46/texture.py +456 -0
  52. gl46-0.1.0/src/gl46/transformfeedback.py +75 -0
  53. gl46-0.1.0/src/gl46/utils.py +143 -0
  54. gl46-0.1.0/src/gl46/version.py +39 -0
  55. gl46-0.1.0/src/gl46/vertexarray.py +80 -0
  56. gl46-0.1.0/src/gl46/window.py +189 -0
  57. gl46-0.1.0/src/gl46/window_imgui.py +126 -0
  58. gl46-0.1.0/src/gl46/window_qt.py +121 -0
  59. gl46-0.1.0/tests/__init__.py +0 -0
  60. gl46-0.1.0/tests/conftest.py +20 -0
  61. gl46-0.1.0/tests/fake_gl.py +850 -0
  62. gl46-0.1.0/tests/test_barrier.py +99 -0
  63. gl46-0.1.0/tests/test_base.py +78 -0
  64. gl46-0.1.0/tests/test_buffer.py +257 -0
  65. gl46-0.1.0/tests/test_buffer_extra.py +193 -0
  66. gl46-0.1.0/tests/test_camera.py +98 -0
  67. gl46-0.1.0/tests/test_constants.py +21 -0
  68. gl46-0.1.0/tests/test_debug.py +110 -0
  69. gl46-0.1.0/tests/test_errors.py +55 -0
  70. gl46-0.1.0/tests/test_fake_gl.py +99 -0
  71. gl46-0.1.0/tests/test_framebuffer.py +274 -0
  72. gl46-0.1.0/tests/test_gl.py +211 -0
  73. gl46-0.1.0/tests/test_material.py +50 -0
  74. gl46-0.1.0/tests/test_pipeline.py +83 -0
  75. gl46-0.1.0/tests/test_program.py +231 -0
  76. gl46-0.1.0/tests/test_query.py +101 -0
  77. gl46-0.1.0/tests/test_renderbuffer.py +47 -0
  78. gl46-0.1.0/tests/test_sampler.py +69 -0
  79. gl46-0.1.0/tests/test_shader.py +75 -0
  80. gl46-0.1.0/tests/test_texture.py +211 -0
  81. gl46-0.1.0/tests/test_texture_extra.py +132 -0
  82. gl46-0.1.0/tests/test_transformfeedback.py +80 -0
  83. gl46-0.1.0/tests/test_utils.py +167 -0
  84. gl46-0.1.0/tests/test_version.py +35 -0
  85. gl46-0.1.0/tests/test_vertexarray.py +79 -0
  86. gl46-0.1.0/tests/test_window.py +166 -0
  87. gl46-0.1.0/tests/test_window_imgui.py +145 -0
  88. gl46-0.1.0/tests/test_window_qt.py +131 -0
  89. 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