Estoy escribiendo un juego en C ++ usando OpenGL.
Para aquellos que no saben, con la API de OpenGL haces muchas llamadas a cosas como glGenBuffers
y glCreateShader
etc. Estos tipos de devolución GLuint
son identificadores únicos de lo que acabas de crear. Lo que se está creando vive en la memoria de la GPU.
Teniendo en cuenta que la memoria GPU a veces es limitada, no desea crear dos cosas que sean iguales cuando sean utilizadas por múltiples objetos.
Por ejemplo, Shaders. Vincula un programa de sombreado y luego tiene un GLuint
. Cuando hayas terminado con el Shader, deberías llamar glDeleteShader
(o algo por el estilo).
Ahora, digamos que tengo una jerarquía de clase superficial como:
class WorldEntity
{
public:
/* ... */
protected:
ShaderProgram* shader;
/* ... */
};
class CarEntity : public WorldEntity
{
/* ... */
};
class PersonEntity: public WorldEntity
{
/* ... */
};
Cualquier código que haya visto requeriría que todos los Constructores hayan ShaderProgram*
pasado para ser almacenados en el WorldEntity
. ShaderProgram
es mi clase que encapsula el enlace de GLuint
a al estado actual del sombreador en el contexto de OpenGL, así como algunas otras cosas útiles que debe hacer con los sombreadores.
El problema que tengo con esto es:
- Hay muchos parámetros necesarios para construir un
WorldEntity
(considere que puede haber una malla, un sombreador, un montón de texturas, etc., todos los cuales podrían compartirse, por lo que se pasan como punteros) - Lo que sea que esté creando las
WorldEntity
necesidades para saber loShaderProgram
que necesita - Esto probablemente requiera algún tipo de clase de trago
EntityManager
que sepa qué instancia de quéShaderProgram
pasar a diferentes entidades.
Así que ahora porque hay una Manager
necesidad de las clases para registrarse EntityManager
con la ShaderProgram
instancia que necesitan, o necesito un gran imbécil switch
en el administrador que necesito actualizar para cada nuevo WorldEntity
tipo derivado.
Mi primer pensamiento fue crear una ShaderManager
clase (lo sé, los gerentes son malos) que paso por referencia o puntero a las WorldEntity
clases para que puedan crear lo ShaderProgram
que quieran, a través de ShaderManager
y ShaderManager
pueden realizar un seguimiento de los ShaderProgram
s ya existentes , para que pueda devuelva uno que ya existe o cree uno nuevo si es necesario.
(Podría almacenar el ShaderProgram
correo electrónico a través del hash de los nombres de archivo del ShaderProgram
código fuente real)
Y ahora:
- Ahora estoy pasando punteros a en
ShaderManager
lugar deShaderProgram
, por lo que todavía hay muchos parámetros - No necesito un
EntityManager
, las entidades mismas sabrán qué instanciaShaderProgram
crear, yShaderManager
manejarán elShaderProgram
s real . - Pero ahora no sé cuándo
ShaderManager
puede eliminar de forma segura unoShaderProgram
que contiene.
Así que ahora he agregado un recuento de referencias a mi ShaderProgram
clase que elimina su GLuint
vía interna glDeleteProgram
y la elimino ShaderManager
.
Y ahora:
- Un objeto puede crear lo
ShaderProgram
que necesita - Pero ahora hay duplicados
ShaderProgram
porque no hay un administrador externo que realice un seguimiento
Finalmente vengo a tomar una de dos decisiones:
1. Clase estática
A static class
que se invoca para crear ShaderProgram
s. Mantiene un seguimiento interno de ShaderProgram
s basado en un hash de los nombres de archivo, esto significa que ya no necesito pasar punteros o referencias a ShaderProgram
s o ShaderManager
s, por lo que menos parámetros: WorldEntities
tienen todo el conocimiento sobre la instancia de ShaderProgram
que quieren crear
Esta nueva static ShaderManager
necesita:
- mantengo un recuento de la cantidad de veces que
ShaderProgram
se usa a y no hagoShaderProgram
copias O ShaderProgram
s cuenta sus referencias y solo llamaglDeleteProgram
a su destructor cuando el recuento es0
YShaderManager
periódicamente buscaShaderProgram
's con un recuento de 1 y los descarta.
Las desventajas de este enfoque que veo son:
Tengo una clase global estática que podría ser un problema. El contexto OpenGL debe crearse antes de invocar cualquier
glX
función. Por lo tanto,WorldEntity
podría crearse un e intentar crear unoShaderProgram
antes de la creación del contexto OpenGL, lo que provocará un bloqueo.La única forma de evitar esto es volver a pasar todo como punteros / referencias, o tener una clase global GLContext que se pueda consultar, o mantener todo en una clase que crea el Contexto en la construcción. O tal vez solo un booleano global
IsContextCreated
que se puede verificar. Pero me preocupa que esto me dé un código feo en todas partes.A lo que puedo ver la devolución es:
- La gran
Engine
clase que tiene todas las demás clases ocultas dentro de ella para que pueda controlar el orden de construcción / deconstrucción adecuadamente. Esto parece un gran lío de código de interfaz entre el usuario del motor y el motor, como un contenedor sobre un contenedor - Toda una serie de clases "Manager" que realizan un seguimiento de las instancias y eliminan cosas cuando es necesario. Esto podría ser un mal necesario?
- La gran
Y
- ¿Cuándo borrar realmente
ShaderProgram
s de lastatic ShaderManager
? ¿Cada pocos minutos? Cada juego de bucle? Estoy manejando con gracia la recompilación de un sombreador en el caso en queShaderProgram
se eliminó un pero luego un nuevo loWorldEntity
solicita; Pero estoy seguro de que hay una mejor manera.
2. Un mejor método
Eso es lo que pido aquí
WorldEntity
s; ¿No es eso cambiar algo del problema? Porque ahora la clase WorldFactory necesita pasar a cada WolrdEntity el ShaderProgram correcto.