en el tiempo que llevo aprendiendo opengl habré empezado al menos 20 renderers. algunos fueron terminados, otros fueron usados para otro proyecto y algunos fueron abandonados en mi carpeta de proyectos para no ser vistos nunca más.
gracias a eso y a una breve experiencia laboral en la que usaba opengl, conseguí crear una linea general de reglas que me gusta seguir para hacer la base del renderer más ordenada y segura en terminos de memoria. sigue siendo muy amateur pero a mí me funciona bastante bien para mis casos de uso. procederé a explicar más a detalle esto.
la idea general es separar completamente windowing de opengl.
Window se encarga únicamente de estado y plataforma:
no tiene ninguna dependencia directa con opengl.
por otro lado, GLObject existe como base para cualquier recurso de opengl. su responsabilidad principal es garantizar que:
también es un buen lugar para centralizar cosas como asserts, logging o validaciones.
esto evita errores comunes como crear buffers o shaders antes de tener un contexto válido.
a partir de GLObject, construyo wrappers chicos y explícitos:
cada wrapper:
por ejemplo, Buffer solo:
no intento convertirlo en una estructura genérica o “inteligente” en ningún caso.
uso RAII para manejar recursos de opengl. entiendo que hay otras manera de manejar recursos que pueden ser más beneficiosas cuando de opengl se trata, pero desconozco cuales sean.
cada objeto:
esto hace que el ciclo de vida sea explícito y reduce leaks.
también evito copia de objetos (= delete) y permito move semantics cuando tiene sentido.
reglas simples que sigo:
esto es especialmente importante con VAOs, ya que dependen del estado de buffers externos. de todas maneras siempre me encuentro con problemas relacionados a VAOs por alguna razón. son magia arcana.
mantengo un orden estricto:
romper este orden suele generar errores silenciosos o crashes difíciles de debuggear.
el loop principal es intencionalmente simple:
el renderer recibe datos ya preparados (mallas, transformaciones, etc). no mezclo lógica de aplicación con llamadas de opengl.
intento minimizar el estado global implícito de opengl.
algunas decisiones:
generalmente en mi código promedio haya millones de .Bind() y .Unbind(), esto hace el código más predecible, aunque un poco más verboso.
todavía hay varias cosas que no están bien resueltas:
para proyectos chicos esto es suficiente, pero no escala bien todavía.
no es un diseño “correcto”, es simplemente el resultado de iterar varias veces sobre el mismo problema.
lo que más valor me dio:
seguramente tenga limitaciones, pero como base para proyectos personales funciona bien. si algún lector tiene alguna opinión o crítica me encantaría escucharla.
+------------------+
| main() |
+--------+---------+
|
v
+------------------+
| Window |
| (no OpenGL) |
+--------+---------+
|
v
+------------------+
| GL Context |
| (GLFW / SDL) |
+--------+---------+
|
v
+------------------+
| GLObject::init |
| (GLAD load) |
+--------+---------+
|
v
+--------------------------+
| GLObject (base) |
+-----------+--------------+
|
+--------+--------+
| | |
v v v
Buffer Shader Texture
| | |
+--------+--------+
|
v
+---------+
| Renderer|
+----+----+
|
v
draw calls