Skip to content

Modern Mode

Mark Brooks edited this page Sep 7, 2025 · 2 revisions

Modern Mode

Modern Mode in PicoGL is built for shader‑based, OpenGL Core‑profile style rendering. It focuses on programmable pipelines, Vertex Array Objects (VAOs), and explicit buffer management — keeping boilerplate low while giving you access to the full power of your GPU.

⚠️ Note: Modern Mode and Legacy Mode are currently designed as separate paths. Mixing them in the same render context is not yet supported.


✨ Key Features

  • Shader‑centric workflow – Vertex, Fragment (and optional Geometry) shaders are first‑class citizens.
  • VAO/VBO management – Explicit control with convenience helpers.
  • Uniforms & attributes – Easy binding and updating.
  • Core profile compliance – Ideal for macOS (until full deprecation) and modern Linux/Windows drivers.
  • Full OpenGL escape hatch – Drop down to raw gl* calls anytime.

🏗️ Minimal Example (Cube)

from pathlib import Path
from typing import NoReturn

from examples.data.cube_data import g_vertex_buffer_data, g_color_buffer_data
from picogl.renderer import MeshData
from picogl.ui.backend.glut.window.object import RenderWindow

GLSL_DIR = Path(__file__).parent / "glsl" / "cube"

def main() -> NoReturn:
    """Initialize and render a cube using Modern Mode."""
    data = MeshData.from_raw(vertices=g_vertex_buffer_data, colors=g_color_buffer_data)
    render_window = RenderWindow(
        width=800,
        height=600,
        title="Modern Cube",
        glsl_dir=GLSL_DIR,
        data=data,
    )
    render_window.initialize()
    render_window.run()

if __name__ == "__main__":
    main()

🎨 Renderer Structure

from OpenGL.raw.GL.VERSION.GL_1_0 import GL_TRIANGLES
from picogl.backend.modern.core.vertex.array.object import VertexArrayObject
from picogl.renderer import GLContext, MeshData, RendererBase

class ModernCubeRenderer(RendererBase):
    def __init__(self, context: GLContext, data: MeshData, glsl_dir: str):
        super().__init__()
        self.context, self.data, self.glsl_dir = context, data, glsl_dir

    def initialize_shaders(self):
        self.context.create_shader_program(
            vertex_source_file="vertex.glsl",
            fragment_source_file="fragment.glsl",
            glsl_dir=self.glsl_dir,
        )

    def initialize_buffers(self):
        vao = VertexArrayObject()
        vao.add_vbo(index=0, data=self.data.vbo, size=3)
        vao.add_vbo(index=1, data=self.data.cbo, size=3)
        self.context.vaos = {"cube": vao}

    def render(self) -> None:
        vao = self.context.vaos["cube"]
        shader = self.context.shader
        with shader, vao:
            shader.uniform("mvp_matrix", self.context.mvp_matrix)
            shader.uniform("model_matrix", self.context.model_matrix)
            vao.draw(mode=GL_TRIANGLES, index_count=self.data.vertex_count)

🔧 Tips & Best Practices

  • Organize GLSL source files alongside your Python code for clarity.
  • Use MeshData.from_raw() for quick tests; migrate to structured loaders (OBJ/GLTF) for real assets.
  • Encapsulate per‑object state in renderer classes to reduce coupling.
  • Remember to manage OpenGL context creation (PicoGL windows handle this automatically).

📚 Next Steps

  • Explore the examples/modern folder for more complex demos.
  • Check out the Documentation for shader uniforms, texture binding, and advanced usage.
  • File issues or feature requests on GitHub to help shape PicoGL’s Modern API.

Clone this wiki locally