Metadata-Version: 2.4
Name: SimVX-Graphics
Version: 0.0.0.dev1496+gc464d742
Summary: Pure Python Vulkan graphics engine
License-Expression: AGPL-3.0-or-later
Classifier: Development Status :: 1 - Planning
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Games/Entertainment
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.14
Requires-Dist: freetype-py>=2.5.0
Requires-Dist: glfw>=2.10.0
Requires-Dist: numpy>=2.0
Requires-Dist: pillow>=10.0
Requires-Dist: pygltflib>=1.16
Requires-Dist: simvx-core==0.0.0.dev1496+gc464d742
Requires-Dist: vulkan>=1.3
Provides-Extra: all
Requires-Dist: glfw>=2.7; extra == 'all'
Requires-Dist: pysdl3>=0.1; extra == 'all'
Requires-Dist: pyside6>=6.7; extra == 'all'
Requires-Dist: texture2ddecoder>=1.0; extra == 'all'
Requires-Dist: zstandard>=0.22; extra == 'all'
Provides-Extra: compressed
Requires-Dist: texture2ddecoder>=1.0; extra == 'compressed'
Requires-Dist: zstandard>=0.22; extra == 'compressed'
Provides-Extra: dev
Requires-Dist: pytest-forked>=1.6; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Provides-Extra: glfw
Requires-Dist: glfw>=2.7; extra == 'glfw'
Provides-Extra: qt
Requires-Dist: pyside6>=6.7; extra == 'qt'
Provides-Extra: sdl3
Requires-Dist: pysdl3>=0.1; extra == 'sdl3'
Provides-Extra: streaming
Requires-Dist: aiohttp>=3.9; extra == 'streaming'
Description-Content-Type: text/markdown

# SimVX Graphics

Pure Python Vulkan rendering backend for game and simulation engines.

SimVX Graphics provides GPU-accelerated rendering via Vulkan, designed to be driven by an external engine that owns the scene graph, game loop, and entity management. The backend handles all GPU plumbing: pipeline management, descriptor sets, synchronization, multi-draw indirect batching, and frustum culling.

## Requirements

- Python 3.14+
- Vulkan 1.2+ capable GPU and drivers
- `glslc` shader compiler (from the Vulkan SDK or standalone)
- GLFW3 system library

## Installation

```bash
uv pip install -e .
```

## Architecture

```
External Engine                          SimVX Graphics Backend
+-----------------+                      +---------------------------+
| Scene graph     |   load_mesh()        | Mesh Registry             |
| Entity manager  | ------------------>  |   GPU buffer management   |
| Game loop       |   load_texture()     |   MeshHandle references   |
|                 | ------------------>  | Bindless Texture Array    |
|                 |                      |                           |
|  per frame:     |   submit_instance()  | Forward Renderer          |
|   for visible:  | ------------------>  |   Per-viewport culling    |
|                 |   render(cmd)        |   SSBO construction       |
|                 | ------------------>  |   Multi-draw indirect     |
+-----------------+                      +---------------------------+
```

### Engine drives, backend renders

The external engine owns the main loop and scene. SimVX provides the rendering backend:

```python
from simvx.graphics.engine import Engine

engine = Engine(1280, 720, "My App")

# Upload assets once
cube = engine.load_mesh("assets/cube.gltf")       # -> MeshHandle
tex  = engine.texture_manager.load("assets/albedo.png")  # -> int (texture index)

# Create renderer
renderer = engine.create_renderer("forward")
renderer.set_materials(materials_array)

# Create viewport
vp = renderer.viewport_manager.create_viewport(
    0, 0, 1280, 720, camera.view_matrix, camera.projection_matrix
)

# Each frame
renderer.begin_frame()
for obj in visible_objects:
    renderer.submit_instance(obj.mesh, obj.transform, obj.material_id, vp)
renderer.render(cmd)
renderer.end_frame(cmd)
```

## Features

### GPU-Driven Rendering

All rendering uses `vkCmdDrawIndexedIndirect`: no Python loops during draw recording. Object transforms, materials, and draw commands are packed into flat numpy arrays and uploaded to SSBOs/indirect buffers once per frame.

- **Multi-draw indirect**: Thousands of objects rendered in a single Vulkan draw call
- **SSBO-driven data**: Transforms, materials, and lights stored in Shader Storage Buffer Objects
- **Bindless textures**: Up to 4096 textures in a single descriptor array (configurable via `max_textures`)
- **GPU batch system**: `GPUBatch` manages indirect draw buffer construction and upload

### Mesh Registry

Upload mesh data to the GPU once, reference it cheaply by handle for the lifetime of the application.

```python
handle = engine.load_mesh("model.gltf")  # MeshHandle(id, vertex_count, index_count, bounding_radius, aabb_min, aabb_max)
vb, ib = engine.mesh_registry.get_buffers(handle)
```

- Accepts glTF files (positions, normals, UVs)
- Computes bounding sphere at upload time (used for frustum culling)
- Vertex format: position (vec3) + normal (vec3) + uv (vec2) = 32 bytes

### Texture System

Bindless texture array: load textures and reference them by integer index from any shader.

```python
idx = engine.texture_manager.load("diffuse.png")  # PNG or JPG (goes through TextureManager cache)
materials[0]["albedo_tex"] = idx
```

- Backed by `VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER` array
- Default limit 4096, configurable: `Engine(max_textures=8192)`
- Unbounded array syntax in shaders (`sampler2D global_textures[]`)

### Multi-Viewport Rendering

Support for split-screen, picture-in-picture, minimap cameras, and offscreen render targets.

```python
left  = renderer.viewport_manager.create_viewport(0,   0, 640, 720, cam_l.view, cam_l.proj)
right = renderer.viewport_manager.create_viewport(640, 0, 640, 720, cam_r.view, cam_r.proj)
```

Each viewport has independent camera matrices and frustum culling.

### Frustum Culling

CPU-side per-viewport frustum culling using Griggs-Hartmann plane extraction.

- Bounding sphere test per instance (from mesh metadata)
- AABB test available for tighter bounds
- Vectorized `cull_spheres()` for batch culling large instance arrays
- Integrated into `Renderer.render()`: culling is automatic

### Forward Renderer

Implements the `Renderer` ABC: the primary rendering backend.

```python
renderer = engine.create_renderer("forward")
```

**Per-frame API:**
| Method | Purpose |
|---|---|
| `begin_frame()` | Clear submission lists |
| `submit_instance(mesh, transform, material_id, viewport_id)` | Queue a mesh instance |
| `render(cmd)` | Record all Vulkan commands |
| `end_frame(cmd)` | Finalize frame |
| `set_materials(array)` | Upload material SSBO |
| `set_lights(array)` | Upload light SSBO |

Internally handles: pipeline binding, descriptor sets, push constants, viewport/scissor, vertex/index buffer binding, and indirect draw dispatch.

### Entity Picking

Off-screen render pass that writes entity IDs to an `R32_UINT` framebuffer for pixel-perfect mouse picking.

```python
engine.enable_picking(descriptor_layout, descriptor_set)
entity_id = engine.pick_entity(mouse_x, mouse_y, view_proj_bytes, vb, ib, index_count, instance_count)
```

### 2D Drawing

Immediate-mode 2D API (`Draw2D`): rects, lines, circles, text, textures, and nine-patches,
submitted from `on_draw` callbacks. Adjacent ops that share a pipeline, clip, and texture are
coalesced into batched GPU draws, and the engine's widget toolkit (`simvx.core.ui`) renders
through the same path.

```python
def on_draw(self, renderer):
    renderer.draw_texture(self.bg_texture, 0, 0, 320, 240)
    renderer.draw_rect((0, 0), (320, 240), filled=True, colour=(0, 0, 0, 0.7))
    renderer.draw_text("Paused", (140, 110), colour=(1, 1, 1, 1), scale=2.0)
```

See `docs/graphics/draw2d.md` for ordering, coalescing, and clipping rules.

### Offscreen Render Targets

Create offscreen framebuffers for render-to-texture effects.

```python
rt = engine.create_render_target(512, 512, use_depth=True)
```

### Shader Pipeline

GLSL shaders compiled to SPIR-V at runtime via `glslc`. Shared struct definitions in `common.glsl` match the numpy dtypes in `types.py`.

**Shader data layout:**
| Set | Binding | Type | Content |
|---|---|---|---|
| 0 | 0 | SSBO | Transform buffer (model, normal_mat, material_index) |
| 0 | 1 | SSBO | Material buffer (albedo, metallic, roughness, texture indices, features) |
| 0 | 2 | SSBO | Light buffer (position, direction, colour, params) |
| 1 | 0 | Sampler2D[] | Bindless texture array |

**Push constants:** view matrix (mat4) + projection matrix (mat4) = 128 bytes

**Material features bitmask:**
| Flag | Bit |
|---|---|
| `HAS_ALBEDO` | 0 |
| `HAS_NORMAL` | 1 |
| `HAS_METALLIC_ROUGHNESS` | 2 |
| `HAS_EMISSIVE` | 3 |
| `HAS_AO` | 4 |

### Platform Backends

| Backend | Status |
|---|---|
| GLFW | Installed by default |
| SDL3 | Optional; preferred over GLFW when installed (multi-touch + app lifecycle) |
| PySide6 (Qt) | Optional; for embedding in a Qt host |

See `docs/core/backends.md` for the per-backend feature matrix and selection rules.

## Data Types

All GPU data uses numpy structured arrays that mirror the GLSL layouts:

```python
from simvx.graphics.types import (
    VERTEX_DTYPE,       # position(3f) + normal(3f) + uv(2f) = 32 bytes
    TRANSFORM_DTYPE,    # model(4x4f) + normal_mat(4x4f) + material_index(u32) + pad = 144 bytes
    MATERIAL_DTYPE,     # albedo(4f) + metallic(f) + roughness(f) + 5x tex_idx(i32) + features(u32)
    LIGHT_DTYPE,        # position(4f) + direction(4f) + colour(4f) + params(4f)
    INDIRECT_DRAW_DTYPE,# VkDrawIndexedIndirectCommand layout
    MeshHandle,         # NamedTuple(id, vertex_count, index_count, bounding_radius, aabb_min, aabb_max)
    Viewport,           # Dataclass(x, y, width, height, camera_view, camera_proj, render_target)
)
```

## Limits

| Resource | Default | Configurable |
|---|---|---|
| Textures | 4096 | `Engine(max_textures=N)` |
| Lights | 1024 | `MAX_LIGHTS` constant |
| Objects | 65536 | `MAX_OBJECTS` constant |
| Frames in flight | 2 | `FRAMES_IN_FLIGHT` constant |

## Project Structure

```
src/simvx/graphics/
    app.py        # Windowed application shell: game loop, input, backend selection
    engine.py     # Top-level Engine: device, swapchain, asset upload
    types.py      # Shared dtypes, constants, MeshHandle, Viewport
    gpu/          # Vulkan plumbing: instance, device, memory, pipelines, sync
    renderer/     # Forward renderer, 2D passes, post-processing, culling
    render2d/     # Retained 2D item pipeline
    scene/        # Camera matrices, frustum culling, draw batching
    picking/      # Entity-ID picking pass + CPU raycast
    assets/       # glTF, image, KTX2, and DDS loaders
    materials/    # Material system, custom shaders, glslc wrapper
    platform/     # GLFW / SDL3 / Qt windowing backends
    streaming/    # WebSocket frame-streaming server (remote viewing)
    shaders/      # GLSL sources + compiled SPIR-V
demos/            # Engine-internal smoke-test demos
```

## Not Implemented

- Deferred shading (G-buffer + separate lighting pass). The forward renderer instead ships
  clustered light culling, cascaded + point shadows, Hi-Z GPU occlusion culling, mesh LOD,
  and an opt-in thin G-buffer for screen-space effects (SSAO, SSR, SSGI).

## License

Part of the SimVX engine: **AGPL-3.0-or-later** with [Additional Terms](../../LICENSE-ADDENDUM.md)
and a [Game-Distribution Exception](../../LICENSE-EXCEPTION.md); a
[commercial licence](../../COMMERCIAL-LICENSE.md) is also available. See the repository-root
[LICENSE](../../LICENSE).
