opengl.py

lunaengine/backend/opengl.py

OpenGL-based hardware-accelerated renderer for LunaEngine
- GPU-accelerated particles via instancing (CPU physics)
- Full filter system (post-processing)
- 2D drawing primitives (lines, circles, polygons, etc.)
- Rounded rectangles with per-corner radii
- Texture and surface rendering

This version uses the reliable CPU particle system and only uses OpenGL
for rendering - no compute shaders, no GPU physics.
v0.2.3

class FilterType

Description

Enumeration of all available post-processing filter types.

Attributes
NONE: Any = 'none'
VIGNETTE: Any = 'vignette'
BLUR: Any = 'blur'
SEPIA: Any = 'sepia'
GRAYSCALE: Any = 'grayscale'
INVERT: Any = 'invert'
TEMPERATURE_WARM: Any = 'temperature_warm'
TEMPERATURE_COLD: Any = 'temperature_cold'
NIGHT_VISION: Any = 'night_vision'
CRT: Any = 'crt'
PIXELATE: Any = 'pixelate'
BLOOM: Any = 'bloom'
EDGE_DETECT: Any = 'edge_detect'
EMBOSS: Any = 'emboss'
SHARPEN: Any = 'sharpen'
POSTERIZE: Any = 'posterize'
NEON: Any = 'neon'
RADIAL_BLUR: Any = 'radial_blur'
FISHEYE: Any = 'fisheye'
TWIRL: Any = 'twirl'
Methods

No methods defined.

class FilterRegionType

Description

Defines the shape/region where a filter is applied.

Attributes
FULLSCREEN: Any = 'fullscreen'
RECTANGLE: Any = 'rectangle'
CIRCLE: Any = 'circle'
Methods

No methods defined.

class Filter

Description

Represents a post-processing filter with configurable region and intensity.

Attributes:
   filter_type (FilterType): The type of effect to apply.
   intensity (float): Strength of the effect (0.0 to 1.0).
   region_type (FilterRegionType): Shape of the affected area.
   region_pos (Tuple[float, float]): Position (top‑left for rect, center for circle).
   region_size (Tuple[float, float]): Width and height of rectangular region.
   radius (float): Radius for circular regions.
   feather (float): Edge softness in pixels.
   blend_mode (str): How the filter blends with the background (e.g., "normal").
   enabled (bool): Whether this filter is active.
   time (float): Accumulated time for animated filters.

Methods
def __init__(self: Any, filter_type: FilterType = FilterType.NONE, intensity: float = 1.0, region_type: FilterRegionType = FilterRegionType.FULLSCREEN, region_pos: Tuple[float, float] = (0, 0), region_size: Tuple[float, float] = (100, 100), radius: float = 50.0, feather: float = 10.0, blend_mode: str = 'normal') -> Any
Initialize a filter with all parameters.

Args:
   filter_type: Type of filter effect.
   intensity: Filter strength (0.0 to 1.0).
   region_type: Shape of filter region.
   region_pos: Position of region (top-left for rect, center for circle).
   region_size: Size of region (width, height).
   radius: Radius for circular regions.
   feather: Edge softness in pixels.
   blend_mode: How filter blends with background.
def update(self: Any, dt: float) -> None
Update filter for animation (e.g., time‑based effects).
def copy(self: Any) -> Filter
Create a deep copy of this filter.

class ShaderProgram

Description

Base class for all shader programs with automatic uniform location caching.

Attributes:
   vertex_source (str | None): Path or source code for vertex shader.
   fragment_source (str | None): Path or source code for fragment shader.
   geometry_source (str | None): Path or source code for geometry shader.
   program (int | None): OpenGL shader program handle.
   vao (int | None): Vertex Array Object handle.
   vbo (int | None): Vertex Buffer Object handle.
   ebo (int | None): Element Buffer Object handle.

Attributes
vertex_source: str | None = None
fragment_source: str | None = None
geometry_source: str | None = None
Methods
def get_source(self: Any, source: str | None, type: str) -> None
Load shader source from a file if the provided string is a file name.

Args:
   source: File name or raw source code.
   type: Type of shader ('vertex', 'fragment', 'geometry').
def __init__(self: Any, vertex_source: str, fragment_source: str, geometry_source: Optional[str] = None) -> Any
Initialize the shader program by loading sources and compiling.

Args:
   vertex_source: File name or raw source for vertex shader.
   fragment_source: File name or raw source for fragment shader.
   geometry_source: Optional file name or raw source for geometry shader.
def _get_uniform_location(self: Any, name: str) -> int
Get cached uniform location (avoids repeated glGetUniformLocation calls).
def _create_program(self: Any, vertex_source: str, fragment_source: str, geometry_source: Optional[str] = None) -> None
Compile vertex/fragment (and optional geometry) shaders and link the program.
def use(self: Any) -> None
Activate this shader program for rendering.
def unuse(self: Any) -> None
Deactivate the currently active shader program.
def _setup_geometry(self: Any) -> None
Set up vertex arrays, VBOs, etc. Overridden by subclasses.

class ParticleShader

Description

Instanced particle rendering shader. Each particle is a point with size and colour.

Methods
def __init__(self: Any) -> Any
Initialise the particle shader from external vertex/fragment files.
def _setup_geometry(self: Any) -> None
Set up a dummy VAO with a single vertex (used for instancing).
Also creates VBOs for instance data (position, size, alpha) and instance colours.

class SimpleShader

Description

Simple shader for drawing solid colour rectangles and shapes.

Methods
def __init__(self: Any) -> Any
Initialise the simple shader from external files.
def _setup_geometry(self: Any) -> None
Set up a unit quad (0,0) to (1,1) with index buffer.

class TextureShader

Description

Shader for rendering textured quads (surfaces, images).

Methods
def __init__(self: Any) -> Any
Initialise the texture shader from external files.
def _setup_geometry(self: Any) -> None
Set up a quad with position and UV coordinates.

class RoundedRectShader

Description

Shader that draws rectangles with per‑corner rounded corners using signed distance fields.

Methods
def __init__(self: Any) -> Any
Initialise the rounded rectangle shader from external files.
def _setup_geometry(self: Any) -> None
Set up a simple quad geometry (the shader handles rounding).

class FilterShader

Description

Full‑screen quad shader for applying post‑processing filters and effects.

Methods
def __init__(self: Any) -> Any
Initialise the filter shader from external files.
def _setup_geometry(self: Any) -> None
Set up a full‑screen quad (clip space coordinates).

class OpenGLRenderer

Description

Main hardware‑accelerated renderer for LunaEngine.

Manages OpenGL context, shaders, framebuffers, geometry caches,
filters, particles, and all drawing operations.

Attributes:
   width (int): Current viewport width.
   height (int): Current viewport height.
   camera_position (pygame.math.Vector2): World camera position.
   filters (List[Filter]): Active post‑processing filters.

Attributes
camera_position: Any = pygame.math.Vector2(0, 0)
Methods
def __init__(self: Any, width: int, height: int) -> Any
Initialise the OpenGL renderer with given screen dimensions.

Args:
   width: Initial viewport width.
   height: Initial viewport height.
def get_opengl_version(self: Any) -> str
No documentation
def set_text_cache_timeout(self: Any, seconds: float) -> None
Set how long a text texture stays in cache without being used.

Args:
   seconds: Timeout in seconds (>= 0.1).
def get_cache_usage(self: Any, target: str = 'all', humanize: bool = False) -> float | Dict[str, float]
Return cache usage as size in bytes or as a dictionary of sizes.

Args:
   target: One of 'text', 'texture', 'circle', 'polygon', 'total', or 'all'.
   humanize: If True, return human‑readable strings (e.g., "1.2 KB").

Returns:
   Size in bytes (or humanised string) or a dictionary of all caches.
def max_particles(self: Any) -> int
Maximum number of particles that can be rendered simultaneously.
def max_particles(self: Any, value: int) -> None
Change the maximum particle count and trigger resize callbacks.

Args:
   value: New maximum number of particles.
def initialize(self: Any) -> bool
Set up OpenGL context, compile shaders, create framebuffers.

Returns:
   True if initialisation succeeded, False otherwise.
def _initialize_filter_framebuffer(self: Any) -> bool
Create the off‑screen framebuffer used for applying filters.

Returns:
   True if successful, False otherwise.
def add_filter(self: Any, filter_obj: Filter) -> None
Add a filter to the active filter stack.
def remove_filter(self: Any, filter_obj: Filter) -> None
Remove a specific filter from the stack.
def clear_filters(self: Any) -> None
Remove all active filters.
def create_quick_filter(self: Any, filter_type: FilterType, intensity: float = 1.0, x: float = 0, y: float = 0, width: float = None, height: float = None, radius: float = 50.0, feather: float = 10.0) -> Filter
Create and add a filter with common sensible defaults.

Args:
   filter_type: Type of filter effect.
   intensity: Strength (0.0 to 1.0).
   x, y: Position of the region.
   width, height: Dimensions of rectangular region (if None, uses screen size).
   radius: Circle radius (if circular region).
   feather: Edge softness.

Returns:
   The created Filter object (already added to the stack).
def apply_vignette(self: Any, intensity: float = 0.7, feather: float = 100.0) -> Filter
Add a vignette (darkened edges) effect.
def apply_blur(self: Any, intensity: float = 0.5, x: float = 0, y: float = 0, width: float = None, height: float = None) -> Filter
Add a gaussian‑like blur effect, optionally within a rectangle.
def apply_sepia(self: Any, intensity: float = 1.0) -> Filter
Add a sepia tone (warm brownish) effect.
def apply_grayscale(self: Any, intensity: float = 1.0) -> Filter
Convert colours to grayscale.
def apply_invert(self: Any, intensity: float = 1.0) -> Filter
Invert colours (negative).
def apply_warm_temperature(self: Any, intensity: float = 0.5) -> Filter
Increase warm colours (orange/red).
def apply_cold_temperature(self: Any, intensity: float = 0.5) -> Filter
Increase cold colours (blue).
def apply_night_vision(self: Any, intensity: float = 0.9) -> Filter
Simulate night vision (green tint + noise).
def apply_crt_effect(self: Any, intensity: float = 0.8) -> Filter
Add CRT monitor scanlines and curvature.
def apply_pixelate(self: Any, intensity: float = 0.7) -> Filter
Pixelate the image (lower resolution).
def apply_bloom(self: Any, intensity: float = 0.5) -> Filter
Add a bloom (glow) effect around bright areas.
def apply_edge_detect(self: Any, intensity: float = 0.8) -> Filter
Detect and highlight edges.
def apply_emboss(self: Any, intensity: float = 0.7) -> Filter
Apply an emboss (raised edges) effect.
def apply_sharpen(self: Any, intensity: float = 0.5) -> Filter
Sharpen the image.
def apply_posterize(self: Any, intensity: float = 0.6) -> Filter
Reduce the number of colour bands.
def apply_neon(self: Any, intensity: float = 0.7) -> Filter
Add a neon glow effect.
def apply_radial_blur(self: Any, intensity: float = 0.5) -> Filter
Blur outward from the centre.
def apply_fisheye(self: Any, intensity: float = 0.4) -> Filter
Apply a fisheye lens distortion.
def apply_twirl(self: Any, intensity: float = 0.3) -> Filter
Twirl (swirl) the image around its centre.
def apply_circular_grayscale(self: Any, center_x: float, center_y: float, radius: float = 100.0, intensity: float = 1.0) -> Filter
Apply grayscale inside a circular region.

Args:
   center_x, center_y: Centre of the circle.
   radius: Radius of the circle.
   intensity: Effect strength.
def apply_rectangular_blur(self: Any, x: float, y: float, width: float, height: float, intensity: float = 0.5) -> Filter
Apply blur only inside a rectangular region.

Args:
   x, y: Top‑left corner of the rectangle.
   width, height: Size of the rectangle.
   intensity: Blur strength.
def begin_frame(self: Any) -> None
Start rendering a new frame.

Binds the filter framebuffer if filters are active and clears the buffer.
def end_frame(self: Any) -> None
Finish rendering the frame and apply any active filters.

If filters are present, they are applied before swapping buffers.
def _apply_filters(self: Any) -> None
Stack and apply all active filters to the screen (or to the filter framebuffer).
def get_surface(self: Any) -> pygame.Surface
Return the main display surface (created by pygame).
def set_surface(self: Any, surface: Optional[pygame.Surface]) -> None
Redirect rendering to a custom pygame surface (using a framebuffer).

Args:
   surface: Target surface, or None to reset to the main display.
def _surface_to_texture(self: Any, surface: pygame.Surface) -> int
Convert a pygame Surface into an OpenGL texture (without caching).

Args:
   surface: Source surface (should have alpha channel for best results).

Returns:
   OpenGL texture ID.
def _surface_to_texture_cached(self: Any, surface: pygame.Surface) -> int
Convert a surface to a texture, using a cache keyed by surface id.

Args:
   surface: Source surface.

Returns:
   OpenGL texture ID (cached).
def _convert_color(self: Any, color: Union[Tuple[int, int, int, float], Tuple[int, int, int], Tuple[float, float, float, float], Color, ThemeStyle]) -> Tuple[float, float, float, float]
Normalise a colour to (r, g, b, a) floats in [0, 1].

Args:
   color: Colour as (r, g, b) or (r, g, b, a) with values 0‑255 or 0‑1 (alpha 0‑255 also allowed).

Returns:
   Normalised (r, g, b, a) where each component is in [0, 1].
def enable_scissor(self: Any, x: int | float, y: int | float, width: int | float, height: int | float) -> None
Push a scissor rectangle, intersecting with any existing one.
def disable_scissor(self: Any) -> None
Pop the top scissor rectangle and restore the previous one.
def draw_rect(self: Any, x: int | float, y: int | float, width: int | float, height: int | float, color: Union[Tuple[int, int, int, float], Tuple[int, int, int], Color, Tuple[float, float, float, float], ThemeStyle], fill: bool = True, pivot: tuple = (0.0, 0.0), border_width: int = 1, surface: Optional[pygame.Surface] = None, corner_radius: Union[int, float, Tuple[int, int, int, int], Tuple[float, float, float, float]] = 0, border_color: Optional[Union[Tuple[int, int, int, float], Tuple[int, int, int], Color, Tuple[float, float, float, float], ThemeStyle]] = None) -> None
Draw a rectangle with optional fill, border, and rounded corners.

Args:
   x, y: Position (before anchor adjustment).
   width, height: Rectangle dimensions.
   color: Fill colour (if fill=True).
   fill: If True, filled rectangle; otherwise outline only.
   pivot: Anchor as (fx, fy) where (0,0) is top‑left, (1,1) bottom‑right.
   border_width: Thickness of the border in pixels (used when border_color provided or fill=False).
   surface: Optional target surface (defaults to current render target).
   corner_radius: Radius for all corners (int) or per‑corner (top‑left, top‑right, bottom‑right, bottom‑left).
   border_color: Colour of the border (if None and fill=False, uses `color` for outline).
def draw_isosceles_triangle(self: Any, x: int | float, y: int | float, width: int | float, height: int | float, color: Union[Tuple[int, int, int, float], Tuple[int, int, int], Color, Tuple[float, float, float, float], ThemeStyle], fill: bool = True, border_width: int = 1, border_color: Optional[Union[Tuple[int, int, int, float], Tuple[int, int, int], Color, Tuple[float, float, float, float], ThemeStyle]] = None) -> None
Draw an isosceles triangle with a horizontal base at the bottom.

The triangle is defined by a bounding box:
   top vertex at (x + width/2, y)
   bottom-left at (x, y + height)
   bottom-right at (x + width, y + height)

Args:
   x, y: Top‑left corner of the bounding box.
   width, height: Bounding box dimensions.
   color: Fill colour (if fill=True) or outline colour (if no border_color).
   fill: If True, fills the triangle; otherwise draws only the outline.
   border_width: Thickness of the outline (used when fill=False or border_color given).
   border_color: Colour for the outline; if provided, an outline is drawn
               on top of the fill (if fill=True) or as the only outline
               (if fill=False). If None and fill=False, `color` is used.
def draw_equilateral_triangle(self: Any, x: int | float, y: int | float, width: int | float, color: Union[Tuple[int, int, int, float], Tuple[int, int, int], Color, Tuple[float, float, float, float], ThemeStyle], fill: bool = True, border_width: int = 1, border_color: Optional[Union[Tuple[int, int, int, float], Tuple[int, int, int], Color, Tuple[float, float, float, float], ThemeStyle]] = None) -> None
Draw an equilateral triangle with a horizontal base at the bottom.

The triangle is defined by the side length `width`. The bounding box
has width = side length and height = side * sqrt(3)/2, with top-left at (x, y).

Args:
   x, y: Top‑left corner of the bounding box.
   width: Side length of the equilateral triangle.
   color: Fill colour (if fill=True) or outline colour (if no border_color).
   fill: If True, fills the triangle; otherwise draws only the outline.
   border_width: Thickness of the outline (used when fill=False or border_color given).
   border_color: Colour for the outline; if provided, an outline is drawn
               on top of the fill (if fill=True) or as the only outline
               (if fill=False). If None and fill=False, `color` is used.
def _draw_outline_rect(self: Any, x: int, y: int, width: int, height: int, color: tuple, border_width: int, radii: Tuple[int, int, int, int]) -> None
Internal: draw only the outline of a rectangle.
def _draw_sharp_rect(self: Any, x: int, y: int, width: int, height: int, color: tuple, fill: bool = True, border_width: int = 1) -> None
Internal: draw a rectangle without rounded corners (sharp).
def _draw_rounded_rect(self: Any, x: int, y: int, w: int, h: int, color: tuple, fill: bool = True, border_width: int = 1, radii: Tuple[int, int, int, int] = (0, 0, 0, 0)) -> None
Internal: draw a rounded rectangle (filled or outline) using the rounded rect shader.
def draw_line(self: Any, start_x: int | float, start_y: int | float, end_x: int | float, end_y: int | float, color: tuple, width: int = 2, surface: Optional[pygame.Surface] = None) -> None
Draw a thick line between two points.

Args:
   start_x, start_y: Start point.
   end_x, end_y: End point.
   color: RGB or RGBA colour.
   width: Line thickness in pixels.
   surface: Optional target surface.
def _draw_thick_line(self: Any, x1: float, y1: float, x2: float, y2: float, color: tuple, width: float) -> None
Internal: draw a line by creating a quad perpendicular to the direction.
def draw_lines(self: Any, points: List[Tuple[Tuple[int, int], Tuple[int, int]]], color: tuple, width: int = 2, surface: Optional[pygame.Surface] = None) -> None
Draw multiple line segments.

Args:
   points: List of ((x1,y1), (x2,y2)) pairs.
   color: Line colour.
   width: Line thickness.
   surface: Optional target surface.
def draw_circle(self: Any, center_x: int | float, center_y: int | float, radius: int | float, color: tuple, fill: bool = True, border_width: int = 1, surface: Optional[pygame.Surface] = None, pivot: Tuple[float, float] = (0.5, 0.5)) -> None
Draw a circle (filled or outline) with caching.

Args:
   center_x, center_y: Centre position (before anchor).
   radius: Circle radius.
   color: Fill or outline colour.
   fill: If True, filled circle; otherwise outline.
   border_width: Thickness of outline (used when fill=False).
   surface: Optional target surface.
   pivot: Anchor point for positioning (default centre).
def _generate_filled_circle_geometry(self: Any, segments: int) -> Tuple[np.ndarray, np.ndarray]
Generate vertex and index arrays for a filled circle (unit coordinates).
def _generate_hollow_circle_geometry(self: Any, segments: int, border_width: float, radius: float) -> Tuple[np.ndarray, np.ndarray]
Generate geometry for a hollow circle (outline) as a single triangle strip.
def _upload_geometry(self: Any, vertices: np.ndarray, indices: np.ndarray) -> Tuple[int, int, int]
Create VAO, VBO, EBO and upload vertex/index data. Returns (vao, vbo, ebo).
def draw_polygon(self: Any, points: List[Tuple[float, float]], color: tuple, fill: bool = True, border_width: int = 1, surface: Optional[pygame.Surface] = None, pivot: Tuple[float, float] = (0.0, 0.0)) -> None
Draw a convex polygon (filled or outline).

Args:
   points: List of (x, y) vertices.
   color: Fill or outline colour.
   fill: If True, filled polygon; otherwise outline.
   border_width: Outline thickness (only used when fill=False).
   surface: Optional target surface.
   pivot: Offset applied to all points.
def _generate_filled_polygon_geometry(self: Any, points: List[Tuple[float, float]]) -> Tuple[np.ndarray, np.ndarray]
Generate triangulation for a filled polygon (assumes convex).
def _generate_hollow_polygon_geometry(self: Any, points: List[Tuple[float, float]], border_width: float) -> Tuple[np.ndarray, np.ndarray]
Generate geometry for a hollow polygon outline.
def draw_text(self: Any, text: str, x: int, y: int, color: Any, font: pygame.font.Font, surface: Optional[pygame.Surface] = None, pivot: Tuple[float, float] = (0.0, 0.0), flip: Tuple[bool, bool] = (False, False), rotate: float = 0.0, *args: tuple, **kwargs: dict) -> None
Draw text using a pygame font, with optional flip, rotation, and caching.

Args:
   text: The string to render.
   x, y: Position (before anchor).
   color: Any colour format accepted (pygame.Color, tuple, etc.).
   font: Pygame font object.
   surface: Optional target surface.
   pivot: Anchor (fx, fy) relative to text size.
   flip: (flip_horizontal, flip_vertical).
   rotate: Rotation angle in degrees (clockwise).
def draw_rich_text(self: Any, text: str, x: int, y: int, default_color: Any, font: pygame.font.Font, surface: Optional[pygame.Surface] = None, pivot: Tuple[float, float] = (0.0, 0.0), **kwargs: dict) -> None
Draw rich text using the rich text parser.

Args:
   text: Rich text string with markup
   x, y: Position
   default_color: Default text color (can be Color, ThemeStyle, or tuple)
   font: Base font
   surface: Optional target surface
   pivot: Anchor point
   **kwargs: Additional arguments for draw_text
def draw_rich_text_line(self: Any, line: List['RichTextSegment'], x: int, y: int, default_color: Any, font: pygame.font.Font, surface: Optional[pygame.Surface] = None) -> None
Draw a pre-parsed rich text line.

Args:
   line: List of RichTextSegment objects
   x, y: Position
   default_color: Default text color (can be Color, ThemeStyle, or tuple)
   font: Base font
   surface: Optional target surface
def _cleanup_text_cache(self: Any, now: float) -> None
Remove text textures that have not been used for longer than the timeout.
def draw_surface(self: Any, surface: pygame.Surface, x: int, y: int, pivot: Tuple[float, float] = (0.0, 0.0), use_cache: bool = True) -> None
Alias for blit with optional cache control.
def blit(self: Any, source: pygame.Surface, dest: Union[Tuple[int, int], pygame.Rect], area: Optional[pygame.Rect] = None, special_flags: int = 0, pivot: Tuple[float, float] = (0.0, 0.0), use_cache: bool = False) -> None
No documentation
def fill_screen(self: Any, color: Tuple[int, int, int, float] | Tuple[int, int, int]) -> None
Fill the entire screen (or current framebuffer) with a solid colour.

Args:
   color: RGBA tuple with components 0‑255 (RGB) and alpha 0‑1, or RGB tuple with components 0‑255.
def clear(self: Any) -> None
Clear the colour and depth buffers.
def render_particles(self: Any, particle_data: Dict[str, Any], camera: Any) -> None
Render particle system using instancing.

Args:
   particle_data: Dictionary with keys:
       - 'active_count': number of active particles
       - 'positions': numpy array (N,2) world positions
       - 'sizes': numpy array (N,) sizes (world units)
       - 'colors': numpy array (N,3) uint8 RGB
       - 'alphas': numpy array (N,) uint8 alpha
   camera: Camera object that provides world_to_screen and convert_size_zoom.
def _ensure_particle_capacity(self: Any, required: int) -> None
Resize instance buffers if needed to accommodate `required` particles.
def cleanup(self: Any) -> None
Release all OpenGL resources (shaders, textures, buffers, framebuffers).
def set_blend_mode(self: Any, mode: str) -> None
Set the OpenGL blending mode.

Args:
   mode: One of 'normal', 'add', 'multiply', 'screen'.
Back to Backend Module