Buscar..


Observaciones

OpenGL es un estándar abierto para renderizar gráficos en 2D y 3D aprovechando el hardware de gráficos. OpenGL se ha implementado en una impresionante variedad de plataformas que permiten que las aplicaciones dirigidas a OpenGL sean extremadamente flexibles.

Versiones

Versión Fecha de lanzamiento
1.1 1997-03-04
1.2 1998-03-16
1.2.1 1998-10-14
1.3 2001-08-14
1.4 2002-07-24
1.5 2003-07-29
2.0 2004-09-07
2.1 2006-07-02
3.0 2008-08-11
3.1 2009-03-24
3.2 2009-08-03
3.3 2010-03-11
4.0 2010-03-11
4.1 2010-07-26
4.2 2011-08-08
4.3 2012-08-06
4.4 2013-07-22
4.5 2014-08-11

Obtención de OpenGL

Una de las ideas erróneas más comunes acerca de OpenGL es que era una biblioteca que podía instalarse desde fuentes de terceros. Este concepto erróneo lleva a muchas preguntas en la forma "cómo instalar OpenGL" o "dónde descargar el SDK de OpenGL".

Así no es como OpenGL encuentra el camino en el sistema informático. OpenGL en sí mismo es simplemente un conjunto de especificaciones sobre qué comandos debe seguir una implementación. Así que lo que importa es la implementación. Y por el momento, las implementaciones de OpenGL son parte de los controladores de GPU. Esto podría cambiar en el futuro, cuando la nueva interfaz de programación de GPU permita implementar verdaderamente OpenGL como una biblioteca, pero por ahora es una API de programación para los controladores de gráficos.

Cuando OpenGL se lanzó por primera vez, la API encontró su camino en el contrato ABI (Interfaz Binario de la Aplicación) de Windows, Solaris y Linux (Escritorio LSB-4), además de su origen, Sun Irix. Apple siguió y, de hecho, integró OpenGL tan profundamente en MacOS X, que la versión de OpenGL disponible está estrechamente unida a la versión de MacOS X instalada. Esto tiene el efecto notable de que los entornos de programación del sistema para estos sistemas operativos (es decir, la cadena de herramientas del compilador y el enlazador que se orienta de forma nativa a estos sistemas) también deben proporcionar definiciones de API OpenGL. Por lo tanto, no es necesario instalar realmente un SDK para OpenGL. Es técnicamente posible programar OpenGL en estos sistemas operativos sin el requisito de instalar un SDK dedicado, suponiendo que se instale un entorno de compilación después de la ABI objetivo.

Un efecto secundario de estas estrictas reglas ABI es que la versión OpenGL expuesta a través de la interfaz de enlace es el denominador común más bajo que los programas que se ejecutan en la plataforma de destino pueden esperar que estén disponibles. Por lo tanto, se puede acceder a las funciones modernas de OpenGL a través del mecanismo de extensión, que se describe en profundidad por separado.

Linux

En Linux es bastante común compartimentar los paquetes de desarrollo para diferentes aspectos del sistema, para que estos puedan actualizarse individualmente. En la mayoría de las distribuciones de Linux, los archivos de desarrollo para OpenGL están contenidos en un paquete dedicado, que suele ser una dependencia para un meta-paquete de desarrollo de aplicaciones de escritorio. Por lo tanto, la instalación de los archivos de desarrollo de OpenGL para Linux generalmente se realiza con la instalación del / los paquetes de desarrollo de escritorio. *

Microsoft Windows

La biblioteca de enlace de API opengl32.dll (llamada así para las versiones de Windows de 32 y 64 bits) se envía de forma predeterminada con todas las versiones de Windows desde Windows NT-4 y Windows 95B (ambas hacia 1997). Sin embargo, esta DLL no proporciona una implementación real de OpenGL (aparte de un respaldo de software cuyo único propósito es actuar como una red de seguridad para los programas si no se instala otra implementación de OpenGL). Este DLL pertenece a Windows y no debe modificarse ni moverse. Las versiones modernas de OpenGL se envían como parte del llamado Controlador de cliente instalable (ICD) y se accede a ellas a través del opengl32.dll predeterminado que viene preinstalado con cada versión de Windows. Microsoft decidió, sin embargo, que los controladores de gráficos instalados a través de Windows Update no instalarían / ​​actualizarían un ICD OpenGL. Como tales, las nuevas instalaciones de Windows con controladores instalados automáticamente carecen de soporte para las características modernas de OpenGL. Para obtener un ICD OpenGL con características modernas, los controladores de gráficos deben descargarse directamente desde el sitio web del proveedor de la GPU e instalarse manualmente.

En cuanto al desarrollo, no se deben tomar medidas adicionales per se. Todos los compiladores de C / C ++ que siguen las especificaciones de Windows ABI se envían con encabezados y el código auxiliar del vinculador (opengl32.lib) necesarios para compilar y vincular los ejecutables que utilizan OpenGL.

Configuración manual de OpenGL en Windows

Código de ejemplo completo incluido al final

Componentes de Windows para OpenGL

WGL

WGL (se puede pronunciar meneo ) significa "Windows-GL", como en "una interfaz entre Windows y OpenGL", un conjunto de funciones de la API de Windows para comunicarse con OpenGL. Las funciones WGL tienen un prefijo wgl y sus tokens tienen un prefijo WGL_ .

La versión predeterminada de OpenGL compatible con los sistemas de Microsoft es 1.1. Esa es una versión muy antigua (la más reciente es 4.5). La forma de obtener las versiones más recientes es actualizar sus controladores de gráficos, pero su tarjeta gráfica debe ser compatible con esas nuevas versiones.

La lista completa de funciones WGL se puede encontrar aquí .

Interfaz de dispositivo gráfico (GDI)

GDI (actualizado hoy a GDI +) es una interfaz de dibujo en 2D que le permite dibujar en una ventana en Windows. Necesita GDI para inicializar OpenGL y permitir que interactúe con él (pero en realidad no utilizará GDI).

En GDI, cada ventana tiene un contexto de dispositivo (DC) que se utiliza para identificar el destino del dibujo al llamar a las funciones (se pasa como parámetro). Sin embargo, OpenGL utiliza su propio contexto de representación (RC) . Por lo tanto, DC se utilizará para crear RC.


Configuración básica

Creando una ventana

Entonces, para hacer cosas en OpenGL, necesitamos RC, y para obtener RC, necesitamos DC, y para obtener DC necesitamos una ventana. Crear una ventana usando la API de Windows requiere varios pasos. Esta es una rutina básica, por lo que para una explicación más detallada, debe consultar otra documentación, ya que no se trata de usar la API de Windows.

Esta es una configuración de Windows, por lo que se debe incluir Windows.h , y el punto de entrada del programa debe ser el procedimiento WinMain con sus parámetros. El programa también debe estar vinculado a opengl32.dll y a gdi32.dll (independientemente de si está en un sistema de 64 o 32 bits).

Primero necesitamos describir nuestra ventana usando la estructura WNDCLASS . Contiene información sobre la ventana que queremos crear:

/* REGISTER WINDOW */
WNDCLASS window_class;

// Clear all structure fields to zero first
ZeroMemory(&window_class, sizeof(window_class));

// Define fields we need (others will be zero)
window_class.style = CS_OWNDC;
window_class.lpfnWndProc = window_procedure; // To be introduced later
window_class.hInstance = instance_handle;
window_class.lpszClassName = TEXT("OPENGL_WINDOW");

// Give our class to Windows
RegisterClass(&window_class);
/* *************** */

Para una explicación precisa del significado de cada campo (y para una lista completa de campos), consulte la documentación de MSDN.

Luego, podemos crear una ventana usando CreateWindowEx . Una vez creada la ventana, podemos adquirir su DC:

/* CREATE WINDOW */
HWND window_handle = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
                                    TEXT("OPENGL_WINDOW"),
                                    TEXT("OpenGL window"),
                                    WS_OVERLAPPEDWINDOW,
                                    0, 0,
                                    800, 600,
                                    NULL,
                                    NULL,
                                    instance_handle,
                                    NULL);

HDC dc = GetDC(window_handle);

ShowWindow(window_handle, SW_SHOW);
/* ************* */

Finalmente, necesitamos crear un bucle de mensajes que reciba eventos de ventana desde el SO:

/* EVENT PUMP */
MSG msg;

while (true) {
    if (PeekMessage(&msg, window_handle, 0, 0, PM_REMOVE)) {
        if (msg.message == WM_QUIT)
            break;
        
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    
    // draw(); <- there goes your drawing

    SwapBuffers(dc); // To be mentioned later
}
/* ********** */

Formato pixel

OpenGL necesita conocer cierta información sobre nuestra ventana, como el bitness de color, el método de almacenamiento en búfer, etc. Para ello, utilizamos un formato de píxel . Sin embargo, solo podemos sugerir al sistema operativo qué tipo de formato de píxel necesitamos, y el sistema operativo proporcionará el más compatible , no tenemos control directo sobre él. Es por eso que solo se llama descriptor .

/* PIXEL FORMAT */
PIXELFORMATDESCRIPTOR descriptor;

// Clear all structure fields to zero first
ZeroMemory(&descriptor, sizeof(descriptor));

// Describe our pixel format
descriptor.nSize = sizeof(descriptor);
descriptor.nVersion = 1;
descriptor.dwFlags = PFD_DRAW_TO_WINDOW | PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL | PFD_GENERIC_ACCELERATED | PFD_DOUBLEBUFFER | PFD_SWAP_LAYER_BUFFERS;
descriptor.iPixelType = PFD_TYPE_RGBA;
descriptor.cColorBits = 32;
descriptor.cRedBits = 8;
descriptor.cGreenBits = 8;
descriptor.cBlueBits = 8;
descriptor.cAlphaBits = 8;
descriptor.cDepthBits = 32;
descriptor.cStencilBits = 8;

// Ask for a similar supported format and set it
int pixel_format = ChoosePixelFormat(dc, &descriptor);
SetPixelFormat(dc, pixel_format, &descriptor);
/* *********************** */

Hemos habilitado el búfer doble en el campo dwFlags , por lo que debemos llamar a SwapBuffers para ver las cosas después del dibujo.

Contexto de representación

Después de eso, simplemente podemos crear nuestro contexto de representación:

/* RENDERING CONTEXT */
HGLRC rc = wglCreateContext(dc);
wglMakeCurrent(dc, rc);
/* ***************** */

Tenga en cuenta que solo un hilo puede usar el RC a la vez. Si desea usarlo desde otro hilo más tarde, debe llamar a wglMakeCurrent allí para activarlo nuevamente (esto lo desactivará en el hilo que está actualmente activo, y así sucesivamente).

Obteniendo funciones OpenGL

Las funciones de OpenGL se obtienen mediante el uso de punteros de función. El procedimiento general es:

  1. De alguna manera obtener tipos de punteros de función (esencialmente los prototipos de función)
  2. Declare cada función que nos gustaría usar (con su tipo de puntero de función)
  3. Obtener la función real

Por ejemplo, considere glBegin:

// We need to somehow find something that contains something like this,
// as we can't know all the OpenGL function prototypes
typedef void (APIENTRY *PFNGLBEGINPROC)(GLenum);

// After that, we need to declare the function in order to use it
PFNGLBEGINPROC glBegin;

// And finally, we need to somehow make it an actual function

("PFN" significa "puntero a función", luego sigue el nombre de una función OpenGL, y "PROC" al final - ese es el nombre habitual del tipo de puntero de función OpenGL).

Así es como se hace en Windows. Como se mencionó anteriormente, Microsoft solo ofrece OpenGL 1.1. Primero, los tipos de punteros de función para esa versión se pueden encontrar incluyendo GL/gl.h Después de eso, declaramos todas las funciones que pretendemos usar como se muestra arriba (hacer eso en un archivo de encabezado y declararlas "externas" nos permitiría usarlas todas después de cargarlas una vez, solo incluyéndolas). Finalmente, la carga de las funciones de OpenGL 1.1 se realiza abriendo la DLL:

HMODULE gl_module = LoadLibrary(TEXT("opengl32.dll"));

/* Load all the functions here */
glBegin = (PFNGLBEGINPROC)GetProcAddress("glBegin");
// ...
/* *************************** */

FreeLibrary(gl_module);

Sin embargo, probablemente queremos un poco más que OpenGL 1.1. Pero Windows no nos proporciona los prototipos de funciones ni las funciones exportadas para nada que se encuentre por encima de eso. Los prototipos deben ser adquiridos desde el registro de OpenGL . Hay tres archivos que nos interesan: GL/glext.h , GL/glcorearb.h , y GL/wglext.h .

Para completar GL/gl.h proporcionado por Windows, necesitamos GL/glext.h . Contiene (como se describe en el registro) "OpenGL 1.2 y superiores interfaces de extensión y perfil de compatibilidad" (más información sobre perfiles y extensiones más adelante, donde veremos que en realidad no es una buena idea usar esos dos archivos ).

Las funciones reales deben ser obtenidas por wglGetProcAddress (no hay necesidad de abrir la DLL para este tipo, no están ahí, solo use la función). Con él, podemos obtener todas las funciones de OpenGL 1.2 y superiores (pero no 1.1). Tenga en cuenta que, para que funcione correctamente, el contexto de representación OpenGL debe crearse y actualizarse . Así, por ejemplo, glClear :

// Include the header from the OpenGL registry for function pointer types

// Declare the functions, just like before
PFNGLCLEARPROC glClear;
// ...

// Get the function
glClear = (PFNGLCLEARPROC)wglGetProcAddress("glClear");

En realidad, podemos construir un envoltorio procedimiento get_proc que use wglGetProcAddress y GetProcAddress :

// Get function pointer
void* get_proc(const char *proc_name)
{
    void *proc = (void*)wglGetProcAddress(proc_name);
    if (!proc) proc = (void*)GetProcAddress(gl_module, proc_name); // gl_module must be somewhere in reach

    return proc;
}

Así que para terminar, crearíamos un archivo de encabezado lleno de declaraciones de punteros de función como esta:

extern PFNGLCLEARCOLORPROC glClearColor;
extern PFNGLCLEARDEPTHPROC glClearDepth;
extern PFNGLCLEARPROC glClear;
extern PFNGLCLEARBUFFERIVPROC glClearBufferiv;
extern PFNGLCLEARBUFFERFVPROC glClearBufferfv;
// And so on...

Luego podemos crear un procedimiento como load_gl_functions que llamamos solo una vez, y funciona así:

glClearColor = (PFNGLCLEARCOLORPROC)get_proc("glClearColor");
glClearDepth = (PFNGLCLEARDEPTHPROC)get_proc("glClearDepth");
glClear = (PFNGLCLEARPROC)get_proc("glClear");
glClearBufferiv = (PFNGLCLEARBUFFERIVPROC)get_proc("glClearBufferiv");
glClearBufferfv = (PFNGLCLEARBUFFERFVPROC)get_proc("glClearBufferfv");

¡Y ya está todo listo! Solo incluye el encabezado con los punteros a la función y GL fuera.


Mejor configuracion

Perfiles de OpenGL

OpenGL ha estado en desarrollo durante más de 20 años, y los desarrolladores siempre fueron estrictos con respecto a la compatibilidad con versiones anteriores (BC) . Agregar una nueva característica es muy difícil debido a eso. Así, en 2008, se separó en dos "perfiles". Núcleo y compatibilidad . El perfil central rompe BC a favor de mejoras de rendimiento y algunas de las nuevas características. Incluso elimina por completo algunas características heredadas. El perfil de compatibilidad mantiene BC con todas las versiones hasta 1.0, y algunas características nuevas no están disponibles en él. Solo se debe utilizar para sistemas antiguos y heredados, todas las aplicaciones nuevas deben usar el perfil central.

Debido a eso, hay un problema con nuestra configuración básica: solo proporciona el contexto que es compatible con OpenGL 1.0. El formato de píxeles es limitado también. Hay un mejor enfoque, utilizando extensiones.

Extensiones de OpenGL

Cualquier adición a la funcionalidad original de OpenGL se llama extensiones. En general, pueden legalizar algunas cosas que no existían antes, extender el rango de valores de los parámetros, extender GLSL e incluso agregar una funcionalidad completamente nueva.

Hay tres grupos principales de extensiones: proveedor, EXT y ARB. Las extensiones de proveedor provienen de un proveedor específico y tienen una marca específica de proveedor, como AMD o NV. Extensiones EXT son hechas por varios proveedores trabajando juntos. Después de algún tiempo, pueden convertirse en extensiones ARB, que son todas las que cuentan con el respaldo oficial y las aprobadas por ARB.

Para adquirir tipos de puntero de función y prototipos de función de todas las extensiones y como se mencionó anteriormente, todos los tipos de puntero de función de OpenGL 1.2 y superiores , uno debe descargar los archivos de encabezado del registro de OpenGL . Como se comentó, para las aplicaciones nuevas es mejor usar el perfil del núcleo, por lo que sería preferible incluir GL/glcorearb.h lugar de GL/gl.h y GL/glext.h (si está usando GL/glcorearb.h entonces don no incluye GL/gl.h ).

También hay extensiones para el WGL, en GL/wglext.h . Por ejemplo, la función para obtener la lista de todas las extensiones compatibles es en realidad una extensión en sí misma, wglGetExtensionsStringARB (devuelve una cadena grande con una lista separada por espacios de todas las extensiones compatibles).

La obtención de extensiones también se maneja a través de wglGetProcAddress , por lo que podemos usar nuestro contenedor como antes.

Formato avanzado de píxeles y creación de contexto

La extensión WGL_ARB_pixel_format nos permite la creación avanzada de formato de píxeles. A diferencia de antes, no usamos una estructura. En su lugar, pasamos la lista de atributos deseados.

int pixel_format_arb;
UINT pixel_formats_found;

int pixel_attributes[] = {
    WGL_SUPPORT_OPENGL_ARB, 1,
    WGL_DRAW_TO_WINDOW_ARB, 1,
    WGL_DRAW_TO_BITMAP_ARB, 1,
    WGL_DOUBLE_BUFFER_ARB, 1,
    WGL_SWAP_LAYER_BUFFERS_ARB, 1,
    WGL_COLOR_BITS_ARB, 32,
    WGL_RED_BITS_ARB, 8,
    WGL_GREEN_BITS_ARB, 8,
    WGL_BLUE_BITS_ARB, 8,
    WGL_ALPHA_BITS_ARB, 8,
    WGL_DEPTH_BITS_ARB, 32,
    WGL_STENCIL_BITS_ARB, 8,
    WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
    WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
    0
};

BOOL result = wglChoosePixelFormatARB(dc, pixel_attributes, NULL, 1, &pixel_format_arb, &pixel_formats_found);

De manera similar, la extensión WGL_ARB_create_context nos permite la creación avanzada de contexto:

GLint context_attributes[] = {
    WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
    WGL_CONTEXT_MINOR_VERSION_ARB, 3,
    WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
    0
};

HGLRC new_rc = wglCreateContextAttribsARB(dc, 0, context_attributes);

Para una explicación precisa de los parámetros y funciones, consulte la especificación de OpenGL.

¿Por qué no empezamos con ellos? Bueno, eso es porque las extensiones nos permiten hacer esto, y para obtener las extensiones necesitamos wglGetProcAddress , pero eso solo funciona con un contexto activo válido. Entonces, en esencia, antes de que seamos capaces de crear el contexto que queremos, necesitamos tener algún contexto activo ya, y generalmente se lo denomina contexto ficticio .

Sin embargo, Windows no permite configurar el formato de píxeles de una ventana más de una vez. Por eso, la ventana necesita ser destruida y recreada para poder aplicar cosas nuevas:

wglMakeCurrent(dc, NULL);
wglDeleteContext(rc);
ReleaseDC(window_handle, dc);
DestroyWindow(window_handle);

// Recreate the window...

Código de ejemplo completo:

/* We want the core profile, so we include GL/glcorearb.h. When including that, then
   GL/gl.h should not be included.

   If using compatibility profile, the GL/gl.h and GL/glext.h need to be included.

   GL/wglext.h gives WGL extensions.

   Note that Windows.h needs to be included before them. */

#include <cstdio>
#include <Windows.h>
#include <GL/glcorearb.h>
#include <GL/wglext.h>

LRESULT CALLBACK window_procedure(HWND, UINT, WPARAM, LPARAM);
void* get_proc(const char*);

/* gl_module is for opening the DLL, and the quit flag is here to prevent
   quitting when recreating the window (see the window_procedure function) */

HMODULE gl_module;
bool quit = false;

/* OpenGL function declarations. In practice, we would put these in a
   separate header file and add "extern" in front, so that we can use them
   anywhere after loading them only once. */

PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB;
PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;
PFNGLGETSTRINGPROC glGetString;

int WINAPI WinMain(HINSTANCE instance_handle, HINSTANCE prev_instance_handle, PSTR cmd_line, int cmd_show) {
    /* REGISTER WINDOW */
    WNDCLASS window_class;

    // Clear all structure fields to zero first
    ZeroMemory(&window_class, sizeof(window_class));

    // Define fields we need (others will be zero)
    window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    window_class.lpfnWndProc = window_procedure;
    window_class.hInstance = instance_handle;
    window_class.lpszClassName = TEXT("OPENGL_WINDOW");

    // Give our class to Windows
    RegisterClass(&window_class);
    /* *************** */
        
    /* CREATE WINDOW */
    HWND window_handle = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
                                        TEXT("OPENGL_WINDOW"),
                                        TEXT("OpenGL window"),
                                        WS_OVERLAPPEDWINDOW,
                                        0, 0,
                                        800, 600,
                                        NULL,
                                        NULL,
                                        instance_handle,
                                        NULL);
        
    HDC dc = GetDC(window_handle);
        
    ShowWindow(window_handle, SW_SHOW);
    /* ************* */
        
    /* PIXEL FORMAT */
    PIXELFORMATDESCRIPTOR descriptor;
        
    // Clear all structure fields to zero first
    ZeroMemory(&descriptor, sizeof(descriptor));
        
    // Describe our pixel format
    descriptor.nSize = sizeof(descriptor);
    descriptor.nVersion = 1;
    descriptor.dwFlags = PFD_DRAW_TO_WINDOW | PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL | PFD_GENERIC_ACCELERATED | PFD_DOUBLEBUFFER | PFD_SWAP_LAYER_BUFFERS;
    descriptor.iPixelType = PFD_TYPE_RGBA;
    descriptor.cColorBits = 32;
    descriptor.cRedBits = 8;
    descriptor.cGreenBits = 8;
    descriptor.cBlueBits = 8;
    descriptor.cAlphaBits = 8;
    descriptor.cDepthBits = 32;
    descriptor.cStencilBits = 8;
        
    // Ask for a similar supported format and set it
    int pixel_format = ChoosePixelFormat(dc, &descriptor);
    SetPixelFormat(dc, pixel_format, &descriptor);
    /* *********************** */
        
    /* RENDERING CONTEXT */
    HGLRC rc = wglCreateContext(dc);
    wglMakeCurrent(dc, rc);
    /* ***************** */

    /* LOAD FUNCTIONS (should probably be put in a separate procedure) */
    gl_module = LoadLibrary(TEXT("opengl32.dll"));

    wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)get_proc("wglGetExtensionsStringARB");
    wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)get_proc("wglChoosePixelFormatARB");
    wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)get_proc("wglCreateContextAttribsARB");
    glGetString = (PFNGLGETSTRINGPROC)get_proc("glGetString");
    
    FreeLibrary(gl_module);
    /* ************** */

    /* PRINT VERSION */
    const GLubyte *version = glGetString(GL_VERSION);
    printf("%s\n", version);
    fflush(stdout);
    /* ******* */

    /* NEW PIXEL FORMAT*/
    int pixel_format_arb;
    UINT pixel_formats_found;
    
    int pixel_attributes[] = {
        WGL_SUPPORT_OPENGL_ARB, 1,
        WGL_DRAW_TO_WINDOW_ARB, 1,
        WGL_DRAW_TO_BITMAP_ARB, 1,
        WGL_DOUBLE_BUFFER_ARB, 1,
        WGL_SWAP_LAYER_BUFFERS_ARB, 1,
        WGL_COLOR_BITS_ARB, 32,
        WGL_RED_BITS_ARB, 8,
        WGL_GREEN_BITS_ARB, 8,
        WGL_BLUE_BITS_ARB, 8,
        WGL_ALPHA_BITS_ARB, 8,
        WGL_DEPTH_BITS_ARB, 32,
        WGL_STENCIL_BITS_ARB, 8,
        WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
        WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
        0
    };

    BOOL result = wglChoosePixelFormatARB(dc, pixel_attributes, NULL, 1, &pixel_format_arb, &pixel_formats_found);

    if (!result) {
        printf("Could not find pixel format\n");
        fflush(stdout);
        return 0;
    }
    /* **************** */

    /* RECREATE WINDOW */
    wglMakeCurrent(dc, NULL);
    wglDeleteContext(rc);
    ReleaseDC(window_handle, dc);
    DestroyWindow(window_handle);
    
    window_handle = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
                                        TEXT("OPENGL_WINDOW"),
                                        TEXT("OpenGL window"),
                                        WS_OVERLAPPEDWINDOW,
                                        0, 0,
                                        800, 600,
                                        NULL,
                                        NULL,
                                        instance_handle,
                                        NULL);
        
    dc = GetDC(window_handle);
        
    ShowWindow(window_handle, SW_SHOW);
    /* *************** */

    /* NEW CONTEXT */
    GLint context_attributes[] = {
        WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
        WGL_CONTEXT_MINOR_VERSION_ARB, 3,
        WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
        0
    };

    rc = wglCreateContextAttribsARB(dc, 0, context_attributes);
    wglMakeCurrent(dc, rc);
    /* *********** */
        
    /* EVENT PUMP */
    MSG msg;
        
    while (true) {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT) 
                break;
                
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
            
        // draw(); <- there goes your drawing
            
        SwapBuffers(dc);
    }
    /* ********** */
        
    return 0;
}

// Procedure that processes window events
LRESULT CALLBACK window_procedure(HWND window_handle, UINT message, WPARAM param_w, LPARAM param_l)
{
    /* When destroying the dummy window, WM_DESTROY message is going to be sent,
       but we don't want to quit the application then, and that is controlled by
       the quit flag. */

    switch(message) {
    case WM_DESTROY:
        if (!quit) quit = true;
        else PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(window_handle, message, param_w, param_l);
}

/* A procedure for getting OpenGL functions and OpenGL or WGL extensions.
   When looking for OpenGL 1.2 and above, or extensions, it uses wglGetProcAddress,
   otherwise it falls back to GetProcAddress. */
void* get_proc(const char *proc_name)
{
    void *proc = (void*)wglGetProcAddress(proc_name);
    if (!proc) proc = (void*)GetProcAddress(gl_module, proc_name);

    return proc;
}

Compilado con g++ GLExample.cpp -lopengl32 -lgdi32 con MinGW / Cygwin o cl GLExample.cpp opengl32.lib gdi32.lib user32.lib con el compilador cl GLExample.cpp opengl32.lib gdi32.lib user32.lib . Sin embargo, asegúrese de que los encabezados del registro de OpenGL estén en la ruta de inclusión. Si no es así, utilice el indicador -I para g++ o /I para cl para indicar al compilador dónde están.

Creando OpenGL 4.1 con C ++ y Cocoa

Nota: Habrá algo de Objective-c en este ejemplo ... Haremos un envoltorio para C ++ en este ejemplo, así que no se preocupe mucho por eso.

Primero inicia Xcode y crea un proyecto.

introduzca la descripción de la imagen aquí

Y selecciona una aplicación de cacao. introduzca la descripción de la imagen aquí

Elimine todas las fuentes excepto el archivo Info.plist. (Su aplicación no funcionará sin él)

Cree 4 nuevos archivos de origen: un archivo y un encabezado de Objective-c ++ (he llamado el mío MacApp) Una clase de C ++ (he llamado el mío (la aplicación)

En la parte superior izquierda (con el nombre del proyecto), haga clic en él y agregue marcos y bibliotecas vinculados. Añadir: OpenGL.Framework AppKit.Framework GLKit.Framework

Tu proyecto se verá probablemente así:

introduzca la descripción de la imagen aquí

La aplicación NSA es la clase principal que utiliza al crear una aplicación MacOS. Te permite registrar ventanas y atrapar eventos.

Queremos registrar (nuestra propia) ventana en la aplicación NSA. Primero cree en su encabezado de object -c ++ una clase de object -c que herede de NSWindow e implementa NSApplicationDelegate. NSWindow necesita un puntero a la aplicación C ++, una vista openGL y un temporizador para el ciclo de dibujo.

//Mac_App_H
#import <Cocoa/Cocoa.h>
#import "Application.hpp"
#import <memory>
NSApplication* application;

@interface MacApp : NSWindow <NSApplicationDelegate>{
    std::shared_ptr<Application> appInstance;
}
@property (nonatomic, retain) NSOpenGLView* glView;
-(void) drawLoop:(NSTimer*) timer;
@end

Llamamos a esto desde el principio con

int main(int argc, const char * argv[]) {
    MacApp* app;
    application = [NSApplication sharedApplication];
    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; 
    //create a window with the size of 600 by 600   
    app = [[MacApp alloc] initWithContentRect:NSMakeRect(0, 0, 600, 600)              styleMask:NSTitledWindowMask | NSClosableWindowMask |  NSMiniaturizableWindowMask   backing:NSBackingStoreBuffered defer:YES];    
    [application setDelegate:app];
    [application run];
}

La implementación de nuestra ventana es bastante fácil. Primero, declaramos sintetizando nuestra vista global y agregamos un booleano objetivo-c global cuando la ventana debería cerrarse.

#import "MacApp.h"

@implementation MacApp

@synthesize glView;

BOOL shouldStop = NO;

Ahora para el constructor. Mi preferencia es usar el initWithContentRect.

-(id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag{
if(self = [super initWithContentRect:contentRect styleMask:aStyle backing:bufferingType defer:flag]){
    //sets the title of the window (Declared in Plist)
    [self setTitle:[[NSProcessInfo processInfo] processName]];
 
    //This is pretty important.. OS X starts always with a context that only supports openGL 2.1
    //This will ditch the classic OpenGL and initialises openGL 4.1
    NSOpenGLPixelFormatAttribute pixelFormatAttributes[] ={
        NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
            NSOpenGLPFAColorSize    , 24                           ,
            NSOpenGLPFAAlphaSize    , 8                            ,
            NSOpenGLPFADoubleBuffer ,
            NSOpenGLPFAAccelerated  ,
            NSOpenGLPFANoRecovery   ,
            0
    };

    NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc]initWithAttributes:pixelFormatAttributes];
    //Initialize the view 
    glView = [[NSOpenGLView alloc]initWithFrame:contentRect pixelFormat:format];
    
    //Set context and attach it to the window
    [[glView openGLContext]makeCurrentContext];
  
    //finishing off
    [self setContentView:glView];
    [glView prepareOpenGL];
    [self makeKeyAndOrderFront:self];
    [self setAcceptsMouseMovedEvents:YES];
    [self makeKeyWindow];
    [self setOpaque:YES];

    //Start the c++ code
    appInstance = std::shared_ptr<Application>(new Application());

}
return self;
}

Bien ... ahora tenemos una aplicación ejecutable ... Es posible que veas una pantalla negra o parpadeos.

Vamos a empezar a dibujar un triángulo impresionante (en c ++)

Mi encabezado de aplicación

#ifndef Application_hpp
#define Application_hpp
#include <iostream>
#include <OpenGL/gl3.h>
class Application{
private:
    GLuint          program;
    GLuint          vao;
public:
    Application();
    void update();
    ~Application();

};

#endif /* Application_hpp */

La implementación:

Application::Application(){
 static const char * vs_source[] =
    {
        "#version 410 core                                                 \n"
        "                                                                  \n"
        "void main(void)                                                   \n"
        "{                                                                 \n"
        "    const vec4 vertices[] = vec4[](vec4( 0.25, -0.25, 0.5, 1.0),  \n"
        "                                   vec4(-0.25, -0.25, 0.5, 1.0),  \n"
        "                                   vec4( 0.25,  0.25, 0.5, 1.0)); \n"
        "                                                                  \n"
        "    gl_Position = vertices[gl_VertexID];                          \n"
        "}                                                                 \n"
    };

    static const char * fs_source[] =
    {
        "#version 410 core                                                 \n"
        "                                                                  \n"
        "out vec4 color;                                                   \n"
        "                                                                  \n"
        "void main(void)                                                   \n"
        "{                                                                 \n"
        "    color = vec4(0.0, 0.8, 1.0, 1.0);                             \n"
        "}                                                                 \n"
    };

    program = glCreateProgram();
    GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fs, 1, fs_source, NULL);
    glCompileShader(fs);

    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vs, 1, vs_source, NULL);
    glCompileShader(vs);

    glAttachShader(program, vs);
    glAttachShader(program, fs);

    glLinkProgram(program);

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
}

void Application::update(){
    static const GLfloat green[] = { 0.0f, 0.25f, 0.0f, 1.0f };
    glClearBufferfv(GL_COLOR, 0, green);

    glUseProgram(program);
    glDrawArrays(GL_TRIANGLES, 0, 3);
}


Application::~Application(){
    glDeleteVertexArrays(1, &vao);
    glDeleteProgram(program);
}

Ahora solo necesitamos llamar a la actualización una y otra vez (si desea que algo se mueva) Implementar en su clase de objetivo-c

-(void) drawLoop:(NSTimer*) timer{

if(shouldStop){
    [self close];
    return;
}
if([self isVisible]){
  
       appInstance->update();
    [glView update];
    [[glView openGLContext] flushBuffer];
}

}

Y agregue este método en la implementación de su clase de objetivo-c:

- (void)applicationDidFinishLaunching:(NSNotification *)notification {
    [NSTimer scheduledTimerWithTimeInterval:0.000001 target:self selector:@selector(drawLoop:) userInfo:nil repeats:YES];
}

Esto llamará a la función de actualización de su clase c ++ una y otra vez (cada 0.000001 segundos para ser precisos)

Para finalizar cerramos la ventana cuando se presiona el botón de cerrar:

- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication     *)theApplication{
    return YES;
}

- (void)applicationWillTerminate:(NSNotification *)aNotification{
    shouldStop = YES;
}

Felicitaciones, ahora tiene una ventana increíble con un triángulo OpenGL sin ningún marco de terceros. resultado final

Creación de contexto OpenGL multiplataforma (utilizando SDL2)

Creación de una ventana con contexto OpenGL (extensión de carga a través de GLEW ):

#define GLEW_STATIC

#include <GL/glew.h>
#include <SDL2/SDL.h>

int main(int argc, char* argv[])
{
    SDL_Init(SDL_INIT_VIDEO); /* Initialises Video Subsystem in SDL */

    /* Setting up OpenGL version and profile details for context creation */
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
    
    /* A 800x600 window. Pretty! */
    SDL_Window* window = SDL_CreateWindow
        (
        "SDL Context",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        800, 600,
        SDL_WINDOW_OPENGL
        );
    
    /* Creating OpenGL Context */
    SDL_GLContext gl_context = SDL_GL_CreateContext(window);

    /* Loading Extensions */
    glewExperimental = GL_TRUE;
    glewInit();

    /* The following code is for error checking. 
    *  If OpenGL has initialised properly, this should print 1.
    *  Remove it in production code.
    */
    GLuint vertex_buffer;
    glGenBuffers(1, &vertex_buffer);
    printf("%u\n", vertex_buffer);
    /* Error checking ends here */

    /* Main Loop */
    SDL_Event window_event;
    while(1) {
        if (SDL_PollEvent(&window_event)) {
            if (window_event.type == SDL_QUIT) {
                /* If user is exiting the application */
                break;
            }
        }
        /* Swap the front and back buffer for flicker-free rendering */
        SDL_GL_SwapWindow(window);
    }
    
    /* Freeing Memory */
    glDeleteBuffers(1, &vertex_buffer);
    SDL_GL_DeleteContext(gl_context);
    SDL_Quit();

    return 0;
}

Configuración moderna de OpenGL 4.1 en macOS (Xcode, GLFW y GLEW)

1. Instale GLFW

El primer paso es crear una ventana de OpenGL. GLFW es una biblioteca multiplataforma de código abierto para crear ventanas con OpenGL, para instalar GLFW, primero descargue sus archivos de www.glfw.org

Página de GLFW

Extrae la carpeta GLFW y su contenido se verá así

Contenidos de la carpeta GLFW

Descarga e instala CMake para compilar GLFW. Vaya a www.cmake.org/download/ , descargue CMake e instale para MAC OS X

Página de descargas CMake

Si Xcode no está instalado. Descargue e instale Xcode desde Mac App Store.

Xcode del Mac App Store

Crear una nueva carpeta Construir dentro de la carpeta GLFW

Carpeta GLFW después de crear la carpeta "Build"

Abra CMake, haga clic en el botón Buscar fuente para seleccionar la carpeta GLFW (asegúrese de que CMakeLists.txt) se encuentre dentro de esa carpeta. Después de eso, haga clic en el botón Explorar compilación y seleccione la carpeta de compilación recién creada en el paso anterior.

CMake Paths

Ahora haga clic en el botón Configurar y seleccione Xcode como generador con la opción Usar compiladores nativos predeterminados , y haga clic en Listo .

Makefile para Xcode

Marque la opción BUILD_SHARED_LIBS y luego haga clic nuevamente en el botón Configurar y finalmente haga clic en el botón Generar .

Seleccione BUILD_SHARED_LIBS

Después de la generación, CMake debería verse así.

Final CMake

Ahora abra Finder y goto / usr , cree un nombre de carpeta local si no está ya allí. Abra la carpeta local y cree dos carpetas include y lib si aún no está allí.

Ahora abra la carpeta GLFW y vaya a Crear (donde CMake había creado los archivos). Abra el archivo GLFW.xcodeproj en Xcode.

Archivo de Proyecto Xcode

Seleccione instalar> Mi Mac y luego haga clic en ejecutar (botón Reproducir en forma).

Instalar GLFW

Ahora está instalado con éxito (ignorar las advertencias).

Para asegurarse de que Open Finder y la carpeta goto / usr / local / lib y tres archivos de la biblioteca GLFW ya estarán presentes allí (de lo contrario, abra la carpeta Build dentro de la carpeta GLFW y vaya a src / Debug copie todos los archivos a / usr / local / lib )

Archivos GLFW Lib

Open Finder y goto / usr / local / include y una carpeta GLFW ya estarán presentes allí con dos archivos de encabezado dentro de él con el nombre de glfw3.h y glfw3native.h

Archivos de encabezado GLFW

2. Instale GLEW

GLEW es una biblioteca multiplataforma que ayuda a consultar y cargar extensiones OpenGL. Proporciona mecanismos de tiempo de ejecución para determinar qué extensiones OpenGL son compatibles con la plataforma de destino. Es solo para OpenGL moderno (OpenGL versión 3.2 y superior que requiere que las funciones se determinen en tiempo de ejecución). Para instalar primero descargue sus archivos desde glew.sourceforge.net

Página de GLEW

Extraiga la carpeta GLFW y su contenido se verá así.

Contenido de la carpeta GLEW

Ahora abra la Terminal, navegue a la carpeta GLEW y escriba los siguientes comandos

make
sudo make install 
make clean

Ahora GLEW está instalado con éxito. Para asegurarse de que esté instalado, Open Finder, vaya a / usr / local / include y una carpeta GL ya estará presente allí con tres archivos de encabezado dentro de él con el nombre de glew.h , glxew.h y wglew.h

Archivos de encabezado GLEW

Abra el Finder y vaya a / usr / local / lib y los archivos de la biblioteca GLEW ya estarán presentes allí

Archivos de biblioteca GLEW

3. Probar y ejecutar

Ahora hemos instalado con éxito GLFW y GLEW. Es hora de codificar. Abre Xcode y crea un nuevo proyecto de Xcode. Seleccione la herramienta de línea de comandos y luego continúe y seleccione C ++ como idioma.

Proyecto Xcode

Xcode creará un nuevo proyecto de línea de comandos.

Haga clic en el nombre del proyecto y, en la pestaña Configuraciones de compilación, cambie de Básico a Todo , en la sección Rutas de búsqueda , agregue / usr / local / include en Rutas de búsqueda de encabezado y agregue / usr / local / lib en Rutas de búsqueda de biblioteca

Rutas de búsqueda

Haga clic en el nombre del proyecto y en la pestaña Build Phases y en Link With Binary Libraries agregue OpenGL.framework y también agregue las bibliotecas GLFW y GLEW creadas recientemente desde / usr / local / lib

Vincular binarios

Ahora estamos listos para codificar en Modern Open GL 4.1 en macOS utilizando C ++ y Xcode. El siguiente código creará una ventana OpenGL usando GLFW con salida de pantalla en blanco.

#include <GL/glew.h> 
#include <GLFW/glfw3.h>

// Define main function
int main() 
{
    // Initialize GLFW
    glfwInit();

    // Define version and compatibility settings
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); 
    glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); 
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    // Create OpenGL window and context
    GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL", NULL, NULL);
    glfwMakeContextCurrent(window);

    // Check for window creation failure
    if (!window) 
    {
        // Terminate GLFW
        glfwTerminate();
        return 0; 
    }

    // Initialize GLEW
    glewExperimental = GL_TRUE; glewInit();

    // Event loop
    while(!glfwWindowShouldClose(window)) 
    {
        // Clear the screen to black
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT);
        glfwSwapBuffers(window);
        glfwPollEvents(); 
    }

    // Terminate GLFW
    glfwTerminate(); return 0;
}

Ventana OpenGL en blanco

Crea Opengl Context con Java y LWJGL 3.0

En este código de ejemplo, crearemos una ventana Opengl en blanco con LWJGL 3.0+, esto no contiene los pasos para crear el proyecto en su IDE.

introduzca la descripción de la imagen aquí

  1. Cree un nombre de clase WindowManager que contendrá todo el código de la placa de la caldera para crear una ventana de contexto de opengl en la pantalla

WindowManager.java

import org.lwjgl.glfw.*;
import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryUtil.*;

/**
 * Class Containing code related to inflating Opengl Window
 */
public class Displaymanager {

    private static long window;

    public static void createDisplay(){
        // Setup an error callback. The default implementation
        // will print the error message in System.err.
        GLFWErrorCallback.createPrint(System.err).set();

        // Initialize GLFW. Most GLFW functions will not work before doing this.
        if ( !glfwInit() )
            throw new IllegalStateException("Unable to initialize GLFW");

        // Configure our window
        glfwDefaultWindowHints(); // optional, the current window hints are already the default
        glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // the window will stay hidden after creation
        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // the window will be resizable

        int WIDTH = 300;
        int HEIGHT = 300;

        // Create the window
        window = glfwCreateWindow(WIDTH, HEIGHT, "Hello World!", NULL, NULL);
        if ( window == NULL )
            throw new RuntimeException("Failed to create the GLFW window");

        // Setup a key callback. It will be called every time a key is pressed, repeated or released.
        glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
            if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE )
                glfwSetWindowShouldClose(window, true); // We will detect this in our rendering loop
        });

        // Get the resolution of the primary monitor
        GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        // Center our window
        glfwSetWindowPos(
                window,
                (vidmode.width() - WIDTH) / 2,
                (vidmode.height() - HEIGHT) / 2
        );

        // Make the OpenGL context current
        glfwMakeContextCurrent(window);
        // Enable v-sync
        glfwSwapInterval(1);

        // Make the window visible
        glfwShowWindow(window);
    }

    public static boolean isCloseRequested(){
        return glfwWindowShouldClose(window);
    }

    public static void updateDisplay(){
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer

        glfwSwapBuffers(window); // swap the color buffers

        // Poll for window events. The key callback above will only be
        // invoked during this call.
        glfwPollEvents();
    }

    public static void destroyDisplay(){
        // Terminate GLFW and free the error callback
        cleanUp();
        glfwTerminate();
        glfwSetErrorCallback(null).free();
    }

    private static void cleanUp() {
        // Free the window callbacks and destroy the window
        glfwFreeCallbacks(window);
        glfwDestroyWindow(window);
    }
}
  1. A continuación, cree una clase que contenga el bucle de representación principal, que llamará a todas las funciones creadas anteriormente

OpenGlMain.java

import org.lwjgl.opengl.GL;
import renderEngine.Displaymanager;
import static org.lwjgl.opengl.GL11.glClearColor;


/**
 * Class to test the opengl Window
 */
public class OpenGlMain {

    public static void main(String[] args) {

        Displaymanager.createDisplay();

        // This line is critical for LWJGL's interoperation with GLFW's
        // OpenGL context, or any context that is managed externally.
        // LWJGL detects the context that is current in the current thread,
        // creates the GLCapabilities instance and makes the OpenGL
        // bindings available for use.
        GL.createCapabilities();

        while (!Displaymanager.isCloseRequested()){

            // Set the clear color
            glClearColor(1.0f, 0.0f, 0.0f, 0.0f);

            Displaymanager.updateDisplay();
        }

        Displaymanager.destroyDisplay();
    }
}

Para más detalles consulte la guía oficial de LWJGL



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow