利用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.dllChoosePixelFormat地址,让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

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值