Hi,
I created a sample that reproduces the problem:
It creates a multisample openGL window and draws some lines (you can move/zoom/rotate with the mouse)
Pressing the ‘C’ key I call the
void CaptureTexture()
function that copies the framebuffer to an FBO and reads the texture from it, and then the
successive draws are done by the
void DrawQuadWithTexture()
that simply draws a quad with the mapped texture.
On the NVidia card you can see a difference between using the texture and doing a standard DrawFrame().
Pressing ‘C’ again switches back to the standard drawing.
Here is the code for the 2 files needed:
main.cpp
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <cmath>
#include <sstream>
#include <stdexcept>
#include <string>
#if defined(_DEBUG)
#include <crtdbg.h>
#endif
#include "bitmap.h"
#include "gl_font.h"
#include "WGL_ARB_multisample.h"
#include "glext.h"
//-----------------------------------------------------------------------------
// Constants.
//-----------------------------------------------------------------------------
#define APP_TITLE "OpenGL Anti-Aliasing Demo"
// Windows Vista compositing support.
#if !defined(PFD_SUPPORT_COMPOSITION)
#define PFD_SUPPORT_COMPOSITION 0x00008000
#endif
// GL_EXT_texture_filter_anisotropic
#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
// GL_SGIS_generate_mipmap
#define GL_GENERATE_MIPMAP_SGIS 0x8191
#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0
#define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9
#define GL_READ_FRAMEBUFFER_EXT 0x8CA8
#define GL_FRAMEBUFFER_EXT 0x8D40
#define GL_RENDERBUFFER_EXT 0x8D41
static PFNGLRENDERBUFFERSTORAGEEXTPROC glRenderbufferStorageEXT ;
static PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC glFramebufferRenderbufferEXT;
static PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC glCheckFramebufferStatusEXT ;
static PFNGLGENRENDERBUFFERSEXTPROC glGenRenderbuffersEXT ;
static PFNGLBINDFRAMEBUFFEREXTPROC glBindFramebufferEXT ;
static PFNGLBLITFRAMEBUFFEREXTPROC glBlitFramebufferEXT ;
static PFNGLGENFRAMEBUFFERSEXTPROC glGenFramebuffersEXT;
static PFNGLBINDRENDERBUFFEREXTPROC glBindRenderbufferEXT;
GLuint capturedTexture = 0;
GLuint fbo;
GLuint rbColor;
GLuint renderBuffer;
int texWidth;
int texHeight;
#define CAMERA_FOVY 45.0f
#define CAMERA_ZFAR 10.0f
#define CAMERA_ZNEAR 0.1f
#define MOUSE_ORBIT_SPEED 0.30f // 0 = SLOWEST, 1 = FASTEST
#define MOUSE_DOLLY_SPEED 0.02f // same as above...but much more sensitive
#define MOUSE_TRACK_SPEED 0.005f // same as above...but much more sensitive
//-----------------------------------------------------------------------------
// Globals.
//-----------------------------------------------------------------------------
HWND g_hWnd;
HDC g_hDC;
HGLRC g_hRC;
HINSTANCE g_hInstance;
int g_framesPerSecond;
int g_windowWidth;
int g_windowHeight;
int g_maxAnisotrophy;
float g_heading;
float g_pitch;
float g_cameraPos[3] = {0.0f, 0.0f, 4.0f};
float g_targetPos[3];
bool g_isFullScreen;
bool g_hasFocus;
bool g_enableVerticalSync;
bool g_enableAlphaToCoverage;
bool g_supportsGenerateMipMap;
bool g_displayHelp;
GLuint g_decalMap;
bool UseCopyTexture;
//-----------------------------------------------------------------------------
// Functions Prototypes.
//-----------------------------------------------------------------------------
void Cleanup();
void CleanupApp();
HWND CreateAppWindow(const WNDCLASSEX &wcl, const char *pszTitle);
void DrawFrame();
void DrawText();
void EnableVerticalSync(bool enableVerticalSync);
bool ExtensionSupported(const char *pszExtensionName);
float GetElapsedTimeInSeconds();
bool Init();
void InitApp();
void InitGL();
void Log(const char *pszMessage);
void ProcessMouseInput(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
void SetProcessorAffinity();
void ToggleFullScreen();
void UpdateFrame(float elapsedTimeSec);
void UpdateFrameRate(float elapsedTimeSec);
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
//-----------------------------------------------------------------------------
// Functions.
//-----------------------------------------------------------------------------
void DrawQuadWithTexture()
{
// Draw a screen Waud with the texture
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, g_windowWidth, g_windowHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, g_windowWidth, 0, g_windowHeight, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_LIGHTING);
//glDisable(GL_DEPTH_TEST);
glPolygonMode(GL_FRONT, GL_FILL);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, capturedTexture);
glColor3f(1, 1, 1);
double wRatio = (double)g_windowWidth / texWidth;
double hRatio = (double)g_windowHeight / texHeight;
glBegin(GL_QUADS);
glTexCoord2d(0, 0);
glVertex2i(0, 0);
glTexCoord2d(wRatio, 0);
glVertex2i(g_windowWidth, 0);
glTexCoord2d(wRatio, hRatio);
glVertex2i(g_windowWidth, g_windowHeight);
glTexCoord2d(0, hRatio);
glVertex2i(0, g_windowHeight);
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
glEnable(GL_DEPTH_TEST);
DrawText();
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
#if defined _DEBUG
_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
#endif
MSG msg = {0};
WNDCLASSEX wcl = {0};
wcl.cbSize = sizeof(wcl);
wcl.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
wcl.lpfnWndProc = WindowProc;
wcl.cbClsExtra = 0;
wcl.cbWndExtra = 0;
wcl.hInstance = g_hInstance = hInstance;
wcl.hIcon = LoadIcon(0, IDI_APPLICATION);
wcl.hCursor = LoadCursor(0, IDC_ARROW);
wcl.hbrBackground = 0;
wcl.lpszMenuName = 0;
wcl.lpszClassName = "GLWindowClass";
wcl.hIconSm = 0;
if (!RegisterClassEx(&wcl))
return 0;
g_hWnd = CreateAppWindow(wcl, APP_TITLE);
if (g_hWnd)
{
SetProcessorAffinity();
if (Init())
{
ShowWindow(g_hWnd, nShowCmd);
UpdateWindow(g_hWnd);
while (true)
{
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (msg.message == WM_QUIT)
break;
if (g_hasFocus)
{
UpdateFrame(GetElapsedTimeInSeconds());
if (UseCopyTexture)
DrawQuadWithTexture();
else
DrawFrame();
SwapBuffers(g_hDC);
}
else
{
WaitMessage();
}
}
}
Cleanup();
UnregisterClass(wcl.lpszClassName, hInstance);
}
return static_cast<int>(msg.wParam);
}
void CaptureTexture()
{
if (capturedTexture == 0)
{
// Initialize Texture and FBO
texWidth = pow(2, ceil(log10 ((double)g_windowWidth) / log10((double)2)));
texHeight = pow(2, ceil(log10((double)g_windowHeight) / log10((double)2)));
glGenTextures(1, &capturedTexture);
glBindTexture( GL_TEXTURE_2D, capturedTexture);
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture( GL_TEXTURE_2D, 0);
glGenFramebuffersEXT(1, &fbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
glGenRenderbuffersEXT(1, &rbColor);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rbColor);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA, g_windowWidth, g_windowHeight);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, rbColor);
int _status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
}
DrawFrame();
// Copy Frambuffer to FBO
glReadBuffer(GL_BACK);
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, fbo);
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0);
glBlitFramebufferEXT(0, 0, g_windowWidth, g_windowHeight, 0, 0, g_windowWidth, g_windowHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0);
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
// Copy FrameBuffer to the texture
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
glBindTexture(GL_TEXTURE_2D, capturedTexture);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, g_windowWidth, g_windowHeight);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glReadBuffer(GL_BACK);
}
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_ACTIVATE:
switch (wParam)
{
default:
break;
case WA_ACTIVE:
case WA_CLICKACTIVE:
g_hasFocus = true;
break;
case WA_INACTIVE:
if (g_isFullScreen)
ShowWindow(hWnd, SW_MINIMIZE);
g_hasFocus = false;
break;
}
break;
case WM_CHAR:
switch (static_cast<int>(wParam))
{
case VK_ESCAPE:
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
case VK_SPACE:
if (g_enableAlphaToCoverage = !g_enableAlphaToCoverage)
glEnable(GL_MULTISAMPLE_ARB);
else
glDisable(GL_MULTISAMPLE_ARB);
break;
case 'c':
case 'C':
UseCopyTexture = !UseCopyTexture;
if (UseCopyTexture)
CaptureTexture();
break;
case 'h':
case 'H':
g_displayHelp = !g_displayHelp;
break;
case 'v':
case 'V':
EnableVerticalSync(!g_enableVerticalSync);
break;
default:
break;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_SIZE:
g_windowWidth = static_cast<int>(LOWORD(lParam));
g_windowHeight = static_cast<int>(HIWORD(lParam));
break;
case WM_SYSKEYDOWN:
if (wParam == VK_RETURN)
ToggleFullScreen();
break;
default:
ProcessMouseInput(hWnd, msg, wParam, lParam);
break;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
void Cleanup()
{
CleanupApp();
if (g_hDC)
{
if (g_hRC)
{
wglMakeCurrent(g_hDC, 0);
wglDeleteContext(g_hRC);
g_hRC = 0;
}
ReleaseDC(g_hWnd, g_hDC);
g_hDC = 0;
}
}
void CleanupApp()
{
if (g_decalMap)
{
glDeleteTextures(1, &g_decalMap);
g_decalMap = 0;
}
}
HWND CreateAppWindow(const WNDCLASSEX &wcl, const char *pszTitle)
{
// Create a window that is centered on the desktop. It's exactly 1/4 the
// size of the desktop. Don't allow it to be resized.
DWORD wndExStyle = WS_EX_OVERLAPPEDWINDOW;
DWORD wndStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
WS_MINIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
HWND hWnd = CreateWindowEx(wndExStyle, wcl.lpszClassName, pszTitle,
wndStyle, 0, 0, 0, 0, 0, 0, wcl.hInstance, 0);
if (hWnd)
{
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
int halfScreenWidth = screenWidth / 2;
int halfScreenHeight = screenHeight / 2;
int left = (screenWidth - halfScreenWidth) / 2;
int top = (screenHeight - halfScreenHeight) / 2;
RECT rc = {0};
SetRect(&rc, left, top, left + halfScreenWidth, top + halfScreenHeight);
AdjustWindowRectEx(&rc, wndStyle, FALSE, wndExStyle);
MoveWindow(hWnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE);
GetClientRect(hWnd, &rc);
g_windowWidth = rc.right - rc.left;
g_windowHeight = rc.bottom - rc.top;
}
return hWnd;
}
void DrawFrame()
{
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
// Setup view port.
glViewport(0, 0, g_windowWidth, g_windowHeight);
glClearColor(0.3f, 0.5f, 0.9f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Setup projection matrix.
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(CAMERA_FOVY,
static_cast<float>(g_windowWidth) / static_cast<float>(g_windowHeight),
CAMERA_ZNEAR, CAMERA_ZFAR);
// Setup view matrix.
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(g_cameraPos[0], g_cameraPos[1], g_cameraPos[2],
g_targetPos[0], g_targetPos[1], g_targetPos[2],
0.0f, 1.0f, 0.0f);
// Rotate the scene using the mouse.
glRotatef(g_pitch, 1.0f, 0.0f, 0.0f);
glRotatef(g_heading, 0.0f, 1.0f, 0.0f);
glDisable(GL_TEXTURE_2D);
glColor3f(0,0,0);
glBegin(GL_LINE_STRIP);
for (int xInc = 0; xInc <10; xInc++)
for (int yInc = 0; yInc <10; yInc++)
for (int zInc = 0; zInc <10; zInc++)
{
glVertex3f(xInc/10.0, yInc/10.0, zInc/10.0);
}
glEnd();
// Display text.
DrawText();
}
void DrawText()
{
static RECT rcClient;
static char szBuffer[512];
if (g_displayHelp)
{
if (UseCopyTexture)
sprintf(szBuffer,
"Left mouse click and drag to track the camera
"
"Middle mouse click and drag to dolly the camera
"
"Right mouse click and drag to orbit the camera
"
"Press V to enable/disable vertical sync
"
"Press SPACE to enable/disable alpha to coverage sampling
"
"Press ALT + ENTER to toggle full screen
"
"Press ESC to exit
"
"Press H to hide help
"
"DRAWING THE CAPTURED TEXTURE");
else
sprintf(szBuffer,
"Left mouse click and drag to track the camera
"
"Middle mouse click and drag to dolly the camera
"
"Right mouse click and drag to orbit the camera
"
"Press V to enable/disable vertical sync
"
"Press SPACE to enable/disable alpha to coverage sampling
"
"Press ALT + ENTER to toggle full screen
"
"Press ESC to exit
"
"Press H to hide help");
}
else
{
if (UseCopyTexture)
sprintf(szBuffer,
"FPS: %d
"
"Vertical sync: %s
"
"Anti-aliasing: %s
"
"Anisotropic filtering: %dx
"
"Alpha to coverage: %s
"
"Press H to display help
"
"DRAWING THE CAPTURED TEXTURE",
g_framesPerSecond,
((g_enableVerticalSync) ? "enabled" : "disabled"),
GetAntiAliasingPixelFormatString(),
g_maxAnisotrophy,
((g_enableAlphaToCoverage) ? "enabled" : "disabled"));
else
sprintf(szBuffer,
"FPS: %d
"
"Vertical sync: %s
"
"Anti-aliasing: %s
"
"Anisotropic filtering: %dx
"
"Alpha to coverage: %s
"
"Press H to display help",
g_framesPerSecond,
((g_enableVerticalSync) ? "enabled" : "disabled"),
GetAntiAliasingPixelFormatString(),
g_maxAnisotrophy,
((g_enableAlphaToCoverage) ? "enabled" : "disabled"));
}
}
void EnableVerticalSync(bool enableVerticalSync)
{
// WGL_EXT_swap_control.
typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(GLint);
static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT =
reinterpret_cast<PFNWGLSWAPINTERVALEXTPROC>(
wglGetProcAddress("wglSwapIntervalEXT"));
if (wglSwapIntervalEXT)
{
wglSwapIntervalEXT(enableVerticalSync ? 1 : 0);
g_enableVerticalSync = enableVerticalSync;
}
}
bool ExtensionSupported(const char *pszExtensionName)
{
static const char *pszGLExtensions = 0;
static const char *pszWGLExtensions = 0;
if (!pszGLExtensions)
pszGLExtensions = reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
if (!pszWGLExtensions)
{
// WGL_ARB_extensions_string.
typedef const char *(WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC);
PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB =
reinterpret_cast<PFNWGLGETEXTENSIONSSTRINGARBPROC>(
wglGetProcAddress("wglGetExtensionsStringARB"));
if (wglGetExtensionsStringARB)
pszWGLExtensions = wglGetExtensionsStringARB(g_hDC);
}
if (!strstr(pszGLExtensions, pszExtensionName))
{
if (!strstr(pszWGLExtensions, pszExtensionName))
return false;
}
return true;
}
float GetElapsedTimeInSeconds()
{
// Returns the elapsed time (in seconds) since the last time this function
// was called. This elaborate setup is to guard against large spikes in
// the time returned by QueryPerformanceCounter().
static const int MAX_SAMPLE_COUNT = 50;
static float frameTimes[MAX_SAMPLE_COUNT];
static float timeScale = 0.0f;
static float actualElapsedTimeSec = 0.0f;
static INT64 freq = 0;
static INT64 lastTime = 0;
static int sampleCount = 0;
static bool initialized = false;
INT64 time = 0;
float elapsedTimeSec = 0.0f;
if (!initialized)
{
initialized = true;
QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&freq));
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&lastTime));
timeScale = 1.0f / freq;
}
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&time));
elapsedTimeSec = (time - lastTime) * timeScale;
lastTime = time;
if (fabsf(elapsedTimeSec - actualElapsedTimeSec) < 1.0f)
{
memmove(&frameTimes[1], frameTimes, sizeof(frameTimes) - sizeof(frameTimes[0]));
frameTimes[0] = elapsedTimeSec;
if (sampleCount < MAX_SAMPLE_COUNT)
++sampleCount;
}
actualElapsedTimeSec = 0.0f;
for (int i = 0; i < sampleCount; ++i)
actualElapsedTimeSec += frameTimes[i];
if (sampleCount > 0)
actualElapsedTimeSec /= sampleCount;
return actualElapsedTimeSec;
}
bool Init()
{
try
{
InitGL();
InitApp();
return true;
}
catch (const std::exception &e)
{
std::ostringstream msg;
msg << "Application initialization failed!" << std::endl << std::endl;
msg << e.what();
Log(msg.str().c_str());
return false;
}
}
void InitApp()
{
}
void InitGL()
{
if (!(g_hDC = GetDC(g_hWnd)))
throw std::runtime_error("GetDC() failed.");
int pf = 0;
PIXELFORMATDESCRIPTOR pfd = {0};
OSVERSIONINFO osvi = {0};
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;
pfd.cDepthBits = 16;
pfd.iLayerType = PFD_MAIN_PLANE;
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
if (!GetVersionEx(&osvi))
throw std::runtime_error("GetVersionEx() failed.");
// When running under Windows Vista or later support desktop composition.
if (osvi.dwMajorVersion > 6 || (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion >= 0))
pfd.dwFlags |= PFD_SUPPORT_COMPOSITION;
// Setting up your Windows application to use multisample anti-aliasing
// (MSAA) is platform dependent. The actual process varies between
// operating systems. Under Windows we use the WGL_ARB_pixel_format and the
// WGL_ARB_multisample OpenGL extensions to enumerate an extended pixel
// format that supports MSAA.
//
// For further details check out the source code for these functions
// (found in the WGL_ARB_multisample.cpp file):
// ChooseBestAntiAliasingPixelFormat()
// ChooseAntiAliasingPixelFormat()
// Select a pixel format supporting the highest quality MSAA.
ChooseBestAntiAliasingPixelFormat(pf);
g_enableAlphaToCoverage = true;
UseCopyTexture = false;
// Here's an example of how to select a pixel format that supports a
// specific number of samples. Here we have chosen 4x MSAA.
if (false)
{
ChooseAntiAliasingPixelFormat(pf, 4);
if (pf)
g_enableAlphaToCoverage = true;
}
// It's always a good idea to fall back to a standard pixel format in case
// MSAA is not supported. Most modern video cards should support some form
// of MSAA - one exception is older embedded video cards.
if (!pf)
{
pf = ChoosePixelFormat(g_hDC, &pfd);
g_enableAlphaToCoverage = false;
}
if (!SetPixelFormat(g_hDC, pf, &pfd))
throw std::runtime_error("SetPixelFormat() failed.");
if (!(g_hRC = wglCreateContext(g_hDC)))
throw std::runtime_error("wglCreateContext() failed.");
if (!wglMakeCurrent(g_hDC, g_hRC))
throw std::runtime_error("wglMakeCurrent() failed.");
EnableVerticalSync(false);
// Check for GL_EXT_texture_filter_anisotropic support.
if (ExtensionSupported("GL_EXT_texture_filter_anisotropic"))
glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &g_maxAnisotrophy);
else
g_maxAnisotrophy = 1;
// Check for GL_SGIS_generate_mipmap support.
if (ExtensionSupported("GL_SGIS_generate_mipmap"))
g_supportsGenerateMipMap = true;
else
g_supportsGenerateMipMap = false;
glRenderbufferStorageEXT = (PFNGLRENDERBUFFERSTORAGEEXTPROC)wglGetProcAddress("glRenderbufferStorageEXT");
glFramebufferRenderbufferEXT = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)wglGetProcAddress("glFramebufferRenderbufferEXT");
glCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)wglGetProcAddress("glCheckFramebufferStatusEXT");
glGenRenderbuffersEXT = (PFNGLGENRENDERBUFFERSEXTPROC)wglGetProcAddress("glGenRenderbuffersEXT");
glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC)wglGetProcAddress("glBindFramebufferEXT");
glBlitFramebufferEXT = (PFNGLBLITFRAMEBUFFEREXTPROC)wglGetProcAddress("glBlitFramebufferEXT");
glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC)wglGetProcAddress("glGenFramebuffersEXT");
glBindRenderbufferEXT = (PFNGLBINDRENDERBUFFEREXTPROC)wglGetProcAddress("glBindRenderbufferEXT");
}
void Log(const char *pszMessage)
{
MessageBox(0, pszMessage, "Error", MB_ICONSTOP);
}
void ProcessMouseInput(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Use the left mouse button to track the camera.
// Use the middle mouse button to dolly the camera.
// Use the right mouse button to orbit the camera.
enum CameraMode {CAMERA_NONE, CAMERA_TRACK, CAMERA_DOLLY, CAMERA_ORBIT};
static CameraMode cameraMode = CAMERA_NONE;
static POINT ptMousePrev = {0};
static POINT ptMouseCurrent = {0};
static int mouseButtonsDown = 0;
static float dx = 0.0f;
static float dy = 0.0f;
switch (msg)
{
case WM_LBUTTONDOWN:
cameraMode = CAMERA_TRACK;
++mouseButtonsDown;
SetCapture(hWnd);
ptMousePrev.x = static_cast<int>(static_cast<short>(LOWORD(lParam)));
ptMousePrev.y = static_cast<int>(static_cast<short>(HIWORD(lParam)));
ClientToScreen(hWnd, &ptMousePrev);
break;
case WM_RBUTTONDOWN:
cameraMode = CAMERA_ORBIT;
++mouseButtonsDown;
SetCapture(hWnd);
ptMousePrev.x = static_cast<int>(static_cast<short>(LOWORD(lParam)));
ptMousePrev.y = static_cast<int>(static_cast<short>(HIWORD(lParam)));
ClientToScreen(hWnd, &ptMousePrev);
break;
case WM_MBUTTONDOWN:
cameraMode = CAMERA_DOLLY;
++mouseButtonsDown;
SetCapture(hWnd);
ptMousePrev.x = static_cast<int>(static_cast<short>(LOWORD(lParam)));
ptMousePrev.y = static_cast<int>(static_cast<short>(HIWORD(lParam)));
ClientToScreen(hWnd, &ptMousePrev);
break;
case WM_MOUSEMOVE:
ptMouseCurrent.x = static_cast<int>(static_cast<short>(LOWORD(lParam)));
ptMouseCurrent.y = static_cast<int>(static_cast<short>(HIWORD(lParam)));
ClientToScreen(hWnd, &ptMouseCurrent);
switch (cameraMode)
{
case CAMERA_TRACK:
dx = static_cast<float>(ptMouseCurrent.x - ptMousePrev.x);
dx *= MOUSE_TRACK_SPEED;
dy = static_cast<float>(ptMouseCurrent.y - ptMousePrev.y);
dy *= MOUSE_TRACK_SPEED;
g_cameraPos[0] -= dx;
g_cameraPos[1] += dy;
g_targetPos[0] -= dx;
g_targetPos[1] += dy;
break;
case CAMERA_DOLLY:
dy = static_cast<float>(ptMouseCurrent.y - ptMousePrev.y);
dy *= MOUSE_DOLLY_SPEED;
g_cameraPos[2] += dy;
if (g_cameraPos[2] > (CAMERA_ZFAR - 2.0f))
g_cameraPos[2] = CAMERA_ZFAR - 2.0f;
if (g_cameraPos[2] < (CAMERA_ZNEAR + 1.0f))
g_cameraPos[2] = CAMERA_ZNEAR + 1.0f;
break;
case CAMERA_ORBIT:
dx = static_cast<float>(ptMouseCurrent.x - ptMousePrev.x);
dx *= MOUSE_ORBIT_SPEED;
dy = static_cast<float>(ptMouseCurrent.y - ptMousePrev.y);
dy *= MOUSE_ORBIT_SPEED;
g_heading += dx;
g_pitch += dy;
if (g_pitch > 90.0f)
g_pitch = 90.0f;
if (g_pitch < -90.0f)
g_pitch = -90.0f;
break;
}
ptMousePrev.x = ptMouseCurrent.x;
ptMousePrev.y = ptMouseCurrent.y;
break;
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
if (--mouseButtonsDown <= 0)
{
mouseButtonsDown = 0;
cameraMode = CAMERA_NONE;
ReleaseCapture();
}
else
{
if (wParam & MK_LBUTTON)
cameraMode = CAMERA_TRACK;
else if (wParam & MK_RBUTTON)
cameraMode = CAMERA_ORBIT;
else if (wParam & MK_MBUTTON)
cameraMode = CAMERA_DOLLY;
}
break;
default:
break;
}
}
void SetProcessorAffinity()
{
// Assign the current thread to one processor. This ensures that timing
// code runs on only one processor, and will not suffer any ill effects
// from power management.
//
// Based on DXUTSetProcessorAffinity() function from the DXUT framework.
DWORD_PTR dwProcessAffinityMask = 0;
DWORD_PTR dwSystemAffinityMask = 0;
HANDLE hCurrentProcess = GetCurrentProcess();
if (!GetProcessAffinityMask(hCurrentProcess, &dwProcessAffinityMask, &dwSystemAffinityMask))
return;
if (dwProcessAffinityMask)
{
// Find the lowest processor that our process is allowed to run against.
DWORD_PTR dwAffinityMask = (dwProcessAffinityMask & ((~dwProcessAffinityMask) + 1));
// Set this as the processor that our thread must always run against.
// This must be a subset of the process affinity mask.
HANDLE hCurrentThread = GetCurrentThread();
if (hCurrentThread != INVALID_HANDLE_VALUE)
{
SetThreadAffinityMask(hCurrentThread, dwAffinityMask);
CloseHandle(hCurrentThread);
}
}
CloseHandle(hCurrentProcess);
}
void ToggleFullScreen()
{
static DWORD savedExStyle;
static DWORD savedStyle;
static RECT rcSaved;
g_isFullScreen = !g_isFullScreen;
if (g_isFullScreen)
{
// Moving to full screen mode.
savedExStyle = GetWindowLong(g_hWnd, GWL_EXSTYLE);
savedStyle = GetWindowLong(g_hWnd, GWL_STYLE);
GetWindowRect(g_hWnd, &rcSaved);
SetWindowLong(g_hWnd, GWL_EXSTYLE, 0);
SetWindowLong(g_hWnd, GWL_STYLE, WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
SetWindowPos(g_hWnd, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);
g_windowWidth = GetSystemMetrics(SM_CXSCREEN);
g_windowHeight = GetSystemMetrics(SM_CYSCREEN);
SetWindowPos(g_hWnd, HWND_TOPMOST, 0, 0,
g_windowWidth, g_windowHeight, SWP_SHOWWINDOW);
}
else
{
// Moving back to windowed mode.
SetWindowLong(g_hWnd, GWL_EXSTYLE, savedExStyle);
SetWindowLong(g_hWnd, GWL_STYLE, savedStyle);
SetWindowPos(g_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);
g_windowWidth = rcSaved.right - rcSaved.left;
g_windowHeight = rcSaved.bottom - rcSaved.top;
SetWindowPos(g_hWnd, HWND_NOTOPMOST, rcSaved.left, rcSaved.top,
g_windowWidth, g_windowHeight, SWP_SHOWWINDOW);
}
}
void UpdateFrame(float elapsedTimeSec)
{
UpdateFrameRate(elapsedTimeSec);
}
void UpdateFrameRate(float elapsedTimeSec)
{
static float accumTimeSec = 0.0f;
static int frames = 0;
accumTimeSec += elapsedTimeSec;
if (accumTimeSec > 1.0f)
{
g_framesPerSecond = frames;
frames = 0;
accumTimeSec = 0.0f;
}
else
{
++frames;
}
}
wgl_Arb_multisample.cpp
//-----------------------------------------------------------------------------
// Copyright (c) 2006-2008 dhpoware. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include <cassert>
#include <cstdio>
#include <cstring>
#include "WGL_ARB_multisample.h"
namespace
{
WNDCLASSEX g_wcl;
HWND g_hWnd;
HDC g_hDC;
HGLRC g_hRC;
char g_szAAPixelFormat[32];
LRESULT CALLBACK DummyGLWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
if (!(g_hDC = GetDC(hWnd)))
return -1;
break;
case WM_DESTROY:
if (g_hDC)
{
if (g_hRC)
{
wglMakeCurrent(g_hDC, 0);
wglDeleteContext(g_hRC);
g_hRC = 0;
}
ReleaseDC(hWnd, g_hDC);
g_hDC = 0;
}
PostQuitMessage(0);
return 0;
default:
break;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
bool CreateDummyGLWindow()
{
g_wcl.cbSize = sizeof(g_wcl);
g_wcl.style = CS_OWNDC;
g_wcl.lpfnWndProc = DummyGLWndProc;
g_wcl.hInstance = reinterpret_cast<HINSTANCE>(GetModuleHandle(0));
g_wcl.lpszClassName = "DummyGLWindowClass";
if (!RegisterClassEx(&g_wcl))
return false;
g_hWnd = CreateWindow(g_wcl.lpszClassName, "", WS_OVERLAPPEDWINDOW,
0, 0, 0, 0, 0, 0, g_wcl.hInstance, 0);
if (!g_hWnd)
return false;
PIXELFORMATDESCRIPTOR pfd = {0};
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;
pfd.cDepthBits = 16;
pfd.iLayerType = PFD_MAIN_PLANE;
int pf = ChoosePixelFormat(g_hDC, &pfd);
if (!SetPixelFormat(g_hDC, pf, &pfd))
return false;
if (!(g_hRC = wglCreateContext(g_hDC)))
return false;
if (!wglMakeCurrent(g_hDC, g_hRC))
return false;
return true;
}
void ChooseBestCSAAPixelFormat(int &pf)
{
struct CSAAPixelFormat
{
int numColorSamples;
int numCoverageSamples;
const char *pszDescription;
};
CSAAPixelFormat csaaPixelFormats[] =
{
{ 4, 8, "8x CSAA" },
{ 4, 16, "16x CSAA" },
{ 8, 8, "8xQ (Quality) CSAA" },
{ 8, 16, "16xQ (Quality) CSAA" }
};
int totalCSAAFormats = static_cast<int>(sizeof(csaaPixelFormats) /
sizeof(CSAAPixelFormat));
int attributes[] =
{
WGL_SAMPLE_BUFFERS_ARB, 1,
WGL_COLOR_SAMPLES_NV, 0,
WGL_COVERAGE_SAMPLES_NV, 0,
WGL_DOUBLE_BUFFER_ARB, 1,
0, 0
};
int returnedPixelFormat = 0;
UINT numFormats = 0;
BOOL bStatus = FALSE;
for (int i = totalCSAAFormats - 1; i >= 0; --i)
{
attributes[3] = csaaPixelFormats[i].numColorSamples;
attributes[5] = csaaPixelFormats[i].numCoverageSamples;
bStatus = wglChoosePixelFormatARB(g_hDC, attributes, 0, 1,
&returnedPixelFormat, &numFormats);
if (bStatus == TRUE && numFormats)
{
pf = returnedPixelFormat;
strcpy(g_szAAPixelFormat, csaaPixelFormats[i].pszDescription);
break;
}
}
if (bStatus == FALSE)
g_szAAPixelFormat[0] = '\0';
}
void ChooseBestMSAAPixelFormat(int &pf)
{
int attributes[] =
{
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
WGL_COLOR_BITS_ARB, 24,
WGL_ALPHA_BITS_ARB, 8,
WGL_DEPTH_BITS_ARB, 24,
WGL_STENCIL_BITS_ARB, 8,
WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
WGL_SAMPLES_ARB, 0,
0, 0
};
int returnedPixelFormat = 0;
UINT numFormats = 0;
BOOL bStatus = FALSE;
for (int samples = 16; samples > 0; samples /= 2)
{
attributes[17] = samples;
bStatus = wglChoosePixelFormatARB(g_hDC, attributes, 0, 1,
&returnedPixelFormat, &numFormats);
if (bStatus == TRUE && numFormats)
{
pf = returnedPixelFormat;
sprintf(g_szAAPixelFormat, "%dx MSAA", samples);
break;
}
}
if (bStatus == FALSE)
g_szAAPixelFormat[0] = '\0';
}
void ChooseCSAAPixelFormat(int &pf, int samples)
{
struct CSAAPixelFormat
{
int numColorSamples;
int numCoverageSamples;
const char *pszDescription;
};
CSAAPixelFormat csaaPixelFormats[] =
{
{ 4, 16, "16x CSAA" },
{ 4, 8, "8x CSAA" }
};
CSAAPixelFormat csaaQualityPixelFormats[] =
{
{ 8, 16, "16xQ (Quality) CSAA" },
{ 8, 8, "8xQ (Quality) CSAA" }
};
CSAAPixelFormat *pCSAAFormats = 0;
int attributes[] =
{
WGL_SAMPLE_BUFFERS_ARB, 1,
WGL_COLOR_SAMPLES_NV, 0,
WGL_COVERAGE_SAMPLES_NV, 0,
WGL_DOUBLE_BUFFER_ARB, 1,
0, 0
};
int returnedPixelFormat = 0;
UINT numFormats = 0;
BOOL bStatus = FALSE;
if (samples >= 8)
pCSAAFormats = csaaQualityPixelFormats;
else
pCSAAFormats = csaaPixelFormats;
for (int i = 0; i < 2; ++i)
{
attributes[3] = pCSAAFormats[i].numColorSamples;
attributes[5] = pCSAAFormats[i].numCoverageSamples;
bStatus = wglChoosePixelFormatARB(g_hDC, attributes, 0, 1,
&returnedPixelFormat, &numFormats);
if (bStatus == TRUE && numFormats)
{
pf = returnedPixelFormat;
strcpy(g_szAAPixelFormat, pCSAAFormats[i].pszDescription);
break;
}
}
if (bStatus == FALSE)
g_szAAPixelFormat[0] = '\0';
}
void ChooseMSAAPixelFormat(int &pf, int samples)
{
int attributes[] =
{
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
WGL_COLOR_BITS_ARB, 24,
WGL_ALPHA_BITS_ARB, 8,
WGL_DEPTH_BITS_ARB, 24,
WGL_STENCIL_BITS_ARB, 8,
WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
WGL_SAMPLES_ARB, samples,
0, 0
};
int returnedPixelFormat = 0;
UINT numFormats = 0;
BOOL bStatus = wglChoosePixelFormatARB(g_hDC, attributes, 0, 1,
&returnedPixelFormat, &numFormats);
if (bStatus == TRUE && numFormats)
{
pf = returnedPixelFormat;
sprintf(g_szAAPixelFormat, "%dx MSAA", samples);
}
else
{
g_szAAPixelFormat[0] = '\0';
}
}
void DestroyDummyGLWindow()
{
if (g_hWnd)
{
PostMessage(g_hWnd, WM_CLOSE, 0, 0);
BOOL bRet;
MSG msg;
while ((bRet = GetMessage(&msg, 0, 0, 0)) != 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
UnregisterClass(g_wcl.lpszClassName, g_wcl.hInstance);
}
bool ExtensionSupported(const char *pszExtensionName)
{
static const char *pszGLExtensions = 0;
static const char *pszWGLExtensions = 0;
if (!pszGLExtensions)
pszGLExtensions = reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
if (!pszWGLExtensions)
{
// WGL_ARB_extensions_string.
typedef const char *(WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC);
PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB =
reinterpret_cast<PFNWGLGETEXTENSIONSSTRINGARBPROC>(
wglGetProcAddress("wglGetExtensionsStringARB"));
if (wglGetExtensionsStringARB)
pszWGLExtensions = wglGetExtensionsStringARB(wglGetCurrentDC());
}
if (!strstr(pszGLExtensions, pszExtensionName))
{
if (!strstr(pszWGLExtensions, pszExtensionName))
return false;
}
return true;
}
}
void ChooseBestAntiAliasingPixelFormat(int &pf)
{
pf = 0;
if (!CreateDummyGLWindow())
{
DestroyDummyGLWindow();
return;
}
if (ExtensionSupported("GL_NV_multisample_coverage") &&
ExtensionSupported("WGL_NV_multisample_coverage"))
{
ChooseBestCSAAPixelFormat(pf);
}
else
{
ChooseBestMSAAPixelFormat(pf);
}
DestroyDummyGLWindow();
}
void ChooseAntiAliasingPixelFormat(int &pf, int samples)
{
pf = 0;
if (!CreateDummyGLWindow())
{
DestroyDummyGLWindow();
return;
}
if (ExtensionSupported("GL_NV_multisample_coverage") &&
ExtensionSupported("WGL_NV_multisample_coverage"))
{
ChooseCSAAPixelFormat(pf, samples);
}
else
{
ChooseMSAAPixelFormat(pf, samples);
}
DestroyDummyGLWindow();
}
const char *GetAntiAliasingPixelFormatString()
{
return g_szAAPixelFormat;
}
// GL_ARB_multisample
void glSampleCoverageARB(GLclampf value, GLboolean invert)
{
typedef void (APIENTRY * PFNGLSAMPLECOVERAGEARBPROC)(GLclampf value, GLboolean invert);
static PFNGLSAMPLECOVERAGEARBPROC pfnSampleCoverageARB = 0;
if (!pfnSampleCoverageARB)
{
pfnSampleCoverageARB = reinterpret_cast<PFNGLSAMPLECOVERAGEARBPROC>(wglGetProcAddress("glSampleCoverageARB"));
assert(pfnSampleCoverageARB != 0);
}
pfnSampleCoverageARB(value, invert);
}
// WGL_ARB_pixel_format
BOOL wglGetPixelFormatAttribivARB(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues)
{
typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues);
static PFNWGLGETPIXELFORMATATTRIBIVARBPROC pfnGetPixelFormatAttribivARB = 0;
if (!pfnGetPixelFormatAttribivARB)
{
pfnGetPixelFormatAttribivARB = reinterpret_cast<PFNWGLGETPIXELFORMATATTRIBIVARBPROC>(wglGetProcAddress("wglGetPixelFormatAttribivARB"));
assert(pfnGetPixelFormatAttribivARB != 0);
}
return pfnGetPixelFormatAttribivARB(hdc, iPixelFormat, iLayerPlane, nAttributes, piAttributes, piValues);
}
BOOL wglGetPixelFormatAttribfvARB(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues)
{
typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVARBPROC)(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues);
static PFNWGLGETPIXELFORMATATTRIBFVARBPROC pfnGetPixelFormatAttribfvARB = 0;
if (!pfnGetPixelFormatAttribfvARB)
{
pfnGetPixelFormatAttribfvARB = reinterpret_cast<PFNWGLGETPIXELFORMATATTRIBFVARBPROC>(wglGetProcAddress("wglGetPixelFormatAttribfvARB"));
assert(pfnGetPixelFormatAttribfvARB != 0);
}
return pfnGetPixelFormatAttribfvARB(hdc, iPixelFormat, iLayerPlane, nAttributes, piAttributes, pfValues);
}
BOOL wglChoosePixelFormatARB(HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats)
{
typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats);
static PFNWGLCHOOSEPIXELFORMATARBPROC pfnChoosePixelFormatARB = 0;
if (!pfnChoosePixelFormatARB)
{
pfnChoosePixelFormatARB = reinterpret_cast<PFNWGLCHOOSEPIXELFORMATARBPROC>(wglGetProcAddress("wglChoosePixelFormatARB"));
assert(pfnChoosePixelFormatARB != 0);
}
return pfnChoosePixelFormatARB(hdc, piAttribIList, pfAttribFList, nMaxFormats, piFormats, nNumFormats);
}