gl33
FreeBodyEngine.graphics.gl33
#
The OpenGL 3.3 implementation of the FB graphics system.
GLFramebuffer(width, height, attachments, transparent=False)
#
Bases: Framebuffer
The GL 3.3 implementation of Framebuffer: a real glGenFramebuffers
object with one GL_TEXTURE_2D per color attachment (so it can also be
sampled from later, e.g. a G-buffer channel) and a single shared
renderbuffer for whichever depth/stencil/depth-stencil attachment was
requested. self.attachments[name] (inherited from the base class) is
repurposed here to hold each color attachment's actual
GL_COLOR_ATTACHMENT0 + n enum rather than the (AttachmentType,
AttachmentFormat) pair the constructor received - that original pair is
kept separately in self._attachments since resize() needs it again to
recreate storage at the new size.
Creates the FBO and, for every requested attachment, the backing
GL object: a mipmapless linear-filtered GL_TEXTURE_2D for each COLOR
attachment (bound to consecutive GL_COLOR_ATTACHMENTn slots), or one
shared renderbuffer for a DEPTH/STENCIL/DEPTH_STENCIL attachment.
Color attachments are also collected into draw_buffers and wired up
via glDrawBuffers so a shader with multiple @output fields
actually renders to all of them; with no color attachments at all,
glDrawBuffer(GL_NONE)/glReadBuffer(GL_NONE) are set instead
(a depth-only FBO, e.g. a shadow map). Raises RuntimeError if the
finished FBO fails glCheckFramebufferStatus. transparent enables
standard alpha blending for subsequent draws into this FBO.
depth_renderbuffer = glGenRenderbuffers(1)
instance-attribute
#
depth_texture_name = name
instance-attribute
#
fbo = glGenFramebuffers(1)
instance-attribute
#
num_color_attachments = color_attachment_index
instance-attribute
#
textures = {}
instance-attribute
#
bind()
#
Binds this FBO as the current GL_FRAMEBUFFER and sets the GL viewport to its full size, so subsequent draws render into it at the correct resolution instead of whatever viewport the previously-bound target left set.
clear_color_attachment(name, value=(0.0, 0.0, 0.0, 0.0))
#
Clears the named color attachment to value via
glClearBufferfv(GL_COLOR, draw_buffer_index, ...), targeting only
that attachment's own draw-buffer index rather than every bound draw
buffer at once (the effect a plain glClear(GL_COLOR_BUFFER_BIT)
would have) - see the abstract method's docstring for why that
distinction matters for a multi-attachment G-buffer.
draw(attachment, size=None)
#
Draw a named attachment to the screen.
get_attachment_texture(attachment_name)
#
Returns the raw GL texture id backing the named color attachment
(there's nothing to return for a depth/stencil attachment - those are
renderbuffers, not textures - so only entries in self.textures
apply).
read(attachment_name)
#
Reads back the named color attachment's pixels via
glReadPixels, always as GL_FLOAT regardless of the attachment's own
storage type, and reshapes the raw buffer into a (height, width,
channels) float32 array (channels derived from the attachment's GL
format via GL_CHANNEL_COUNT). This is a synchronous GPU->CPU stall -
see the abstract method's docstring for when that's acceptable.
resize(size)
#
Recreates every attachment's storage at the new size, mirroring
init's attachment loop: each color texture is deleted and
regenerated at the new dimensions (a GL texture's storage can't be
resized in place), and the shared depth/stencil renderbuffer is
likewise deleted and regenerated if one exists. Also re-runs the
draw-buffers wiring and completeness check init does, and updates
the GL viewport to match. Raises RuntimeError if the resized FBO is
incomplete.
set_draw_buffers(names)
#
See Framebuffer.set_draw_buffers. Assumes this FBO is already bound.
Builds the same full-width, position-equals-attachment-index array
WebGL2Framebuffer.set_draw_buffers() is forced to use (GL_NONE at
every color attachment not in names) rather than the more
compact [self.attachments[n] for n in names] this used to be -
desktop GL doesn't require that shape (it can remap an arbitrary
subset onto sequential fragment-output locations starting at 0),
but PBRPipeline's shaders (see graphics/pbr/shaders.py's
LIGHTING_COMPOSITE_FRAG/default_forward.fbfrag) declare their real
@output field at whatever location its physical attachment index
is - padded with unused leading fields to get there - specifically
so the same FBUSL source compiles correctly on WebGL2, which has
no remapping at all (see WebGL2Framebuffer.set_draw_buffers()'s
own docstring). Matching that convention here means one shared
assumption ("output location N always means physical attachment
N") holds on both backends instead of desktop silently tolerating
a mismatch WebGL2 can't.
unbind()
#
Rebinds the default framebuffer (0), i.e. the window's own backbuffer.
GLImage(data)
#
Bases: Image
The GL 3.3 implementation of Image: a thin wrapper that reads its pixel data and rect straight off the underlying GLTextureManager-owned Texture rather than holding any separate GPU state of its own.
Forwards data (the Texture this image wraps) to Image.init,
which stores it as self.texture.
GLMesh(attributes, indices=None, primitive=None, index_type=None, usage=None)
#
Bases: Mesh
The GL 3.3 implementation of Mesh: owns a real VAO, one VBO per
vertex attribute, and an optional EBO for indexed drawing. Maps the
abstract PrimitiveType onto the actual GL draw-mode enum once at
construction (_render_mode) rather than re-resolving it on every
draw().
Generates the VAO and, if indices is given, the EBO (VBOs
themselves are created lazily per-attribute in upload()), resolves
primitive to its GL draw-mode enum, and immediately calls upload()
to push the mesh's initial data to the GPU.
ebo = glGenBuffers(1) if indices is not None else None
instance-attribute
#
vao = glGenVertexArrays(1)
instance-attribute
#
vbos = {}
instance-attribute
#
destroy()
#
Deletes every attribute VBO, the EBO if this mesh has one, and the VAO itself, releasing all of this mesh's GPU resources.
draw()
#
Issues the draw call: glDrawElements against the EBO if this
mesh has indices, else glDrawArrays over a vertex count derived
from the first attribute's raw array length divided by 3 (i.e. this
non-indexed path assumes that first attribute is 3 components wide,
such as a "verticies" vec3 channel).
upload()
#
(Re)creates one VBO per entry in self.attributes and uploads its
data, wiring each up as a sequential vertex attribute starting at
location 0 in self.attributes' iteration order - so the order
attributes are inserted into that dict is exactly the order the
matching shader's in locations must line up with. Also uploads
self.indices into the EBO if this mesh has one, recording its GL
index-element type (gl_index_type) for draw() to use.
GLShader(vertex_source, fragment_source, injector, geometry_source=None)
#
Bases: Shader
The GL 3.3 implementation of Shader: compiles FBUSL source via GL33Generator into a real GL program, introspects its uniforms, and caches each uniform's last-set value so set_uniform() can skip a redundant glUniform* call when the value hasn't actually changed.
generator_cls is a class attribute (not hardcoded inline in
init) specifically so graphics/gles/shader.py's GLESShader can
reuse every method here unchanged and only override which generator
produces the GLSL text - the real GL program creation/introspection/
uniform dispatch below is all plain PyOpenGL calls, valid against a
GLES 3.0 context exactly the same as a desktop 3.3 one.
Compiles vertex_source/fragment_source(/geometry_source)
into a linked GL program (via self.generator_cls) and
introspects its active uniforms into self.uniforms, seeding
uniform_cache with None for each so the first set_uniform()
call for any uniform always goes through.
generator_cls = GL33Generator
class-attribute
instance-attribute
#
uniform_cache = {}
instance-attribute
#
uniforms = {}
instance-attribute
#
check_val_type(val, gl_type, name)
#
Validates that val is an acceptable Python value for a uniform
of GL type gl_type - logging an engine error and returning False
if not. Color/Vector/Vector3 are accepted directly for the
vector GL types they map onto, alongside a plain tuple/list/ndarray
of the right length.
get_uniform(name)
#
Returns the introspected GLUniform record (location/size/type)
for uniform name.
rebuild(injector=..., vertex_source=None, fragment_source=None, geometry_source=...)
#
Recompiles this shader in place (same object, new GL program) -
used by dev-mode hot reload (see Material.reload_shader()). Passing
vertex_source/fragment_source re-fetches from those (a fresh
FileResource, not whatever's cached on self already) rather than
this shader's existing ones, since a stale FileResource can be
holding a file handle to a since-replaced inode (editors that save
via write-to-temp-then-rename) and silently never see new content.
geometry_source defaults to self.geometry_source unchanged
(... rather than None, since None is itself a valid "no
geometry shader" value some callers legitimately want to keep).
set_buffer(name, buffer)
#
Binds buffer to the uniform block declared as name in this
shader's source (self.data['buffers'], populated by
generator-produced metadata) - warns instead of raising if name
isn't a known buffer block.
set_uniform(name, val)
#
Sets uniform name to val, after check_val_type() validates
it. Skips the actual glUniform* call (and the cache update) if val
equals the value already cached for this uniform, avoiding redundant
driver calls when the same value is set every frame - as material
properties typically are.
setup_uniforms()
#
Populates self.uniforms from the program's active uniforms
(glGetActiveUniform), normalizing the name PyOpenGL hands back
(which can come as str, bytes, or a numpy array depending on
driver/binding) to a plain, null-terminated string.
use()
#
Activates this shader's program, updates the TIME builtin
uniform if the shader declares one, and binds every currently-cached
texture/texture-stack uniform (see _bind_textures).