← CC2018

Main Render Loop

Semestre 02, 2026

Objetivo

  • Comprender qué es y cómo funciona el Main Render Loop.
  • Saber cómo manejar entradas de usuario (teclado, mouse, etc.) dentro del loop.
  • Aplicar buenas prácticas para mantener un framerate constante y un loop eficiente.

Definición

Main Render Loop

El ciclo principal que ejecuta cualquier aplicación gráfica o videojuego.

🎥 Resultado

Logra la ilusión de movimiento e interactividad en tiempo real.

  • Procesar entradas de usuario.
  • Actualizar el estado de la aplicación.
  • Renderizar la escena en pantalla de manera continua.

Importancia

Sin el render loop, las aplicaciones gráficas serían estáticas. Con el loop:

Se puede responder al usuario en tiempo real.
Se pueden crear animaciones fluidas.
Se puede mantener actualizada la lógica de la aplicación.

Funcionamiento

En cada iteración del loop se realizan 3 pasos principales:

1Handle Input
  • Procesar entradas de teclado, mouse, etc.
2Update State
  • Actualizar posiciones, físicas, animaciones.
3Render Frame
  • Dibujar la nueva escena en pantalla.

Flujo del loop


init_window(width, height)

while not window_should_close():
    process_input()
    update_scene()
    render_frame()
    swap_buffers()
    limit_framerate()

init_window(width, height)

while not window_should_close():
    process_input()
    update_scene()
    render_frame()
    swap_buffers()
    limit_framerate()
init_window(width, height)

Antes del loop se crea la ventana y se cargan los recursos: texturas, modelos y shaders.


init_window(width, height)

while not window_should_close():
    process_input()
    update_scene()
    render_frame()
    swap_buffers()
    limit_framerate()
while not window_should_close():

El ciclo se repite frame tras frame hasta que el usuario cierra la ventana.


init_window(width, height)

while not window_should_close():
    process_input()
    update_scene()
    render_frame()
    swap_buffers()
    limit_framerate()
process_input()

Se procesan las entradas de teclado, mouse y controladores de esta iteración.


init_window(width, height)

while not window_should_close():
    process_input()
    update_scene()
    render_frame()
    swap_buffers()
    limit_framerate()
update_scene()

Se actualiza el estado: posiciones de los objetos, físicas y animaciones.


init_window(width, height)

while not window_should_close():
    process_input()
    update_scene()
    render_frame()
    swap_buffers()
    limit_framerate()
render_frame()

Se dibuja la nueva escena en el framebuffer con el estado ya actualizado.


init_window(width, height)

while not window_should_close():
    process_input()
    update_scene()
    render_frame()
    swap_buffers()
    limit_framerate()
swap_buffers()

Se intercambia el framebuffer actual con el próximo para mostrar el frame sin parpadeos.


init_window(width, height)

while not window_should_close():
    process_input()
    update_scene()
    render_frame()
    swap_buffers()
    limit_framerate()
limit_framerate()

Se limita el framerate con vsync o retrasos manuales para mantenerlo estable.

Inicialización

  • Crear la ventana.
  • Cargar recursos: texturas, modelos, shaders.

Handle Input

El render loop escucha eventos de entrada:

Teclado
Mouse
Joysticks o controladores

Eventos de entrada

KEY_DOWN: tecla presionada.
KEY_UP: tecla liberada.
MOUSE_MOVE: movimiento del cursor.
MOUSE_BUTTON: clics.

Update State

  • Calcula nueva posición de objetos.
  • Aplica físicas y lógica del juego.
  • Controla animaciones basadas en el tiempo transcurrido.

Render Frame

  • Dibuja la escena actualizada en el framebuffer.
  • Limpia la pantalla antes de dibujar para evitar artefactos.
  • Muestra el resultado en la ventana.

Swap Buffers y sincronización

Swap Buffers

Cambia entre el framebuffer actual y el próximo. Evita parpadeos (flickering).

Limitación de FPS

Mantiene un framerate estable con vsync o retrasos manuales.

Presupuesto de frame

Cada frame dispone de un tiempo fijo, que comparten input, update y render.

60 FPS → ~16.67 ms por frame.
30 FPS → ~33.3 ms por frame.
La cuenta

$$t_{frame} = \frac{1000\ \text{ms}}{\text{FPS}}$$Si el trabajo de un frame excede su presupuesto, los FPS bajan y aparece el stutter.

El problema del movimiento por frame

Si mueves con x += 5 por frame, a 120 FPS avanzas el doble que a 60.
El juego corre distinto en cada máquina según sus FPS.
La solución

Escalar el movimiento por delta time: el tiempo transcurrido desde el frame anterior.

Delta time


last_time = now()

while not window_should_close():
    current_time = now()
    dt = current_time - last_time
    last_time = current_time

    x += velocidad * dt
    render_frame()

last_time = now()

while not window_should_close():
    current_time = now()
    dt = current_time - last_time
    last_time = current_time

    x += velocidad * dt
    render_frame()
last_time = now()

Antes del loop guardamos el tiempo inicial como referencia.


last_time = now()

while not window_should_close():
    current_time = now()
    dt = current_time - last_time
    last_time = current_time

    x += velocidad * dt
    render_frame()
current_time = now()

Al inicio de cada frame tomamos el tiempo actual.


last_time = now()

while not window_should_close():
    current_time = now()
    dt = current_time - last_time
    last_time = current_time

    x += velocidad * dt
    render_frame()
dt = current_time - last_time

Delta time es cuánto tiempo pasó desde el frame anterior.


last_time = now()

while not window_should_close():
    current_time = now()
    dt = current_time - last_time
    last_time = current_time

    x += velocidad * dt
    render_frame()
last_time = current_time

Guardamos el tiempo actual para calcular el dt del siguiente frame.


last_time = now()

while not window_should_close():
    current_time = now()
    dt = current_time - last_time
    last_time = current_time

    x += velocidad * dt
    render_frame()
x += velocidad * dt

Al escalar por dt, la velocidad es constante en tiempo real sin importar los FPS.

vsync y límite de FPS

limit_framerate evita que el loop consuma el 100% de CPU y GPU sin control.

vsync

Sincroniza el swap con el refresco del monitor (p. ej. 60 Hz). Elimina el tearing, pero limita los FPS al refresco.

Límite manual

Duerme (sleep) el tiempo sobrante del presupuesto de frame. Da control fino del ritmo, pero no elimina el tearing por sí solo.

Buenas prácticas

Limpia el framebuffer al inicio de cada frame.
Usa delta time para animaciones independientes del FPS.
No cargues archivos dentro del loop: hazlo en inicialización.
Mantén un framerate constante para evitar “stutter”.

En el proyecto de gráficas

El render loop es lo que envuelve el raycaster o raytracer.

process_input: mueve la cámara o el jugador.
update_scene: actualiza posiciones y ángulos con delta time.
render_frame: lanza los rayos y escribe cada píxel en el framebuffer.
swap_buffers + limit_framerate: muestra el frame y mantiene el ritmo.