2011-08-28 15 views
5

Desde que comencé a aprender OpenGL, pensé que sería mejor escribir un pequeño framework C++ (para mí) para evitar las náuseas que el excesivo uso del código C-ish aparentemente está causando. :) Como tengo la intención de seguir con Qt, el framework usa algunas clases de Qt.Diseñando una clase shader

Lo primero que realmente necesitaba era una forma fácil de usar sombreadores y programas. Aquí está mi idea de la clase shader.

class Shader 
{ 
public: 
    //create a shader with no source code 
    explicit Shader(GLenum shaderType); 
    //create a shader with source code and compile it 
    Shader(GLenum shaderType, const QString& sourceCode); 
    //create a shader from source file and compile it 
    Shader(GLenum shaderType, QFile& sourceFile); 
    ~Shader(); 

    //change the source code and recompile 
    void Source(QFile& sourceFile); 
    void Source(const QString& sourceCode); 

    GLuint get() const; //get the handle 

private: 
    //common part for creation in different constructors 
    void createShader(GLenum shaderType); 
    //compile 
    void compile(); 

private: 
    GLuint handle; 
}; 

Debe ser bastante obvio lo que están haciendo las diferentes funciones. Cada uno llama a las rutinas relevantes de OpenGL, comprueba si hay errores y arroja excepciones en caso de falla. El constructor llama al glCreateShader. Ahora la parte difícil. El destructor necesita llamar al glDeleteShader(handle); pero en este caso tengo un dilema:

Opción 1: Desactivar asignación y copia. Esto tiene el lado positivo de evitar el recuento de referencias y el inconveniente de verse obligado a usar shared_pointers para ponerlos en vectores y circular en general.

Opción 2: Habilita el recuento de referencias. Esto tiene el lado obvio de habilitar la copia y, por lo tanto, almacenarla en contenedores (que necesitaré para luego pasar una gama de sombreadores a un programa). La desventaja es la siguiente:

Shader s1(GL_VERTEX_SHADER, QFile("MyVertexShader.vp")); 
Shader s2(s1); 
s2.Source(QFile("MyOtherVertexShader.vp")); 

Como se puede ver, he cambiado la fuente de s1 a través s2, ya que comparten el mismo identificador de sombreado interno. Para ser honesto, no veo un gran problema aquí. Escribí la clase, así que sé que su semántica de copia es así y estoy de acuerdo. El problema es que no estoy seguro de que este tipo de diseño sea aceptable. Todo esto se puede lograr con los punteros compartidos Opción1 +, con la única diferencia de que no quiero tener un puntero compartido cada vez que creo un sombreador (no por razones de rendimiento, solo por conveniencia sintáctica).

Q1: Comente las opciones y, opcionalmente, toda la idea.
P2: Si tuviera que elegir la opción 2, tengo que aplicar a mí mismo o hay una clase de lista en aumento o Qt que podría derivar de o tener un miembro y me gustaría obtener una conteo de referencia gratis?
P3: ¿Está de acuerdo que hacer Shader una clase abstracta y tener tres clases derivadas VertexShaderFragmentShader, y GeometryShader sería una exageración?

Si me debe hacer referencia a un marco de OpenGL existentes C++, eso es muy bueno (ya que no he encontrado realmente uno), pero que realmente debería haber una nota al margen de una respuesta a mis preguntas . También tenga en cuenta que he visto una clase QGLShader en algún lugar de los documentos, pero aparentemente no está presente en mi versión de Qt y tengo mis razones para evitar la actualización en este momento.

ACTUALIZACIÓN

Gracias por las respuestas. Finalmente decidí hacer inmutable mi clase de sombreado eliminando las funciones de origen. El sombreador se compila en la creación y no tiene funciones miembro no const. Por lo tanto, un simple conteo de referencias resuelve todos mis problemas a la vez.

+2

Para obtener un marco C++ OpenGL, puede consultar (¿la?) Biblioteca de visualización. Ofrece un montón de cosas, desde los conceptos básicos hasta un algoritmo de geometría de cubos de marcha. Gran biblioteca, algo compleja pero muy completa. http://www.visualizationlibrary.org – ssube

+0

@peachykeen: Estoy mirando a través de los documentos: ¡se ve impresionante! ¡Muchas gracias! –

+0

El autor también es extremadamente receptivo y útil. Hay algunas rarezas, en particular con ventanas, pero si encuentra un error y lo informa, generalmente se soluciona dentro de la semana (en mi experiencia). – ssube

Respuesta

3

Digo la opción de uso 1: puede hacer todo, la opción 2 puede (a través de punteros inteligentes), mientras que la opción 2 le hace pagar el costo indirecto incluso cuando no lo necesita. Además de eso, es posiblemente más fácil de escribir.

De manera similar, una vez consideré usar handle-body/PIMPL al envolver una API C, para permitir el retorno de objetos desde las funciones (no se garantizaba que el tipo de manejador C se pudiera copiar, por lo que era necesario el direccionamiento indirecto). Decidí no hacerlo ya que std::unique_ptr<T> hace que la transformación móvil no móvil (como shared_ptr<T> haga que T se pueda copiar). Desde entonces, diseño mis clases para tener la semántica de movimiento/copia más ajustada.

¡Sin embargo, tiene un punto en lo que se refiere al ruido sintáctico! Cosas como Boost.Phoenix y lambdas tienden a ayudar. Si/cuando no son una opción, yo diría que escribir por separadoshared_shader o cualquier envoltorio (envoltorio de envoltura?) Tiene sentido, al menos para el código de nivel de biblioteca (que creo que es el caso aquí). No conozco ninguna utilidad para ayudar con la tediosidad de escribir las funciones de reenvío.

Tampoco sé mucho cuando se trata de shaders, así que no estoy seguro de poder responder a su última pregunta. Creo que hacer una jerarquía de clases tendría sentido si la cantidad de sombreadores diferentes pudiera cambiar con frecuencia. No creo que ese sea el caso; También creo que, incluso si ese fuera el caso, ya que la implementación en su nivel está envolviendo una API preexistente no es demasiado complicado revisar el código para reenviar a esa API si/cuando se agrega un nuevo sombreador.


Ya que está pidiendo un ejemplo de la amabilidad de Phoenix.

Lo que yo quiero hacer suponiendo que no tengo que eliminar la referencia:

std::transform(begin, end, functor); 

lugar:

std::for_each(begin, end, *arg1 = ref(functor)(*arg1)); 

Es posible utilizar todavía std::transform (deseable para mayor claridad) usando algunas instalaciones Phoenix (construct IIRC) pero eso costaría una asignación.

+0

@Armen Eso es correcto. La opción 1 + 2 de proporcionar ambos es la más poderosa (sabia en clave de cliente) pero la más costosa de implementar (en cuanto a código de biblioteca). Se agregó un ejemplo de Phoenix. –

3

Ya he evaluado estas opciones, y he implementado una clase de sombreado de una manera diferente.

El primer punto es que CreateShader y DeleteShader necesitan un contexto actual, lo que no siempre es cierto. Todas las funciones devuelven errores y la última puede causar fugas. Entonces, presentaré una rutina de Crear y Eliminar que realmente llama CreateShader y Delete Shader. De esta forma, es posible destruir un objeto incluso en un hilo separado (el sombreador se destruirá más tarde cuando el contexto sea actual.

El segundo punto es que el objeto sombreador, una vez vinculado a un sombreador programa, puede volver a vincularse en otro programa de sombreado, sin recompilar (excepto en el caso de que la fuente dependa de los símbolos del preprocesador). Por lo tanto, recolectaría una colección de objetos de sombreado utilizados por commoly para reutilizarlos durante las creaciones del programa

El último punto es que la asignación de la clase ShaderObject está bien, siempre y cuando no se filtre un objeto shader creado. En el caso de la fuente, creo que las dos opciones: la fuente se puede cambiar y el sombreador no es válido o el sombreador se convierte sucio y de hecho requiere una compilación.

Como se puede compilar una fuente de sombreado para diferentes etapas de sombreado, sugeriría evitar las derivaciones de Vértice, Fragmento, etc.Opcionalmente, puede tener un valor predeterminado y establecerlo antes de la creación. Por supuesto, es posible al definir el método de creación.

Otro punto es que no es necesario que exista un objeto de sombreado una vez que el programa está vinculado. Por lo tanto, en combinación con una base de datos de sombreador precompilada común, el recuento de referencias solo lo utilizan los programas de sombreadores (especialmente los no vinculados) para indicar que necesitan ese objeto de sombreado para vincular. Siempre en este caso, debe haber una base de datos de programa de sombreado, para evitar creaciones de programa redundantes; en este escenario, la asignación y la copia se convierten en una operación muy rara, que evitaría exponer; en su lugar, defina un método amigo y úselo en su marco.