利用Hook实现glut库支持multisample反走样效果(附效果图)
glut库是个老库了,最新版似乎是3.7.6,也是2001年的东西了。The OpenGL Utility Toolkit (GLUT) Programming Interface
里说glut支持multisample,但是
GLUT MULTISAMPLE Bitmask to select a windowwithmultisampling support. Ifmultisampling is not available, a non-multisamplingwindow will automatically be chosen. Note: both theOpenGL client-side and server-side implementations must support the GLX_SAMPLE_SGIS extension for multisampling to be available.
就是说显卡必须支持GLX_SAMPLE_SGIS
才能开启glut的multisample。
我用了这样:
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_MULTISAMPLE);
glutCreateWindow(argv[0]);
一点效果都没有。
1. 用freeglut替代glut库实现反走样
我在stackoverflow上发现了文献[2],上面说glut库的multisample实现不完整,是个老漏洞了。freeglut修正了这个漏洞。然后给出了代码:
glutSetOption(GLUT_MULTISAMPLE, 8);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE);
然后给出了检查方法:
GLint iMultiSample = 0;
GLint iNumSamples = 0;
glGetIntegerv(GL_SAMPLE_BUFFERS, &iMultiSample);
glGetIntegerv(GL_SAMPLES, &iNumSamples);
printf("%s\n", glGetString(GL_RENDERER));
printf("GL_SAMPLE_BUFFERS = %d, GL_SAMPLES = %d\n", iMultiSample, iNumSamples);
其中GL_SAMPLE_BUFFERS
定义在glext.h
文件里。
用他给出的检查方法,我看了,原来的glut确实没有打开multisample,输出的2个值都是0。
改用freeglut库后,反走样就可以用了。glutSetOption
是freeglut自己的函数,定义在freeglut.h
里。
顺带一提,freeglut是glut库停止维护很久之后,一群人自发组织起来维护的库,本身是glut库的超集,可以在不更改任何代码的情况下替换掉使用glut库的代码。只需要改下include目录和lib目录就可以了。
2. 强行让glut库支持multisample
本来问题到这里已经解决了,但我就是太犟。看到了一篇文献3,里面说用hook解决了glut库的multisample支持问题。还附了两张对比图,表明确实实现了。
这就搞得我很不甘心。但copy了他的代码,有很多编译不过的地方。这是他的代码:
HMODULE hGlut = GetModuleHandle(_T("gdi32.dll"));
if (hGlut == 0)
throw Exception_GLUT_Not_Ready();
if ((this->GetSuper() = (PFNChoosePixelFormat)GetProcAddress(hGlut, "ChoosePixelFormat")) == 0)
throw Exception_Thunk_Not_Found();
PBYTE pBase = (PBYTE)GetModuleHandle(_T("glut32.dll"));
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBase;
if (pDos->e_magic != IMAGE_DOS_SIGNATURE)
throw Exception_Unknow(__LINE__);
PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)(pBase + pDos->e_lfanew);
if (pNT->Signature != IMAGE_NT_SIGNATURE)
throw Exception_Unknow(__LINE__);
PIMAGE_IMPORT_DESCRIPTOR pImort = (PIMAGE_IMPORT_DESCRIPTOR)
(pBase + pNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
for (; pImort->Characteristics; pImort++)
{
if (strcmpi((char*)(pImort->Name + pBase), "gdi32.dll") == 0)
{
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)(pBase + pImort->FirstThunk);
for (; pThunk->u1.Function; pThunk++)
{
if (pThunk->u1.Function == (DWORD)GetSuper())
{
DWORD flag;
MEMORY_BASIC_INFORMATION mi;
VirtualQuery(pThunk, &mi, sizeof(MEMORY_BASIC_INFORMATION));
if (!VirtualProtect(mi.BaseAddress, mi.RegionSize, PAGE_READWRITE, &flag))
throw Exception_Unknow(__LINE__);
pThunk->u1.Function = (DWORD)this->MyChoosePixelFormat;
if (!VirtualProtect(mi.BaseAddress, mi.RegionSize, flag, &flag))
throw Exception_Unknow(__LINE__);
break;
}
}
break;
}
}
没头没尾,函数名都没有。我改写了异常部分,但还有一些模糊的地方。PFNChoosePixelFormat是什么?我搜到了这个:
typedef int (APIENTRY* PFNChoosePixelFormat)(HDC, const PIXELFORMATDESCRIPTOR*);
另外,结合ChoosePixelFormat
函数的参数形式,我觉得答案正确。然后后面有个MyChoosePixelFormat
,这肯定就是另外的一个ChoosePixelFormat
函数,用来替换掉原版的。我把MyChoosePixelFormat
指向wglChoosePixelFormatARB
,一运行就出错。仔细比对,发现两者形参数量都不一样,难怪出错。
至于GetSuper()
,这个Super在java里叫做超类,也就是我们C++的父类,在C++里用this->ParentClass::Fun()
的形式调用。根据这个思路,他又把函数指针和GetSuper做比对,我就直接创建一个函数指针,指向gdi32.dll
的默认ChoosePixelFormat
地址了。
这样理一遍,思路就清楚了,他的目的是用另外的一个MyChoosePixelFormat
替换掉gdi32.dll
的ChoosePixelFormat
地址,让glut在调用过程中指向MyChoosePixelFormat
,实现反走样。
这样,我建立了函数:
int MyChoosePixelFormat(HDC hdc, const PIXELFORMATDESCRIPTOR* ppfd)
{
int PixelFormat = 0;
PixelFormat = ChoosePixelFormat(hdc, ppfd);
return PixelFormat;
}
效果和原来一样,也就是正常运行,没有启用multisample。
仔细阅读了Nehe openGL教程,第46课提到了multisample实现方法,核心关键就是用wglChoosePixelFormatARB
函数的调用取代ChoosePixelFormat
,逻辑还很绕,要先假设不支持multisample,用ChoosePixelFormat
先建立起opengl DC,然后再检查是否支持multisample,再销毁窗口,重新用wglChoosePixelFormatARB
建立窗口。想想都好绕。
我实践了很多错误写法,也总结出了经验:
- wglGetProcAddress的调用必须在wglMakeCurrent之后,这个我也是亲自尝试过的。否则只会得到0。
回到glut。
因为glut的窗口过程已经封装了,我又不想去改glut的源代码。所以,我也用了和NeHe类似的思路,先用glut创建窗口,窗口存续期间得到wglChoosePixelFormatARB
等函数地址,再销毁窗口,在二次创建窗口时,注入Hook,把ChoosePixelFormat
的地址改掉,并自己接管。中间涉及到各类错误判断,总之,看代码吧。我已经全封装好了,调用时只需要这样就可以了:
#include "glutEnableMultiSample.h"
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutEnableMultiSample(16);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);// | GLUT_MULTISAMPLE
glutCreateWindow("MultiSample demo by Tom Willow");
只需要glutEnableMultiSample
一句就够了,什么二次窗口创建啊,获取函数地址啊,注入钩子啦,我全封装好了。另外我发现GLUT_MULTISAMPLE
加不加无所谓。
源码:
glutEnableMultiSample.h:
#pragma once
//用法:
//夹在glutInit和glutInitDisplayMode中间,调用一次即可
//in_sample_number为重采样样本数量,可以传入任意值,程序将自动选择支持的最大值。
//中途出错会抛出runtime_error异常,截获后可自定义信息输出方式。
//函数内有容错机制,抛出异常只要在外部截获,不影响后续运行。
void glutEnableMultiSample(unsigned int in_sample_number=16);
glutEnableMultiSample.cpp:
#include "glutEnableMultiSample.h"
#include <Windows.h>
#include <GL/glut.h>
#include <gl/wglext.h> //PFNWGLCHOOSEPIXELFORMATARBPROC
#include <stdexcept>
#define WGL_SAMPLE_BUFFERS_ARB 0x2041
#define WGL_SAMPLES_ARB 0x2042
unsigned int sample_number = 0;
//全局函数地址
PROC wglGetExtString = NULL;
PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL;
bool WGLisExtensionSupported(const char* extension)
{
const size_t extlen = strlen(extension);
const char* supported = NULL;
// Try To Use wglGetExtensionStringARB On Current DC, If Possible
if (wglGetExtString)
supported = ((char* (__stdcall*)(HDC))wglGetExtString)(wglGetCurrentDC());
// If That Failed, Try Standard Opengl Extensions String
if (supported == NULL)
supported = (char*)glGetString(GL_EXTENSIONS);
// If That Failed Too, Must Be No Extensions Supported
if (supported == NULL)
return false;
// Begin Examination At Start Of String, Increment By 1 On False Match
for (const char* p = supported; ; p++)
{
// Advance p Up To The Next Possible Match
p = strstr(p, extension);
if (p == NULL)
return false; // No Match
// Make Sure That Match Is At The Start Of The String Or That
// The Previous Char Is A Space, Or Else We Could Accidentally
// Match "wglFunkywglExtension" With "wglExtension"
// Also, Make Sure That The Following Character Is Space Or NULL
// Or Else "wglExtensionTwo" Might Match "wglExtension"
if ((p == supported || p[-1] == ' ') && (p[extlen] == '\0' || p[extlen] == ' '))
return true; // Match
}
}
//载入函数地址并判断
//不支持则抛出异常
void LoadWGLFunc()
{
wglGetExtString = wglGetProcAddress("wglGetExtensionsStringARB");
if (!WGLisExtensionSupported("WGL_ARB_multisample"))
{
throw std::runtime_error("WGL_ARB_multisample fail");
}
wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
if (wglChoosePixelFormatARB == NULL)
throw std::runtime_error("load wglChoosePixelFormatARB fail");
}
//未判断函数地址,调用前需自行保证地址有效
//不抛异常,返回成功与否
bool SetMultisample(HDC hDC, int sample, int& pixelFormat)
{
int iAttributes[] =
{
WGL_DRAW_TO_WINDOW_ARB,GL_TRUE,
WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
WGL_COLOR_BITS_ARB,24,
WGL_ALPHA_BITS_ARB,8,
WGL_DEPTH_BITS_ARB,16,
WGL_STENCIL_BITS_ARB,0,
WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
WGL_SAMPLE_BUFFERS_ARB,GL_TRUE,
WGL_SAMPLES_ARB,sample,
0,0
};
int valid;
UINT numFormats;
float fAttributes[] = { 0,0 };
while (1)
{
valid = wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats);
if (valid && numFormats >= 1)
break;//正常
else
{
iAttributes[19] /= 2;//采样级别除以2
if (iAttributes[19] == 0)
return false;//无可用采样
}
}
return true;
}
int MyChoosePixelFormat(HDC hdc, const PIXELFORMATDESCRIPTOR* ppfd)
{
int pixelFormat = 0;
if (SetMultisample(hdc, sample_number, pixelFormat) == false)//失败则调用原版地址
{
pixelFormat = ChoosePixelFormat(hdc, ppfd);
}
return pixelFormat;
}
//进行注入,任意一步失败抛异常
//注入内容没有动态地址,调用前无需opengl dc
//origin url:https://blog.csdn.net/manjian/article/details/2158536
void InjectHook()
{
typedef int (APIENTRY* PFNChoosePixelFormat)(HDC, const PIXELFORMATDESCRIPTOR*);
HMODULE hGlut = GetModuleHandle(TEXT("gdi32.dll"));
if (hGlut == 0)
throw std::runtime_error("GetModuleHandle gdi32.dll fail");
PFNChoosePixelFormat super = 0;//gdi
if ((super = (PFNChoosePixelFormat)GetProcAddress(hGlut, "ChoosePixelFormat")) == 0)
throw std::runtime_error("GetProcAddress ChoosePixelFormat fail");
PBYTE pBase = (PBYTE)GetModuleHandle(TEXT("glut32.dll"));
if (pBase==0)
throw std::runtime_error("GetModuleHandle glut32.dll fail");
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBase;
if (pDos->e_magic != IMAGE_DOS_SIGNATURE)
throw std::runtime_error("Exception_Unknow(__LINE__)");
PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)(pBase + pDos->e_lfanew);
if (pNT->Signature != IMAGE_NT_SIGNATURE)
throw std::runtime_error("Exception_Unknow(__LINE__)");
PIMAGE_IMPORT_DESCRIPTOR pImort = (PIMAGE_IMPORT_DESCRIPTOR)
(pBase + pNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
for (; pImort->Characteristics; pImort++)
{
if (_strcmpi((char*)(pImort->Name + pBase), "gdi32.dll") == 0)
{
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)(pBase + pImort->FirstThunk);
for (; pThunk->u1.Function; pThunk++)
{
if (pThunk->u1.Function == (DWORD)super)
{
DWORD flag;
MEMORY_BASIC_INFORMATION mi;
VirtualQuery(pThunk, &mi, sizeof(MEMORY_BASIC_INFORMATION));
if (!VirtualProtect(mi.BaseAddress, mi.RegionSize, PAGE_READWRITE, &flag))
throw std::runtime_error("Exception_Unknow(__LINE__)");
pThunk->u1.Function = (DWORD)MyChoosePixelFormat;
if (!VirtualProtect(mi.BaseAddress, mi.RegionSize, flag, &flag))
throw std::runtime_error("Exception_Unknow(__LINE__)");
break;
}
}
break;
}
}
}
void glutEnableMultiSample(unsigned int in_sample_number)
{
sample_number = in_sample_number;
auto win = glutCreateWindow("test window");
try
{
LoadWGLFunc();
}
catch (std::runtime_error e)
{
glutDestroyWindow(win);
throw e;
}
glutDestroyWindow(win);
InjectHook();
}
为了充分暴露错误信息,中间的各种出错我没有用stdout
,都抛的异常。要截获异常的话,把glutEnableMultiSample
函数用try包起来,截获runtime_error
异常就行。什么都不做的话,ChoosePixelFormat
就调用原来的,正常工作,只是反走样不生效而已。
效果图
GL_SAMPLES就是重采样数量了,0就是没启用multisample。
参考
[1] Nehe openGL 教程
[2] GLUT + OpenGL multisampling anti aliasing (MSAA)
[3] How do I Make Glut’s Multisample Working on Microsoft Windows