Metadata-Version: 2.5
Name: gl46
Version: 0.1.0
Summary: Python OpenGL 4.6 Direct State Access (DSA) utility library.
Author-email: Yiguo Tang <yiguo.tang@outlook.com>
License: MIT
License-File: LICENSE
Keywords: compute,dsa,gpu,graphics,opengl
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: glfw>=2.10.2
Requires-Dist: numpy
Requires-Dist: pyopengl
Requires-Dist: pyopengl-accelerate>=3.1.10
Provides-Extra: examples
Requires-Dist: pillow>=12.3.0; extra == 'examples'
Provides-Extra: imgui
Requires-Dist: imgui-bundle>=1.92.900; extra == 'imgui'
Provides-Extra: qt
Requires-Dist: pyside6>=6.5; extra == 'qt'
Provides-Extra: test
Requires-Dist: mypy; extra == 'test'
Requires-Dist: pytest; extra == 'test'
Requires-Dist: pytest-cov; extra == 'test'
Requires-Dist: ruff; extra == 'test'
Description-Content-Type: text/markdown

# 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 as `IBO`), `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 as `MemoryBarrier`), `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+
- [uv](https://docs.astral.sh/uv/) as the package manager
- `glfw>=2.10.2`
- `PyOpenGL` (plus `pyopengl-accelerate`) for the real GL backend
- An OpenGL 4.5+ driver; Window requests a 4.6 core context by default.

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]"`.

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):

```bash
pip install gl46
```

Optional extras:

```bash
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](https://docs.astral.sh/uv/):

```bash
uv add gl46
uv add "gl46[imgui]"           # etc.
```

Development install inside this repository:

```bash
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.

```python
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. 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`.

```bash
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)
```

Two examples need extra dependencies beyond the core package:

- `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.
- `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/`.

`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.

PySide6 is optional: `uv sync --extra qt` installs it (needed only for `examples/qt_scene.py` / `gl46.window_qt`).

## 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 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()`).

```python
from gl46.window_imgui import run_imgui_app

def scene_render() -> None: ...  # draw with gl46 objects
def gui() -> None: ...           # imgui widgets

run_imgui_app(scene_render=scene_render, gui=gui,
              width=800, height=600, title="app")
```

### 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).

```python
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

```bash
uv run pytest -q
```

The suite runs against the `FakeGL` backend (no GPU required).

## Quality Gates

```bash
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