Sluggish OpenGL performance using Win32

Started by
13 comments, last by Alex Melbourne 11 years, 9 months ago
Just to simplify everything here is a copy of the source file in question:
[source lang="cpp"]
/*
** main.cpp
*/

# include <stdlib.h>
# include <time.h>

# include <windows.h>
# if defined(_WINDOWS_)
# include <gl\gl.h>
# endif /* defined(_WINDOWS_) */

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

HDC hdc;
PIXELFORMATDESCRIPTOR pfd;
int format;
HGLRC hglrc;

/*
** entry point
*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WNDCLASS WndClass;
HWND hWnd;
MSG msg;

WndClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
WndClass.lpfnWndProc = WndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = hInstance;
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hbrBackground = NULL;
WndClass.lpszMenuName = NULL;
WndClass.lpszClassName = "greyscale";

if(RegisterClass(&WndClass) == 0)
{
MessageBox(NULL, "Unable to register the window's class.", "", MB_OK | MB_ICONERROR);
return 0;
}

hWnd = CreateWindow("greyscale", "greyscale", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 800, 600, NULL, NULL, hInstance, NULL);
if(!hWnd)
{
MessageBox(NULL, "Failed to create window.", "", MB_OK | MB_ICONERROR);
return 0;
}

hdc = GetDC(hWnd);

ZeroMemory(&pfd, sizeof(PIXELFORMATDESCRIPTOR));
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;

format = ChoosePixelFormat(hdc, &pfd);
SetPixelFormat(hdc, format, &pfd);

hglrc = wglCreateContext(hdc);
if(!hglrc)
{
MessageBox(NULL, "Failed to create context for OpenGL.", "", MB_OK | MB_ICONERROR);
return 0;
}

if(wglMakeCurrent(hdc, hglrc) == FALSE)
{
MessageBox(NULL, "Unable to change the current context.", "", MB_OK | MB_ICONERROR);
return 0;
}

ReleaseDC(hWnd, hdc);

ShowWindow(hWnd, nShowCmd);
UpdateWindow(hWnd);

glClearColor(0.0f, 1.0f, 0.0f, 0.0f);

for(;;)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message == WM_QUIT)
break;

TranslateMessage(&msg);
DispatchMessage(&msg);
}

glClear(GL_COLOR_BUFFER_BIT);
glFlush();
}

wglMakeCurrent(NULL, NULL);
wglDeleteContext(hglrc);

return msg.wParam;
}

/*
** message handler
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_NULL:
{
return 0;
}

case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}

case WM_CLOSE:
{
if(MessageBox(hWnd, "Are you sure you want to exit?", "", MB_YESNO | MB_ICONQUESTION) == IDYES)
DestroyWindow(hWnd);

return 0;
}
}

return DefWindowProc(hWnd, msg, wParam, lParam);
}

[/source]

This has been bugging me for a while so I thought I might as well see if it's happened to anyone else or see if someone can tell me how badly I'm going wrong.

I've finally managed to get OpenGL rendering within Win32 (and not through the other third party libraries like GLUT).

My issue though is that when I actually include rendering commands in the main loop the window appears to become unresponsive. I say appears because I can drag it around occasionally and it takes ages to respond.

The issue disappears with I remove the Clear and Flush commands in the loop.

I've added a timer (not shown above) to limit the speed at when the rendering runs and the issue then goes away. The only conclusion I can come to is that the OpenGL commands take to long and cause PeekMessage to miss messages in the queue (that was all I had, I only started Win32 yesterday so I'm probably wrong).

The code compiles correctly and produces no errors in Visual Studio. What exactly is causing this behaviour?
Advertisement
Well, you don't want to use 'glFlush', that's for certain. While that can flush commands to be executed you are basically telling the driver 'clear the same bit of memory over and over' - you need to call the 'swapbuffers' function (I can't recall the Win32 function for it) to make anything really happen.

The other consideration is do you even get a hardware context? If this is executing in software you are basically running a tight loop clearly memory over and over again and the OpenGL1.1 Software implementation is basically a bit load of rubbish so might explain things.

In short;
- check you aren't in software
- don't call glFlush EVER (until you know when you should call it in the future of course)
- even if in hardware your test case is broken so drawing conclusions from it aren't valid - GL calls don't take a long time to execute as most just push commands into a command queue and do work 'later' (generally a few frames later)
Well, you don't want to use 'glFlush', that's for certain.


But this is a single buffered program. What exactly would I end up swapping too?

The other consideration is do you even get a hardware context? [...] the OpenGL1.1 Software implementation is basically a bit load of rubbish so might explain things.


Is that not anything to do with the CreateContext command? I found a copy of the OpenGL Programming Guide (Release 1) so everything I've learnt is from there.

even if in hardware your test case is broken so drawing conclusions from it aren't valid - GL calls don't take a long time to execute as most just push commands into a command queue and do work 'later' (generally a few frames later)


I figured I was probably wrong (otherwise you wouldn't have the 'remove' choice when calling PeekMessage) but I'm honestly confused why it seems to crawl to a standstill when it isn't regulated.
Oh, i missed the single buffered bit.... chances are between that, the 24bit color restriction and just grabbing the first context you might well be running on a slow software path.

Any reason you DONT want double buffering anyway?

Just to simplify everything here is a copy of the source file in question:
[source lang="cpp"]
/*
** main.cpp
*/

# include <stdlib.h>
# include <time.h>

# include <windows.h>
# if defined(_WINDOWS_)
# include <gl\gl.h>
# endif /* defined(_WINDOWS_) */

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

HDC hdc;
PIXELFORMATDESCRIPTOR pfd;
int format;
HGLRC hglrc;

/*
** entry point
*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WNDCLASS WndClass;
HWND hWnd;
MSG msg;

WndClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
WndClass.lpfnWndProc = WndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = hInstance;
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hbrBackground = NULL;
WndClass.lpszMenuName = NULL;
WndClass.lpszClassName = "greyscale";

if(RegisterClass(&WndClass) == 0)
{
MessageBox(NULL, "Unable to register the window's class.", "", MB_OK | MB_ICONERROR);
return 0;
}

hWnd = CreateWindow("greyscale", "greyscale", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 800, 600, NULL, NULL, hInstance, NULL);
if(!hWnd)
{
MessageBox(NULL, "Failed to create window.", "", MB_OK | MB_ICONERROR);
return 0;
}

hdc = GetDC(hWnd);

ZeroMemory(&pfd, sizeof(PIXELFORMATDESCRIPTOR));
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;

format = ChoosePixelFormat(hdc, &pfd);
SetPixelFormat(hdc, format, &pfd);

hglrc = wglCreateContext(hdc);
if(!hglrc)
{
MessageBox(NULL, "Failed to create context for OpenGL.", "", MB_OK | MB_ICONERROR);
return 0;
}

if(wglMakeCurrent(hdc, hglrc) == FALSE)
{
MessageBox(NULL, "Unable to change the current context.", "", MB_OK | MB_ICONERROR);
return 0;
}

ReleaseDC(hWnd, hdc);

ShowWindow(hWnd, nShowCmd);
UpdateWindow(hWnd);

glClearColor(0.0f, 1.0f, 0.0f, 0.0f);

for(;;)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message == WM_QUIT)
break;

TranslateMessage(&msg);
DispatchMessage(&msg);
}

glClear(GL_COLOR_BUFFER_BIT);
glFlush();
}

wglMakeCurrent(NULL, NULL);
wglDeleteContext(hglrc);

return msg.wParam;
}

/*
** message handler
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_NULL:
{
return 0;
}

case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}

case WM_CLOSE:
{
if(MessageBox(hWnd, "Are you sure you want to exit?", "", MB_YESNO | MB_ICONQUESTION) == IDYES)
DestroyWindow(hWnd);

return 0;
}
}

return DefWindowProc(hWnd, msg, wParam, lParam);
}

[/source]

This has been bugging me for a while so I thought I might as well see if it's happened to anyone else or see if someone can tell me how badly I'm going wrong.

I've finally managed to get OpenGL rendering within Win32 (and not through the other third party libraries like GLUT).

My issue though is that when I actually include rendering commands in the main loop the window appears to become unresponsive. I say appears because I can drag it around occasionally and it takes ages to respond.

The issue disappears with I remove the Clear and Flush commands in the loop.

I've added a timer (not shown above) to limit the speed at when the rendering runs and the issue then goes away. The only conclusion I can come to is that the OpenGL commands take to long and cause PeekMessage to miss messages in the queue (that was all I had, I only started Win32 yesterday so I'm probably wrong).

The code compiles correctly and produces no errors in Visual Studio. What exactly is causing this behaviour?


The big killer is this:


if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message == WM_QUIT)
break;

TranslateMessage(&msg);
DispatchMessage(&msg);
}

glClear(GL_COLOR_BUFFER_BIT);
glFlush();


change that if to a while, otherwise you only process 1 window message per frame and the message queue will get flooded if you do anything that generates alot of messages (such as moving the window)
[size="1"]I don't suffer from insanity, I'm enjoying every minute of it.
The voices in my head may not be real, but they have some good ideas!
Any reason you DONT want double buffering anyway?


Not in the slightest. When learning stuff in the past, I've had a tendency to move far to fast and begin stumbling over further topics that I lack any grounding in. I've got the first copy of OpenGL 1.0 and I've done the first 6 chapters (I need to go back over lighting though). The only reason I'm ignore any use of buffers is that I simply haven't learnt it yet.

[...] change that if to a while, otherwise you only process 1 window message per frame...


When I read this I actually wanted to slap myself. I can't believe I missed something so theoretically simple. Thank-you so much.

Although that didn't actually solve the problem... Now I'm running with a while loop to process all the built up messages and I removed call to Flush. The program now slows down my entire laptop.
Post the code...

and add this.


pfd.nSize = sizeof( PIXELFORMATDESCRIPTOR );
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 24;
pfd.cStencilBits = 0;

[quote name='phantom' timestamp='1341791443' post='4957078']Any reason you DONT want double buffering anyway?


Not in the slightest. When learning stuff in the past, I've had a tendency to move far to fast and begin stumbling over further topics that I lack any grounding in. I've got the first copy of OpenGL 1.0 and I've done the first 6 chapters (I need to go back over lighting though). The only reason I'm ignore any use of buffers is that I simply haven't learnt it yet.

[...] change that if to a while, otherwise you only process 1 window message per frame...


When I read this I actually wanted to slap myself. I can't believe I missed something so theoretically simple. Thank-you so much.

Although that didn't actually solve the problem... Now I'm running with a while loop to process all the built up messages and I removed call to Flush. The program now slows down my entire laptop.
[/quote]

That seems to be as expected aswell, without the flush and no double buffering your application should do its best to occupy a full core on your cpu. (If you only got one then the rest of the system will slow down considerably (Which is fine if its a fullscreen game), you could post your modified code again so we can see if you're doing something else thats wierd.

Since you're only using one buffer i'd highly recommend using glFinish instead of glFlush aswell, Flush only tells the driver to send any buffered commands to the GPU (the opengl driver is free to buffer and batch the commands you give it), glFinish will actually wait for the GPU to finish its job (just like a buffer swap would)
[size="1"]I don't suffer from insanity, I'm enjoying every minute of it.
The voices in my head may not be real, but they have some good ideas!
[source lang=cpp"]
/*
** main.cpp
*/

# include <stdlib.h>
# include <time.h>

# include <windows.h>
# if defined(_WINDOWS_)
# include <gl\gl.h>
# endif /* defined(_WINDOWS_) */

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

HDC hdc;
PIXELFORMATDESCRIPTOR pfd;
int format;
HGLRC hglrc;

/*
** entry point
*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WNDCLASS WndClass;
HWND hWnd;
MSG msg;

WndClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
WndClass.lpfnWndProc = WndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = hInstance;
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hbrBackground = NULL;
WndClass.lpszMenuName = NULL;
WndClass.lpszClassName = "greyscale";

if(RegisterClass(&WndClass) == 0)
{
MessageBox(NULL, "Unable to register the window's class.", "", MB_OK | MB_ICONERROR);
return 0;
}

hWnd = CreateWindow("greyscale", "greyscale", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 800, 600, NULL, NULL, hInstance, NULL);
if(!hWnd)
{
MessageBox(NULL, "Failed to create window.", "", MB_OK | MB_ICONERROR);
return 0;
}

hdc = GetDC(hWnd);

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

format = ChoosePixelFormat(hdc, &pfd);
SetPixelFormat(hdc, format, &pfd);

hglrc = wglCreateContext(hdc);
if(!hglrc)
{
MessageBox(NULL, "Failed to create context for OpenGL.", "", MB_OK | MB_ICONERROR);
return 0;
}

if(wglMakeCurrent(hdc, hglrc) == FALSE)
{
MessageBox(NULL, "Unable to change the current context.", "", MB_OK | MB_ICONERROR);
return 0;
}

ShowWindow(hWnd, nShowCmd);
UpdateWindow(hWnd);

glClearColor(0.0f, 1.0f, 0.0f, 0.0f);

for(;;)
{
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message == WM_QUIT)
break;

TranslateMessage(&msg);
DispatchMessage(&msg);
}

glClear(GL_COLOR_BUFFER_BIT);

glColor3f(0.0f, 0.0f, 0.0f);
glBegin(GL_TRIANGLES);
glVertex2f( 0.5f, 0.5f );
glVertex2f(-0.5f, 0.75f);
glVertex2f(-0.35f, -0.4f);
glEnd();

glViewport(0, 0, 800, 600);

SwapBuffers(hdc);
}

wglMakeCurrent(NULL, NULL);
wglDeleteContext(hglrc);

ReleaseDC(hWnd, hdc);

return msg.wParam;
}

/*
** message handler
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_NULL:
{
return 0;
}

case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}

case WM_CLOSE:
{
if(MessageBox(hWnd, "Are you sure you want to exit?", "", MB_YESNO | MB_ICONQUESTION) == IDYES)
DestroyWindow(hWnd);

return 0;
}
}

return DefWindowProc(hWnd, msg, wParam, lParam);
}

[/source]


and add this.


See that addition makes the program run. I think I'm missing something about Window's behaviour and a single OpenGL buffer?

Since you're only using one buffer i'd highly recommend using glFinish instead of glFlush aswell


I had been using that but I thought it might have been the cause so I took it out.
Just out of curiosity I did two things:

  • Leave the call to ReleaseDC at the bottom of WinMain after it had been moved; and
  • Took the program back to a single buffer with no information about the stencil or depth buffer (I left the increased colour bits).

[source lang="cpp"]

/*
** main.cpp
*/

# include <stdlib.h>
# include <time.h>

# include <windows.h>
# if defined(_WINDOWS_)
# include <gl\gl.h>
# endif /* defined(_WINDOWS_) */

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

HDC hdc;
PIXELFORMATDESCRIPTOR pfd;
int format;
HGLRC hglrc;

/*
** entry point
*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WNDCLASS WndClass;
HWND hWnd;
MSG msg;

WndClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
WndClass.lpfnWndProc = WndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = hInstance;
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hbrBackground = NULL;
WndClass.lpszMenuName = NULL;
WndClass.lpszClassName = "greyscale";

if(RegisterClass(&WndClass) == 0)
{
MessageBox(NULL, "Unable to register the window's class.", "", MB_OK | MB_ICONERROR);
return 0;
}

hWnd = CreateWindow("greyscale", "greyscale", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 800, 600, NULL, NULL, hInstance, NULL);
if(!hWnd)
{
MessageBox(NULL, "Failed to create window.", "", MB_OK | MB_ICONERROR);
return 0;
}

hdc = GetDC(hWnd);

ZeroMemory(&pfd, sizeof(PIXELFORMATDESCRIPTOR));
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;

format = ChoosePixelFormat(hdc, &pfd);
SetPixelFormat(hdc, format, &pfd);

hglrc = wglCreateContext(hdc);
if(!hglrc)
{
MessageBox(NULL, "Failed to create context for OpenGL.", "", MB_OK | MB_ICONERROR);
return 0;
}

if(wglMakeCurrent(hdc, hglrc) == FALSE)
{
MessageBox(NULL, "Unable to change the current context.", "", MB_OK | MB_ICONERROR);
return 0;
}

ShowWindow(hWnd, nShowCmd);
UpdateWindow(hWnd);

glClearColor(0.0f, 1.0f, 0.0f, 0.0f);

for(;;)
{
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message == WM_QUIT)
break;

TranslateMessage(&msg);
DispatchMessage(&msg);
}

glClear(GL_COLOR_BUFFER_BIT);

glColor3f(0.0f, 0.0f, 0.0f);
glBegin(GL_TRIANGLES);
glVertex2f( 0.5f, 0.5f );
glVertex2f(-0.5f, 0.75f);
glVertex2f(-0.35f, -0.4f );
glEnd();

glViewport(0, 0, 800, 600);

glFinish();
}

wglMakeCurrent(NULL, NULL);
wglDeleteContext(hglrc);

ReleaseDC(hWnd, hdc);

return msg.wParam;
}

/*
** message handler
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_NULL:
{
return 0;
}

case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}

case WM_CLOSE:
{
if(MessageBox(hWnd, "Are you sure you want to exit?", "", MB_YESNO | MB_ICONQUESTION) == IDYES)
DestroyWindow(hWnd);

return 0;
}
}

return DefWindowProc(hWnd, msg, wParam, lParam);
}

[/source]

The program now runs smoothly; but only if the Finish command is used (instead of Flush).

I think the issue was identified in these places:

[...] the 24bit color restriction...

[source lang="cpp"]pfd.cColorBits = 32;[/source]


It doesn't quit properly; but that's a separate issue I can work on.

Thanks a lot for your help.

This topic is closed to new replies.

Advertisement