zoukankan      html  css  js  c++  java
  • 【HGE】绘图底层

    HGE是基于DX8.0的二维游戏引擎,多年没有更新了。

    而我们知道Dx8.0跟DX9.0C是不同层次的。其实基本绘图逻辑差别不是太大,只是性能方面肯定不在一个水平上面。

    让我感觉很大困惑的是,HGE的绘图结构效率到底适不适合即时大型网络游戏渲染?因为它的绘图逻辑是基于以前的DX7.0的绘图思想。

    先分析它的架构:

     1 (*
    2 ** HGE Primitive type constants
    3 *)

    4 const
    5 HGEPRIM_LINES = 2;
    6 HGEPRIM_TRIPLES = 3;
    7 HGEPRIM_QUADS = 4;
    8
    9 (*
    10 ** HGE Vertex structure
    11 *)

    12 type
    13 THGEVertex = record
    14 X, Y: Single; // screen position
    15 Z: Single; // Z-buffer depth 0..1
    16 Col: Longword; // color
    17 TX, TY: Single; // texture coordinates
    18 end;
    19 PHGEVertex = ^THGEVertex;
    20 THGEVertexArray = array [0..MaxInt div 32 - 1] of THGEVertex;
    21 PHGEVertexArray = ^THGEVertexArray;
    22 TCustomVertex = packed record
    23 x, y, z: single; // Position
    24 rhw: single; // Reciprocal of homogeneous w
    25 Col: Longword; // Vertex Color
    26 tu, tv: single; // Texture coordinates
    27 end;
    28 PCustomVertex = ^TCustomVertex;
    29
    30 (*
    31 ** HGE Triple structure三角形结构
    32 *)

    33 type
    34 THGETriple = record
    35 V: array [0..2] of THGEVertex;
    36 Tex: ITexture;
    37 Blend: Integer;
    38 end;
    39 PHGETriple = ^THGETriple;
    40
    41 (*
    42 ** HGE Quad structure四边形结构
    43 *)

    44 type
    45 THGEQuad = record
    46 V: array [0..3] of THGEVertex;
    47 Tex: ITexture;
    48 Blend: Integer;
    49 end;
    50 PHGEQuad = ^THGEQuad;


    FVF常量定义:

    1 const
    2 D3DFVF_HGEVERTEX = D3DFVF_XYZ or D3DFVF_DIFFUSE or D3DFVF_TEX1;
    3 VertexDef = D3DFVF_XYZRHW or D3DFVF_DIFFUSE or D3DFVF_TEX1;
    4 VERTEX_BUFFER_SIZE = 4000; //静态缓冲区的大小基本参数


    上面这个过程在D3D编程里面,大家应该很熟悉了,定义顶点结构、定义FVF常量结构。

    接着应该是创建顶点缓冲和索引缓冲,HGE定义的是静态缓冲,也就是说缓冲区是在显示卡内存里面的。

    继续看它创建缓冲区的代码:

    其实这部分是非常关键和重要的,直接影响到引擎的性能。

      1 function THGEImpl.InitLost: Boolean;  //接口的子类实现部分
    2 var
    3 Target: IInternalTarget;
    4 PIndices: PWord;
    5 N: Word;
    6 I: Integer;
    7 begin
    8 Result := False;
    9
    10 // Store render target
    11
    12 FScreenSurf := nil;
    13 FScreenDepth := nil;
    14
    15 {$IFDEF HGE_DX8}
    16 FD3DDevice.GetRenderTarget(FScreenSurf);
    17 {$ELSE}
    18 FD3DDevice.GetRenderTarget(0,FScreenSurf);
    19 {$ENDIF}
    20 FD3DDevice.GetDepthStencilSurface(FScreenDepth);
    21
    22 for I := 0 to FTargets.Count - 1 do begin
    23 Target := IInternalTarget(FTargets[I]);
    24 Target.Lost;
    25 end;
    26
    27 // Create Vertex buffer
    28 {$IFDEF HGE_DX8}
    29 if (Failed(FD3DDevice.CreateVertexBuffer(VERTEX_BUFFER_SIZE * SizeOf(THGEVertex),
    30 D3DUSAGE_WRITEONLY,D3DFVF_HGEVERTEX,D3DPOOL_DEFAULT,FVB)))
    31 {$ELSE}//这些是DX9部分
    32 if (Failed(FD3DDevice.CreateVertexBuffer(VERTEX_BUFFER_SIZE * SizeOf(THGEVertex),
    33 D3DUSAGE_WRITEONLY,D3DFVF_HGEVERTEX,D3DPOOL_DEFAULT,FVB,nil)))
    34
    35 //D3DUSAGE_WRITEONLY指定应用程序只能写缓存。它允许驱动程序分配最适合的内存地址作为写缓存。注意如果从创建好的这种缓存中读数据,将会返回错误信息。
    36
    37
    38 {$ENDIF}
    39 then begin
    40 PostError('Can''t create D3D vertex buffer');
    41 Exit;
    42 end;
    43
    44 {$IFDEF HGE_DX8}
    45 FD3DDevice.SetVertexShader(D3DFVF_HGEVERTEX);
    46 FD3DDevice.SetStreamSource(0,FVB,SizeOf(THGEVertex));
    47 {$ELSE}//这些是DX9部分
    48 FD3DDevice.SetVertexShader(nil);
    49 FD3DDevice.SetFVF(D3DFVF_HGEVERTEX);
    50 FD3DDevice.SetStreamSource(0,FVB,0,SizeOf(THGEVertex));
    51 {$ENDIF}
    52
    53 // Create and setup Index buffer
    54
    55 {$IFDEF HGE_DX8}
    56 if (Failed(FD3DDevice.CreateIndexBuffer(VERTEX_BUFFER_SIZE * 6 div 4 * SizeOf(Word),
    57 D3DUSAGE_WRITEONLY,D3DFMT_INDEX16,D3DPOOL_DEFAULT,FIB)))
    58 {$ELSE}//这些是DX9部分
    59 if (Failed(FD3DDevice.CreateIndexBuffer(VERTEX_BUFFER_SIZE * 6 div 4 * SizeOf(Word),
    60 D3DUSAGE_WRITEONLY,D3DFMT_INDEX16,D3DPOOL_DEFAULT,FIB,nil)))
    61 {$ENDIF}
    62 then begin
    63 PostError('Can''t create D3D index buffer');
    64 Exit;
    65 end;
    66
    67 N := 0;
    68 {$IFDEF HGE_DX8}
    69 if (Failed(FIB.Lock(0,0,PByte(PIndices),0))) then
    70 {$ELSE}//这些是DX9部分
    71 if (Failed(FIB.Lock(0,0,Pointer(PIndices),0))) then
    72 {$ENDIF}
    73 begin
    74 PostError('Can''t lock D3D index buffer');
    75 Exit;
    76 end;
    77
    78 for I := 0 to (VERTEX_BUFFER_SIZE div 4) - 1 do begin
    79 PIndices^ := N ; Inc(PIndices);
    80 PIndices^ := N+1; Inc(PIndices);
    81 PIndices^ := N+2; Inc(PIndices);
    82 PIndices^ := N+2; Inc(PIndices);
    83 PIndices^ := N+3; Inc(PIndices);
    84 PIndices^ := N; Inc(PIndices);
    85 Inc(N,4);
    86 end;
    87
    88 FIB.Unlock;
    89 {$IFDEF HGE_DX8}
    90 FD3DDevice.SetIndices(FIB,0);
    91 {$ELSE}//这些是DX9部分
    92 FD3DDevice.SetIndices(FIB);
    93 {$ENDIF}
    94
    95 // Set common render states
    96
    97 //pD3DDevice->SetRenderState( D3DRS_LASTPIXEL, FALSE ); ignore this
    98 FD3DDevice.SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE);
    99 FD3DDevice.SetRenderState(D3DRS_LIGHTING,0);
    100
    101 FD3DDevice.SetRenderState(D3DRS_ALPHABLENDENABLE,1);
    102 FD3DDevice.SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);
    103 FD3DDevice.SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);
    104
    105 FD3DDevice.SetRenderState(D3DRS_ALPHATESTENABLE,1);
    106 FD3DDevice.SetRenderState(D3DRS_ALPHAREF,1);
    107 FD3DDevice.SetRenderState(D3DRS_ALPHAFUNC,D3DCMP_GREATEREQUAL);
    108
    109 FD3DDevice.SetTextureStageState(0,D3DTSS_COLOROP, D3DTOP_MODULATE);
    110 FD3DDevice.SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
    111 FD3DDevice.SetTextureStageState(0,D3DTSS_COLORARG2,D3DTA_DIFFUSE);
    112
    113 FD3DDevice.SetTextureStageState(0,D3DTSS_ALPHAOP, D3DTOP_MODULATE);
    114 FD3DDevice.SetTextureStageState(0,D3DTSS_ALPHAARG1,D3DTA_TEXTURE);
    115 FD3DDevice.SetTextureStageState(0,D3DTSS_ALPHAARG2,D3DTA_DIFFUSE);
    116
    117 {$IFDEF HGE_DX8}
    118 FD3DDevice.SetTextureStageState(0,D3DTSS_MIPFILTER, D3DTEXF_POINT);
    119 {$ELSE}//这些是DX9部分
    120 FD3DDevice.SetSamplerState(0,D3DSAMP_MIPFILTER, D3DTEXF_POINT);
    121 {$ENDIF}
    122
    123 if (FTextureFilter) then begin
    124 {$IFDEF HGE_DX8}
    125 FD3DDevice.SetTextureStageState(0,D3DTSS_MAGFILTER,D3DTEXF_LINEAR);
    126 FD3DDevice.SetTextureStageState(0,D3DTSS_MINFILTER,D3DTEXF_LINEAR);
    127 {$ELSE}//这些是DX9部分
    128 FD3DDevice.SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
    129 FD3DDevice.SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
    130 {$ENDIF}
    131 end else begin
    132 {$IFDEF HGE_DX8}
    133 FD3DDevice.SetTextureStageState(0,D3DTSS_MAGFILTER,D3DTEXF_POINT);
    134 FD3DDevice.SetTextureStageState(0,D3DTSS_MINFILTER,D3DTEXF_POINT);
    135 {$ELSE}//这些是DX9部分
    136 FD3DDevice.SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_POINT);
    137 FD3DDevice.SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_POINT);
    138 {$ENDIF}
    139 end;
    140
    141 FPrim := 0;
    142 FCurPrimType := HGEPRIM_QUADS;
    143 FCurBlendMode := BLEND_DEFAULT;
    144 FCurTexture := nil;
    145
    146 FD3DDevice.SetTransform(D3DTS_VIEW,FMatView);
    147 FD3DDevice.SetTransform(D3DTS_PROJECTION,FMatProj);
    148
    149 Result := True;
    150 end;


    这份代码是HGE包含DX9的,其实很不好,DX9跟DX8在一些方面是不兼容。

     显然顶点缓冲区是只写属性,因为最后是从索引缓冲里面读取数据。

    这个顶点缓冲的大小是固定的,很难说够不够用。如果不够用怎么办?还没有看到这部分的代码在那里。

    或者说,根本没有完全使用完整个顶点缓冲区的容量。

    下面再看看他的代码:

     1 function THGEImpl.Gfx_BeginScene(const Target: ITarget): Boolean;
    2 var
    3 {$IFDEF HGE_DX8}
    4 Surf, Depth: IDirect3DSurface8;
    5 {$ELSE}
    6 Surf, Depth: IDirect3DSurface9;
    7 {$ENDIF}
    8 HR: HResult;
    9 begin
    10 Result := False;
    11
    12 HR := FD3DDevice.TestCooperativeLevel;
    13 if (HR = D3DERR_DEVICELOST) then
    14 Exit;
    15 if (HR = D3DERR_DEVICENOTRESET) then
    16 if (not GfxRestore) then
    17 Exit;
    18
    19 if Assigned(FVertArray) then begin
    20 PostError('Gfx_BeginScene: Scene is already being rendered');
    21 Exit;
    22 end;
    23
    24 if (Target <> FCurTarget) then begin
    25 if Assigned(Target) then begin
    26 Target.Tex.Handle.GetSurfaceLevel(0,Surf);
    27 Depth := (Target as IInternalTarget).Depth;
    28 end else begin
    29 Surf := FScreenSurf;
    30 Depth := FScreenDepth;
    31 end;
    32
    33 {$IFDEF HGE_DX8}
    34 if (Failed(FD3DDevice.SetRenderTarget(Surf,Depth)))
    35 {$ELSE}
    36 if (Failed(FD3DDevice.SetRenderTarget(0,Surf)))
    37 {$ENDIF}
    38 then begin
    39 PostError('Gfx_BeginScene: Can''t set render target');
    40 Exit;
    41 end;
    42 if Assigned(Target) then begin
    43 Surf := nil;
    44 if Assigned((Target as IInternalTarget).Depth) then
    45 FD3DDevice.SetRenderState(D3DRS_ZENABLE,D3DZB_TRUE)
    46 else
    47 FD3DDevice.SetRenderState(D3DRS_ZENABLE,D3DZB_FALSE);
    48 SetProjectionMatrix(Target.Width,Target.Height);
    49 end else begin
    50 if (FZBuffer) then
    51 FD3DDevice.SetRenderState(D3DRS_ZENABLE,D3DZB_TRUE)
    52 else
    53 FD3DDevice.SetRenderState(D3DRS_ZENABLE,D3DZB_FALSE);
    54 SetProjectionMatrix(FScreenWidth,FScreenHeight);
    55 end;
    56
    57 FD3DDevice.SetTransform(D3DTS_PROJECTION,FMatProj);
    58 D3DXMatrixIdentity(FMatView);
    59 FD3DDevice.SetTransform(D3DTS_VIEW,FMatView);
    60
    61 FCurTarget := Target;
    62 end;
    63 FD3DDevice.BeginScene;
    64 {$IFDEF HGE_DX8}
    65 FVB.Lock(0,0,PByte(FVertArray),0);
    66 {$ELSE}
    67 FVB.Lock(0,0,Pointer(FVertArray),0);
    68 {$ENDIF}
    69 Result := True;
    70 end;


    应该看到了,这个是HGE每一帧里面需要调用的开始渲染函数。

    在每一帧开始的时候就锁定顶点缓冲,然后把数据拷贝进缓冲区里面。

    那么我们了解到的情况是:经常对静态缓冲加解锁是不明智的,因为只有等驱动完成了所有挂起的命令之后才能返回该缓冲的指针。如果经常这样做,这会导致CPU和GPU很多不必要的同步,这样性能将会变得很差。

    何况是每一帧开始之后才填充数据,这种方式跟以前的DDRAW7的绘图模式完全是一样的。

    procedure THGEImpl.Gfx_EndScene;
    begin
    RenderBatch(True);
    FD3DDevice.EndScene;
    if (FCurTarget = nil) then
    FD3DDevice.Present(nil,nil,0,nil);
    end;


    结束渲染之前调用了一次RenderBatch函数,并且传入参数为True。

    看看这个函数的功能:

    procedure THGEImpl.RenderBatch(const EndScene: Boolean);
    begin
    if Assigned(FVertArray) then begin
    FVB.Unlock;
    if (FPrim <> 0) then begin
    case FCurPrimType of
    HGEPRIM_QUADS:
    {$IFDEF HGE_DX8}
              FD3DDevice.DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,FPrim shl 2,0,FPrim shl 1);
    {$ELSE}
    FD3DDevice.DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,FPrim shl 2,0,FPrim shl 1);
    {$ENDIF}
    HGEPRIM_TRIPLES:
    FD3DDevice.DrawPrimitive(D3DPT_TRIANGLELIST,0,FPrim);
    HGEPRIM_LINES:
    FD3DDevice.DrawPrimitive(D3DPT_LINELIST,0,FPrim);
    end;

    FPrim := 0; //绘制完毕,清零了,好像不用累计
    end;

    if (EndScene) then //结束渲染之前执行
    FVertArray := nil
    else
    {$IFDEF HGE_DX8}
    FVB.Lock(0,0,PByte(FVertArray),0);
    {$ELSE}
    FVB.Lock(0,0,Pointer(FVertArray),0); //常规的做法,我们是使用一个无类型指针,这里他使用的是FVertArray这样的指针
    {$ENDIF}
    end;
    end;

     

    1 FVertArray: PHGEVertexArray;

    这个函数是创建精灵的时候,需要调用的函数,也是其它单元经常调用的。

     1 procedure THGEImpl.Gfx_RenderQuad(const Quad: THGEQuad);
    2 begin
    3 if Assigned(FVertArray) then begin
    4 if (FCurPrimType <> HGEPRIM_QUADS)
    5 or (FPrim >= VERTEX_BUFFER_SIZE div HGEPRIM_QUADS)
    6 or (FCurTexture <> Quad.Tex)
    7 or (FCurBlendMode <> Quad.Blend)
    8 then begin
    9 RenderBatch;
    10 FCurPrimType := HGEPRIM_QUADS;
    11 if (FCurBlendMode <> Quad.Blend) then
    12 SetBlendMode(Quad.Blend);
    13 if (Quad.Tex <> FCurTexture) then begin
    14 if Assigned(Quad.Tex) then
    15 FD3DDevice.SetTexture(0,Quad.Tex.Handle)
    16 else
    17 FD3DDevice.SetTexture(0,nil);
    18 FCurTexture := Quad.Tex;
    19 end;
    20 end;
    21
    22 Move(Quad.V,FVertArray[FPrim * HGEPRIM_QUADS],
    23 SizeOf(THGEVertex) * HGEPRIM_QUADS);
    24 Inc(FPrim);
    25 end;
    26 end;


    这个函数的调用刚好处于开始渲染和结束渲染之间。Move把Quad的数据复制到顶点缓冲里面。要知道Quad结构的数据在被调用之前就已经填充好了。然后再复制入缓冲区里面。

    就是说,从渲染开始到渲染结束,都是处于Lock锁定的状态下。所以很难看出HGE的绘图高效在那里。显然这样的渲染方式不适合大量绘制图元,更不用说应用于大型或者超大型网络游戏里面了。

    因为锁定状态是按照默认锁定的,就是在GPU绘图这段时间里面,CPU就一直在等待之中,而且系统就处于挂起状态,如果是绘制大量图元呢,结果是可想而知的。

    按照我们常规的逻辑,当引擎渲染开始之后,最理想的状态是,这个时候不必再去计算和处理各种数据或者是再锁定缓冲区去修改里面的数据,而是按照渲染序列,一次性批量地进行渲染。

    说真的,我看了很久,看不出HGE能够胜任大型网络游戏的优点在那里。怎么看,都好像适合以前那些小型游戏开发。

    网上的资料都是研究怎么去学习,还没有看到有人去研究它的实现代码结构方面。希望了解这个引擎的人说下。

     
    好像也不对,看一段代码:

     1 hge->Gfx_BeginScene();     //开始渲染,LOCK锁定缓冲区
    2 bgspr->Render(0,0); //第一次调用 FHGE.Gfx_RenderQuad(FQuad);
    3
    4 for(i=0;i<nObjects;i++)
    5 {
    6 pObjects[i].x+=pObjects[i].dx*dt;
    7 if(pObjects[i].x>SCREEN_WIDTH || pObjects[i].x<0) pObjects[i].dx=-pObjects[i].dx;
    8 pObjects[i].y+=pObjects[i].dy*dt;
    9 if(pObjects[i].y>SCREEN_HEIGHT || pObjects[i].y<0) pObjects[i].dy=-pObjects[i].dy;
    10 pObjects[i].scale+=pObjects[i].dscale*dt;
    11 if(pObjects[i].scale>2 || pObjects[i].scale<0.5) pObjects[i].dscale=-pObjects[i].dscale;
    12 pObjects[i].rot+=pObjects[i].drot*dt;
    13
    14 spr->SetColor(pObjects[i].color);
               //循环调用 FHGE.Gfx_RenderQuad(FQuad);
    15 spr->RenderEx(pObjects[i].x, pObjects[i].y, pObjects[i].rot, pObjects[i].scale);
    16 }
    17
    18 fnt->printf(7,7,"UP and DOWN to adjust number of hares: %d\nSPACE to change blending mode: %d\nFPS: %d", nObjects, nBlend, hge->Timer_GetFPS());
    19 hge->Gfx_EndScene(); //解锁缓冲区,结束渲染


    int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)  //程序入口

    ``````````````
    fnt=new hgeFont("font2.fnt");
    spr=new hgeSprite(tex,0,0,64,64);
    spr->SetHotSpot(32,32); //在这里生成一个矩形





    ------------------------------------------

    procedure THGESprite.Render(const X, Y: Single);
    var
    TempX1, TempY1, TempX2, TempY2: Single;
    begin
    TempX1 := X - FHotX;
    TempY1 := Y - FHotY;
    TempX2 := X + FWidth - FHotX;
    TempY2 := Y + FHeight - FHotY;

    FQuad.V[0].X := TempX1; FQuad.V[0].Y := TempY1;
    FQuad.V[1].X := TempX2; FQuad.V[1].Y := TempY1;
    FQuad.V[2].X := TempX2; FQuad.V[2].Y := TempY2;
    FQuad.V[3].X := TempX1; FQuad.V[3].Y := TempY2;

    FHGE.Gfx_RenderQuad(FQuad);
    end;


    procedure THGESprite.SetHotSpot(const X, Y: Single);
    begin
    FHotX := X;
    FHotY := Y;
    end;


    显然在锁定缓冲区的同时,进行各种运算生成数据,然后批量地进行绘制图元。从流程可以看出来,第一次生成——就是说初次复制到缓冲区的数据是不被立即渲染,而是在第二次生成数据并且调用Gfx_RenderQuad这个函数的时候,才被渲染。也就是上一次的数据放到下一次进行渲染,这样形成了一个延迟渲染的流程。显然这些针对的是批量渲染算法。

    显然它只需要锁定一次缓冲区,就可以批量地绘制大量的图元,同时它不需要等到把所有的数据填充入缓冲区之后,再批量地绘制。好像比较适合数据量大的情况,当然这些对于绘制二维图元来说,应该说是足够了。

    解决方案在另一个帖子里面。

  • 相关阅读:
    关于“jdk”版本不支持问题的总结
    Linux系统下jdk卸载安装、配置
    weblogic-jdk 问题
    MCU有哪些复位因素
    MCU固件升级(OTA)的几种Flash划分方式
    003_Linux常用命令之文件操作
    002_Linux常用命令之目录操作
    001_Linux常用命令之ls命令
    dup与dup2函数
    Linux 系统查询机器最近重启时间命令
  • 原文地址:https://www.cnblogs.com/GameDelphi/p/HGE.html
Copyright © 2011-2022 走看看