Sign in to follow this  

OpenGL A question about GLEW Windows setup

This topic is 2326 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hey everybody, I've been programming an application with wgl and glew, and there's one thing that's been bothering me with the code. It's required that a rendering context needs to be initialized before glew itself can be initialized. But what's getting to me is that, in my case, the [i]rendering context[/i] requires [i]glew[/i] to be initialized as well (since I call wglChoosePixelFormatARB for the context's pixel format, which is part of glew). This over all requires me to create a "dummy" rendering context just to initialize glew, and thus an extra device context and window (all of which would be deleted and reallocated later on). Is there a better way to implement this? I'd rather my code be a bit more cohesive so that I can reuse my variables more effectively, and managing the creation of these "dummy" variables bulks up my code to an extra 50 lines of code at least.

Here's the code in question, in my case:

[code]bool SetupWindow(int nWidth, int nHeight, bool bUseFS)
{
bool bRetVal = true;

int nWindowX = 0;
int nWindowY = 0;
int nPixelFormat = NULL;
PIXELFORMATDESCRIPTOR pfd;

DWORD dwExtStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
DWORD dwWindStyle = WS_OVERLAPPEDWINDOW;

HINSTANCE g_hInstance = GetModuleHandle(NULL);

if(bUseFS)
{
// Prepare for a mode set to the requested resolution
DEVMODE dm;
memset(&dm,0,sizeof(dm));
dm.dmSize=sizeof(dm);
dm.dmPelsWidth = nWidth;
dm.dmPelsHeight = nHeight;
dm.dmBitsPerPel = 32;
dm.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

long error = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);

if (error != DISP_CHANGE_SUCCESSFUL)
{
// Oops, something went wrong, let the user know.
if (MessageBox(NULL, TEXT("Could not set fullscreen mode.\n\
Your video card may not support the requested mode.\n\
Use windowed mode instead?"),
g_szAppName, MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
g_InFullScreen = false;
dwExtStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
dwWindStyle = WS_OVERLAPPEDWINDOW;
}
else
{
MessageBox(NULL, TEXT("Program will exit."), TEXT("ERROR"), MB_OK|MB_ICONSTOP);
return false;
}
}
else
{
// Mode set passed, setup the styles for fullscreen
g_InFullScreen = true;
dwExtStyle = WS_EX_APPWINDOW;
dwWindStyle = WS_POPUP;
ShowCursor(FALSE);
}
}

// setup window class
g_windClass.lpszClassName = g_szClassName; // Set the name of the Class
g_windClass.lpfnWndProc = (WNDPROC)WndProc;
g_windClass.hInstance = g_hInstance; // Use this module for the module handle
g_windClass.hCursor = LoadCursor(NULL, IDC_ARROW);// Pick the default mouse cursor
g_windClass.hIcon = LoadIcon(NULL, IDI_WINLOGO);// Pick the default windows icons
g_windClass.hbrBackground = NULL; // No Background
g_windClass.lpszMenuName = NULL; // No menu for this window
g_windClass.style = CS_HREDRAW | CS_OWNDC | // set styles for this class, specifically to catch
CS_VREDRAW; // window redraws, unique DC, and resize
g_windClass.cbClsExtra = 0; // Extra class memory
g_windClass.cbWndExtra = 0; // Extra window memory

// Register the newly defined class
if(!RegisterClass( &g_windClass ))
bRetVal = false;

g_windowRect.left = nWindowX;
g_windowRect.right = nWindowX + nWidth;
g_windowRect.top = nWindowY;
g_windowRect.bottom = nWindowY + nHeight;

// Setup window width and height
AdjustWindowRectEx(&g_windowRect, dwWindStyle, FALSE, dwExtStyle);

//Adjust for adornments
int nWindowWidth = g_windowRect.right - g_windowRect.left;
int nWindowHeight = g_windowRect.bottom - g_windowRect.top;

// Create window
g_hWnd = CreateWindowEx(dwExtStyle, // Extended style
g_szClassName, // class name
g_szAppName, // window name
dwWindStyle |
WS_CLIPSIBLINGS |
WS_CLIPCHILDREN,// window stlye
nWindowX, // window position, x
nWindowY, // window position, y
nWindowWidth, // height
nWindowHeight, // width
NULL, // Parent window
NULL, // menu
g_hInstance, // instance
NULL); // pass this to WM_CREATE

// now that we have a window, setup the pixel format descriptor
g_hDC = GetDC(g_hWnd);

// Set a dummy pixel format so that we can get access to wgl functions
SetPixelFormat( g_hDC, 1, &pfd);
// Create OGL context and make it current
g_hRC = wglCreateContext( g_hDC );
wglMakeCurrent( g_hDC, g_hRC );

if (g_hDC == 0 ||
g_hRC == 0)
{
bRetVal = false;
MessageBox(NULL,
TEXT("!!! An error occured creating an OpenGL window.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);
}

// Setup GLEW which loads OGL function pointers
GLenum err = glewInit();
if (GLEW_OK != err)
{
/* Problem: glewInit failed, something is seriously wrong. */
bRetVal = false;
printf("Error: %s\n", glewGetErrorString(err));
}
const GLubyte *oglVersion = glGetString(GL_VERSION);
printf("This system supports OpenGL Version %s.\n", oglVersion);

// Now that extensions are setup, delete window and start over picking a real format.
wglMakeCurrent(NULL, NULL);
wglDeleteContext(g_hRC);
ReleaseDC(g_hWnd, g_hDC);
DestroyWindow(g_hWnd);

// Create the window again
g_hWnd = CreateWindowEx(dwExtStyle, // Extended style
g_szClassName, // class name
g_szAppName, // window name
dwWindStyle |
WS_CLIPSIBLINGS |
WS_CLIPCHILDREN,// window stlye
nWindowX, // window position, x
nWindowY, // window position, y
nWindowWidth, // height
nWindowHeight, // width
NULL, // Parent window
NULL, // menu
g_hInstance, // instance
NULL); // pass this to WM_CREATE

g_hDC = GetDC(g_hWnd);

int nPixCount = 0;

// Specify the important attributes we care about
int pixAttribs[] = { WGL_SUPPORT_OPENGL_ARB, 1, // Must support OGL rendering
WGL_DRAW_TO_WINDOW_ARB, 1, // pf that can run a window
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, // must be HW accelerated
WGL_COLOR_BITS_ARB, 24, // 8 bits of each R, G and B
WGL_DEPTH_BITS_ARB, 16, // 16 bits of depth precision for window
WGL_DOUBLE_BUFFER_ARB, GL_TRUE, // Double buffered context
WGL_SAMPLE_BUFFERS_ARB, GL_TRUE, // MSAA on
WGL_SAMPLES_ARB, 8, // 8x MSAA
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, // pf should be RGBA type
0}; // NULL termination

// Ask OpenGL to find the most relevant format matching our attribs
// Only get one format back.
wglChoosePixelFormatARB(g_hDC, pixAttribs, NULL, 1, &nPixelFormat, (UINT*)&nPixCount);

if(nPixelFormat == NULL)
{
MessageBox(NULL,
TEXT("!!! An error occurred trying to find a MSAA pixel format with the requested attribs.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);

// Try again without MSAA
pixAttribs[15] = 1;
wglChoosePixelFormatARB(g_hDC, pixAttribs, NULL, 1, &nPixelFormat, (UINT*)&nPixCount);

if(nPixelFormat == NULL)
{
// Couldn't find a format, perhaps no 3D HW or drivers are installed
g_hDC = 0;
g_hRC = 0;
bRetVal = false;
MessageBox(NULL,
TEXT("!!! An error occurred trying to find a pixel format with the requested attribs.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);
}
}

if(nPixelFormat != NULL)
{
// Check for MSAA
int attrib[] = { WGL_SAMPLES_ARB };
int nResults = 0;
wglGetPixelFormatAttribivARB(g_hDC, nPixelFormat, 0, 1, attrib, &nResults);
printf("Chosen pixel format is MSAA with %d samples.\n", nResults);

// Got a format, now set it as the current one
SetPixelFormat( g_hDC, nPixelFormat, &pfd );

GLint attribs[] = {WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 3,
0 };

g_hRC = wglCreateContextAttribsARB(g_hDC, 0, attribs);
if (g_hRC == NULL)
{
MessageBox(NULL,
TEXT("!!! Could not create an OpenGL 3.3 context.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);

attribs[3] = 2;
g_hRC = wglCreateContextAttribsARB(g_hDC, 0, attribs);

if (g_hRC == NULL)
{
MessageBox(NULL,
TEXT("!!! Could not create an OpenGL 3.2 context.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);

attribs[3] = 1;
g_hRC = wglCreateContextAttribsARB(g_hDC, 0, attribs);

if (g_hRC == NULL)
{
MessageBox(NULL,
TEXT("!!! Could not create an OpenGL 3.1 context.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);

attribs[3] = 0;
g_hRC = wglCreateContextAttribsARB(g_hDC, 0, attribs);

if (g_hRC == NULL)
{
MessageBox(NULL,
TEXT("!!! Could not create an OpenGL 3.0 context.\n!!! OpenGL 3.0 and higher are not supported on this system.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);
}
}
}
}

wglMakeCurrent( g_hDC, g_hRC );
}

if (g_hDC == 0 ||
g_hRC == 0)
{
bRetVal = false;
MessageBox(NULL,
TEXT("!!! An error occured creating an OpenGL window."),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);
}

// If everything went as planned, display the window
if( bRetVal )
{
ShowWindow( g_hWnd, SW_SHOW );
SetForegroundWindow( g_hWnd );
SetFocus( g_hWnd );
g_ContinueRendering = true;
}

return bRetVal;
}[/code]

Help is greatly appreciated!

Side note: I know that there is also WGLs ChoosePixelFormat function, but as far as I know that is older than the glew version and not necessarily recommended by the ARB.

Share this post


Link to post
Share on other sites
[quote name='AutoBot' timestamp='1311304790' post='4838759']
Is there a better way to implement this?
[/quote]

No.

[quote]
I'd rather my code be a bit more cohesive so that I can reuse my variables more effectively, and managing the creation of these "dummy" variables bulks up my code to an extra 50 lines of code at least.
[/quote]

Which are (1) incorrect and (2) can be reduced to about 10. Incorrect because I don't see where you'd set up PIXELFORMATDESCRIPTOR to create dummy context. You may end up with no GL context or 1.1 Microsoft context, and then wonder why glew doesn't work like you'd expect.
Can be reduced because your dummy window doesn't need a style or particular size (1x1 window is enough).
And once you've written that code, you'll hardly be ever touching it again, so in my opinion no big deal here.

[quote]
Side note: I know that there is also WGLs ChoosePixelFormat function, but as far as I know that is older than the glew version and not necessarily recommended by the ARB.
[/quote]


What makes you think so? ChoosePixelFormat just doesn't allow you to set some additional attributes, that's all. And you still need it to properly set up pfd when creating dummy window.

Share this post


Link to post
Share on other sites
So basically your code is
PIXELFORMATDESCRIPTOR pfd;
SetPixelFormat( g_hDC, 1, &pfd);

Any reason why you are not using ChoosePixelFormat? And why do you pass a non-initialized variable to SetPixelFormat? There are examples all over the net on how to setup a pixel format.

Share this post


Link to post
Share on other sites
You need the dummy context to get the function pointers. There's really no way around that. But what I suggest you do instead of initializing GLEW in the middle of your window manager, just load the handful of function pointers you need to proceed, then go ahead to create the real one using your new functions and destroy the dummy context. Once the dummy context is created, it's just one additional line of code per function you want to use.
[source]
PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormat = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
[/source]
The reason you need the dummy rendering context is that function pointers is rendering context dependent. This is important to be aware of because if the dummy context is not from your hardware driver, you likely won't get the correct function pointer to set the pixel format, if you get anything at all. It also implies that you shouldn't load function pointers [i]at all[/i] until the real rendering context is created. So don't initialize GLEW until after the real rendering context is created, so load the handful of functions you need manually.

edit: Well, what the above said about the pixel format applies of course. But at some point you may need these intermediate functions to manage your rendering context; for example if you do need an exclusive pixel format, or simply a post-3.0 context, then you need to consider this.

Share this post


Link to post
Share on other sites
I have a bit more understanding about how this is supposed to work, so thanks for the insight so far everybody. Hopefully someone can resolve my last few questions.

[quote]It also implies that you shouldn't load function pointers [i]at all[/i] until the real rendering context is created. [/quote]
Is wglChoosePixelFormatARB an exception to this? It is part of the creation of the rendering context itself, after all.
And that actually raises another question in my head: how is wglChoosePixelFormatARB itself dependent on the rendering context being initialized?

[quote]Any reason why you are not using ChoosePixelFormat? [/quote]
I suppose it's just a tendency on my part to have the most updated material possible to work with. Just personal preference, I suppose. What Brother Bob said at the end of his post.

[quote]And why do you pass a non-initialized variable to SetPixelFormat? [/quote]
Are you referring to the pfd? Isn't SetPixelFormat supposed to initialize that to the data held in the pixel format?

[quote]Which are (1) incorrect and (2) can be reduced to about 10. Incorrect because I don't see where you'd set up PIXELFORMATDESCRIPTOR to create dummy context. You may end up with no GL context or 1.1 Microsoft context, and then wonder why glew doesn't work like you'd expect.
Can be reduced because your dummy window doesn't need a style or particular size (1x1 window is enough).[/quote]
Do you mean that I don't even need a pfd for the dummy contexts? Or a window?

EDIT: I've tried to trim down the code a bit:

[code]bool SetupWindow(int nWidth, int nHeight)
{
bool bRetVal = true;

int nWindowX = 0;
int nWindowY = 0;
int nPixelFormat = NULL;
PIXELFORMATDESCRIPTOR pfd;

DWORD dwExtStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
DWORD dwWindStyle = WS_OVERLAPPEDWINDOW;

HINSTANCE g_hInstance = GetModuleHandle(NULL);

// setup window class
g_windClass.lpszClassName = g_szClassName; // Set the name of the Class
g_windClass.lpfnWndProc = (WNDPROC)WndProc;
g_windClass.hInstance = g_hInstance; // Use this module for the module handle
g_windClass.hCursor = LoadCursor(NULL, IDC_ARROW);// Pick the default mouse cursor
g_windClass.hIcon = LoadIcon(NULL, IDI_WINLOGO);// Pick the default windows icons
g_windClass.hbrBackground = NULL; // No Background
g_windClass.lpszMenuName = NULL; // No menu for this window
g_windClass.style = CS_HREDRAW | CS_OWNDC | // set styles for this class, specifically to catch
CS_VREDRAW; // window redraws, unique DC, and resize
g_windClass.cbClsExtra = 0; // Extra class memory
g_windClass.cbWndExtra = 0; // Extra window memory

// Register the newly defined class
if(!RegisterClass( &g_windClass ))
bRetVal = false;

g_windowRect.left = nWindowX;
g_windowRect.right = nWindowX + nWidth;
g_windowRect.top = nWindowY;
g_windowRect.bottom = nWindowY + nHeight;

// Setup window width and height
AdjustWindowRectEx(&g_windowRect, dwWindStyle, FALSE, dwExtStyle);

//Adjust for adornments
nWidth = g_windowRect.right - g_windowRect.left;
nHeight = g_windowRect.bottom - g_windowRect.top;

// Create the window
g_hWnd = CreateWindowEx(dwExtStyle, // Extended style
g_szClassName, // class name
g_szAppName, // window name
dwWindStyle |
WS_CLIPSIBLINGS |
WS_CLIPCHILDREN,// window stlye
nWindowX, // window position, x
nWindowY, // window position, y
nWidth, // height
nHeight, // width
NULL, // Parent window
NULL, // menu
g_hInstance, // instance
NULL); // pass this to WM_CREATE

g_hDC = GetDC(g_hWnd);
SetPixelFormat( g_hDC, 1, &pfd); // Set a dummy pixel format so that we can get access to wgl functions
g_hRC = wglCreateContext( g_hDC ); // Create OGL context
wglMakeCurrent( g_hDC, g_hRC ); // and make it current

PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormat = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");

int nPixCount = 0;

// Specify the important attributes we care about
int pixAttribs[] = { WGL_SUPPORT_OPENGL_ARB, 1, // Must support OGL rendering
WGL_DRAW_TO_WINDOW_ARB, 1, // pf that can run a window
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, // must be HW accelerated
WGL_COLOR_BITS_ARB, 24, // 8 bits of each R, G and B
WGL_DEPTH_BITS_ARB, 16, // 16 bits of depth precision for window
WGL_DOUBLE_BUFFER_ARB, GL_TRUE, // Double buffered context
WGL_SAMPLE_BUFFERS_ARB, GL_TRUE, // MSAA on
WGL_SAMPLES_ARB, 8, // 8x MSAA
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, // pf should be RGBA type
0}; // NULL termination

// Ask OpenGL to find the most relevant format matching our attribs
// Only get one format back.
wglChoosePixelFormat(g_hDC, pixAttribs, NULL, 1, &nPixelFormat, (UINT*)&nPixCount);

if(nPixelFormat == NULL)
{
MessageBox(NULL,
TEXT("!!! An error occurred trying to find a MSAA pixel format with the requested attribs.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);

// Try again without MSAA
pixAttribs[15] = 1;
wglChoosePixelFormat(g_hDC, pixAttribs, NULL, 1, &nPixelFormat, (UINT*)&nPixCount);

if(nPixelFormat == NULL)
{
// Couldn't find a format, perhaps no 3D HW or drivers are installed
g_hDC = 0;
g_hRC = 0;
bRetVal = false;
MessageBox(NULL,
TEXT("!!! An error occurred trying to find a pixel format with the requested attribs.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);
}
}

if(nPixelFormat != NULL)
{
// Got a format, now set it as the current one
SetPixelFormat( g_hDC, nPixelFormat, &pfd );

GLint attribs[] = {WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 3,
0 };

g_hRC = wglCreateContextAttribs(g_hDC, 0, attribs);
if (g_hRC == NULL)
{
MessageBox(NULL,
TEXT("!!! Could not create an OpenGL 3.3 context.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);

attribs[3] = 2;
g_hRC = wglCreateContextAttribs(g_hDC, 0, attribs);

if (g_hRC == NULL)
{
MessageBox(NULL,
TEXT("!!! Could not create an OpenGL 3.2 context.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);

attribs[3] = 1;
g_hRC = wglCreateContextAttribs(g_hDC, 0, attribs);

if (g_hRC == NULL)
{
MessageBox(NULL,
TEXT("!!! Could not create an OpenGL 3.1 context.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);

attribs[3] = 0;
g_hRC = wglCreateContextAttribs(g_hDC, 0, attribs);

if (g_hRC == NULL)
{
MessageBox(NULL,
TEXT("!!! Could not create an OpenGL 3.0 context.\n!!! OpenGL 3.0 and higher are not supported on this system.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);
}
}
}
}

wglMakeCurrent( g_hDC, g_hRC );
}

if (g_hDC == 0 ||
g_hRC == 0)
{
bRetVal = false;
MessageBox(NULL,
TEXT("!!! An error occured creating an OpenGL window."),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);
}

// Setup GLEW which loads OGL function pointers
GLenum err = glewInit();
if (GLEW_OK != err)
{
/* Problem: glewInit failed, something is seriously wrong. */
bRetVal = false;
printf("Error: %s\n", glewGetErrorString(err));
}
const GLubyte *oglVersion = glGetString(GL_VERSION);
printf("This system supports OpenGL Version %s.\n", oglVersion);

// If everything went as planned, display the window
if( bRetVal )
{
ShowWindow( g_hWnd, SW_SHOW );
SetForegroundWindow( g_hWnd );
SetFocus( g_hWnd );
g_ContinueRendering = true;
}

return bRetVal;
}[/code]

This makes the code a bit cleaner, imo. I've also removed the full screen check just so it's more to the point. Unfortunately this code doesn't work, as all of my other OpenGL calls won't work now! GLEW doesn't return an error in this case, so I'm not sure what could be wrong now... Should I just leave the code as it was in the first place?

Share this post


Link to post
Share on other sites
[quote name='AutoBot' timestamp='1311354406' post='4839018']
I have a bit more understanding about how this is supposed to work, so thanks for the insight so far everybody. Hopefully someone can resolve my last few questions.

[quote]It also implies that you shouldn't load function pointers [i]at all[/i] until the real rendering context is created. [/quote]
Is wglChoosePixelFormatARB an exception to this? It is part of the creation of the rendering context itself, after all.
And that actually raises another question in my head: how is wglChoosePixelFormatARB itself dependent on the rendering context being initialized?
[/quote]
Yes, it is a part of context creation, but it is part of an [i]extension[/i], and in order to determine if it's even present, you first need to create that legacy dummy context with plain WinAPI calls.

[quote]
[quote]Any reason why you are not using ChoosePixelFormat? [/quote]
I suppose it's just a tendency on my part to have the most updated material possible to work with. Just personal preference, I suppose. What Brother Bob said at the end of his post.
[/quote]
What Brother Bob said at the end of his post was that he confirmed what I and V-man tried to tell you: that your originally posted code shouldn't work.

[quote]
[quote]And why do you pass a non-initialized variable to SetPixelFormat? [/quote]
Are you referring to the pfd? Isn't SetPixelFormat supposed to initialize that to the data held in the pixel format?
[/quote]
What data? Held where? It is [b]you[/b] who should initialize the descriptor to tell Windows that you want driver-provided OpenGL. That's not even funny. It seems you misunderstood all of us and the documentation to boot. See below.

[quote]
[quote]Which are (1) incorrect and (2) can be reduced to about 10. Incorrect because I don't see where you'd set up PIXELFORMATDESCRIPTOR to create dummy context. You may end up with no GL context or 1.1 Microsoft context, and then wonder why glew doesn't work like you'd expect.
Can be reduced because your dummy window doesn't need a style or particular size (1x1 window is enough).[/quote]
Do you mean that I don't even need a pfd for the dummy contexts? Or a window?
[/quote]
No, all's the other way around. You need both dummy context and dummy window. What I meant, and what V-man specifically pointed out, is that in your original snippet (and the current one also), you just declare pfd variable, do not initialize it in any way, do not use ChoosePixelFormat, but simply call SetPixelFormat right away. This is completely and utterly wrong. I'm wondering how it even worked for you before. Either it was a miracle, or your originally posted snippet was incomplete.

The procedure should be like this:
1) Create PIXELFORMATDESCRIPTOR variable
2) Properly initialize it (set cbSize field, buffer sizes, flags (specifically PFD_SUPPORT_OPENGL))
3) Call ChoosePixelFormat to get index of the pixel format that is a closest match to the one you describe in PIXELFORMATDESCRIPTOR variable
4) Call SetPixelFormat
5) Call wglCreateContext.

That's it, your dummy-trow-away-later-context is ready, you can now make it current and proceed with GL initialization as you want.

Both your snippets miss points (2) and (3), which means that best case scenario is that you may get GL 1.1 context as implemented by Microsoft. Which wouldn't have any fancy wglChoosePixelFormat and wglCreateContextAttribs. Worst case - you won't get any context at all. Most terrible case - in release build you may end up with access violation.

Be sure to read carefully through [url="http://www.opengl.org/wiki/Creating_an_OpenGL_Context"]this[/url] to clear things up further, specifically the section "Proper context creation".

Share this post


Link to post
Share on other sites
(Please keep in mind that I'm referring to the code in the OP, not in my latter post which I already know doesn't work)

At the beginning of my code I do only call SetPixelFormat with an uninitialized pfd and so forth because its supposed to be a dummy pixel format. It's only purpose is to help give access to the wgl functions, so that later on I can query for a more sophisticated pixel format with MSAA and other features not supported by the generic wgl implementation.

I had assumed that wglChoosePixelFormatARB used the attributes array ("int attribs[]" in my code) to supersede the pfd. Thus when I called SetPixelFormat, it would supply the pfd with essential information from the attributes and newly found pixel format to supplement for more generic wgl functions that demand a pfd argument. I did some searching though and found that this isn't completely correct, I need to use DescribePixelFormat to apply the pixel format's data to the pfd. For some reason the code seemed to work beforehand on Visual C++ 2010, maybe because the pfd info isn't as critical when using ARB extensions. Maybe because DescribePixelFormat is already called inside the SetPixelFormat function. I honestly don't know. But just to be safe I'm going to put DescribePixelFormat in just for the sake of firm code. Is that the best thing to do?

In terms of the dummy window and contexts, I'm probably just going to leave those in and initialize glew before the pixel format setup like I have, which is probably how it's supposed to work anyway. But advice is still greatly appreciated if there's a more efficient way to go about doing this! I also have a more object-oriented version of this program in the works which might make it a bit cleaner, I'll post it here once I'm done if anybody's interested.

EDIT: I've read the wiki page as suggested, definitely some great stuff in there, it also confirms that it's best to leave my code as it is for the most part. One thing that's a bit vague is where (in "Pixel Format Extensions") it says:

[quote][font="sans-serif"][size="2"]Once you have a pixel format number, you can set it just like any pixel format with SetPixelFormat.[/size][/font][/quote]
They don't say anything much about the pfd in this case, and it seems to me that the attribs parameter would basically make the pfd redundant, other than for legacy purposes. I suppose that's what DescribePixelFormat is for though.

And sorry if I've offended you at all, capricorn... :S

Share this post


Link to post
Share on other sites
[quote name='AutoBot' timestamp='1311379705' post='4839156']
(Please keep in mind that I'm referring to the code in the OP, not in my latter post which I already know doesn't work)

At the beginning of my code I do only call SetPixelFormat with an uninitialized pfd and so forth because its supposed to be a dummy pixel format. It's only purpose is to help give access to the wgl functions, so that later on I can query for a more sophisticated pixel format with MSAA and other features not supported by the generic wgl implementation.
[/quote]

I think you've overestimated the term 'dummy' here :) It is not supposed to be a dummy pixel format. It is supposed to be a real pixel format suitable for OpenGL context creation. Yes, the context is 'dummy' in the sense that you won't do any rendering with it and you'll throw it away after getting a couple of function pointers (wglChoosePixelFormat and wglCreateContextAttribs), but nevertheless it should be a proper OpenGL context. Trying to create one without setting proper pixel format for DC won't get you far (I already mentioned what you might get).
To set proper pixel format, you need to initialize your pfd struct and [b]feed it to WinAPI's ChoosePixelFormat() function[/b]. It will return [b]pixel format index[/b], which you should use in SetPixelFormat() call. Alternatively, you could enumerate all available pixel formats to find suitable index without using ChoosePixelFormat().
In your original snippet you just use index 1, which I suspect just by coincidence happens to be an index for OpenGL-compatible pixel format [b]on your system[/b] (the "miracle" I mentioned earlier). You need to substitute this '1' with proper index.
pfd struct won't have any behavioral effects on SetPixelFormat() call. And yes, you may use that same pfd struct later when you call SetPixelFormat() with another index that you get from wglChoosePixelFormatARB(), it won't do any harm. Again, it's [b]pixel format index[/b] that matters in the SetPixelFormat() call, not the descriptor.


[quote]
I had assumed that wglChoosePixelFormatARB used the attributes array ("int attribs[]" in my code) to supersede the pfd.
[/quote]
Precisely so.

[quote]
Thus when I called SetPixelFormat, it would supply the pfd with essential information from the attributes and newly found pixel format to supplement for more generic wgl functions that demand a pfd argument.
[/quote]
SetPixelFormat() doesn't supply anything. Take a closer look at it's signature, or even better, at it's documentation.

[quote]
I did some searching though and found that this isn't completely correct, I need to use DescribePixelFormat to apply the pixel format's data to the pfd.
[/quote]
Well, you could do it if only for consistency's sake, but it doesn't really matter. As I explained above, the affecting parameter for SetPixelFormat() is pixel format index, not pfd struct.

[quote]
In terms of the dummy window and contexts, I'm probably just going to leave those in and initialize glew before the pixel format setup like I have, which is probably how it's supposed to work anyway.
[/quote]
Yep, if you finally get that 'dummy' context creation right.

[quote]
But advice is still greatly appreciated if there's a more efficient way to go about doing this!
[/quote]
What's inefficient in this approach? It won't hurt your framerate in any way. And as I mentioned earlier, it'd be precisely that kind of code you're unlikely to ever touch again. If you think it's too much lines of code, you can always reformat 'dummy' window creation to one line.

[quote]
EDIT: I've read the wiki page as suggested, definitely some great stuff in there, it also confirms that it's best to leave my code as it is for the most part. One thing that's a bit vague is where (in "Pixel Format Extensions") it says:

[quote][font="sans-serif"][size="2"]Once you have a pixel format number, you can set it just like any pixel format with SetPixelFormat.[/size][/font][/quote]
They don't say anything much about the pfd in this case, and it seems to me that the attribs parameter would basically make the pfd redundant, other than for legacy purposes. I suppose that's what DescribePixelFormat is for though.
[/quote]
They don't say anything because it doesn't matter. Though I must say it'd be nice if it was explicitly mentioned :)

[quote]
And sorry if I've offended you at all, capricorn... :S
[/quote]
Nah, I wasn't offended, I was frustrated. [img]http://public.gamedev.net/public/style_emoticons/default/cool.gif[/img]

Share this post


Link to post
Share on other sites
[quote name='capricorn' timestamp='1311391934' post='4839202']
[quote name='AutoBot' timestamp='1311379705' post='4839156']
(Please keep in mind that I'm referring to the code in the OP, not in my latter post which I already know doesn't work)

At the beginning of my code I do only call SetPixelFormat with an uninitialized pfd and so forth because its supposed to be a dummy pixel format. It's only purpose is to help give access to the wgl functions, so that later on I can query for a more sophisticated pixel format with MSAA and other features not supported by the generic wgl implementation.
[/quote]
I think you've overestimated the term 'dummy' here :) It is not supposed to be a dummy pixel format. It is supposed to be a real pixel format suitable for OpenGL context creation. Yes, the context is 'dummy' in the sense that you won't do any rendering with it and you'll throw it away after getting a couple of function pointers (wglChoosePixelFormat and wglCreateContextAttribs), but nevertheless it should be a proper OpenGL context. Trying to create one without setting proper pixel format for DC won't get you far (I already mentioned what you might get).
To set proper pixel format, you need to initialize your pfd struct and [b]feed it to WinAPI's ChoosePixelFormat() function[/b]. It will return [b]pixel format index[/b], which you should use in SetPixelFormat() call. Alternatively, you could enumerate all available pixel formats to find suitable index without using ChoosePixelFormat().
In your original snippet you just use index 1, which I suspect just by coincidence happens to be an index for OpenGL-compatible pixel format [b]on your system[/b] (the "miracle" I mentioned earlier). You need to substitute this '1' with proper index.
pfd struct won't have any behavioral effects on SetPixelFormat() call. And yes, you may use that same pfd struct later when you call SetPixelFormat() with another index that you get from wglChoosePixelFormatARB(), it won't do any harm. Again, it's [b]pixel format index[/b] that matters in the SetPixelFormat() call, not the descriptor.
[/quote]
I would like to extend on this part and explain why it is important to do everything correct, even if it's just a dummy context, and it's from my first reply here where I say that function pointers are context dependent.

Consider what happens if you supply some random pixel format like you do. Windows tries to match your random pixel format with some suitable pixel format that is actually supported. What do you think happens to the rest of your program if that pixel format is only supported by Microsoft's own software implementation? The first thing that happens is that wglChoosePixelFormatARB cannot be loaded, becuase Microsoft's implementation doesn't support it. So if you're not setting a proper pixel format, you may not be able to proceed at all.

This is why it's important to do everything properly, even for the pixel format and the context you're not going to use for the rest of the program, but you [i]are [/i]using it for a short time, and it has to be set up properly like any other pixel format or rendering context.

Share this post


Link to post
Share on other sites
This is the definition of
[b]BOOL SetPixelFormat( HDC [/b][i] hdc[/i][b], [/b][b] int [/b][i] iPixelFormat[/i][b],[/b] [b] CONST PIXELFORMATDESCRIPTOR * [/b][i] ppfd[/i] [b]);[/b]

so SetPixelFormat won't modify ppfd. You must set it up and provide it to SetPixelFormat. Also, check MSDN if you want to see how the function works.

Share this post


Link to post
Share on other sites
[quote name='V-man' timestamp='1311418907' post='4839248']
This is the definition of
[b]BOOL SetPixelFormat( HDC [/b][i] hdc[/i][b], [/b][b] int [/b][i] iPixelFormat[/i][b],[/b] [b] CONST PIXELFORMATDESCRIPTOR * [/b][i] ppfd[/i] [b]);[/b]

so SetPixelFormat won't modify ppfd. You must set it up and provide it to SetPixelFormat. Also, check MSDN if you want to see how the function works.
[/quote]

It is not necessary to provide relevant pfd to SetPixelFormat. It'll still either set or fail to set the pixel format you specify via iPixelFormat.

Share this post


Link to post
Share on other sites
[quote name='capricorn' timestamp='1311457482' post='4839374']
[quote name='V-man' timestamp='1311418907' post='4839248']
This is the definition of
[b]BOOL SetPixelFormat( HDC [/b][i] hdc[/i][b], [/b][b] int [/b][i] iPixelFormat[/i][b],[/b] [b] CONST PIXELFORMATDESCRIPTOR * [/b][i] ppfd[/i] [b]);[/b]

so SetPixelFormat won't modify ppfd. You must set it up and provide it to SetPixelFormat. Also, check MSDN if you want to see how the function works.
[/quote]

It is not necessary to provide relevant pfd to SetPixelFormat. It'll still either set or fail to set the pixel format you specify via iPixelFormat.
[/quote]



The MSDN documentation says something about the metafile component using the pfd argument for recording information, probably if someone wanted to access it later, or something.

And I understand about the dummy pixel format now, I think. It seems I got lucky with the '1' format, lol. I thought it was the index to some "default" index before, but now I know that isn't correct. I am a little confused at one part though: what exactly do I [i]have[/i] to initialize in the pfd in order for the ARB functions to work? So far what I've done is made it as close as possible to my attribs array... I suppose that's good enough though. The code still works with that anyway.

I've actually made a lot of improvements to the code, including giving it an object-oriented interface which I mentioned earlier. This time I'm just going to post the whole entire program just for entirety, but keep in mind that I've been using the "GL Tools" library from the OGL Superbible as I'm still learning the low-level functions. Here it is:

[code]#ifndef GLEW_STATIC
#define GLEW_STATIC
#endif

#include <GL\glew.h>
#include <GL\wglew.h>

#include "GLTools.h"
#include "GLShaderManager.h"

class Application
{
public:
void Start(int width = 800, int height = 600);
void End();

void MainLoop();

bool running;
const TCHAR *name;
} app;

class WindowManager
{
HINSTANCE hInstance;

WNDPROC wndProc;

WNDCLASS wndClass;

DWORD wndStyle;
DWORD extStyle;

RECT dimensions;
bool fullScreen;

public:
HWND hWnd;

void CoreInit(int width, int height);
void Start(int width, int height);
void End();

void FullStart(int width, int height)
{
CoreInit(width, height),
Start(width, height);
}

void Restart()
{
// Destroy the window
if(hWnd)
{
DestroyWindow(hWnd);
hWnd = NULL;
}

// Recreate Window
Start(
dimensions.right - dimensions.left,
dimensions.bottom - dimensions.top);
}

void Show()
{
ShowWindow( hWnd, SW_SHOW ),
SetForegroundWindow( hWnd ),
SetFocus( hWnd );
}
} windowManager;

class GraphicsManager
{
HGLRC renderingContext;
HDC deviceContext;

GLBatch triangleBatch;
GLShaderManager shaderManager;

bool sceneReady;

public:
void Start(int width, int height);
void End();

void SetupScene();
void RenderScene();

void ChangeSize(int width, int height);

void CheckErrors(GLuint progName = 0);
} graphicsManager;

///////////////////////////////////////////////////////////////////////////////
// Main program function, called on startup
// First setup the window and OGL state, then enter rendering loop
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
app.Start();

graphicsManager.SetupScene();
app.MainLoop();

app.End();
return 0;
}

///////////////////////////////////////////////////////////////////////////////
// Callback functions to handle all window functions this app cares about.
// Once complete, pass message on to next app in the hook chain.
LRESULT CALLBACK WndProc( HWND hWnd, // Handle For This Window
UINT uMsg, // Message For This Window
WPARAM wParam, // Additional Message Information
LPARAM lParam) // Additional Message Information
{
// Handle relevant messages individually
switch(uMsg)
{
case WM_SIZE:
graphicsManager.ChangeSize(LOWORD(lParam),HIWORD(lParam));
graphicsManager.RenderScene();
break;
case WM_CLOSE:
app.running = false;
PostQuitMessage(0);
return 0;
default:
// Nothing to do now
break;
}

// Pass All Unhandled Messages To DefWindowProc
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

void Application::Start(int width, int height)
{
running = true;
name = TEXT("Triangle");

windowManager.FullStart(width, height);
graphicsManager.Start(width, height);
}

void Application::End()
{
graphicsManager.End();
windowManager.End();
}

void Application::MainLoop()
{
while(running)
{
MSG msg;

// Check for waiting mssgs
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
if (msg.message==WM_QUIT)
{
running = false;
}
else
{
// Deal with mssgs
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else
{
graphicsManager.RenderScene();
}

Sleep(0);
}
}

void WindowManager::CoreInit(int width, int height)
{
hInstance = GetModuleHandle(NULL);
wndProc = (WNDPROC)WndProc;

extStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
wndStyle = WS_OVERLAPPEDWINDOW;

if(MessageBox(NULL, TEXT("Would you like to run in Fullscreen Mode?"), app.name, MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
// Prepare for a mode set to the requested resolution
DEVMODE dm;
memset (&dm, 0, sizeof(dm));

dm.dmSize = sizeof(dm);
dm.dmPelsWidth = width;
dm.dmPelsHeight = height;
dm.dmBitsPerPel = 32;
dm.dmFields = DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

long error = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);

if (error != DISP_CHANGE_SUCCESSFUL)
{
// Oops, something went wrong, let the user know.
if (MessageBox(NULL, TEXT("Could not set fullscreen mode.\n\
Your video card may not support the requested mode.\n\
Use windowed mode instead?"),
app.name, MB_YESNO|MB_ICONEXCLAMATION) == IDYES)
{
fullScreen = false;
extStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
wndStyle = WS_OVERLAPPEDWINDOW;
}
else
{
MessageBox(NULL, TEXT("Program will exit."), TEXT("ERROR"), MB_OK|MB_ICONSTOP);
app.running = false;
}
}
else
{
// Mode set passed, setup the styles for fullscreen
fullScreen = true;
extStyle = WS_EX_APPWINDOW;
wndStyle = WS_POPUP;
ShowCursor(FALSE);
}
}

// Setup window width and height
dimensions.left = 0;
dimensions.right = width;
dimensions.top = 0;
dimensions.bottom = height;

AdjustWindowRectEx(&dimensions, wndStyle, FALSE, extStyle);

//Adjust for adornments
width = dimensions.right - dimensions.left;
height = dimensions.bottom - dimensions.top;

// setup window class
wndClass.lpszClassName = TEXT("OGL_WINCLASS"); // Set the name of the Class
wndClass.lpfnWndProc = wndProc; // Set pointer to WndProc
wndClass.hInstance = hInstance; // Use this module for the module handle
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); // Pick the default mouse cursor
wndClass.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Pick the default windows icons
wndClass.hbrBackground = NULL; // No Background
wndClass.lpszMenuName = NULL; // No menu for this window
wndClass.style = CS_HREDRAW | CS_OWNDC | // set styles for this class, specifically to catch
CS_VREDRAW; // window redraws, unique DC, and resize
wndClass.cbClsExtra = 0; // Extra class memory
wndClass.cbWndExtra = 0; // Extra window memory

// Register the newly defined class
if(!RegisterClass( &wndClass ))
{
app.running = false;
MessageBox(NULL,
TEXT("!!! An error occured creating an OpenGL window. (wndClass registration failure)\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);
}
}

void WindowManager::Start(int width, int height)
{
// Create window
hWnd = CreateWindowEx( extStyle, // Extended style
TEXT("OGL_WINCLASS"), // class name
app.name, // window name
wndStyle |
WS_CLIPSIBLINGS |
WS_CLIPCHILDREN, // window stlye
0, // window position, x
0, // window position, y
width, // height
height, // width
NULL, // Parent window
NULL, // menu
hInstance, // instance
NULL); // pass this to WM_CREATE

}

void WindowManager::End()
{
// Disable fullscreen
if (fullScreen)
{
ChangeDisplaySettings(NULL,0);
ShowCursor(TRUE);
}

// Destroy the window
if(hWnd)
{
DestroyWindow(hWnd);
hWnd = NULL;
}

// Delete the window class
UnregisterClass(TEXT("OGL_WINCLASS"), hInstance);
hInstance = NULL;
ShowCursor(TRUE);
}

void GraphicsManager::Start(int width, int height)
{
sceneReady = false;

int nPixelFormat = NULL;
PIXELFORMATDESCRIPTOR pfd;

// now that we have a window, setup the pixel format descriptor
deviceContext = GetDC(windowManager.hWnd);

memset(&pfd, 0, sizeof(pfd));
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_GENERIC_ACCELERATED | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;
pfd.cDepthBits = 16;

nPixelFormat = ChoosePixelFormat(deviceContext, &pfd);

// Set a dummy pixel format so that we can get access to wgl functions
SetPixelFormat( deviceContext, nPixelFormat, &pfd);

// Create OGL context and make it current
renderingContext = wglCreateContext( deviceContext );
wglMakeCurrent( deviceContext, renderingContext );

if (deviceContext == 0 ||
renderingContext == 0)
{
app.running = false;
MessageBox(NULL,
TEXT("!!! An error occured creating an OpenGL window. (Context failure)\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);
}

// Setup GLEW which loads OGL function pointers
if (GLEW_OK != glewInit())
{
app.running = false;
MessageBox(NULL,
TEXT("!!! An error occured creating an OpenGL window. (GLEW failure)\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);
}

// Now that extensions are setup, delete window and start over picking a real format.
wglMakeCurrent(NULL, NULL);
wglDeleteContext(renderingContext);
ReleaseDC(windowManager.hWnd, deviceContext);
windowManager.Restart();

deviceContext = GetDC(windowManager.hWnd);

UINT nPixCount = 0;

// Specify the important attributes we care about
int pixAttribs[] = { WGL_SUPPORT_OPENGL_ARB, 1, // Must support OGL rendering
WGL_DRAW_TO_WINDOW_ARB, 1, // pf that can run a window
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, // must be HW accelerated
WGL_COLOR_BITS_ARB, 24, // 8 bits of each R, G and B
WGL_DEPTH_BITS_ARB, 16, // 16 bits of depth precision for window
WGL_DOUBLE_BUFFER_ARB, GL_TRUE, // Double buffered context
WGL_SAMPLE_BUFFERS_ARB, GL_TRUE, // MSAA on
WGL_SAMPLES_ARB, 8, // 8x MSAA
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, // pf should be RGBA type
0}; // NULL termination

// Ask OpenGL to find the most relevant format matching our attribs
// Only get one format back.
wglChoosePixelFormatARB(deviceContext, pixAttribs, NULL, 1, &nPixelFormat, &nPixCount);

if(nPixelFormat == NULL)
{
MessageBox(NULL,
TEXT("!!! An error occurred trying to find a MSAA pixel format with the requested attribs.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);

// Try again without MSAA
pixAttribs[15] = 1;
wglChoosePixelFormatARB(deviceContext, pixAttribs, NULL, 1, &nPixelFormat, &nPixCount);

if(nPixelFormat == NULL)
{
// Couldn't find a format, perhaps no 3D HW or drivers are installed
deviceContext = 0;
renderingContext = 0;
app.running = false;
MessageBox(NULL,
TEXT("!!! An error occurred trying to find a pixel format with the requested attribs.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);
}
}

if(nPixelFormat != NULL)
{
// Got a format, now set it as the current one
memset(&pfd, 0, sizeof(pfd));
DescribePixelFormat(deviceContext, nPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
SetPixelFormat( deviceContext, nPixelFormat, &pfd );

GLint attribs[] = {WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 3,
0 };

renderingContext = wglCreateContextAttribsARB(deviceContext, 0, attribs);
if (renderingContext == NULL)
{
MessageBox(NULL,
TEXT("!!! Could not create an OpenGL 3.3 context.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);

attribs[3] = 2;
renderingContext = wglCreateContextAttribsARB(deviceContext, 0, attribs);

if (renderingContext == NULL)
{
MessageBox(NULL,
TEXT("!!! Could not create an OpenGL 3.2 context.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);

attribs[3] = 1;
renderingContext = wglCreateContextAttribsARB(deviceContext, 0, attribs);

if (renderingContext == NULL)
{
MessageBox(NULL,
TEXT("!!! Could not create an OpenGL 3.1 context.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);

attribs[3] = 0;
renderingContext = wglCreateContextAttribsARB(deviceContext, 0, attribs);

if (renderingContext == NULL)
{
MessageBox(NULL,
TEXT("!!! Could not create an OpenGL 3.0 context.\n!!! OpenGL 3.0 and higher are not supported on this system.\n"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);
}
}
}
}

wglMakeCurrent( deviceContext, renderingContext );
}

if (deviceContext == 0 ||
renderingContext == 0)
{
app.running = false;
MessageBox(NULL,
TEXT("!!! An error occured creating an OpenGL window. (Context failure)"),
TEXT("ERROR"),
MB_OK|MB_ICONEXCLAMATION);
}

// If everything went as planned, display the window
if( app.running )
windowManager.Show();

ChangeSize(width, height);
}

void GraphicsManager::End()
{
//Cleanup OGL RC
if(renderingContext)
{
wglMakeCurrent(NULL, NULL);
wglDeleteContext(renderingContext);
renderingContext = NULL;
}

// release the DC
if(deviceContext)
{
ReleaseDC(windowManager.hWnd, deviceContext);
deviceContext = NULL;
}
}

void GraphicsManager::SetupScene()
{
if(app.running == false) return;

// Blue background
glClearColor(0.0f, 0.0f, 1.0f, 1.0f );

shaderManager.InitializeStockShaders();

// Load up a triangle
GLfloat vVerts[] = { -0.5f, 0.0f, 0.0f,
0.5f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f };

triangleBatch.Begin(GL_TRIANGLES, 3);
triangleBatch.CopyVertexData3f(vVerts);
triangleBatch.End();

sceneReady = true;
}

void GraphicsManager::RenderScene()
{
if(app.running == false || sceneReady == false) return;

// Clear the window with current clearing color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
triangleBatch.Draw();

// Do the buffer Swap
SwapBuffers(deviceContext);

CheckErrors();
}

void GraphicsManager::ChangeSize(int width, int height)
{
glViewport(0, 0, width, height);
}

void GraphicsManager::CheckErrors(GLuint progName)
{
GLenum error = glGetError();

if (error != GL_NO_ERROR)
{
fprintf(stderr, "A GL Error has occured\n");
}

GLenum fboStatus = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);

if(fboStatus != GL_FRAMEBUFFER_COMPLETE)
{
switch (fboStatus)
{
case GL_FRAMEBUFFER_UNDEFINED:
// Oops, no window exists?
MessageBox(NULL, TEXT("GL_FRAMEBUFFER_UNDEFINED"), app.name, MB_OK|MB_ICONEXCLAMATION);
break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
// Check the status of each attachment
MessageBox(NULL, TEXT("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"), app.name, MB_OK|MB_ICONEXCLAMATION);
break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
// Attach at least one buffer to the FBO
MessageBox(NULL, TEXT("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"), app.name, MB_OK|MB_ICONEXCLAMATION);
break;
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
// Check that all attachments enabled via
// glDrawBuffers exist in FBO
MessageBox(NULL, TEXT("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"), app.name, MB_OK|MB_ICONEXCLAMATION);
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
// Check that the buffer specified via
// glReadBuffer exists in FBO
MessageBox(NULL, TEXT("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"), app.name, MB_OK|MB_ICONEXCLAMATION);
break;
case GL_FRAMEBUFFER_UNSUPPORTED:
// Reconsider formats used for attached buffers
MessageBox(NULL, TEXT("GL_FRAMEBUFFER_UNSUPPORTED"), app.name, MB_OK|MB_ICONEXCLAMATION);
break;
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
// Make sure the number of samples for each
// attachment is the same
MessageBox(NULL, TEXT("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"), app.name, MB_OK|MB_ICONEXCLAMATION);
break;
//case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
// Make sure the number of layers for each
// attachment is the same
//break;
}
fprintf(stderr,"The framebuffer is not complete\n");
}

if (progName != 0)
{
glValidateProgram(progName);
int iIsProgValid = 0;
glGetProgramiv(progName, GL_VALIDATE_STATUS, &iIsProgValid);
if(iIsProgValid == 0)
{
MessageBox(NULL, TEXT("The current program is not valid\n"), app.name, MB_YESNO|MB_ICONEXCLAMATION);
fprintf(stderr,"The current program is not valid\n");
}
}
}[/code]


This is hopefully the start of a game engine subsystem layout, and of course its only a start at the moment. It still makes the code a lot more readable in my opinion, not to mention easier to use. Any opinions on it? (Sorry if the indentations look all screwed up, it looks a lot better in my IDE for some reason)

Tons of thanks to capricorn, V-man, and Brother Bob for helping me out with this, I definitely know a lot more than before. I'm not sure if there's much more to worry about now, I'm satisfied with how the program's turned out for the most part. Any further suggestions are still welcome, of course. Thanks again!

Share this post


Link to post
Share on other sites
[quote name='AutoBot' timestamp='1311473147' post='4839439']
And I understand about the dummy pixel format now, I think. It seems I got lucky with the '1' format, lol. I thought it was the index to some "default" index before, but now I know that isn't correct. I am a little confused at one part though: what exactly do I [i]have[/i] to initialize in the pfd in order for the ARB functions to work? So far what I've done is made it as close as possible to my attribs array... I suppose that's good enough though. The code still works with that anyway.
[/quote]
Theoretically, you should match them exactly. Any difference in the final pixel formats may result in different contexts, and the function pointers won't work. But in practice, as close as possible is typically good enough. As long as the dummy context is created is a hardware accelerated context, you should be fine. Just be aware that having small differences may tip the scale different ways when choosing between what contexts are created (hardware vs. software implementation), but any reasonable pixel format usually tips it towards the hardware context.

Share this post


Link to post
Share on other sites
[quote name='AutoBot' timestamp='1311473147' post='4839439']
The MSDN documentation says something about the metafile component using the pfd argument for recording information, probably if someone wanted to access it later, or something.
[/quote]
Almost. It's for recording [url="http://msdn.microsoft.com/en-us/library/dd145051(v=VS.85).aspx"]metafiles[/url]. As long as you don't do that, it does not matter at all what pfd you pass to SetPixelFormat().

[quote]
I am a little confused at one part though: what exactly do I [i]have[/i] to initialize in the pfd in order for the ARB functions to work? So far what I've done is made it as close as possible to my attribs array... I suppose that's good enough though. The code still works with that anyway.
[/quote]
Yeah, I'd say that's good enough... almost :) Actually, it'd be better if you removed that PFD_GENERIC_ACCELERATED flag. It's only meaningful to have it for the software GDI pixel formats.

[quote]
This is hopefully the start of a game engine subsystem layout, and of course its only a start at the moment. It still makes the code a lot more readable in my opinion, not to mention easier to use. Any opinions on it? (Sorry if the indentations look all screwed up, it looks a lot better in my IDE for some reason)
[/quote]
Regarding GL, I'd only encourage you to heed Brother Bob's advice and load GL functions after your real context creation. But I can't say for sure if GLEW is currently capable of properly doing that, and it's quite a work to implement it by hand. So you may consider this for the future work at least.

[quote]
Tons of thanks to capricorn, V-man, and Brother Bob for helping me out with this, I definitely know a lot more than before. I'm not sure if there's much more to worry about now, I'm satisfied with how the program's turned out for the most part. Any further suggestions are still welcome, of course. Thanks again!
[/quote]
You're welcome.

Share this post


Link to post
Share on other sites
[quote name='capricorn' timestamp='1311505255' post='4839549']
Regarding GL, I'd only encourage you to heed Brother Bob's advice and load GL functions after your real context creation. But I can't say for sure if GLEW is currently capable of properly doing that, and it's quite a work to implement it by hand. So you may consider this for the future work at least.
[/quote]
Quite a work? I showed how to do it in my first post; it is one line of code for every function you need, and at the moment he only needs two function.

Share this post


Link to post
Share on other sites
[quote name='Brother Bob' timestamp='1311508750' post='4839562']
[quote name='capricorn' timestamp='1311505255' post='4839549']
Regarding GL, I'd only encourage you to heed Brother Bob's advice and load GL functions after your real context creation. But I can't say for sure if GLEW is currently capable of properly doing that, and it's quite a work to implement it by hand. So you may consider this for the future work at least.
[/quote]
Quite a work? I showed how to do it in my first post; it is one line of code for every function you need, and at the moment he only needs two function.
[/quote]

Quite a work doing this by hand ("...[i]load GL functions after your real context creation"[/i]). I mentioned this because I'm not sure if GLEW is consistent with GL's deprecation/removal model (e.g. strict interface for core GL3.3 profile does not include all functions from previous GL versions, but GLEW loader might try and load them anyway, and, what's most frustrating, even succeed if the driver's not conformant). Maybe it's worth asking the authors, it's hard to tell from GLEW code at a glance.

I myself load all GL function pointers by hand (i'm currently working in D so I don't have the luxury of GLEW. Could use Derelict instead, but it doesn't work entirely well, and I don't think this is the proper place to explain why [img]http://public.gamedev.net/public/style_emoticons/default/wink.gif[/img] ), and I'm already considering splitting GL interface into different sections consistent with deprecation model.

EDIT: writing that, I remembered I've read in GL specs that "calling removed functions would result in an INVALID_OPERATION", so I've gone and rechecked that. It seems that conformant driver should still provide removed functions, but they just simply operate as the docs state. Well, maybe it is better to have that than calling a function via null pointer (e.g. if functions were totally absent). Edited by capricorn

Share this post


Link to post
Share on other sites
[quote name='capricorn' timestamp='1311505255' post='4839549']
Regarding GL, I'd only encourage you to heed Brother Bob's advice and load GL functions after your real context creation. But I can't say for sure if GLEW is currently capable of properly doing that, and it's quite a work to implement it by hand. So you may consider this for the future work at least.[/quote]

Yeah, that is a good thing to be aware of just in case GLEW doesn't do the job, though you have to admit it is pretty nice if you want immediate access to GL functions :D

Share this post


Link to post
Share on other sites
[quote name='Brother Bob' timestamp='1311502691' post='4839539']
[quote name='AutoBot' timestamp='1311473147' post='4839439']
And I understand about the dummy pixel format now, I think. It seems I got lucky with the '1' format, lol. I thought it was the index to some "default" index before, but now I know that isn't correct. I am a little confused at one part though: what exactly do I [i]have[/i] to initialize in the pfd in order for the ARB functions to work? So far what I've done is made it as close as possible to my attribs array... I suppose that's good enough though. The code still works with that anyway.
[/quote]
Theoretically, you should match them exactly. Any difference in the final pixel formats may result in different contexts, and the function pointers won't work. But in practice, as close as possible is typically good enough. As long as the dummy context is created is a hardware accelerated context, you should be fine. Just be aware that having small differences may tip the scale different ways when choosing between what contexts are created (hardware vs. software implementation), but any reasonable pixel format usually tips it towards the hardware context.
[/quote]

From what I understand, you either get the GL driver, from which you can get your tons of GL function pointers or #2, you get Microsoft GDI which is GL 1.1

What is there to worry about? Your graphics driver is not going to give you some other set of pointers.
Has anyone ever seen a case where you had a different set of pointers for a hw accelerated context GL 1.2 or 1.3 or 1.3 or 1.4 or 1.5 or 2.0......?

If you read the MSDN documentation, different pixel formats can give you a different set of pointers but I think in practice, you just have 2 cases.

Share this post


Link to post
Share on other sites
[quote name='V-man' timestamp='1311795939' post='4841260']

From what I understand, you either get the GL driver, from which you can get your tons of GL function pointers or #2, you get Microsoft GDI which is GL 1.1
If you read the MSDN documentation, different pixel formats can give you a different set of pointers but I think in practice, you just have 2 cases.
[/quote]


In practice, it's implementation-specific. As I mentioned above, specifications for modern GL versions state that calling removed functions should result in GL_INVALID_OPERATION error. It means that GL implementations (i.e. drivers) [b]will[/b] provide different functionality for different GL versions. Alas, while it is determined what functionality is available for a specific GL version, it is not clearly specified [b]at what level[/b] it is controlled. Function behavior may be controlled on a context level (i.e. function call is forwarded to implementation-specific context object, thus, same function pointer but different behavior on different GL versions), or at function acquisition level (i.e. when you fetch function pointer from the driver via e.g. wglGetProcAddress, thus, different function pointers for different GL versions). The latter approach may introduce very unpleasant (and platform/driver-specific) bugs to your programs, but since it is not explicitly covered by specification, one should not act on assumption that no implementation would ever do this. So to be completely safe, it's better to tie your own function pointers to a specific GL version and load them while a context of that specific version is active. This does no harm (save some programming effort) but deals with both possible cases equally well. Perhaps at some point, the specs will become explicit in this regard, but for now this is the safest route to take.

Share this post


Link to post
Share on other sites
Brother Bob, I guess I misunderstood you.

capricorn, you say that it is implementation specific. I am speaking in terms of what I have experienced.
Sure, keep multiple sets of pointers because according to MSDN, you should.

As for GLEW, IIRC, when I setup my multisampled pixelformat on my window and called glewInit again, it farted. Since glewInit did not like to be called twice, I commented out the one for my multisampled Window.
There is the multiple context GLEW (GLEW MX) but I didn't bother with it.

Share this post


Link to post
Share on other sites
[quote name='V-man' timestamp='1311855127' post='4841542']
capricorn, you say that it is implementation specific. I am speaking in terms of what I have experienced.
Sure, keep multiple sets of pointers because according to MSDN, you should.
[/quote]

I didn't mean to offend, and I apologize if that is the case. I as well have not encountered any problems with contexts in the past, but only recently I realized that it's more luck than anything.

What I meant doesn't only concern MSDN's wglGetProcAddress documentation or Microsoft at all. Here's an excerpt from wglCreateContextAttribs spec (exactly the same holds for glXCreateContextAttribs too):

[quote]
[font="monospace"][size="2"]All OpenGL 3.2 implementations are required to implement the core profile, but implementation of the compatibility profile is optional.[/size][/font]

[font="monospace"][size="2"]...[/size][/font]

[font="monospace"][size="2"]The legacy context creation routines can only return OpenGL 3.1 [/size][/font][font="monospace"][size="2"]contexts if the GL_ARB_compatibility extension is supported, and can [/size][/font][font="monospace"][size="2"]only return OpenGL 3.2 or greater contexts implementing the [/size][/font][font="monospace"][size="2"]compatibility profile. This ensures compatibility for existing[/size][/font][font="monospace"][size="2"] applications. However, 3.0-aware applications are encouraged to use[/size][/font][font="monospace"][size="2"] wglCreateContextAttribsARB instead of the legacy routines. [/size][/font]
[/quote]
This implies that drivers do not have to even expose modern GL functionality when you create context via wglCreateContext (or glXCreateContext). Yes, it seems that currently, both major vendors create compatibility profile of maximum-supported GL versions for legacy contexts, so there seems to be nothing to worry about. Furthermore, nVidia [url="http://developer.nvidia.com/opengl-driver"]clearly states[/url] that it will continue to do so in the future. I've seen no similar claims from AMD.

Here is what happens if I simulate the situation where there is no compatibility profile, thus wglCreateContext creates 2.1 context. I manually create two contexts, 2.1 and 4.1 and try to fetch glDrawArraysInstanced function from both. Here's the code (written in D2):

[code]

auto dc = GetDC(hwnd);

int[] attribs21 =
[
WGL_CONTEXT_MAJOR_VERSION_ARB, 2,
WGL_CONTEXT_MINOR_VERSION_ARB, 1,
0,
];

int[] attribs41 =
[
WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
WGL_CONTEXT_MINOR_VERSION_ARB, 1,
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
0,
];

auto rc21 = wglCreateContextAttribsARB(dc, null, attribs21.ptr);
auto rc41 = wglCreateContextAttribsARB(dc, null, attribs41.ptr);

enum testFunc = "glDrawArraysInstanced";
// Try to fetch glDrawArraysInstanced from 2.1 context
wglMakeCurrent(dc, rc21);
writeln("GL", to!string(cast(char*)glGetString(GL_VERSION)), ", ", testFunc, " address == ", wglGetProcAddress(testFunc));
// Try to fetch glDrawArraysInstanced from 4.1 context
wglMakeCurrent(dc, rc41);
writeln("GL", to!string(cast(char*)glGetString(GL_VERSION)), ", ", testFunc, " address == ", wglGetProcAddress(testFunc));
[/code]

Here are the results (GeForce GTS 450 with 275.33 driver):

[quote]
GL2.1.2, glDrawArraysInstanced address == 0
GL4.1.0, glDrawArraysInstanced address == 69A31840
[/quote]

As you can see, should the driver create 2.1 context instead of compatibility profile of modern one, blind loading of all GL functions on legacy context creation stops working. I would suggest that similar behavior is possible for functions removed from modern version, though on my setup I get the same address for, say, glVertexPointer, it simply behaves as it should on 3.1+ (i.e. returns GL_INVALID_OPERATION). Why I think so is because technically, maintaining the same function pointer in this case must come with an overhead (i.e. version comparison), albeit currently negligible. The more functions will get removed, the more it will become noticeable for programs that still use them (i.e. today's software).

Again, at the current moment, I think both AMD and nVidia create a compatibility profile for legacy contexts. How it will hold in the future is entirely up to them. Personally, I think that one's better off counting on specs rather than on various company claims, because the company can't override specification due to, say, "marketing reasons" and stay compliant. If this sounds only like a matter of preference, then I prefer to not have a headache in a year or two from unexpected bugs on new hardware/drivers.

Share this post


Link to post
Share on other sites
No, you didn't offend me.
My point is that there is 1 single driver and it will always return the same pointer no matter what hw accelerated pixel format you chose.

I'm not sure why you would create a GL 2.1 context and call wglGetProcAddress("glDrawArraysInstanced"). That doesn't make any sense.It would be like calling wglGetProcAddress("EatCrap") and expecting to get a valid pointer.

Share this post


Link to post
Share on other sites
[quote name='V-man' timestamp='1311944524' post='4842123']
No, you didn't offend me.
[/quote]

I'm glad to hear it. English is not my native, and I can't say I'm good at "toning" my messages right, so I was afraid I'm ruining the discussion.

[quote]
My point is that there is 1 single driver and it will always return the same pointer no matter what hw accelerated pixel format you chose.
[/quote]
The issue I'm talking about is not related to pixel formats at all. But the driver is a whole different story. You say there is one single driver. But in fact, there is one [b]current driver version[/b]. What will the new driver version do in half a year? The driver for a next OS version? What will the driver from the same manufacturer, but made for a different platform, do? How can you be sure it'll behave exactly as it does now, considering that specification allows it to behave differently?

[quote]
I'm not sure why you would create a GL 2.1 context and call wglGetProcAddress("glDrawArraysInstanced"). That doesn't make any sense.It would be like calling wglGetProcAddress("EatCrap") and expecting to get a valid pointer.
[/quote]
Yeah, I can see I wasn't clear enough. I'll try to elaborate. The common practice to use modern GL is:

1) create a legacy context via wglCreateContext
2) load [b]all[/b] GL function pointers (including "non-legacy" ones, i.e. 3.0+ functions)
3) create modern GL context
4) use modern GL functionality

According to the specs, there is a flaw in this approach. The drivers don't have to provide modern GL functionality for legacy contexts. They [b]may[/b] provide it, and they currently do, but that is not a requirement. In other words, wglCreateContext (or glXCreateContext, or whatever it is on Mac) may either return 3.1+ compatibility profile context, or 3.0 (or even earlier) one. With my test case I tried to demonstrate what would happen if the driver didn't provide compatibility profile. Namely, point (2) from the above list wouldn't work as it should: not all GL function pointers would be loaded. I took version 2.1 just as a common pre-GL3 version. I might as well have taken 3.0 and some 3.3 function instead. Today's drivers do implement compatibility profile, which means they can provide modern contexts for legacy context creation calls, and thus allow for the above point (2) to work. Maybe they'll continue doing so in the future, maybe they won't. Well, nVidia currently claims it will. AMD - I don't know. Other vendors - I don't know. Sticking with the specs (and loading functions according to GL version) would make the developer a bit (in my case - a lot) happier if the driver vendor drops compatibility profile at some point.

Share this post


Link to post
Share on other sites
[quote name='capricorn' timestamp='1311986468' post='4842373']
The issue I'm talking about is not related to pixel formats at all. But the driver is a whole different story. You say there is one single driver. But in fact, there is one [b]current driver version[/b]. What will the new driver version do in half a year? The driver for a next OS version? What will the driver from the same manufacturer, but made for a different platform, do? How can you be sure it'll behave exactly as it does now, considering that specification allows it to behave differently?[/quote]

That's fine. Code your program to handle the situation in case you get a different set of pointers for different pixel formats. That is what everyone is suppose to be doing anyway. I think you got confused when I said that their is 1 driver and hw accelerated pixel formats, you get the same pointer. I was only speaking in terms of my observation.

[quote name='capricorn' timestamp='1311986468' post='4842373'] According to the specs, there is a flaw in this approach. The drivers don't have to provide modern GL functionality for legacy contexts[/quote]

That's not a flaw. The spec is always right.

Share this post


Link to post
Share on other sites

This topic is 2326 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Forum Statistics

    • Total Topics
      628634
    • Total Posts
      2983964
  • Similar Content

    • By Vortez
      Hi guys, im having a little problem fixing a bug in my program since i multi-threaded it. The app is a little video converter i wrote for fun. To help you understand the problem, ill first explain how the program is made. Im using Delphi to do the GUI/Windows part of the code, then im loading a c++ dll for the video conversion. The problem is not related to the video conversion, but with OpenGL only. The code work like this:

       
      DWORD WINAPI JobThread(void *params) { for each files { ... _ConvertVideo(input_name, output_name); } } void EXP_FUNC _ConvertVideo(char *input_fname, char *output_fname) { // Note that im re-initializing and cleaning up OpenGL each time this function is called... CGLEngine GLEngine; ... // Initialize OpenGL GLEngine.Initialize(render_wnd); GLEngine.CreateTexture(dst_width, dst_height, 4); // decode the video and render the frames... for each frames { ... GLEngine.UpdateTexture(pY, pU, pV); GLEngine.Render(); } cleanup: GLEngine.DeleteTexture(); GLEngine.Shutdown(); // video cleanup code... }  
      With a single thread, everything work fine. The problem arise when im starting the thread for a second time, nothing get rendered, but the encoding work fine. For example, if i start the thread with 3 files to process, all of them render fine, but if i start the thread again (with the same batch of files or not...), OpenGL fail to render anything.
      Im pretty sure it has something to do with the rendering context (or maybe the window DC?). Here a snippet of my OpenGL class:
      bool CGLEngine::Initialize(HWND hWnd) { hDC = GetDC(hWnd); if(!SetupPixelFormatDescriptor(hDC)){ ReleaseDC(hWnd, hDC); return false; } hRC = wglCreateContext(hDC); wglMakeCurrent(hDC, hRC); // more code ... return true; } void CGLEngine::Shutdown() { // some code... if(hRC){wglDeleteContext(hRC);} if(hDC){ReleaseDC(hWnd, hDC);} hDC = hRC = NULL; }  
      The full source code is available here. The most relevant files are:
      -OpenGL class (header / source)
      -Main code (header / source)
       
      Thx in advance if anyone can help me.
    • By DiligentDev
      This article uses material originally posted on Diligent Graphics web site.
      Introduction
      Graphics APIs have come a long way from small set of basic commands allowing limited control of configurable stages of early 3D accelerators to very low-level programming interfaces exposing almost every aspect of the underlying graphics hardware. Next-generation APIs, Direct3D12 by Microsoft and Vulkan by Khronos are relatively new and have only started getting widespread adoption and support from hardware vendors, while Direct3D11 and OpenGL are still considered industry standard. New APIs can provide substantial performance and functional improvements, but may not be supported by older hardware. An application targeting wide range of platforms needs to support Direct3D11 and OpenGL. New APIs will not give any advantage when used with old paradigms. It is totally possible to add Direct3D12 support to an existing renderer by implementing Direct3D11 interface through Direct3D12, but this will give zero benefits. Instead, new approaches and rendering architectures that leverage flexibility provided by the next-generation APIs are expected to be developed.
      There are at least four APIs (Direct3D11, Direct3D12, OpenGL/GLES, Vulkan, plus Apple's Metal for iOS and osX platforms) that a cross-platform 3D application may need to support. Writing separate code paths for all APIs is clearly not an option for any real-world application and the need for a cross-platform graphics abstraction layer is evident. The following is the list of requirements that I believe such layer needs to satisfy:
      Lightweight abstractions: the API should be as close to the underlying native APIs as possible to allow an application leverage all available low-level functionality. In many cases this requirement is difficult to achieve because specific features exposed by different APIs may vary considerably. Low performance overhead: the abstraction layer needs to be efficient from performance point of view. If it introduces considerable amount of overhead, there is no point in using it. Convenience: the API needs to be convenient to use. It needs to assist developers in achieving their goals not limiting their control of the graphics hardware. Multithreading: ability to efficiently parallelize work is in the core of Direct3D12 and Vulkan and one of the main selling points of the new APIs. Support for multithreading in a cross-platform layer is a must. Extensibility: no matter how well the API is designed, it still introduces some level of abstraction. In some cases the most efficient way to implement certain functionality is to directly use native API. The abstraction layer needs to provide seamless interoperability with the underlying native APIs to provide a way for the app to add features that may be missing. Diligent Engine is designed to solve these problems. Its main goal is to take advantages of the next-generation APIs such as Direct3D12 and Vulkan, but at the same time provide support for older platforms via Direct3D11, OpenGL and OpenGLES. Diligent Engine exposes common C++ front-end for all supported platforms and provides interoperability with underlying native APIs. It also supports integration with Unity and is designed to be used as graphics subsystem in a standalone game engine, Unity native plugin or any other 3D application. Full source code is available for download at GitHub and is free to use.
      Overview
      Diligent Engine API takes some features from Direct3D11 and Direct3D12 as well as introduces new concepts to hide certain platform-specific details and make the system easy to use. It contains the following main components:
      Render device (IRenderDevice  interface) is responsible for creating all other objects (textures, buffers, shaders, pipeline states, etc.).
      Device context (IDeviceContext interface) is the main interface for recording rendering commands. Similar to Direct3D11, there are immediate context and deferred contexts (which in Direct3D11 implementation map directly to the corresponding context types). Immediate context combines command queue and command list recording functionality. It records commands and submits the command list for execution when it contains sufficient number of commands. Deferred contexts are designed to only record command lists that can be submitted for execution through the immediate context.
      An alternative way to design the API would be to expose command queue and command lists directly. This approach however does not map well to Direct3D11 and OpenGL. Besides, some functionality (such as dynamic descriptor allocation) can be much more efficiently implemented when it is known that a command list is recorded by a certain deferred context from some thread.
      The approach taken in the engine does not limit scalability as the application is expected to create one deferred context per thread, and internally every deferred context records a command list in lock-free fashion. At the same time this approach maps well to older APIs.
      In current implementation, only one immediate context that uses default graphics command queue is created. To support multiple GPUs or multiple command queue types (compute, copy, etc.), it is natural to have one immediate contexts per queue. Cross-context synchronization utilities will be necessary.
      Swap Chain (ISwapChain interface). Swap chain interface represents a chain of back buffers and is responsible for showing the final rendered image on the screen.
      Render device, device contexts and swap chain are created during the engine initialization.
      Resources (ITexture and IBuffer interfaces). There are two types of resources - textures and buffers. There are many different texture types (2D textures, 3D textures, texture array, cubmepas, etc.) that can all be represented by ITexture interface.
      Resources Views (ITextureView and IBufferView interfaces). While textures and buffers are mere data containers, texture views and buffer views describe how the data should be interpreted. For instance, a 2D texture can be used as a render target for rendering commands or as a shader resource.
      Pipeline State (IPipelineState interface). GPU pipeline contains many configurable stages (depth-stencil, rasterizer and blend states, different shader stage, etc.). Direct3D11 uses coarse-grain objects to set all stage parameters at once (for instance, a rasterizer object encompasses all rasterizer attributes), while OpenGL contains myriad functions to fine-grain control every individual attribute of every stage. Both methods do not map very well to modern graphics hardware that combines all states into one monolithic state under the hood. Direct3D12 directly exposes pipeline state object in the API, and Diligent Engine uses the same approach.
      Shader Resource Binding (IShaderResourceBinding interface). Shaders are programs that run on the GPU. Shaders may access various resources (textures and buffers), and setting correspondence between shader variables and actual resources is called resource binding. Resource binding implementation varies considerably between different API. Diligent Engine introduces a new object called shader resource binding that encompasses all resources needed by all shaders in a certain pipeline state.
      API Basics
      Creating Resources
      Device resources are created by the render device. The two main resource types are buffers, which represent linear memory, and textures, which use memory layouts optimized for fast filtering. Graphics APIs usually have a native object that represents linear buffer. Diligent Engine uses IBuffer interface as an abstraction for a native buffer. To create a buffer, one needs to populate BufferDesc structure and call IRenderDevice::CreateBuffer() method as in the following example:
      BufferDesc BuffDesc; BufferDesc.Name = "Uniform buffer"; BuffDesc.BindFlags = BIND_UNIFORM_BUFFER; BuffDesc.Usage = USAGE_DYNAMIC; BuffDesc.uiSizeInBytes = sizeof(ShaderConstants); BuffDesc.CPUAccessFlags = CPU_ACCESS_WRITE; m_pDevice->CreateBuffer( BuffDesc, BufferData(), &m_pConstantBuffer ); While there is usually just one buffer object, different APIs use very different approaches to represent textures. For instance, in Direct3D11, there are ID3D11Texture1D, ID3D11Texture2D, and ID3D11Texture3D objects. In OpenGL, there is individual object for every texture dimension (1D, 2D, 3D, Cube), which may be a texture array, which may also be multisampled (i.e. GL_TEXTURE_2D_MULTISAMPLE_ARRAY). As a result there are nine different GL texture types that Diligent Engine may create under the hood. In Direct3D12, there is only one resource interface. Diligent Engine hides all these details in ITexture interface. There is only one  IRenderDevice::CreateTexture() method that is capable of creating all texture types. Dimension, format, array size and all other parameters are specified by the members of the TextureDesc structure:
      TextureDesc TexDesc; TexDesc.Name = "My texture 2D"; TexDesc.Type = TEXTURE_TYPE_2D; TexDesc.Width = 1024; TexDesc.Height = 1024; TexDesc.Format = TEX_FORMAT_RGBA8_UNORM; TexDesc.Usage = USAGE_DEFAULT; TexDesc.BindFlags = BIND_SHADER_RESOURCE | BIND_RENDER_TARGET | BIND_UNORDERED_ACCESS; TexDesc.Name = "Sample 2D Texture"; m_pRenderDevice->CreateTexture( TexDesc, TextureData(), &m_pTestTex ); If native API supports multithreaded resource creation, textures and buffers can be created by multiple threads simultaneously.
      Interoperability with native API provides access to the native buffer/texture objects and also allows creating Diligent Engine objects from native handles. It allows applications seamlessly integrate native API-specific code with Diligent Engine.
      Next-generation APIs allow fine level-control over how resources are allocated. Diligent Engine does not currently expose this functionality, but it can be added by implementing IResourceAllocator interface that encapsulates specifics of resource allocation and providing this interface to CreateBuffer() or CreateTexture() methods. If null is provided, default allocator should be used.
      Initializing the Pipeline State
      As it was mentioned earlier, Diligent Engine follows next-gen APIs to configure the graphics/compute pipeline. One big Pipelines State Object (PSO) encompasses all required states (all shader stages, input layout description, depth stencil, rasterizer and blend state descriptions etc.). This approach maps directly to Direct3D12/Vulkan, but is also beneficial for older APIs as it eliminates pipeline misconfiguration errors. With many individual calls tweaking various GPU pipeline settings it is very easy to forget to set one of the states or assume the stage is already properly configured when in fact it is not. Using pipeline state object helps avoid these problems as all stages are configured at once.
      Creating Shaders
      While in earlier APIs shaders were bound separately, in the next-generation APIs as well as in Diligent Engine shaders are part of the pipeline state object. The biggest challenge when authoring shaders is that Direct3D and OpenGL/Vulkan use different shader languages (while Apple uses yet another language in their Metal API). Maintaining two versions of every shader is not an option for real applications and Diligent Engine implements shader source code converter that allows shaders authored in HLSL to be translated to GLSL. To create a shader, one needs to populate ShaderCreationAttribs structure. SourceLanguage member of this structure tells the system which language the shader is authored in:
      SHADER_SOURCE_LANGUAGE_DEFAULT - The shader source language matches the underlying graphics API: HLSL for Direct3D11/Direct3D12 mode, and GLSL for OpenGL and OpenGLES modes. SHADER_SOURCE_LANGUAGE_HLSL - The shader source is in HLSL. For OpenGL and OpenGLES modes, the source code will be converted to GLSL. SHADER_SOURCE_LANGUAGE_GLSL - The shader source is in GLSL. There is currently no GLSL to HLSL converter, so this value should only be used for OpenGL and OpenGLES modes. There are two ways to provide the shader source code. The first way is to use Source member. The second way is to provide a file path in FilePath member. Since the engine is entirely decoupled from the platform and the host file system is platform-dependent, the structure exposes pShaderSourceStreamFactory member that is intended to provide the engine access to the file system. If FilePath is provided, shader source factory must also be provided. If the shader source contains any #include directives, the source stream factory will also be used to load these files. The engine provides default implementation for every supported platform that should be sufficient in most cases. Custom implementation can be provided when needed.
      When sampling a texture in a shader, the texture sampler was traditionally specified as separate object that was bound to the pipeline at run time or set as part of the texture object itself. However, in most cases it is known beforehand what kind of sampler will be used in the shader. Next-generation APIs expose new type of sampler called static sampler that can be initialized directly in the pipeline state. Diligent Engine exposes this functionality: when creating a shader, textures can be assigned static samplers. If static sampler is assigned, it will always be used instead of the one initialized in the texture shader resource view. To initialize static samplers, prepare an array of StaticSamplerDesc structures and initialize StaticSamplers and NumStaticSamplers members. Static samplers are more efficient and it is highly recommended to use them whenever possible. On older APIs, static samplers are emulated via generic sampler objects.
      The following is an example of shader initialization:
      ShaderCreationAttribs Attrs; Attrs.Desc.Name = "MyPixelShader"; Attrs.FilePath = "MyShaderFile.fx"; Attrs.SearchDirectories = "shaders;shaders\\inc;"; Attrs.EntryPoint = "MyPixelShader"; Attrs.Desc.ShaderType = SHADER_TYPE_PIXEL; Attrs.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL; BasicShaderSourceStreamFactory BasicSSSFactory(Attrs.SearchDirectories); Attrs.pShaderSourceStreamFactory = &BasicSSSFactory; ShaderVariableDesc ShaderVars[] = {     {"g_StaticTexture", SHADER_VARIABLE_TYPE_STATIC},     {"g_MutableTexture", SHADER_VARIABLE_TYPE_MUTABLE},     {"g_DynamicTexture", SHADER_VARIABLE_TYPE_DYNAMIC} }; Attrs.Desc.VariableDesc = ShaderVars; Attrs.Desc.NumVariables = _countof(ShaderVars); Attrs.Desc.DefaultVariableType = SHADER_VARIABLE_TYPE_STATIC; StaticSamplerDesc StaticSampler; StaticSampler.Desc.MinFilter = FILTER_TYPE_LINEAR; StaticSampler.Desc.MagFilter = FILTER_TYPE_LINEAR; StaticSampler.Desc.MipFilter = FILTER_TYPE_LINEAR; StaticSampler.TextureName = "g_MutableTexture"; Attrs.Desc.NumStaticSamplers = 1; Attrs.Desc.StaticSamplers = &StaticSampler; ShaderMacroHelper Macros; Macros.AddShaderMacro("USE_SHADOWS", 1); Macros.AddShaderMacro("NUM_SHADOW_SAMPLES", 4); Macros.Finalize(); Attrs.Macros = Macros; RefCntAutoPtr<IShader> pShader; m_pDevice->CreateShader( Attrs, &pShader );
      Creating the Pipeline State Object
      After all required shaders are created, the rest of the fields of the PipelineStateDesc structure provide depth-stencil, rasterizer, and blend state descriptions, the number and format of render targets, input layout format, etc. For instance, rasterizer state can be described as follows:
      PipelineStateDesc PSODesc; RasterizerStateDesc &RasterizerDesc = PSODesc.GraphicsPipeline.RasterizerDesc; RasterizerDesc.FillMode = FILL_MODE_SOLID; RasterizerDesc.CullMode = CULL_MODE_NONE; RasterizerDesc.FrontCounterClockwise = True; RasterizerDesc.ScissorEnable = True; RasterizerDesc.AntialiasedLineEnable = False; Depth-stencil and blend states are defined in a similar fashion.
      Another important thing that pipeline state object encompasses is the input layout description that defines how inputs to the vertex shader, which is the very first shader stage, should be read from the memory. Input layout may define several vertex streams that contain values of different formats and sizes:
      // Define input layout InputLayoutDesc &Layout = PSODesc.GraphicsPipeline.InputLayout; LayoutElement TextLayoutElems[] = {     LayoutElement( 0, 0, 3, VT_FLOAT32, False ),     LayoutElement( 1, 0, 4, VT_UINT8, True ),     LayoutElement( 2, 0, 2, VT_FLOAT32, False ), }; Layout.LayoutElements = TextLayoutElems; Layout.NumElements = _countof( TextLayoutElems ); Finally, pipeline state defines primitive topology type. When all required members are initialized, a pipeline state object can be created by IRenderDevice::CreatePipelineState() method:
      // Define shader and primitive topology PSODesc.GraphicsPipeline.PrimitiveTopologyType = PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; PSODesc.GraphicsPipeline.pVS = pVertexShader; PSODesc.GraphicsPipeline.pPS = pPixelShader; PSODesc.Name = "My pipeline state"; m_pDev->CreatePipelineState(PSODesc, &m_pPSO); When PSO object is bound to the pipeline, the engine invokes all API-specific commands to set all states specified by the object. In case of Direct3D12 this maps directly to setting the D3D12 PSO object. In case of Direct3D11, this involves setting individual state objects (such as rasterizer and blend states), shaders, input layout etc. In case of OpenGL, this requires a number of fine-grain state tweaking calls. Diligent Engine keeps track of currently bound states and only calls functions to update these states that have actually changed.
      Binding Shader Resources
      Direct3D11 and OpenGL utilize fine-grain resource binding models, where an application binds individual buffers and textures to certain shader or program resource binding slots. Direct3D12 uses a very different approach, where resource descriptors are grouped into tables, and an application can bind all resources in the table at once by setting the table in the command list. Resource binding model in Diligent Engine is designed to leverage this new method. It introduces a new object called shader resource binding that encapsulates all resource bindings required for all shaders in a certain pipeline state. It also introduces the classification of shader variables based on the frequency of expected change that helps the engine group them into tables under the hood:
      Static variables (SHADER_VARIABLE_TYPE_STATIC) are variables that are expected to be set only once. They may not be changed once a resource is bound to the variable. Such variables are intended to hold global constants such as camera attributes or global light attributes constant buffers. Mutable variables (SHADER_VARIABLE_TYPE_MUTABLE) define resources that are expected to change on a per-material frequency. Examples may include diffuse textures, normal maps etc. Dynamic variables (SHADER_VARIABLE_TYPE_DYNAMIC) are expected to change frequently and randomly. Shader variable type must be specified during shader creation by populating an array of ShaderVariableDesc structures and initializing ShaderCreationAttribs::Desc::VariableDesc and ShaderCreationAttribs::Desc::NumVariables members (see example of shader creation above).
      Static variables cannot be changed once a resource is bound to the variable. They are bound directly to the shader object. For instance, a shadow map texture is not expected to change after it is created, so it can be bound directly to the shader:
      PixelShader->GetShaderVariable( "g_tex2DShadowMap" )->Set( pShadowMapSRV ); Mutable and dynamic variables are bound via a new Shader Resource Binding object (SRB) that is created by the pipeline state (IPipelineState::CreateShaderResourceBinding()):
      m_pPSO->CreateShaderResourceBinding(&m_pSRB); Note that an SRB is only compatible with the pipeline state it was created from. SRB object inherits all static bindings from shaders in the pipeline, but is not allowed to change them.
      Mutable resources can only be set once for every instance of a shader resource binding. Such resources are intended to define specific material properties. For instance, a diffuse texture for a specific material is not expected to change once the material is defined and can be set right after the SRB object has been created:
      m_pSRB->GetVariable(SHADER_TYPE_PIXEL, "tex2DDiffuse")->Set(pDiffuseTexSRV); In some cases it is necessary to bind a new resource to a variable every time a draw command is invoked. Such variables should be labeled as dynamic, which will allow setting them multiple times through the same SRB object:
      m_pSRB->GetVariable(SHADER_TYPE_VERTEX, "cbRandomAttribs")->Set(pRandomAttrsCB); Under the hood, the engine pre-allocates descriptor tables for static and mutable resources when an SRB objcet is created. Space for dynamic resources is dynamically allocated at run time. Static and mutable resources are thus more efficient and should be used whenever possible.
      As you can see, Diligent Engine does not expose low-level details of how resources are bound to shader variables. One reason for this is that these details are very different for various APIs. The other reason is that using low-level binding methods is extremely error-prone: it is very easy to forget to bind some resource, or bind incorrect resource such as bind a buffer to the variable that is in fact a texture, especially during shader development when everything changes fast. Diligent Engine instead relies on shader reflection system to automatically query the list of all shader variables. Grouping variables based on three types mentioned above allows the engine to create optimized layout and take heavy lifting of matching resources to API-specific resource location, register or descriptor in the table.
      This post gives more details about the resource binding model in Diligent Engine.
      Setting the Pipeline State and Committing Shader Resources
      Before any draw or compute command can be invoked, the pipeline state needs to be bound to the context:
      m_pContext->SetPipelineState(m_pPSO); Under the hood, the engine sets the internal PSO object in the command list or calls all the required native API functions to properly configure all pipeline stages.
      The next step is to bind all required shader resources to the GPU pipeline, which is accomplished by IDeviceContext::CommitShaderResources() method:
      m_pContext->CommitShaderResources(m_pSRB, COMMIT_SHADER_RESOURCES_FLAG_TRANSITION_RESOURCES); The method takes a pointer to the shader resource binding object and makes all resources the object holds available for the shaders. In the case of D3D12, this only requires setting appropriate descriptor tables in the command list. For older APIs, this typically requires setting all resources individually.
      Next-generation APIs require the application to track the state of every resource and explicitly inform the system about all state transitions. For instance, if a texture was used as render target before, while the next draw command is going to use it as shader resource, a transition barrier needs to be executed. Diligent Engine does the heavy lifting of state tracking.  When CommitShaderResources() method is called with COMMIT_SHADER_RESOURCES_FLAG_TRANSITION_RESOURCES flag, the engine commits and transitions resources to correct states at the same time. Note that transitioning resources does introduce some overhead. The engine tracks state of every resource and it will not issue the barrier if the state is already correct. But checking resource state is an overhead that can sometimes be avoided. The engine provides IDeviceContext::TransitionShaderResources() method that only transitions resources:
      m_pContext->TransitionShaderResources(m_pPSO, m_pSRB); In some scenarios it is more efficient to transition resources once and then only commit them.
      Invoking Draw Command
      The final step is to set states that are not part of the PSO, such as render targets, vertex and index buffers. Diligent Engine uses Direct3D11-syle API that is translated to other native API calls under the hood:
      ITextureView *pRTVs[] = {m_pRTV}; m_pContext->SetRenderTargets(_countof( pRTVs ), pRTVs, m_pDSV); // Clear render target and depth buffer const float zero[4] = {0, 0, 0, 0}; m_pContext->ClearRenderTarget(nullptr, zero); m_pContext->ClearDepthStencil(nullptr, CLEAR_DEPTH_FLAG, 1.f); // Set vertex and index buffers IBuffer *buffer[] = {m_pVertexBuffer}; Uint32 offsets[] = {0}; Uint32 strides[] = {sizeof(MyVertex)}; m_pContext->SetVertexBuffers(0, 1, buffer, strides, offsets, SET_VERTEX_BUFFERS_FLAG_RESET); m_pContext->SetIndexBuffer(m_pIndexBuffer, 0); Different native APIs use various set of function to execute draw commands depending on command details (if the command is indexed, instanced or both, what offsets in the source buffers are used etc.). For instance, there are 5 draw commands in Direct3D11 and more than 9 commands in OpenGL with something like glDrawElementsInstancedBaseVertexBaseInstance not uncommon. Diligent Engine hides all details with single IDeviceContext::Draw() method that takes takes DrawAttribs structure as an argument. The structure members define all attributes required to perform the command (primitive topology, number of vertices or indices, if draw call is indexed or not, if draw call is instanced or not, if draw call is indirect or not, etc.). For example:
      DrawAttribs attrs; attrs.IsIndexed = true; attrs.IndexType = VT_UINT16; attrs.NumIndices = 36; attrs.Topology = PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; pContext->Draw(attrs); For compute commands, there is IDeviceContext::DispatchCompute() method that takes DispatchComputeAttribs structure that defines compute grid dimension.
      Source Code
      Full engine source code is available on GitHub and is free to use. The repository contains two samples, asteroids performance benchmark and example Unity project that uses Diligent Engine in native plugin.
      AntTweakBar sample is Diligent Engine’s “Hello World” example.

       
      Atmospheric scattering sample is a more advanced example. It demonstrates how Diligent Engine can be used to implement various rendering tasks: loading textures from files, using complex shaders, rendering to multiple render targets, using compute shaders and unordered access views, etc.

      Asteroids performance benchmark is based on this demo developed by Intel. It renders 50,000 unique textured asteroids and allows comparing performance of Direct3D11 and Direct3D12 implementations. Every asteroid is a combination of one of 1000 unique meshes and one of 10 unique textures.

      Finally, there is an example project that shows how Diligent Engine can be integrated with Unity.

      Future Work
      The engine is under active development. It currently supports Windows desktop, Universal Windows and Android platforms. Direct3D11, Direct3D12, OpenGL/GLES backends are now feature complete. Vulkan backend is coming next, and support for more platforms is planned.
    • By michaeldodis
      I've started building a small library, that can render pie menu GUI in legacy opengl, planning to add some traditional elements of course.
      It's interface is similar to something you'd see in IMGUI. It's written in C.
      Early version of the library
      I'd really love to hear anyone's thoughts on this, any suggestions on what features you'd want to see in a library like this? 
      Thanks in advance!
    • By Michael Aganier
      I have this 2D game which currently eats up to 200k draw calls per frame. The performance is acceptable, but I want a lot more than that. I need to batch my sprite drawing, but I'm not sure what's the best way in OpenGL 3.3 (to keep compatibility with older machines).
      Each individual sprite move independently almost every frame and their is a variety of textures and animations. What's the fastest way to render a lot of dynamic sprites? Should I map all my data to the GPU and update it all the time? Should I setup my data in the RAM and send it to the GPU all at once? Should I use one draw call per sprite and let the matrices apply the transformations or should I compute the transformations in a world vbo on the CPU so that they can be rendered by a single draw call?
    • By zolgoz
      Hi!

      I've recently started with opengl and just managed to write my first shader using the phong model. I want to learn more but I don't know where to begin, does anyone know of good articles or algorithms to start with?
      Thanks in advance.
  • Popular Now