Estou escrevendo um jogo em C ++ usando o OpenGL.
Para quem não sabe, com a API do OpenGL, você faz muitas chamadas para coisas como glGenBufferse glCreateShaderetc. Esses tipos de retorno GLuintsão identificadores exclusivos do que você acabou de criar. A coisa que está sendo criada fica na memória da GPU.
Considerando que a memória da GPU às vezes é limitada, você não deseja criar duas coisas iguais quando forem usadas por vários objetos.
Por exemplo, Shaders. Você vincula um programa Shader e, em seguida, possui um GLuint. Quando terminar o Shader, você deve ligar glDeleteShader(ou algo a esse respeito).
Agora, digamos que eu tenha uma hierarquia de classes superficial como:
class WorldEntity
{
public:
/* ... */
protected:
ShaderProgram* shader;
/* ... */
};
class CarEntity : public WorldEntity
{
/* ... */
};
class PersonEntity: public WorldEntity
{
/* ... */
};
Qualquer código que eu já vi exigiria que todos os Construtores ShaderProgram*passassem para ele para serem armazenados no WorldEntity. ShaderProgramé minha classe que encapsula a ligação de a GLuintao estado atual do shader no contexto OpenGL, além de algumas outras coisas úteis que você precisa fazer com o Shaders.
O problema que tenho com isso é:
- Existem muitos parâmetros necessários para construir um
WorldEntity(considere que pode haver uma malha, um sombreador, um monte de texturas etc., todos os quais podem ser compartilhados, para que sejam passados como ponteiros) - O que quer que esteja criando as
WorldEntitynecessidades para saber oShaderProgramque precisa - Provavelmente, isso requer algum tipo de classe gulp
EntityManagerque sabe qual instância do queShaderProgrampassar para diferentes entidades.
Portanto, agora, porque existe uma Managerclasse, as classes precisam se registrar EntityManagerjunto com a ShaderPrograminstância de que precisam, ou eu preciso de um imbecil switchno gerenciador que preciso atualizar para cada novo WorldEntitytipo derivado.
Meu primeiro pensamento foi criar uma ShaderManagerclasse (eu sei, os gerentes são ruins) que passo por referência ou ponteiro para as WorldEntityclasses, para que elas possam criar o ShaderProgramque quiserem, através do ShaderManagere ShaderManagerpossam acompanhar os ShaderPrograms já existentes , para que possam retorne um que já exista ou crie um novo, se necessário.
(Eu poderia armazenar os ShaderPrograms através do hash dos nomes de arquivos do ShaderProgramcódigo fonte real do s)
Então agora:
- Agora estou passando ponteiros para, em
ShaderManagervez deShaderProgram, então ainda há muitos parâmetros - Não preciso de um
EntityManager, as próprias entidades saberão em que instânciaShaderProgramcriar eShaderManagermanipularão osShaderPrograms reais . - Mas agora não sei quando é
ShaderManagerpossível excluir com segurança umShaderProgramque ele contém.
Então agora eu adicionei uma contagem de referência à minha ShaderProgramclasse que exclui sua GLuintvia interna glDeletePrograme acabo com ela ShaderManager.
Então agora:
- Um objeto pode criar o
ShaderProgramque precisar - Mas agora existem
ShaderPrograms duplicados porque não há um gerente externo acompanhando
Finalmente, tomo uma de duas decisões:
1. Classe estática
Um static classque é chamado para criar ShaderPrograms. Ele mantém uma faixa interna de ShaderPrograms com base em um hash dos nomes de arquivos - isso significa que não preciso mais passar ponteiros ou referências a ShaderPrograms ou ShaderManagers ao redor, portanto, menos parâmetros - eles WorldEntitiestêm todo o conhecimento sobre a instância ShaderProgramque desejam criar
Este novo static ShaderManagerprecisa:
- manter uma contagem do número de vezes que a
ShaderProgramé usada e euShaderProgramnão faço nenhuma cópia OU ShaderPrograms contam suas referências e só chamamglDeleteProgramseu destruidor quando a contagem é0ANDShaderManagerperiodicamente verifica se háShaderProgramuma contagem de 1 e as descarta.
As desvantagens dessa abordagem que vejo são:
Eu tenho classe estática global que pode ser um problema. O contexto do OpenGL precisa ser criado antes da chamada de qualquer
glXfunção. Então, potencialmente, umWorldEntitypode ser criado e tente criar umShaderProgramantes da criação do OpenGL Context, o que resultará em uma falha.A única maneira de contornar isso é repassar tudo ao redor como ponteiros / referências, ou ter uma classe GLContext global que pode ser consultada ou manter tudo em uma classe que cria o Contexto na construção. Ou talvez apenas um booleano global
IsContextCreatedque possa ser verificado. Mas eu me preocupo que isso me dê um código feio em todo lugar.O que eu posso ver é:
- A grande
Engineclasse que tem todas as outras classes ocultas dentro dela para poder controlar a ordem de construção / desconstrução de maneira apropriada. Parece uma grande confusão de código de interface entre o usuário e o mecanismo, como um invólucro sobre um invólucro - Uma série de classes "Manager" que controlam instâncias e excluem coisas quando necessário. Isso pode ser um mal necessário?
- A grande
E
- Quando realmente limpar
ShaderPrograms fora dostatic ShaderManager? A cada poucos minutos? Cada loop de jogo? Estou lidando graciosamente com a recompilação de um sombreador no caso em que umShaderProgramfoi excluído, mas depois um novoWorldEntitysolicita; mas tenho certeza de que há uma maneira melhor.
2. Um método melhor
É isso que estou pedindo aqui
WorldEntitys; Isso não está mudando parte do problema? Porque agora a classe WorldFactory precisa passar para cada WolrdEntity o ShaderProgram correto.