topameng's profileQuake3 启示录PhotosBlogListsMore Tools Help

Blog


    January 20

    我对D3D中的顶点缓存的概念感到迷惑,特别是其分配和锁定的操作,不知道这些名称的含义是什么(转载)?

    当"allocating vertex buffers_分配顶点缓存"时:

    当使用 STATIC(这个是.net 情况,对于非.net版本dx是没有指定D3DUSAGE_DYNAMIC)标识分配顶点缓存时,这块缓存位于显存中。它的典型应用是只写一次并不被读回内存的情况。
    只有当标志 D3DUSAGE_DYNAMIC被设置时 D3DUSAGE_WRITEONLY标志才有意义。
    如果使用DYNAMIC标志创建缓存 (使用D3DUSAGE_DYNAMIC创建的缓存) ,并且D3DUSAGE_WRITEONLY 被使用,缓存被分配到显存中。

    如果使用DYNAMIC标志创建缓存 (使用D3DUSAGE_DYNAMIC创建的缓存) ,并且D3DUSAGE_WRITEONLY 不被使用, 缓存被分配到AGP 缓存中。(CPU从AGP缓存中读取的速度比显存中快,但比内存慢,是个很好的折中)
    如果没有足够的显存,顶点缓存被分配到AGP缓存中;如果AGP缓存也不够时,创建失败,除非使用POOL_ MANAGED 创建。在这种情况下,D3D的运行库会释放足够多的显存,用来创建顶点缓存,并在内存中保存创建的缓存的一个拷贝。那些被运行库释放的显存,在需要它们时可以从内存中复制到显存中。注意标志POOL_ MANAGED 并不被发送到驱动程序,因为这是D3D的附加的功能。

    对于NV1X 系列的GPUs, 在进行多数据流渲染得时候,如果有一个顶点缓存位于AGP缓存中,所有的其他位于显存中的顶点缓存会被移动到AGP缓存中。 

    当" locking vertex buffers_锁定顶点缓存"时:

    如果顶点缓存的创建标志是POOL_DEFAULT: 

    如果没有指定任何标志,程序将被暂停,因为它强制程序和GPU同步操作。->低效

    如果指定D3DLOCK_NOOVERWRITE 标志,应用程序不会改变缓存区已存在的内容,运行库会在以后继续使用这块缓存。当应用程序调用这个函数时,驱动程序立即返回。->高效

    如果指定D3DLOCK_DISCARD 标志,程序将会更新整个缓存的值,实际上是分配一块新的缓存,即驱动程序重命名它。->高效

    [注解:因为CPU和GPU是异步的操作,所以当CPU通过系统总线和GPU同步时,需要等到GPU把当前的工作做完。例如,当GPU正在对一块缓存进行DMA操作时,但往往CPU并不对GPU操作的那块缓存进行操作,所以CPU可以和GPU一起工作。当不指定操作标志时,CPU等待GPU完成绘制工作才更新顶点缓存,所以低效。如果指定D3DLOCK_NOOVERWRITE,表示CPU只更新顶点缓存中剩余的缓存,不考虑是否有其他图形绘制是正在使用这个的缓冲区段绘制图形,强制更新那段缓存并返回,而不像默认参数0那样等待前面的绘制结束,而不更新已经写入的顶点值,所以在CPU写入的时候,GPU可以并行的对那些已经存在的顶点值进行DMA等操作,所以高效;如果使用D3DLOCK_DISCARD 标志,说明当前分配的缓存大小不够了,需要重新使用缓存,CPU对这些新分配的缓存区域进行写操作,GPU这时可能还在异步处理旧的缓存区,所以这种调用也是高效的。调用完毕,收回释放的缓存。]


    来自 GameRes 说明了 Lock 的标志含义,我改了一部分内容。因为 D3DLOCK_NOOVERWRITE 和 D3DLOCK_DISCARD 是要经常使用的。越熟练越好
    NOOVERWRITE 并不是不覆盖数据,如果2个三角形在缓存中地址相同。后面定点内容会覆盖前面的。而前面的三角形将不能画出来。

    如果缓冲区开的足够大,render 几帧才能使用完这个缓冲。可以一直使用NOOVERWRITE 就可以。当不够使用时在从头开始使用这个顶点缓冲。如果足够大。开头的顶点基本确定已经画完(都是前几帧的图形顶点内容了。被覆盖也无所谓),如果缓冲很小lock时可以调用DISCARD,表示丢弃缓冲区内容(被丢弃的内容如果在使用中还可以被GPU继续使用。NOOVERWRITE 不会保留,所以速度更好)。如下使用开头的6个顶点画2个四边形。但该用NOOVERWRITE 将之后后面的四边形

       Quad->Lock(0, 6*sizeof(Vertex), (void**)&v, D3DLOCK_DISCARD);

       // quad built from two triangles, note texture coordinates:
       //v += 6;
       v[0] = Vertex(0.0f, 0.0f, 1.25f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
       v[1] = Vertex(0.0f,  1.0f, 1.25f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
       v[2] = Vertex( 1.0f,  1.0f, 1.25f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);

       v[3] = Vertex(0.0f, 0.0f, 1.25f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
       v[4] = Vertex( 1.0f,  1.0f, 1.25f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
       v[5] = Vertex( 1.0f, 0.0f, 1.25f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);

       Quad->Unlock();

       Device->SetTexture(0, Tex1);
       Device->SetStreamSource(0, Quad, 0, sizeof(Vertex));
       Device->SetFVF(Vertex::FVF);
       Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);

       Quad->Lock(0, 6*sizeof(Vertex), (void**)&v, D3DLOCK_DISCARD);
         // quad built from two triangles, note texture coordinates:
       v[0] = Vertex(-1.0f, -1.0f, 1.25f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
       v[1] = Vertex(-1.0f,  0.0f, 1.25f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
       v[2] = Vertex( 0.0f,  0.0f, 1.25f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);

       v[3] = Vertex(-1.0f, -1.0f, 1.25f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
       v[4] = Vertex( 0.0f,  0.0f, 1.25f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
       v[5] = Vertex( 0.0f, -1.0f, 1.25f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);

       Quad->Unlock();

       Device->SetTexture(0, Tex);
       Device->SetStreamSource(0, Quad, 0, sizeof(Vertex));
       Device->SetFVF(Vertex::FVF);
       Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);

    当使用 D3DLOCK_NOOVERWRITE  标志时,似乎Lock 使用偏移地址没什么意义.或许可以提高性能

    January 14

    双核原子操作

    #include "stdafx.h"
    #include <windows.h>
    #include <process.h>

    struct Tf
    {
     unsigned int tid;
     unsigned int a;
     unsigned int b;
     __int64 time;
    };
    Tf thread[400];

    inline __int64 GetCycleCount()
    {
     __asm
     {
      rdtsc
     }
    }

    static unsigned int __stdcall QueueThreadFunc(void * pv)
    {
     unsigned int tid = GetCurrentThreadId();
     Tf* thread = (Tf *) pv;
     thread->tid = tid;
     static int a = 10;
     bool f;
     int b;
     while(1)
     {
      b = a;
      int c = b+1;
      thread->b = b;
      _asm
      {
       lea ecx,a
       mov eax,b
       mov ebx,c
       lock cmpxchg [ecx],ebx
       mov eax,dword ptr [ecx]      //对于双核cpu 马上读回锁定的数据,并不能保证(对于单核到是可以)原子操作。
       mov dword ptr c,eax
       setz f
      }
      if(f)
      {
       thread->a = c;
       thread->time = GetCycleCount();
       break;
      }
     }
     

    }

    int _tmain(int argc, _TCHAR* argv[])
    {
     HANDLE ThreadData[400];
     __int64 base = GetCycleCount();
     for(int i = 0; i < 400; ++i)
     {
      ThreadData[i]= (HANDLE)_beginthreadex(NULL, 0, QueueThreadFunc,&thread[i],CREATE_SUSPENDED,0);
     }

     for(int i = 0; i < 400; ++i)
     {
      ResumeThread(ThreadData[i]);   //这样并发性更强
     }

     Sleep(100);

     for(int i=0;i<400;i++)
     {
      printf("tid :%d, a:%d b:%d time:%d \n",thread[i].tid,thread[i].a,thread[i].b,thread[i].time - base); 
      if(thread[i].a != thread[i].b + 1)
      {
       printf("not equal \n");
      }
     }
     return 0;
    }

    January 11

    网络游戏数据传输和防火墙穿越(转)

    一般来说,client和server之间的数据交换,分为几个优先级,大部分情况下是下面3种:
    1. 不可以丢失,但是不要求速度。
    2. 不可以丢失,但是要求速度,确并不是非常严格。
    3. 可以丢失,但是要求速度

    对于1来说,最直接的例子就是聊天信息,动态的地图信息。这些数据不是time-critical的,所以应该使用TCP连接。 在大多数情况下,有专门的  voice/chat server和map server,client到每个server有个TCP连接。一个client有多少个连接根据需要决定。同时有2-3个连接并没有多大开销。当然,如果开销很大,就是程序设计的问题了。其实你想想开BT的时候同时有4,50个连接,你用其他程序的速度并没有慢多少。

    对于2来说,比较显著的例子是战斗信息,动作序列。 比如一个人玩游戏,用鼠标点了下地图上的一个坐标,那么人物向这个坐标前进。这个信息从server转发到其他的client,当然越快越好,如果client没收到,就要重发,否则client就看不到这个动作。如果你玩过wow的盗贼,就会发现在网络卡的时候,经常先偷袭,按下键盘以后要等一会才看到攻击动作,就是这个原理。当然这里数据需要经过server的处理,每次打包多个client的动作,和顺序,然后发送。需要复杂的server端逻辑处理。这种应该使用UDP,同时对所有的UDP包编号(用来防止2次处理),使用slide window类似的协议进行重传。

    对于3来说,就是位置信息。 位置信息可以丢失,但是由于这种信息更新的最频繁,所以即使丢失其中一个,也可以根据 dead reckoning 算法进行位置预测和修正。 dead reckoning算法有很多变种,适合多类情况。 比如你见过wow里,如果一个人掉线,但是一直往前不停的跑,那多半就是算法进行的预测。偶尔你会发现网络卡的时候,刚刚走到一个地方就退回到原来的位置,就是因为你的动作数据包(2里的)没发送出去,导致update world position的时候,算法进行位置修正的结果。这类数据应该使用UDP.

    需要说明的是 UDP和TCP无明显分界。 TCP相比UDP,有3个主要缺点,1是slow start, 2是 throughput jitter, 3是insistence on reliability(相对的,不时绝对缺点). 在数据传送量比较小,网络状况比较稳定的情况下,使用TCP和UDP无大分别。

    对于防火墙穿透,如果你是做client的,不需要关心这个问题,因为你往Server发数据,建立连接,都不会受到防火墙的影响。不过server端对于放火墙可以有几种实现方式:

    1. 最差的方式是client发送SYN包给服务器,在防火墙或网关上建立NAT地址,然后 Server之需要取得这个NAT地址,把所有的数据包都封装成SYN-ACK包,发给client就行拉。这样做比较省事。但是无法穿透 stateful firewall

    2. 是比较好的方式。 不过需要先建立TCP连接,然后对server发送正常的UDP包。大部分的NAT网关会为UDP专门建立个NAT地址,那么通过这个地址,server就可以发UDP包了。但是并不是所有的firewall都会为UDP建立单独的NAT地址。

    3.  是最好的方式,由于防火墙不会阻止内部网发起的TCP连接,所以TCP进行数据传输没有任何问题。对于Server/Client来说,只要使用 Raw Socket模拟TCP协议,但是这个模拟的TCP协议使用UDP的本质,没有slide window, 没有 congestion control, 没有flow control等等,这种实现最麻烦,但是几乎能处理所有的防火墙。

    4. 其实没有4,不过实在想提下这种最强技术. 就是所有的Server到Client的数据包都可以是ICMP echo reply message. 由于种种原因,firewall不太可能禁止ICMP echo,所以这类消息也是很容易传送到client,但是。。。。。。。。Client如果有 IDS system,很容易把你的sever归类到入侵扫描的范围。所以不用最好。

    如果你们不是采用第3种方式,并且你只写client,那么防火墙跟你就没有什么关系。都是server的事。

    January 10

    Alpha Blend 和 SetTextureStageState

    使用 alpha Blend 必须激活 D3DRS_ALPHABLENDENABLE
    m_pDevice9->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE);
    而使用 SetTextureStageState  混合多纹理时必须关闭 D3DRS_ALPHABLENDENABLE .使用D3DRS_SRCBLEND 和 D3DRS_DESTBLEND 时候。当前纹理层的颜色操作也必须是默认的 D3DTA_MODULATE.
    否者。。。。
    m_pDevice9->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE);
    m_pDevice9->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE);
    相当于 D3DTA_ADD (发光映射)
    m_pDevice9->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
    m_pDevice9->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);
    相当于 D3DTA_ADDSIGNED (细节映射)
    m_pDevice9->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
    m_pDevice9->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);
    相当于 D3DTA_MODULATE (黑暗映射)

    因为我公司的破机器只能支持2个纹理层混合(尽管dx有8个纹理stage,但家里的nvdia 6200 才支持3个纹理,该换了)。所有当纹理过多时。2层混合后就要提交顶点。后面纹理采用SetRenderState 的办法和已经提交的纹理进行混合(当然后面的也可以是2个纹理混合的)
    7个纹理,出了第一个是单层外。后面都是2层纹理一画。(法线贴图 + 2个火颜色混合 + 火和灯光混合)
    m_pDevice9->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE); 
    m_pDevice9->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE);
    m_pDevice9->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE);

    BindTexture(0,g_ptex1);         //火
    DxUpdateTSS(0,0,NULL);        //设置纹理层0为默认 MODULATE 操作
    DisableStage(2);                  //不使用纹理层2
    BindTexture(1,g_ptex6);        //灯
    DxUpdateTSS(1,RSS_TSS_COLOR_ADD,NULL);
    m_pDevice9->SetStreamSource(0, g_pvbQuad1, 0, sizeof(Vertex));
    m_pDevice9->SetFVF(Vertex::FVF);       
    m_pDevice9->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);



                                             多层纹理
    似乎 D3DCREATE_MULTITHREADED 也有些问题。我一直用多线程load free 纹理。一定时间后,最后屏幕上纹理消失了。奇怪的是跟踪进去。发现纹理指针还在。而且很不容易出现,有时放2个小时都不会出现。不过本来该避免这个参数,把load 文件放入了线程。而加载纹理放在主进程。

    Normal map 法线贴图

        D3DXVECTOR3 vLight;
        POINT kPoint;
        g_pDIMouse->GetAbsolutePos(&kPoint);
        // Create a vector from the cursor position (no Z axis for this, the mouse is in 2D)
        //主要记住如何用 鼠标坐标获得光照方向 L xy 范围在 -1 ~ 1 . 当然也可以调整成一个固定值
        vLight.x = ((2*(float)kPoint.x / (float)g_D3DSettings.m_nDeviceWidth) - 1);
        vLight.y = ((2*(float)kPoint.y / (float)g_D3DSettings.m_nDeviceHeight) - 1);
        vLight.z = 1.0f;
        // Normalize the vector. See the tutorial for more info! Normalization reduces the
        // magnitude of a vector to 1, but keeps the original direction. This makes it a
        // unit vector. Also see the Cheaters Guide to 3D Maths!
        // 归一化
        D3DXVec3Normalize( &vLight, &vLight );
        // Now that we have our light vector, we need to turn it back into a greyscale
        // RGB to use to modulate the texture with. Remember that we've normalized this
        // vector, so .x and .y will be between 0.0 and 1.0f (which is why we need to
        // * 127.0f and + 128.0f). Calculate it yourself, if vLight.x was 0.5f.
        DWORD r = (DWORD)(127.0f * vLight.x + 128.0f);
        DWORD g = (DWORD)(127.0f * vLight.y + 128.0f);
        DWORD b = (DWORD)(127.0f * vLight.z + 128.0f);
        DWORD dwFactor = D3DCOLOR_XRGB(r, g, b);
        // Set the texture factor to the rgb calculated above. For more info on the texture
        // factor and how it works with the D3DTA_TFACTOR state, refer to DirectX Basics
        // Series 3 tutorial 4.
        g_pDevice->SetRenderState(D3DRS_TEXTUREFACTOR, dwFactor);  (N * L)
        // Modulate the texture (the normal map) with the light vector (stored
        // above in the texture factor). From the SDK: "Modulate the components of each argument
        // as signed components, add their products; then replicate the sum to all color
        // channels, including alpha".
        g_pDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
        g_pDevice->SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_DOTPRODUCT3 );
        g_pDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_TFACTOR );
        // Set the texture to be the normal map
        g_pDevice->SetTexture( 0, g_pNormalMapTexture );
    法线贴图使用 Phong 着色会好一些:
        m_pDevice9->SetRenderState(D3DRS_SHADEMODE,D3DSHADE_PHONG);
    例子地址
    http://www.thehavok.co.uk/scene/32bits/tutorials/directx/techniques/source/dot3dinput.zip

    January 05

    LoadLibrary 998

    郁闷的问题。LoadLibrary 后 FreeLibrary 在load 任何dll 都会失败。用GetLastError 获得的错误码是998.浪费了2-3个小时,最后调试看卸载过程发现了 vld .在dll中引入了vld的头文件检查内存泄漏。但 FreeLibrary 并没有把它卸载掉。或许因为主进程中也用了vld.导致FreeLibrary dll 不能释放它(vld 加载释放由vld自己决定的)。当再次LoadLibrary 时因为资源没有释放完全,就会发生访问冲突
    调试菜单->异常->win32 exceptions ->C00000005 Acess violation 修改为中断。可以在调试时中断访问违规错误。尽管杯水车薪,还算学到了一手。

    January 04

    获取cpu时间戳

    在Pentium以上的CPU中,提供了一条机器指令RDTSC(Read Time Stamp Counter)来读取这个时间戳的数字,并将其保存在EDX:EAX寄存器对中。由于EDX:EAX寄存器对恰好是Win32平台下C++语言保存函数返回值的寄存器,所以我们可以把这条指令看成是一个普通的函数调用。vc2003像这样:
    inline unsigned __int64 GetTimeStampCount()
    {
    __asm RDTSC
    }
    对于vc6或者其他编译器可能不行,因为RDTSC不被C++的内嵌汇编器直接支持,所以我们要用_emit伪指令直接嵌入该指令的机器码形式0X0F、0X31,如下:
    inline unsigned __int64 GetTimeStampCount()
    {
    __asm _emit 0x0F
    __asm _emit 0x31
    }