jueves, 4 de julio de 2013

Inicialización de OpenGL

Hoy vamos a hacer nuestra primera inicialización de un contexto OpenGL en SDL.
Para ello me he basado en el "Hola Mundo II".
En el archivo de cabecera hemos hecho algunos cambios:
#ifndef DEMO_3_H_
#define DEMO_3_H_
#include "SDL2/SDL.h"

class Demo_3 {
private:
    bool running;
    SDL_Window* window;
    SDL_GLContext ctxt;

    int aux;

    static const uint32_t   WIN_HEIGHT = 512; //px
    static const uint32_t   WIN_WIDTH  = 512; //px
    static const char*      WIN_TITLE; //px
public:

    Demo_3();
    int Execute(){ return OnExecute(); }
    bool Init(){ return OnInit(); }
    void Loop(){ return OnLoop(); }
    void Render(){ return OnRender(); }
    void Cleanup(){ return OnCleanup(); }
    void Event(SDL_Event* Event){ OnEvent(Event); }



    int OnExecute();

    bool OnInit();
    void OnEvent(SDL_Event* Event);
    void OnLoop();
    void OnRender();
    void OnCleanup();



    void setupOpenGl();

};


#endif /* DEMO_3_H_ */
Hemos añadido dos variables, una es el contexto de OpenGL (SDL_GLContext) que es el lugar donde se va a dibujar, y aux, que es una variable dummy para poder realizar una pequeña interactuación con el entorno.
Al final del todo he añadido una función llamada: setupOpenGl que inicializa todo lo necesario para utilizar el contexto.
El header como siempre tiene poco que explicar, vayámonos al .cpp
#include "Demo_3.h"
#include "GL/gl.h"
#include <iostream>

const char* Demo_3::WIN_TITLE = "Titulo de la Ventana";

Demo_3::Demo_3() : running(false), window(NULL), ctxt(NULL), aux(0){}

void Demo_3::OnEvent(SDL_Event* event) {
    switch (event->type) {
        case SDL_KEYUP:
            switch(event->key.keysym.sym){
                case SDLK_KP_PLUS:
                    aux++;
                    aux %= 3;
                    break;
                case SDLK_v:
                    std::cout << glGetString(GL_VERSION) << std::endl;
                    break;
                case SDLK_ESCAPE:
                    running = false;
                    break;
                default:
                    break;
            }
            break;
        case SDL_QUIT:
            running = false;
            break;
        default:
            break;
    }
}
void Demo_3::OnLoop() {}
void Demo_3::OnRender() {

    glClearColor(aux==0? 1:0, aux==1? 1:0, aux==2? 1:0, 1.0);
    glClear( GL_COLOR_BUFFER_BIT);
    SDL_GL_SwapWindow(window);
}

void Demo_3::OnCleanup() {
    SDL_GL_DeleteContext(ctxt);
    SDL_DestroyWindow(window);
    SDL_Quit();
}

bool Demo_3::OnInit() {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
        return false;

    window = SDL_CreateWindow(WIN_TITLE,
            SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            WIN_WIDTH, WIN_HEIGHT,
            SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
    if(!window)
        return false;

    setupOpenGl();

    running = true;

    return true;
}

int Demo_3::OnExecute() {
    if (!Init())
        return -1;

    SDL_Event event;

    while (running) {

        while (SDL_PollEvent(&event))
            Event(&event);

        Loop();
        Render();
    }

    Cleanup();

    return 0;
}


void Demo_3::setupOpenGl(){
   //SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
   //SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
   SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
   SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 32);
   ctxt = SDL_GL_CreateContext(window);

   //vsync ON
   SDL_GL_SetSwapInterval(1);

}
Empecemos por OnInit, como siempre inicializa SDL y crea la ventana, pero si habéis estado atentos hay un flag más: SDL_WINDOW_OPENGL, esto le dice a la ventana que se prepare para el entorno OpenGL, el segundo cambio es la llamada a setupOpenGl.
setupOpenGl configura los atributos del entorno OpenGL, crea el contexto y activa la sincronización vertical, vayamos linea por linea:
  • Las dos primeras lineas que están comentadas especifican que versión del contexto OpenGL deben utilizar, en este caso estarían indicando la versión 3.2. Si omites esas dos líneas crea el contexto con la versión más reciente utilizada en el ordenador, en mi caso en la actualidad 4.3.0 que es el entorno que vamos a aprender a usar.
  • La siguiente línea activa el "Doble Buffer", si no se activa, cuando tengamos objetos dibujados en el entorno, la imagen parpadeara. Esto es debido a que OpenGL utilizaría el mismo buffer para escribir que para mandar la imagen a la pantalla. Al utilizar Doble Buffer el ordenador utilizaría un buffer para escribir y otro para mandar la imagen a la pantalla, en el siguiente ciclo, los buffer se intercambiarían, el de la pantalla pasaría a rellenarse y el relleno pasaría a la pantalla.
    En los juegos siempre sale la opción: triple buffer!!. Informándome por Internet he leído que el triple buffer en OpenGL no existe por linea de comandos, pero existe un triple buffer simulado. El problema de esto es que NVidia (e imagino que ATI tendrá algo parecido) por lo visto tiene una opción para activar el triple buffer, con lo cual con esa simulación estaríamos haciendo un cuadrabuffer lo que reventaría definitivamente nuestra aplicación. Por lo tanto nos quedamos con nuestro doble buffer. Como nota curiosa dicen que un buffer ocupa unos 25-30Mb en memoria, lo que supone relativamente poco para las actuales GPUs ya que, las peores tienen aproximadamente 512Mb. La noticia donde lo leí tenia su tiempo, quizá no tenían en cuenta la resolución HD720p ni HD1080p, o quizá si.
  • La cuarta linea configura si no me equivoco lo que se denomina Z-Buffer. El Z-Buffer es el buffer de profundidad. Cuando toda la linea de ejecución de la tarjeta gráfica se ejecuta, se comprueba en que posición de profundidad se haya ese píxel y si había uno con mas profundidad se sustituye por el nuevo. Pensemos en una mesa en una medio de una habitación, tenemos el buffer que contiene el suelo renderizado, cuando renderice la mesa decidirá, que la mesa está por encima del suelo y, que se tiene que ver. Sin embargo si tomamos cierta perspectiva, una de las cuatro patas de la mesa no debería verse o debería verse parcialmente, aún pudiéndose dibujar después. Si el z-buffer no estuviera activado, cada cosa se dibujaría en el orden de dibujado uno encima de otro no permitiendo entrelazamiento de capas. Por ejemplo, un pañuelo enrollado a un palo en forma de espiral: verías el palo y un pañuelo detrás o un pañuelo tapando un palo, nunca un palo con un pañuelo enrollado.
  • Volviendo a la línea, aumentamos el buffer, de 16bit (que es el actual "por defecto") a 32bit.
  • En la siguiente linea le pedimos a SDL que nos cree un entorno OpenGL en la ventana proporcionada, lo enlace y nos pase una referencia al contexto.
  • Por último activamos la sincronización vertical para evitar el Screen Tearing. Pincha en el enlace para más información.
En la función de inicialización no queda nada que comentar, lo siguiente es la gestión de eventos, he añadido un evento a la letra v para ver la versión que nuestro ordenador ha elegido de OpenGL. También le he añadido un evento a la tecla "+" del pad numérico, para cambiar el color del fondo de nuestra ventana mediante la variable aux.
Lo siguiente es la función Render, que hace tres llamadas:
  1. glClearColor(aux==0? 1:0, aux==1? 1:0, aux==2? 1:0, 1.0); Le dice con que color tiene que rellenar el buffer del contexto OpenGL cuando se llame a glClear. He utilizado aux para seleccionar rojo, verde y azul. Para ahorrar lineas he utilizado el operador ternario, o un "if rápido" como yo lo llamo, que para los que no lo conozcan: la condición se coloca antes del interrogante y después se coloca la instrucción si se cumple, y despues de los dos puntos, la instrucción si no se cumple. El último valor es Alpha o transparencia que colocamos a 1 (significa opaco, 0 = transparente, para el resto de números < 1 y >0 sería translúcido.).
    Para los que tengáis experiencia de diseño en otros entornos y lenguajes como puede ser CSS en html, os resultará extraño que los valores oscilen entre 0.0 y 1.0 en lugar de 0 y 255, pero es así xD.
  2. glClear( GL_COLOR_BUFFER_BIT); Le indica que debe limpiar un buffer, la constante indica el buffer de dibujo, veremos que hay más buffers que se pueden limpiar.
  3. SDL_GL_SwapWindow(window); Esto intercambia los buffers como hemos explicado antes.
Por último pero no menos importante tenemos el cleanup al que hemos añadido una línea que destruye el contexto OpenGL.
Muy importante que no se nos olvide nunca limpiar todo porque puede quedar memoria residual en el ordenador. A nadie le gusta que después de abrir unos cuantos programas tengas que reiniciar porque tu pobre RAM está llena de mierda.

No hay comentarios:

Publicar un comentario