Source code for picogl.shaders.shader_uniform

from dataclasses import dataclass
from enum import Enum
from typing import Any, Optional, Sequence, Union

import numpy as np


[docs] class UniformKind(Enum):
[docs] INT = "int"
[docs] FLOAT = "float"
[docs] BOOL = "bool"
[docs] VEC2 = "vec2"
[docs] VEC3 = "vec3"
[docs] VEC4 = "vec4"
[docs] MAT3 = "mat3"
[docs] MAT4 = "mat4"
@dataclass
[docs] class ShaderUniform:
[docs] name: str
[docs] location: int
[docs] value: Union[int, float, Sequence[float], np.ndarray, bool, None] = None
[docs] uniform_type: Optional[UniformKind] = None
[docs] def __post_init__(self): if not isinstance(self.name, str) or not self.name: raise ValueError("name must be a non-empty string") if not isinstance(self.location, int) or self.location < -1: raise ValueError("location must be an int >= -1") if self.uniform_type is None and self.value is not None: self.uniform_type = self.infer_type(self.value)
@staticmethod
[docs] def infer_type(v: Any) -> UniformKind: # Basic Python types if isinstance(v, bool): return UniformKind.BOOL if isinstance(v, int): return UniformKind.INT if isinstance(v, float): return UniformKind.FLOAT # Sequences if isinstance(v, (list, tuple)): l = len(v) if l == 2: return UniformKind.VEC2 if l == 3: return UniformKind.VEC3 if l == 4: return UniformKind.VEC4 # NumPy arrays if isinstance(v, np.ndarray): if v.ndim == 1: l = v.shape[0] if l == 2: return UniformKind.VEC2 if l == 3: return UniformKind.VEC3 if l == 4: return UniformKind.VEC4 elif v.ndim == 2: if v.shape == (3, 3): return UniformKind.MAT3 if v.shape == (4, 4): return UniformKind.MAT4 raise ValueError(f"Cannot infer uniform type from value: {v!r}")
[docs] def upload(self, gl_module=None) -> None: """ Upload the current value to the bound GL program using PyOpenGL-like calls. If location < 0 (GL returns -1 when not found/used), this is a no-op. - gl_module: optional OpenGL.GL-like module. If None, will try to import PyOpenGL (from OpenGL import GL as GL) at call time. """ # Resolve GL module (PyOpenGL) if gl_module is None: try: from OpenGL import GL as gl except Exception as e: raise RuntimeError( "PyOpenGL is required to upload uniforms (pass gl_module or install PyOpenGL)" ) from e else: gl = gl_module if self.location < 0: # Uniform not active; skip uploading. return t = self.uniform_type v = self.value if t == UniformKind.FLOAT: gl.glUniform1f(self.location, float(v)) elif t == UniformKind.INT: gl.glUniform1i(self.location, int(v)) elif t == UniformKind.BOOL: gl.glUniform1i(self.location, int(bool(v))) elif t == UniformKind.VEC2: arr = np.asarray(v, dtype=np.float32) gl.glUniform2f(self.location, float(arr[0]), float(arr[1])) elif t == UniformKind.VEC3: arr = np.asarray(v, dtype=np.float32) gl.glUniform3f(self.location, float(arr[0]), float(arr[1]), float(arr[2])) elif t == UniformKind.VEC4: arr = np.asarray(v, dtype=np.float32) gl.glUniform4f( self.location, float(arr[0]), float(arr[1]), float(arr[2]), float(arr[3]), ) elif t == UniformKind.MAT3: mat = np.asarray(v, dtype=np.float32) if mat.shape == (3, 3): gl.glUniformMatrix3fv(self.location, 1, gl.GL_FALSE, mat) elif mat.size == 9: gl.glUniformMatrix3fv( self.location, 1, gl.GL_FALSE, mat.reshape((3, 3)) ) else: raise ValueError("mat3 must be a 3x3 matrix or a 9-element vector") elif t == UniformKind.MAT4: mat = np.asarray(v, dtype=np.float32) if mat.shape == (4, 4): gl.glUniformMatrix4fv(self.location, 1, gl.GL_FALSE, mat) elif mat.size == 16: gl.glUniformMatrix4fv( self.location, 1, gl.GL_FALSE, mat.reshape((4, 4)) ) else: raise ValueError("mat4 must be a 4x4 matrix or a 16-element vector") else: raise ValueError(f"Unsupported uniform type: {t}")
[docs] def __str__(self) -> str: return f"ShaderUniform(name={self.name!r}, location={self.location}, type={self.uniform_type}, value={self.value})"