topameng's profileQuake3 启示录PhotosBlogListsMore Tools Help

Blog


    July 26

    PIXELFORMATDESCRIPTOR 格式说明

    像素格式明确了OpenGL绘制平面的特性,如象素缓冲区是单缓冲还是双缓冲,数据是 RGBA方式还是Color Index方式等。 每个OpenGL显示设备一般用名为PIXELFORMATDESCRIPTOR的结构来表示某个的像素 格式,这个结构包含26个属性信息。Win32定义PIXELFORMATDESCRIPTOR如下所示:
    typedef struct tagPIXELFORMATDESCRIPTOR
    { // pfd
      WORD nSize;
      WORD nVersion;
      DWORD dwFlags;
      BYTE iPixelType;
      BYTE cColorBits;
      BYTE cRedBits;
      BYTE cRedShift;
      BYTE cGreenBits;
      BYTE cGreenShift;
      BYTE cBlueBits;
      BYTE cBlueShift;
      BYTE cAlphaBits;
      BYTE cAlphaShift;
      BYTE cAccumBits;
      BYTE cAccumRedBits;
      BYTE cAccumGreenBits;
      BYTE cAccumBlueBits;
      BYTE cAccumAlphaBits;
      BYTE cDepthBits;
      BYTE cStencilBits;
      BYTE cAuxBuffers;
      BYTE iLayerType;
      BYTE bReserved;
      DWORD dwLayerMask;
      DWORD dwVisibleMask;
      DWORD dwDamageMask;
    } PIXELFORMATDESCRIPTOR;

    nSize是象素格式描述子结构的大小,sizeof(PIXELFORMATDESCRIPTOR)设定其值;
    nVersion是PIXELFORMATDESCRIPTOR结构的版本,一般设为1;
    dwFlags是一组表明象素缓冲特性的标志位,如缓冲是否支持GDI或OpenGL等;
    iPixelType 说明象素数据类型是RGBA还是颜色索引;
    cColorBits 每个颜色缓冲区中颜色位平面的数目,对颜色索引方式是缓冲区大小;
    cRedBits 每个RGBA颜色缓冲区中红色位平面的数目;
    cRedShift 每个RGBA颜色缓冲区中红色位平面的偏移数;
    cGreenBits 每个RGBA颜色缓冲区中绿色位平面的数目;
    cGreenShift每个RGBA颜色缓冲区中绿色位平面的偏移数;
    cBlueBits 每个RGBA颜色缓冲区中蓝色位平面的数目;
    cBlueShift 每个RGBA颜色缓冲区中蓝色位平面的偏移数;
    cAlphaBits 每个RGBA颜色缓冲区中alpha位平面的数目(保留的,现不支持);
    cAlphaShift每个RGBA颜色缓冲区中alpha位平面的偏移数(保留的,现不支持);
    cAccumBits 累加缓冲区中全部位平面的数目;
    cAccumRedBits 累加缓冲区中红色位平面的数目;
    cAccumGreenBits累加缓冲区中绿色位平面的数目;
    cAccumBlueBits 累加缓冲区中蓝色位平面的数目;
    cAccumAlphaBits累加缓冲区中alpha位平面的数目;
    cDepthBits   Z(深度)缓冲区的深度;
    cStencilBits  模板缓冲区的深度;
    cAuxBuffers 轴向缓冲区的数量(一般1.0版本不支持);
    iLayerType 被忽略,为了一致性而包含的;
    bReserved 表层和底层平面的数量::位0-3表最多15层表层平面,位4-7表底层;
    dwLayerMask 被忽略,为了一致性而包含的;
    dwVisibleMask 是透明色彩的值(RGBA方式)或是一个底层平面的索引(Index);
    dwDamageMask被忽略,为了一致性而包含的。

    常用值示例:
    PIXELFORMATDESCRIPTOR src =
    {
      sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd
       1,                                           // version number
       PFD_DRAW_TO_WINDOW |     // support window
       PFD_SUPPORT_OPENGL |       // support OpenGL
       PFD_DOUBLEBUFFER,            // double buffered
       PFD_TYPE_RGBA,                  // RGBA type
       24,                                        // 24-bit color depth
       0, 0, 0, 0, 0, 0,                      // color bits ignored
       0,                                         // no alpha buffer
       0,                                         // shift bit ignored
       0,                                         // no accumulation buffer
       0, 0, 0, 0,                             // accum bits ignored
       24,                                       // 24-bit z-buffer
       8,                                        // 8-bit stencil buffer
       0,                                        // no auxiliary buffer
       PFD_MAIN_PLANE,              // main layer
       0,                                        // reserved
       0, 0, 0                                 // layer masks ignored
    };
     
    Windows提供了四个像素格式管理函数,分别介绍如下:
    (1) int ChoosePixelFormat(HDC hdc, PIXELFORMATDESCRIPTOR *ppdf)
    该函数比较传过来的像素格式描述和OpenGL支持的像素格式,返回一个最佳匹配的像素格式索引。该索引值可传给SetPixelFormat为DC设置像素格式。返回值为0表示失败。
    在比较像素格式时,匹配优先级顺序为像素格式描述子结构中的下述各域:
    dwFlags->cColorBits->cAlphaBits->cAccumBits
    ->cDepthBits->cStencilBits->cAuxBuffers->iLayerType
    硬件支持的像素格式优先。
    (2) int DescribePixelFormat(HDC hdc, int iPixelFormat, UINT nBytes,
    LPPIXELFORMATDESCRIPTOR *ppfd)
    该函数用格式索引iPixelFormat说明的像素格式来填写由ppfd所指向的像素格式描述子结构,利用该函数可以枚举像素格式。
    (3) int GetPixelFormat(HDC hdc)
    该函数用于获取hdc的格式索引。
    (4) BOOL SetPixelFormat(HDC hdc, int iPixelFormat,
    LPPIXELFORMATDESCRIPTOR *ppfd)
    该函数用格式索引iPixelFormat来设置hdc的像素格式。在使用该函数之前应该调用ChoosePixelFormat来获取像素格式索引。另外,OpenGL窗口风格必须包含WS_CLIPCHILDREN和WS_CLIPSIBLINGS类型,否则设置失败。
    应该注意的是ChoosePixelFormat函数并不一定返回一个最佳的像素格式值,可以利用DescribePixelFormat来枚举系统所支持的所有像素格式。OpenGL的通常支持24种不同的像素格式,如果系统安装了OpenGL硬件加速器,它可能会支持其它的像素格式。
    July 25

    0x7c921230 处未处理的异常: 用户断点

      幸好这两天代码不多,当调试进程结束时遇到了这个错误,中断到了ntdll内,没有办法跟踪堆栈。 而非调试状态运行程序反而没有这个错误。当排出掉自己写的 int 3 之后(程序中也可以自己写断点中断)。感觉是退出的时候有些问题,在网上查找了一下,有人是DestroyWindow时窗口句柄不存在了。看了一下自己相关的代码没什么问题。无意间想到了在接受WM_CLOSE 消息时,调用了PostQuitMessage(0),case 块后面写成了 break ,进而调用了默认的消息处理进程。把它修改为return 0 之后,问题解决了。不知道是不是还有其他人遇到其它的情况,欢迎拍砖探讨

    检测屏幕分辨率和色深

     HDC hDC;
     int colorBits, xRes, yRes;
     hDC = GetDC( GetDesktopWindow() );    //获得当前桌面进程DC
     colorBits = GetDeviceCaps( hDC, BITSPIXEL ); //获得色深,如32位真彩色返回32
     xRes = GetDeviceCaps( hDC, HORZRES );  //获得分辨率宽度
     yRes = GetDeviceCaps( hDC, VERTRES );  //获得分辨率高度
     ReleaseDC( GetDesktopWindow(), hDC );  //释放DC

    修改分辨率:
    在VC中提供了修改显示设备(如显示器、打印机等等,本文只就显示器而言)属性的函数:ChangeDisplaySettings,该函数能够按照你的需要对显示设备作出相应的修改。其函数申明如下:
      LONG ChangeDisplaySettings(LPDEVMODE lpDevMode, DWORD dwflags);
      其参数的含义如下:
      lpDevMode:一个指向DEVMODE数据结构的指针,DEVMODE的数据结构描述了欲设定显示器的各类属性值。通常情况下使用到的参数有:
      dmSize:所用DEVMODE数据结构的大小(以Bytes为单位)
      dmBitsPerPel :每象素所使用的显存位数(Bits)
      dmPelsWidth Pixel width :水平分辨率(点数)
      dmPelsHeight Pixel height :垂直分辨率(点数)
      dmDisplayFrequency Mode frequency :显示刷新率,以赫兹为单位
      dmFields:通常情况下,不同的显示设备(如打印机)用到的DEVMODE数据结构的内容不同,比如设定打印机时,你不会用到dmDisplayFrequency属性。所以,在你使用DEVMODE数据结构时,应向系统说明你具体用到的有效数据成员,dmFields的用处便在于此。如果在程序中只用到dmPelsWidth(水平分辨率)和dmPelsHeight(垂直分辨率),那么该值应为DM_PELSWIDTH|DM_PELSHEIGHT。DM_DISPLAYFREQUENCY 为刷新频率 DM_BITSPERPEL 表示色深
      Dwflags:表明对显示设备的修改方式。具体取值有以下几种:
      0 :动态改变显示设备属性
      CDS_UPDATEREGISTRY:动态改变显示设备属性并修改注册表相关设置,下次启动计算机时,本次所做的修改依然有效
      CDS_TEST: 测试所做的修改是否有效
         CDS_FULLSCREEN:
      该函数的返回值:
      DISP_CHANGE_SUCCESSFUL:修改成功
      DISP_CHANGE_RESTART :修改后需重新启动(在显示器设定中选择了“应用新的颜色前重新启动计算机”)
      DISP_CHANGE_FAILED :修改失败
      DISP_CHANGE_BADMODE:修改模式错误(比如你的显示器是单色的,但你却将之修改为256色的)

      当If lpDevMode为NULL且dwflags 为0时, 显示设备使用注册表当前值。ChangeDisplaySettings(NULL,0)

     
    July 19

    堆栈丢失的错误 00000000()

      程序报错,中断后堆栈中只有00000000().注意最后的()在vc里很容易被看成0. 造成这种错误的一种情况是:
     1. 调用结构中的成员,此成员为函数指针。但指针没有赋值或者值为NULL。而当调用这个函数的时候会跳出这样的错误情况
     2. 同样在OnTimer 定时器函数中调用一个指向NULL的函数指针也会发生这种错误
     3. 声明了函数指针,但未给其赋值。通过extern 外部引用了这个指针函数。当调用函数时也会出现这种错误

    vc 优化与内存对齐

      终于看到了quake3 的Renderer项目中的代码,发现了这样一段:
    if ( (int)tess.xyz & 15 )   
    {
              Com_Printf( "WARNING: tess.xyz not 16 byte aligned\n" );
    }
      xyz 是一个矢量(float [4]) 数组。
       当我自己的工程 Release 编译时,进入了这个警告。这个警告的意思是 tess的结构成员 xyz 所在的内存地址不是16byte对齐的。而对于quake3 的工程编译后却没有进入这个警告。直觉告诉我肯定是一些vc工程的设置问题,经过层层筛选,最后终于定位到。默认的vc release 工程,链接器-〉优化-〉引用:选择的是/OPT:REF,这个可以优化掉不用的函数减少文件大小。但Renderer 项目作为quake3 项目的依赖项,这样作用并不大。相反这个优化反而导致了内存变化。对于游戏程序来说,矢量对齐到16byte可以提高一定游戏性能。尽管可以在xyz 的声明前加上__declspec (align(16)),强制这个变量内存对齐到16byte边界。但需要修改的地方实在太多。
      如果你的程序不需要计较多几K大小,这个选项还是选为默认吧。即不对其进行优化.
      注意:由于quake3变量顺序安排的巧妙,才使tess.xyz 对齐到16位内存上的。如果改变了变量定义顺序,或者打开了上面的优化。都会导致tess.xyz不能对齐到16byte.如果自己不愿去计算,还是使用__declspec (align(16)) 吧

        刚把所有blog 整理个列表,ms 就更新了。美其名曰:摘要,一页能浏览20个选项。真是。。。
    July 14

    巧用回收的指针

    指针的指针总是容易让人困惑,更能带给你惊奇.
    typedef struct nodetype
    {
        struct    nodetype **head;  
    } node_t;       

    typedef struct
    {
        node_t**    freelist;              
        node_t*      nodePtrs[768];         
    } huff_t;

    static void free_ppnode(huff_t* huff, node_t **ppnode)
    {
        *ppnode = (node_t *)huff->freelist;        //让 ppnode 所在的节点存放 freelist 内存地址
        huff->freelist = ppnode;                        //freelist 指向第一个节点
    }

    //获取一个自由的node_t* 节点,用来存储 head (指向一个块中具有最大节点编号的节点)
    static node_t **get_ppnode(huff_t* huff)
    {
        node_t **tppnode;
        if (!huff->freelist)
        {
            return &(huff->nodePtrs[huff->blocPtrs++]);
        }
        else
        {
            tppnode = huff->freelist;                //freelist 指向的内存。*tppnode 中存贮着下一个自由节点地址
            huff->freelist = (node_t **)(*tppnode);    //freelist 节点指向下一个free node地址,注意强制转换。
            return tppnode;
        }
    }

     
      free_ppnode 每次回收ppnode的时候,在其指向的空间(nodePtrs[768] 中的元素)内容就不需要了,而这时修改ppnode指向空间中的数值(存放的是node_t * 变量,可以暂时认为其为32 位地址数字), 让其存放 freelist 变量值(可以暂时认为其是一个32位地址数字)。这样多次释放之后就形成了一个链表,但不需要任何其他的多余的元素协助。而普通链表中的需要 next 指针协助才能连接两个链表节点。
     当然需要从链表取得下一个元素时,需要对 freelist 进行提领操作来获得以前的freelist 变量值
    July 12

    紫光输入法 冲突

      下面是一段常用的代码,而当使用 ctrl+shift 切换紫光输入法,输入文字。反复切换两次之后。GetMessage 系统函数取消息时冲突。而使用其它输入法都没有问题。经过 google 发现 firefox 也和紫光冲突,也有人和操作系统冲突的。说明此输入法 hook msg 有问题,不得不说紫光输入法确实好用。但下面的代码几乎每个mfc程序都差不多到了,或者有些程序直接调用GetMessage,而没有使用PeekMessage。冲突环境:使用 winxp+sp2 更新了所有系统补丁。紫光拼音3.0 。
      如果你是开发人员,还是建议你不要用这个输入法了。
    while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))  //获得 WM_QUIT 消息
     {
      MSG   msg;
      if ( !GetMessage (&msg, NULL, 0, 0) )    
      {
      }
      // save the msg time, because wndprocs don't have access to the timestamp
      TranslateMessage (&msg);
      DispatchMessage (&msg);
     }
    July 04

    2D bsp 简介

    凸多边形
    所谓凸多边形,就是把一个多边形任意一边向两方无限延长成为一条直线,如果多边形的其他各边均在此直线的同旁,那么这个多边形就叫做凸多边形。如图1,多边形ABCDEF,把线段AF向两方无限延长,此多边形的其他各边AB、BC、CD、DE、EF均在此直线的同旁,所以多边形ABCDEF是凸多边形。
     
     
     
    凹多边形
    把一个各边不自交的多边形任意一边向两方无限延长成为一直线,如果多边形的其他各边不在此直线的同旁,那么这个多边形就叫做凹多边形。
       凹多边形的内角和的解,其实我们可以通过(n-2)·180来计算。实际上是把大于平角的角划分为两个角(如图)

    这不是quake 中bsp实现方式,而是早期doom的。或者说不是3d 关卡的bsp实现方式,希望从简单的2d 开始了解bsp概念的可以看看

    BSP 树
    ------
    解释BSP树的运用,最好是从一个例子开始.设想一个很简单的DOOM关卡的例子.

    这个关卡由一个屋子套在另一个屋子里构成.玩家被封闭在矩形ABHG中.
    先给出几个定义.(如图)

    我们用矢量定义直线,所以

          a = (A,B)  e = (E,C)  f = (C,D)  g = (F,D)

    当一个点在直线矢量方向的左边时,我们称点在直线的左边.

    因此,在这个例子里,a的左边什么也没有;所有的东东都在它的右边.注意这些
    依赖与我们对a的定义,如果我们定义 a = (B,A) 则所有的东东都在a的左边.

    面是玩家看到的直线的一边.例如墙e,就有两个面(e'和e").不是所有的墙都有
    两个面 -- 如果玩家只能看见墙的一面,那么这堵墙就只有一个面.

    面是由矢量方向定义的,直线的两个面分别被称作左表面和右表面.

    这个例子中的BSP树是这样的:


                         f
                        / \
                       /   \
                      /     \
               a,d1,b1       e
                            / \
                           /   \
                          /     \
                     d2,c1       g
                                / \
                               /   \
                              /     \
                            c2       c3,b2


    每个节点都是一条直线.所有在直线左边的东东都在它的左子树上,所有在它
    右边的东东都在它的右子树上.

    注意 d 面不是完全在 f 面的右边或左边.为了描述这种情况,我们把它分为
    了两个部分,一个部分放在左子树,一部分放在右子树.因而,我们必须产生新
    的面来构造BSP树.

    我将在后面解释BSP数是怎样创建的.首先,我将给出使用BSP树来产生一幅画的方法.

    假设玩家站在点'x',看着北方.

    我们从树的顶端直线 f 开始.我们站在直线 f 的右边,所以我们向树的左子
    树进行下去.这是因为我们想最先画最远的多边形.

    我们来到了最左的节点.请在笔记本上记下节点上的东东--"a,d1,b1".

    当我们不能再往下时,回到父节点.现在回到了根节点,我们还不能马上去右子树.
    首先,我们看见了 f 面--写在这个节点上的.我们已经在我们列出的表上得到了
    处在它后面的所有东东,我们还将看见它前面的东东,但是我们必须先把它记入我
    们的表中.注意,f 面有两个表面--f' 和 f".既然我们已经知道我们处在直线 f
    的右边,当然就只能看到它的右表面--所以我们在笔记本上记下 f".现在本子上
    写着 a,d1,b1,f".

    注意,如果我们是看着南方(视线远离 f 面),看不到 f 的任何一个面和 f 的那
    一面后的所有东东.在这种情况下,我们就不必做前面这些.

    现在我们向下到节点 e.我们在 e 的右边,所以要往左子树去,这样便得到了一个
    叶节点.现在把 d2,c1 记下来.

    再回来,看看该记下 e 的哪一面.应该是 e'.现在笔记本上写着
    a,d1,b1,f",d2,c1,e'.

    向右子树,来到 g 节点.我们在左边,所以向右得到 c3,b2,再回来,检查 g (我
    们在左边,应为g'),去最后一个节点得到 c2,回溯,回溯,回溯,回到根节点,遍历
    完成.

    最后笔记本上写着:

    a d1 b1 f" d2 c1 e' c3 b2 g' c2

    如果我们以这个次序来画这些墙,将得到正确的图象.建议你使用3D-buffer而
    不要用画家算法,这样速度要快的多.

    创建 BSP 树
    -----------


    BSP树完全是递归创建的.唯一的困难是知道何时该停止递归.应该注意到叶节
    点将被整个放入表中--因此将一组平面放在一个叶节点上的充分条件是它们能
    够以任何次序画出来而不致有错.也就是说,无论玩家站在哪儿,这一组墙之间
    都不会被别的挡住.

    好吧,让我们开始:选择一个面 f (这个选择相当随便--最好是选一个面,它能
    最少的分割其它面.当然,分割是不可避免的).分割 d 面和 b 面,因为它们被
    直线 f 分开了.(用DOOM中的说法,去分割区域的线被称为节点线 _nodeline_ )

    然后把 f 左边的东东放在左子树,右边也如此:


                                f
                               / \
                              /   \
                             /     \
                      a,d1,b1       b2,c,d2,e,g

    我们可以不再处理左子树--因为墙 a,d1,b1 构成一个凸多边形,从任意角度
    看它们都互不重叠.然而在另一边,平面 e 却使得从特定点去观察平面 d2 会
    被其挡住,所以我们从 e 处分开,这就造成了平面 c 的分割,但是同样被分割
    的平面 a 却不用被分割,这是因为 a 不在现在分析的平面中.

    第二级 BSP 树为:

                                f
                               / \
                              /   \
                             /     \
                      a,d1,b1       e
                                   / \
                                  /   \
                                 /     \
                            d2,c1       b2,c2,g

    现在,c1 和 d2 从不重叠,顾而我们将它们作为另一个叶节点.下一步我们
    从 g 处分开,将 c2 分成 c2 和 c3,剩下的节点都是叶节点.

    下面这棵 BSP 树的最简单运用--再给一个例子来加深印象.考虑一下站在
    y 点向北看的情况.因为看不到 f 面,你只用搜索左子树.这样马上就得到
    了需要的循序: a,d1,b1.

    精华
    ----

    如果我们在每个节点上为每个子树定义一个特定空间,记录子树中的信息,
    这样我们就能以锥形视野比较这些信息将一些不可见的多边形截掉(屏幕
    左边和右边的东东)--如果它们不相交,这样你就不必搜索整个子树.DOOM
    就是这样做的,在一个巨大的 BSP 树中用特定空间储存了每一级的完整
    (*entire*)信息.

    下面是搜索 BSP 树的伪代码.函数 left() 当第二个输入矢量在第一个输
    入矢量的左边时返回 TRUE.这就是两个矢量的点积,...
    ... Sorry,小D这一句不太明白
    >This is a simple dot product, and by pre-calculating the slope of the
    >nodeline can be done with one multiply and one subtract.

    vector  player                         ; player's map position
                                           ; 玩家在地图上的位置矢量
    vector  left_sightline                 ; vector representing a ray cast through
                                           ; the left-most pixel of the screen
                                           ; 描述发射到屏幕最左边的光线的矢量
    vector  right_sightline                ; the right-most pixel of the screen
                                           ; 描述发射到屏幕最右边的光线的矢量


    structure node
    {
      vector vertex1
      vector vertex2
      node   left_subtree
      node   right_subtree
      face   left_face
      face   right_face
      box    bounding_box
      bool   terminal_node
      face   terminal_node_faces[lots]
    }

    recurse(node input)

    if (cone defined by left and right sightlines does not intersect the node's
        bounding box)
      return
    fi

    if node.terminal_node
      ; terminal node - add faces to list
      ; 叶节点--将平面填入表中
      add(node.terminal_node_faces)
      return
    fi

    if left(vertex2-vertex1,player-vertex1)
      ; player is to the left of the nodeline
      ; 玩家在节点线的左边
      if not left(vertex2-vertex1,right_sightline)
        ; sight points right - we are looking at the face
        ; 视线指向右边--我们正看着这个面
        recurse(node.right_subtree)
        add(node.left_face)
      fi
      ; now go down the left subtree
      ; 现在向左子树搜索
      recurse(node.left_subtree)
    else
      ; player is to the right of the nodeline
      ; 玩家在节点线的右边
      if left(vertex2-vertex1,left_sightline)
        ; sight points left - we are looking at the face
        ; 视线指向左边--我们正看着这个面
        recurse(node.left_subtree)
        add(node.right_face)
      fi
      ; now go down the right subtree
      ; 现在向右子树搜索
      recurse(node.right_subtree)
    fi

    return                                                                        

    end recurse

    这不是正规的代码--象数据结构之类的很多东东都没写. :)