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 glGenBuffers
e glCreateShader
etc. Esses tipos de retorno GLuint
sã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 GLuint
ao 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
WorldEntity
necessidades para saber oShaderProgram
que precisa - Provavelmente, isso requer algum tipo de classe gulp
EntityManager
que sabe qual instância do queShaderProgram
passar para diferentes entidades.
Portanto, agora, porque existe uma Manager
classe, as classes precisam se registrar EntityManager
junto com a ShaderProgram
instância de que precisam, ou eu preciso de um imbecil switch
no gerenciador que preciso atualizar para cada novo WorldEntity
tipo derivado.
Meu primeiro pensamento foi criar uma ShaderManager
classe (eu sei, os gerentes são ruins) que passo por referência ou ponteiro para as WorldEntity
classes, para que elas possam criar o ShaderProgram
que quiserem, através do ShaderManager
e ShaderManager
possam acompanhar os ShaderProgram
s já existentes , para que possam retorne um que já exista ou crie um novo, se necessário.
(Eu poderia armazenar os ShaderProgram
s através do hash dos nomes de arquivos do ShaderProgram
código fonte real do s)
Então agora:
- Agora estou passando ponteiros para, em
ShaderManager
vez deShaderProgram
, então ainda há muitos parâmetros - Não preciso de um
EntityManager
, as próprias entidades saberão em que instânciaShaderProgram
criar eShaderManager
manipularão osShaderProgram
s reais . - Mas agora não sei quando é
ShaderManager
possível excluir com segurança umShaderProgram
que ele contém.
Então agora eu adicionei uma contagem de referência à minha ShaderProgram
classe que exclui sua GLuint
via interna glDeleteProgram
e acabo com ela ShaderManager
.
Então agora:
- Um objeto pode criar o
ShaderProgram
que precisar - Mas agora existem
ShaderProgram
s duplicados porque não há um gerente externo acompanhando
Finalmente, tomo uma de duas decisões:
1. Classe estática
Um static class
que é chamado para criar ShaderProgram
s. Ele mantém uma faixa interna de ShaderProgram
s com base em um hash dos nomes de arquivos - isso significa que não preciso mais passar ponteiros ou referências a ShaderProgram
s ou ShaderManager
s ao redor, portanto, menos parâmetros - eles WorldEntities
têm todo o conhecimento sobre a instância ShaderProgram
que desejam criar
Este novo static ShaderManager
precisa:
- manter uma contagem do número de vezes que a
ShaderProgram
é usada e euShaderProgram
não faço nenhuma cópia OU ShaderProgram
s contam suas referências e só chamamglDeleteProgram
seu destruidor quando a contagem é0
ANDShaderManager
periodicamente verifica se háShaderProgram
uma 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
glX
função. Então, potencialmente, umWorldEntity
pode ser criado e tente criar umShaderProgram
antes 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
IsContextCreated
que 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
Engine
classe 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
ShaderProgram
s 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 umShaderProgram
foi excluído, mas depois um novoWorldEntity
solicita; mas tenho certeza de que há uma maneira melhor.
2. Um método melhor
É isso que estou pedindo aqui
WorldEntity
s; Isso não está mudando parte do problema? Porque agora a classe WorldFactory precisa passar para cada WolrdEntity o ShaderProgram correto.