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這個函數的時候,才被渲染。也就是上一次的數據放到下一次進行渲染,這樣形成了一個延遲渲染的流程。顯然這些針對的是批量渲染算法。
顯然它只需要鎖定一次緩沖區,就可以批量地繪制大量的圖元,同時它不需要等到把所有的數據填充入緩沖區之后,再批量地繪制。好像比較適合數據量大的情況,當然這些對於繪制二維圖元來說,應該說是足夠了。
解決方案在另一個帖子里面。