话说学OpenGL也有一些时间了,但是其实感觉自己对很多东西还是一知半懂。在大战开始前夕(考试那段日子被shadow volume原理弄得半崩溃状,曾下决心寒假初一定要自己呕个实现出来~),我得先捡回一些“一知半懂”的知识。这里作为自我修炼的一环,目的在于回头看看自己一直在用的NEHE框架。(友情提醒:如果你只为OPENGL初学者之初学者,而不懂MFC也不想懂MFC,建议碰到类似的文章先别看[包括本文],更有益的是关注渲染部分^^)——ZwqXin.com
此日志自我学习用。
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/MFC/nehe-frame-vc6.html
向导生成了一个 CMainframe类和一个C***App类。先来看看C***App类。
C***App类(头文件部分):
- #if _MSC_VER > 1000 //如果vc编译器的版本大于1000则...
- #pragma once
- #endif // _MSC_VER > 1000
- //关于#pragma once和#ifndef-#define-#endif的区别:
- //1.前者指由编译器保证“某一个文件”(the same file)不会被包含多次
- //(但是如果有多个"同名同内容的头文件",那么它们就造成了重复包含,这时候应该用后者来避免);
- //2.后者指编译器保证"同一个名称的文件"(files with the same name)不会被包含多次
- //(但是因为它是按名称来标识文件的,所以如果有多个“同名而不同内容的头文件“,其中一个就会被掩盖了)。
- //所以,如果有两个同名的.h,(而不想或无法或忘记改名的时候,)如果内容不同,#pragma once; 相同,#ifndef
- #ifndef __AFXWIN_H__
- #error include 'stdafx.h' before including this file for PCH
- #endif
- //这是要求所有源代码模块都包含预编译头文件(这样能够加快编译的速度),如果没包含就报错。
- #include "resource.h"
- //资源文件和源代码文件的桥梁,伙同资源文件.rc,链成.res
- class C***App : public CWinApp //继承CWinApp, 控制整个应用程序
- { //运行程序要将该类实例化,并调用构造和重载的InitInstance()
- //因此事实上是透过CWinApp进入WinMain,WinMain对其InitInstance()
- //的控制导致C***App ::InitInstance()的自动执行,开始了一个应用程序实例的进程
- public: C***App();
- private:
- LONGLONG m_lCurTime; //当前时间
- DWORD m_dwTimeCount; //每帧的用时(没counter的时候就拿它作默认值)
- LONGLONG m_lPerfCounter; // 定时器频率
- BOOL m_bPerFlag; // 选择器,决定哪个定时器起作用
- LONGLONG m_lNextTime;// 渲染下一帧的时间
- LONGLONG m_lLastTime; // 当前帧(开始)的时间
- double m_dTimeElapsed; // 自从当前帧开始到现在的时间
- double m_dTimeScale; // 一个时间的乘数
- LONGLONG m_lFramesPerSecond; // 程序运行时的FPS
- // 重载的函数
- public:
- virtual BOOL InitInstance();
- virtual int Run(); //这个很明显....run
- public:
- //这里留给编译器,谁也别动 (DO NOT EDIT HERE)
- DECLARE_MESSAGE_MAP() //消息映射函数,关于它可可以延伸一下:
- };
- //MFC中提供了四个宏:
- //DECLARE_MESSAGE_MAP() :声明一些函数供接下来使用
- //BEGIN_MESSAGE_MAP(子类,父类) :实现开始( 把消息函数的函数指针联系起来)
- //ON_COMMAND :定义具体的消息和消息对应的函数
- //END_MESSAGE_MAP() : 实现结束. 你看下一篇接下来的实现就明白啦
然后就来看看实现啦:
- #ifdef _DEBUG // 判断是否定义_DEBUG,如果是,则:
- #define new DEBUG_NEW // 定义调试new宏,取代new关键字
- //原来我们一直在用的new早被替换了呀~阴啊阴啊
- //这个DEBUG_NEW可以在DEBUG状态下定位内存泄露并且跟踪文件名
- //追踪它,发现 #define DEBUG_NEW new(THIS_FILE, __LINE__)
- //晕,这是啥米new啊?既不像placement-new也没看见<new>啊
- //网上说是new的重载只要满足第一个参数是个SIZE,接下来可以乱(?)搞啦
- //MFC把这里得到每个new调用的文件名及所在源文件中的行数记录下来,
- //如此当delete时就可以检查所有原申请的内存,
- //如果 发现内存指针没有被释放,呵呵,你就惨了(内存泄露了!!),
- //这时编译器会告诉你是哪个new(在哪行)申请的内存没被释放。
- #undef THIS_FILE // 取消THIS_FILE的宏定义,使得其暂无定义
- static char THIS_FILE[]=__FILE__; // 定义THIS_FILE指向文件名
- //THIS_FILE 变量,如你所见,作为一个数组存储__FILE__
- //__FILE__ 常量,是编译器能识别的预定义ANSI- C 6宏之一,武功盖世,代表当前文件名(**.cpp)
- #endif // 结束
- BEGIN_MESSAGE_MAP(CFgfgfApp, CWinApp)
- //.............. 这啥?没东西?呵呵,都说例子在下篇日志咯,这里COMMAND为空do nothing
- END_MESSAGE_MAP()
- C***App::C***App() //构造函数,基本设0,除了:
- {.........} //m_dwTimeCount = 8(每帧持续8秒)FPS预设90
- C***App theApp; //实例了啊实例了啊
- BOOL C***App::InitInstance()
- {
- //公司注册??汗
- SetRegistryKey(_T("Local AppWizard-Generated Applications"));
- //接下来才是真的:构筑窗口作为应用程序的主窗口对象:
- m_pMainWnd = NULL;
- CMainFrame* pFrame = new CMainFrame; //调用CMainFrame类(框架类)了,下篇讲述
- //这个Create()函数是CMainFrame类的父亲CFrameWnd的,然后CFrameWnd有个父亲叫CWnd,CWnd有个父亲叫CCmdTarget,CCmdTarget有个父亲叫CObject,(它别名可能叫亚当之子)
- if (!pFrame->Create(NULL,"MFC OpenGL"))
- return FALSE;
- //话说回来这个m_pMainWnd是从哪里弹出来的呢?哦~原来是咱们C***App的父亲CWinApp的父亲CWinThread那继承来的啊.(注意,CWinThread父亲就是上面提到的CCmdTarget. 哈都是CObject子孙嘛)
- m_pMainWnd = pFrame; //获得窗口对象
- pFrame->ShowWindow(m_nCmdShow); //显示窗口
- pFrame->UpdateWindow();//更新,重绘屏幕
- return TRUE;
- }
接下来就是一个带计时器功能,控制窗口怎样重绘法的Run()函数啦,别忘记它也是重载而来的哦.
- int C***App::Run()
- {
- if (!m_pMainWnd)
- AfxPostQuitMessage(0);
- MSG msg;
- //STATIC_DOWNCAST .....强制类型转换的宏?请看MSDN
- //m_pMainWnd 被直接转换为指向CMainFrame类(这编译器转型要求此类必须继承于CObject - -)的指针了
- CMainFrame* pFrame = STATIC_DOWNCAST ( CMainFrame, m_pMainWnd );
- //QueryPerformanceFrequency:高精计频器获取一个指向定时器每秒的频率数(m_lPerfCounter ,单位n/s)的指针,并把系统值(CPU时钟频率,每秒嘀哒声的个数)交给它.返回值表明是否支持高精计时
- //LARGE_INTEGER,结构体类型,模拟64位有符号的二进制整数 (或者用__int64)
- if ( QueryPerformanceFrequency ( ( LARGE_INTEGER *) &m_lPerfCounter ) ) {
- m_bPerFlag = TRUE; //选择系统时钟
- //原默认值为8的m_dwTimeCount 现在就是实时的(n/s)/(f/s)=n/f每帧的频数
- m_dwTimeCount = unsigned long(m_lPerfCounter / m_lFramesPerSecond);
- //同样,高精计数器QueryPerformanceCounter 获取用于指向当前"CPU运行到现在的嘀哒数(n)"的指针,并把值交给它(m_lNextTime)来指向
- QueryPerformanceCounter ( ( LARGE_INTEGER * ) &m_lNextTime );
- m_dTimeScale = 1.0 / m_lPerfCounter; // s/n
- } else {
- // 如果系统不支持高精计数器,没办法了,用回粗糙的timeGetTime多媒体计时器
- m_lNextTime = timeGetTime ();
- //当然这时高精计频器也没什么作用了,干脆让乘数0.001(相当于m_lPerfCounter取1000)
- m_dTimeScale = 0.001;
- }
- //保存为当前帧的时间(这里其实用计数表示时间),相当于初始化了,接下来它们俩将交替出现
- m_lLastTime = m_lNextTime;
- //******开始了!无限循环,除非出错或退出程序
- while ( TRUE ) {
- //这里我理解为,当有消息发过来,(譬如你最小化啦遮蔽了窗口啦中断(INT)等等,
- //就先暂停计时器运算,停继续渲染,先处理所有消息,这个延伸起来很复杂,先放放.
- //注意,没有消息来时,PeekMessage会返回一个空值到应用程序,GetMessage会在此时让应用程序休眠。
- if ( ::PeekMessage ( &msg, NULL, 0, 0, PM_NOREMOVE ) ) {
- do //如果消息渠里还有消息
- {
- if ( !PumpMessage () )
- return ExitInstance ();
- }
- while ( ::PeekMessage ( &msg, NULL, 0, 0, PM_NOREMOVE ) );
- } else { //没消息要处理了,就继续:
- //这个摆明了,如果用QueryPerformanceCounter 不行,就用timeGetTime
- if ( m_bPerFlag ) {
- QueryPerformanceCounter ( ( LARGE_INTEGER * ) &m_lCurTime );
- }
- else {
- m_lCurTime=timeGetTime ();
- }
- //如果当前计数(代表着时间)到达先前设定的m_lNextTime,譬如从帧1来到帧2 :
- if ( m_lCurTime > m_lNextTime ) {
- // 计算当前时间(刚到达帧2的时间)与刚才帧1开始时间之差,由计数转换为时间表示
- m_dTimeElapsed = ( m_lCurTime - m_lLastTime ) * m_dTimeScale;
- // 现在当前帧(帧2)就成了名义上的"上一帧"了,接下来会变成帧2到帧3
- m_lLastTime = m_lCurTime;
- //这个m_bAppIsActive在CMainframe类里会经常见到,只有它是TRUE时才渲染
- //也就是初始化呀屏幕像素设置呀搞好后才开始渲染
- if ( !pFrame->m_bAppIsActive )
- WaitMessage();
- else
- pFrame->RenderGLScene ();
- //计算下一帧触发时的计数,即把这个当前计数与刚才高精出来的"每帧的频数(计数)"相加
- m_lNextTime = m_lCurTime + m_dwTimeCount;
- } // 对应if(m_lCurTime > m_lNextTime)那里
- } // 对应"没消息来了"的那个else
- } // end while
- return msg.wParam;//将返回代码依次返回调用层(退出)
- }
好了,接下来的日志就是剖CMainframe了!请看:
ZwqXin:自剖一下自己用的NEHE OpenGL框架(中篇)
呃?怎么那么像其实是在讲MFC? 恩,赶忙加回个MFC的Tag先!
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/MFC/nehe-frame-vc6.html