# Copyright (c) 2023-2024 Thomas Mathieson.
# Distributed under the terms of the MIT license.
from abc import ABC, abstractmethod
from enum import Enum
from typing import Optional, Any, Union, Tuple, Set, Dict
import numpy.typing as npt
[docs]
class ShaderStage(Enum):
"""
An enum representing an OpenGL shader stage.
"""
VERTEX = "vertex"
TESSELLATION = "tessellation"
GEOMETRY = "geometry"
PIXEL = "pixel"
COMPUTE = "compute"
[docs]
class SSVStreamingMode(Enum):
"""
Represents an image/video streaming mode for pySSV. Note that some of these streaming formats may not be
supported on all platforms.
"""
JPG = "jpg"
PNG = "png"
VP8 = "vp8"
VP9 = "vp9"
H264 = "h264"
HEVC = "hevc"
"""Not supported"""
MPEG4 = "mpeg4"
"""Not supported"""
MJPEG = "mjpeg"
[docs]
class SSVRender(ABC):
"""
An abstract rendering backend for SSV
"""
@abstractmethod
def __init__(self, gl_version: Optional[int] = None, use_renderdoc_api: bool = False):
"""
Initialises a new renderer (and backing graphics context) with the given options.
:param gl_version: optionally, the minimum version of OpenGL to support.
:param use_renderdoc_api: whether the renderer should attempt to load the RenderDoc API.
"""
...
[docs]
@abstractmethod
def render(self) -> bool:
"""
Renders a complete frame.
:return: whether the frame rendered successfully.
"""
...
[docs]
@abstractmethod
def read_frame(self, components: int = 4, frame_buffer_uid: int = 0) -> bytes:
"""
Gets the current contents of the frame buffer as a byte array.
:param components: how many components to read from the frame (out of ``RGBA``).
:param frame_buffer_uid: the frame buffer to read from.
:return: the contents of the frame buffer as a bytearray in the ``RGBA`` format.
"""
...
[docs]
@abstractmethod
def read_frame_into(self, buffer: bytearray, components: int = 4, frame_buffer_uid: int = 0) -> None:
"""
Gets the current contents of the frame buffer as a byte array.
:param buffer: the buffer to copy the frame into.
:param components: how many components to read from the frame (out of ``RGBA``).
:param frame_buffer_uid: the frame buffer to read from.
"""
...
[docs]
@abstractmethod
def log_context_info(self, full=False) -> None:
"""
Logs the OpenGL information to the console for debugging.
:param full: whether to log *all* of the OpenGL context information (including extensions).
"""
...
[docs]
@abstractmethod
def get_context_info(self) -> Dict[str, str]:
"""
Returns the OpenGL context information.
"""
...
[docs]
@abstractmethod
def get_supported_extensions(self) -> Set[str]:
"""
Gets the set of supported OpenGL shader compiler extensions.
"""
...
[docs]
@abstractmethod
def update_frame_buffer(self, frame_buffer_uid: int, order: Optional[int], size: Optional[Tuple[int, int]],
uniform_name: Optional[str], components: Optional[int] = 4,
dtype: Optional[str] = "f1") -> None:
"""
Updates the resolution/format of the given frame buffer. Note that framebuffer 0 is always used for output.
If the given framebuffer id does not exist, it is created.
Setting a parameter to ``None`` preserves the current value for that frame buffer.
:param frame_buffer_uid: the uid of the framebuffer to update/create. Buffer 0 is the output framebuffer.
:param order: the sorting order to render the frame buffers in, smaller values are rendered first.
:param size: the new resolution of the framebuffer.
:param uniform_name: the name of the uniform to bind this frame buffer to.
:param components: how many vector components should each pixel have (RGB=3, RGBA=4).
:param dtype: the data type for each pixel component (see:
https://moderngl.readthedocs.io/en/5.8.2/topics/texture_formats.html).
"""
...
[docs]
@abstractmethod
def delete_frame_buffer(self, frame_buffer_uid: int) -> None:
"""
Destroys the given framebuffer. *Note* that framebuffer 0 can't be destroyed as it is the output framebuffer.
:param frame_buffer_uid: the uid of the framebuffer to destroy.
"""
...
[docs]
@abstractmethod
def update_vertex_buffer(self, frame_buffer_uid: int, draw_call_uid: int,
vertex_array: Optional[npt.NDArray], index_array: Optional[npt.NDArray],
vertex_attributes: Optional[Tuple[str]]) -> None:
"""
Updates the data inside a vertex buffer.
:param frame_buffer_uid: the uid of the framebuffer of the vertex buffer to update.
:param draw_call_uid: the uid of the draw call of the vertex buffer to update.
:param vertex_array: a numpy array containing the new vertex data.
:param index_array: optionally, a numpy array containing the indices of vertices ordered to make triangles.
:param vertex_attributes: a tuple of the names of the vertex attributes to map to in the shader, in the order
that they appear in the vertex array.
"""
...
[docs]
@abstractmethod
def delete_vertex_buffer(self, frame_buffer_uid: int, draw_call_uid: int) -> None:
"""
Deletes an existing vertex buffer.
:param frame_buffer_uid: the uid of the framebuffer of the vertex buffer to delete.
:param draw_call_uid: the uid of the draw call of the vertex buffer to delete.
"""
...
[docs]
@abstractmethod
def register_shader(self, frame_buffer_uid: int, draw_call_uid: int,
vertex_shader: str, fragment_shader: Optional[str],
tess_control_shader: Optional[str], tess_evaluation_shader: Optional[str],
geometry_shader: Optional[str], compute_shader: Optional[str],
primitive_type: Optional[str] = None) -> None:
"""
Compiles and registers a shader to a given framebuffer.
:param frame_buffer_uid: the uid of the framebuffer to register the shader to.
:param draw_call_uid: the uid of the draw call to register the shader to.
:param vertex_shader: the preprocessed vertex shader GLSL source.
:param fragment_shader: the preprocessed fragment shader GLSL source.
:param tess_control_shader: the preprocessed tessellation control shader GLSL source.
:param tess_evaluation_shader: the preprocessed tessellation evaluation shader GLSL source.
:param geometry_shader: the preprocessed geometry shader GLSL source.
:param compute_shader: *[Not implemented]* the preprocessed compute shader GLSL source.
:param primitive_type: what type of input primitive to treat the vertex data as. One of ("TRIANGLES", "LINES",
"POINTS), defaults to "TRIANGLES" if ``None``.
"""
...
[docs]
@abstractmethod
def update_texture(self, texture_uid: int, data: npt.NDArray, uniform_name: Optional[str],
override_dtype: Optional[str],
rect: Optional[Union[Tuple[int, int, int, int], Tuple[int, int, int, int, int, int]]],
treat_as_normalized_integer: bool) -> None:
"""
Creates or updates a texture from the NumPy array provided.
:param texture_uid: the uid of the texture to create or update.
:param data: a NumPy array containing the image data to copy to the texture.
:param uniform_name: the name of the shader uniform to associate this texture with.
:param override_dtype: optionally, a moderngl override
:param rect: optionally, a rectangle (left, top, right, bottom) specifying the area of the target texture to
update.
:param treat_as_normalized_integer: when enabled, integer types (singed/unsigned) are treated as normalized
integers by OpenGL, such that when the texture is sampled values in the
texture are mapped to floats in the range [0, 1] or [-1, 1]. See:
https://www.khronos.org/opengl/wiki/Normalized_Integer for more details.
"""
...
[docs]
@abstractmethod
def update_texture_sampler(self, texture_uid: int, repeat_x: Optional[bool], repeat_y: Optional[bool],
linear_filtering: Optional[bool], linear_mipmap_filtering: Optional[bool],
anisotropy: Optional[int], build_mip_maps: bool) -> None:
"""
Updates a texture's sampling settings. Parameters set to ``None`` are not updated.
:param texture_uid: the uid of the texture to update.
:param repeat_x: whether the texture should repeat or be clamped in the x-axis.
:param repeat_y: whether the texture should repeat or be clamped in the y-axis.
:param linear_filtering: whether the texture should use nearest neighbour (``False``) or linear (``True``)
interpolation.
:param linear_mipmap_filtering: whether different mipmap levels should blend linearly (``True``) or not
(``False``).
:param anisotropy: the number of anisotropy samples to use. (minimum of 1 = disabled, maximum of 16)
:param build_mip_maps: when set to ``True``, immediately builds mipmaps for the texture.
"""
...
[docs]
@abstractmethod
def delete_texture(self, texture_uid: int) -> None:
"""
Destroys the given texture object.
:param texture_uid: the uid of the texture to destroy.
"""
...
[docs]
@abstractmethod
def renderdoc_capture_frame(self, filename: Optional[str]) -> None:
"""
Triggers a frame capture with Renderdoc if it's initialised.
:param filename: optionally, the filename and path to save the capture with.
"""
...
[docs]
@abstractmethod
def set_start_time(self, start_time: float) -> None:
"""
Sets the renderer's start time; this is used by the renderer to compute the canvas time which is injected into
shaders.
:param start_time: the start time of the renderer in seconds since the start of the epoch.
"""
...