GraphicsGPU

GPU 3D Scenes

Load a model, instantiate its scene and display it in an interactive OpenGL viewport. Scene objects, mathematics and model codecs are implemented in Klyn.

A Robotic Face

Run klyn samples/graphics3d/RobotFace.kn. The original cobalt, pearl-white and crimson armored helmet, robot-face.glb, lives beside the sample. Its head and neck are closed solids, with complete side and rear armor. The earlier design is saved as robot-face-original.glb beside it. Hold the primary mouse button and drag to orbit; use the wheel to zoom. This is the complete example:

import klyn.graphics3d
import klyn.graphics3d.assets
import klyn.gui.windows
import klyn.gui.windows.graphics3d
import klyn.gui.windows.layouts
import klyn.io

# The model lives beside this sample, independently of the launch directory.
model = FilePath(FolderPath(Application.properties["klyn.sourceDirectory"]), "robot-face.glb")
asset = ModelLoader().load(model)
scene = Scene()
scene.ambient = 0.20f
scene.root.add(asset.instantiate())

window = MainWindow("Aster — robotic face")
window.size = (1000, 800)
layout = VBoxLayout(spacing=8)
window.centralWidget.layout = layout
layout.add(Label("Drag with the left mouse button to orbit · Wheel to zoom"))
view = SceneView(scene)
layout.add(view)
view.frameScene()
OrbitController(view.camera).rotate(0.35, 0.0)
try:
    window.run()
finally:
    view.close()
OpenGL rendering of the original Klyn robotic face, viewed from three quarters
Actual Windows OpenGL rendering. The model contains 93 closed parts and 5,712 triangles. It can be regenerated with python3 tools/graphics3d/generate_robot_face.py. The generator validates outward winding and closed manifold edges. tools/graphics3d/generate_robot_face_original.py reproduces the preserved earlier model.

Packages and Conventions

  • klyn.math.Quaternion: Hamilton quaternions with scalar component first.
  • klyn.math.geometry: immutable Rotation3d and column-major Matrix4, using column vectors and right-handed rotations in radians. A * B applies B first.
  • klyn.graphics3d: immutable Mesh, Material and LinearColor; mutable Scene, SceneNode, PerspectiveCamera and OrbitController; Renderer.
  • klyn.graphics3d.assets: CPU-only loading and export.
  • klyn.gui.windows.graphics3d.SceneView: viewport integration and mouse controls.

The scene API has no GUI dependency. Mesh vertices contain eight Float values each: position XYZ, normal XYZ and UV. UInt indices describe counter-clockwise triangles. Materials use linear RGB, opaque metallic/roughness shading, emission and an optional double-sided flag.

The current renderer uses OpenGL 3.3 or newer on Windows and Linux/X11. GPU device selection belongs to the OS driver. Offscreen Renderer use also requires a display with this desktop backend. WebGL, Vulkan and CUDA/OpenGL resource interoperability are not implemented. The internal klyn.internal.gpu buffer contract is shared with numeric runtime transfers; it is not a public application API.

Load, Instantiate and Export

import klyn.graphics3d.assets
import klyn.io

asset = ModelLoader().load(FilePath("robot.glb"))
instance = asset.instantiate()
report = ModelExporter().save(asset, FilePath("copy.glb"))
report = ModelExporter().save(asset, FilePath("copy.obj"), allowLoss=true)
for loss in report.losses:
    print(loss)

Every load returns a ModelAsset with mesh/material catalogs and scene templates. Instantiation copies nodes while sharing immutable geometry and materials. An explicit scene index is required if several scenes exist without a declared default. File extensions select formats; both synchronous and asynchronous loads accept an explicit ModelFormat.

Export rejects unaccepted semantic losses before opening the destination. Flat formats require exactly one scene and report baked transforms, omitted unused catalog entries and lost material, naming or vertex attributes. OBJ also writes an adjacent MTL sidecar.

FormatCurrent supported contract
glTF 2.0 / GLB 2.0Static indexed or non-indexed triangles; Float32 positions, normals and UV, including interleaved accessors; u8/u16/u32 indices; matrix or TRS nodes; multiple scenes; opaque untextured metallic/roughness materials. Missing normals are generated. Local relative buffers and padded Base64 data URIs are supported.
OBJ + MTLPositions, normals, UV, signed indices, objects/groups, smoothing and planar polygons triangulated by ear clipping. MTL supports Kd, Ke, Pm, Pr and opaque illum 2. Other material directives and textures are explicitly rejected.
PLY 1.0ASCII geometry, optional normals and UV (u/v or s/t), planar polygon faces. Binary PLY, colors and additional elements are rejected.
STLBinary and ASCII triangle geometry on load; binary export. Normals are recomputed from geometry. Binary attributes and vendor color conventions are rejected.

glTF animations, skins, morph targets, sparse accessors, textures, cameras and extensions are explicitly rejected rather than silently discarded. Each binary resource is limited to 512 MiB; codecs also enforce index, vertex and hierarchy limits. These are static format subsets, not general-purpose support for every feature in each standard.

Background Loading

operation = ModelLoader().loadAsync(FilePath("robot.glb"))
# Poll operation.isDone, operation.progress and operation.phase from a GUI Timer.
# Once isDone is true:
asset = operation.awaitResult()
operation.close()

A worker performs CPU parsing only. Polling reads the latest progress, with no accumulating queue. Attach the completed asset to a scene on the GUI thread, then call view.frameScene(). cancel() requests cooperative cancellation; close() cancels and joins. awaitResult() blocks until completion and rethrows any loading failure.

GPU Lifetime and Performance

Mesh takes an immutable CPU snapshot. Its first render uploads geometry once into persistent vertex/index buffers. Subsequent frames submit matrices and material records in one native batch, with one draw call per instance. Camera movement never transforms all vertices on the CPU.

mesh.copyVertices(), mesh.copyIndices() and flat export explicitly copy CPU data. renderer.capture(path) synchronously reads the framebuffer into a BMP. Normal GUI frames present GPU pixels directly; GUI overlays, nonrectangular clipping and window capture require an explicit compositor readback. renderer.statistics exposes frame, upload, byte, readback, draw and resident-mesh counters without GPU synchronization.

Call view.repaint() after scene changes. After replacing a large scene, view.renderer.clearMeshCache() releases unused cached geometry; the next frame uploads the current meshes. Close SceneView or Renderer explicitly on the owning thread. Window teardown also releases its native child surfaces.