"""
Texture loading and OpenGL binding management.
This module is responsible for loading 2D image assets via Pygame, applying transforms,
and safely mapping them into ModernGL `Texture` and `TextureArray` objects.
It enforces strict mipmap generation and anisotropic filtering to guarantee crisp
pixel art visuals across all rendering distances.
"""
from typing import Any
import moderngl as mgl
import pygame as pg
from profiler import global_profiler
from settings import get_path
[docs]
class Textures:
"""
Loads, configures, and binds OpenGL textures and texture arrays.
Handles mipmapping, anisotropic filtering, and texture units for crisp rendering.
It pre-loads all essential visual assets during initialization and assigns them
to specific texture binding locations for the GLSL shaders to sample from.
Args:
app (Any): The main application instance containing the Pygame and ModernGL contexts.
"""
@global_profiler.profile_func('Textures_Init')
def __init__(self, app: Any) -> None:
"""
Instantiates and assigns the various textures to their respective OpenGL
texture locations so shaders can access them simultaneously.
"""
# Initialize context
self.app: Any = app
self.ctx: Any = app.ctx
# Load textures
self.texture_0: Any = self.load('textures/uis/frame.png')
self.texture_array_0: Any = self.load('textures/arrays/texture-array-2.png', is_tex_array=True)
self.texture_breaking: Any = self.load('textures/effects/block-breaking.png')
self.texture_stick: Any = self.load('models/items/stick/stick.png')
self.texture_pickaxe: Any = self.load('models/items/wooden-pickaxe/wooden_pickaxe.png', rotation=-90)
# Assign texture units
self.texture_0.use(location=0)
self.texture_array_0.use(location=1)
self.texture_breaking.use(location=3)
self.texture_stick.use(location=5)
self.texture_pickaxe.use(location=6)
[docs]
@global_profiler.profile_func('Textures_Load')
def load(
self, file_name: str, is_tex_array: bool = False, rotation: int = 0, flip_x: bool = True, flip_y: bool = False
) -> Any:
"""
Reads an image file and converts it into an OpenGL Texture or TextureArray.
Applies requested rotations/flips and automatically calculates 3D texture array
dimensions if the image contains vertical strips.
"""
# Load base image
texture: Any = pg.image.load(get_path(f'assets/{file_name}'))
# Apply transformations
if rotation != 0:
texture = pg.transform.rotate(texture, rotation)
texture = pg.transform.flip(texture, flip_x=flip_x, flip_y=flip_y)
# Create texture object
if is_tex_array:
num_layers = 3 * texture.get_height() // texture.get_width() # 3 textures per layer
texture = self.app.ctx.texture_array(
size=(texture.get_width(), texture.get_height() // num_layers, num_layers),
components=4,
data=pg.image.tobytes(texture, 'RGBA', False),
)
else:
texture = self.ctx.texture(
size=texture.get_size(), components=4, data=pg.image.tobytes(texture, 'RGBA', False)
)
# Configure texture filtering
texture.anisotropy = 32.0
texture.build_mipmaps()
texture.filter = (mgl.NEAREST, mgl.NEAREST)
return texture