2
0
mirror of https://github.com/dpethes/rerogue.git synced 2025-06-07 18:58:32 +02:00

terrain viewer: merge terrain blocks' data into render batches, one batch per texture index

Dramatically reduces glDrawElements calls at the cost of more memory usage.
About 3x faster on A4300M.
This commit is contained in:
dpethes 2015-07-14 23:15:00 +02:00
parent 8aac8cd05c
commit 01684392cb

View File

@ -24,15 +24,24 @@ type
end; //~600B per block end; //~600B per block
PTerrainBlock = ^TTerrainBlock; PTerrainBlock = ^TTerrainBlock;
TRenderBlockBatch = record
texture_gl_index: integer;
blocks: integer;
vertices: PSingle;
normals: PSingle;
face_indices: PInteger;
end;
{ TTerrainMesh } { TTerrainMesh }
TTerrainMesh = class TTerrainMesh = class
const
FacesPerBlock = 4 * 4 * 2;
private private
terrain: TWorld; terrain: TWorld;
blocks: array of array of TTerrainBlock; blocks: array of array of TTerrainBlock;
block_texcoords: array[0..24] of Tvector2_single; //static, 25*2*4 = 200B block_texcoords: array of Tvector2_single; //static, 25*2*4 = 200B
block_face_indices: array[0..16*2*3 - 1] of byte; //static, 96B block_face_indices: array[0..FacesPerBlock*3 - 1] of byte; //static, 96B
textures_glidx: array of integer; render_batches: array of TRenderBlockBatch;
render_blocks: TList;
function TileToBlock(var tile: TTile; basey, basex: integer): TTerrainBlock; function TileToBlock(var tile: TTile; basey, basex: integer): TTerrainBlock;
procedure TransformTiles; procedure TransformTiles;
@ -46,6 +55,18 @@ type
implementation implementation
const
VerticesPerBlock = 5 * 5;
function TerrainBlockOrderByTex(Item1: Pointer; Item2: Pointer): integer;
var
a, b: PTerrainBlock;
begin
a := PTerrainBlock(Item1);
b := PTerrainBlock(Item2);
result := a^.texture_index - b^.texture_index;
end;
{ TileToBlock { TileToBlock
Create single terrain block at given position using RS3D tile. Create single terrain block at given position using RS3D tile.
Basex/y is the position in block units for given dimension. Basex/y is the position in block units for given dimension.
@ -140,6 +161,7 @@ const
VertexStride = 5; VertexStride = 5;
var var
x, y, i, tri_idx: integer; x, y, i, tri_idx: integer;
blk: integer;
begin begin
tri_idx := 0; tri_idx := 0;
//init face indices //init face indices
@ -150,10 +172,12 @@ begin
SetTriData(tri_idx+1, i+1, i+VertexStride+1, i+VertexStride); SetTriData(tri_idx+1, i+1, i+VertexStride+1, i+VertexStride);
tri_idx += 2; tri_idx += 2;
end; end;
//init uv coords //init uv coords. block_texcoords is quite pessimistic
SetLength(block_texcoords, VerticesPerBlock * terrain.TileHeight * terrain.TileWidth);
for blk := 0 to Length(block_texcoords) div VerticesPerBlock - 1 do
for y := 0 to 4 do for y := 0 to 4 do
for x := 0 to 4 do begin for x := 0 to 4 do begin
block_texcoords[y * 5 + x].init(x/4, 1 - y/4); //u, v block_texcoords[blk*25 + y * 5 + x].init(x/4, 1 - y/4); //u, v
end; end;
end; end;
@ -162,20 +186,10 @@ begin
inherited Destroy; inherited Destroy;
end; end;
function OrderByTex(Item1: Pointer; Item2: Pointer): integer;
var
a, b: PTerrainBlock;
begin
a := PTerrainBlock(Item1);
b := PTerrainBlock(Item2);
result := a^.texture_index - b^.texture_index;
end;
procedure TTerrainMesh.Load(level_idx: integer); procedure TTerrainMesh.Load(level_idx: integer);
const const
LevelIds = '0123456789abcdefgh'; LevelIds = '0123456789abcdefgh';
var var
x, y: integer;
c: char; c: char;
begin begin
terrain := TWorld.Create; terrain := TWorld.Create;
@ -190,28 +204,25 @@ begin
WriteLn(Format('terrain size: %dx%d, tris: %d', WriteLn(Format('terrain size: %dx%d, tris: %d',
[terrain.TileWidth, terrain.TileHeight, [terrain.TileWidth, terrain.TileHeight,
terrain.TileWidth * terrain.TileHeight * 4 * 4 * 2])); terrain.TileWidth * terrain.TileHeight * 4 * 4 * 2]));
render_blocks := TList.Create;
for y := 0 to terrain.TileHeight - 1 do
for x := 0 to terrain.TileWidth - 1 do
render_blocks.Add(@blocks[y, x]);
render_blocks.Sort(@OrderByTex);
end; end;
//generate textures. TODO texture atlas? { InitGL
Prepare data for rendering: generate textures, sort blocks by texture and merge them to batches.
}
procedure TTerrainMesh.InitGL; procedure TTerrainMesh.InitGL;
procedure GenTexture(tex_idx: integer; use_mip: boolean); function GenTexture(tex_idx: integer; use_mip: boolean): integer;
const const
TexW = 64; TexW = 64;
TexH = 64; TexH = 64;
var var
texture_glidx: integer;
tex: pbyte; tex: pbyte;
begin begin
tex := terrain.heightmap.textures[tex_idx]; tex := terrain.heightmap.textures[tex_idx];
//pnm_save('tex'+IntToStr(tex_idx) + '.pnm', tex, TexW, TexH); //debug //pnm_save('tex'+IntToStr(tex_idx) + '.pnm', tex, TexW, TexH); //debug
glGenTextures(1, @textures_glidx[tex_idx]); glGenTextures(1, @texture_glidx);
glBindTexture(GL_TEXTURE_2D, textures_glidx[tex_idx]); glBindTexture(GL_TEXTURE_2D, texture_glidx);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, TexW, TexH, 0, GL_RGB, GL_UNSIGNED_BYTE, tex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, TexW, TexH, 0, GL_RGB, GL_UNSIGNED_BYTE, tex);
if use_mip then begin if use_mip then begin
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
@ -223,29 +234,72 @@ procedure TTerrainMesh.InitGL;
end; end;
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
result := texture_glidx;
end; end;
var var
i: integer; x, y: integer;
tex, k, vidx: integer;
render_blocks: TList;
start_idx, end_idx, block_count: integer;
element_array_size: integer;
begin begin
glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_2D);
SetLength(textures_glidx, terrain.heightmap.texture_count);
for i := 0 to terrain.heightmap.texture_count - 1 do render_blocks := TList.Create;
GenTexture(i, true); for y := 0 to terrain.TileHeight - 1 do
for x := 0 to terrain.TileWidth - 1 do
render_blocks.Add(@blocks[y, x]);
render_blocks.Sort(@TerrainBlockOrderByTex);
SetLength(render_batches, terrain.heightmap.texture_count);
start_idx := 0;
end_idx := 0;
for tex := 0 to terrain.heightmap.texture_count - 1 do begin
render_batches[tex].texture_gl_index := GenTexture(tex, true);
while PTerrainBlock(render_blocks.Items[end_idx])^.texture_index = tex do begin
end_idx += 1;
if end_idx > render_blocks.Count - 1 then
break;
end;
block_count := end_idx - start_idx;
render_batches[tex].blocks := block_count;
if block_count = 0 then
continue;
element_array_size := block_count * VerticesPerBlock * sizeof(Tvector3_single);
render_batches[tex].vertices := getmem (element_array_size);
render_batches[tex].normals := getmem (element_array_size);
render_batches[tex].face_indices := getmem (block_count * FacesPerBlock*3 * 4);
for k := 0 to block_count - 1 do begin
move(PTerrainBlock(render_blocks.Items[start_idx + k])^.vertices[0],
(pbyte(render_batches[tex].vertices) + k * VerticesPerBlock * sizeof(Tvector3_single))^,
VerticesPerBlock * sizeof(Tvector3_single));
move(PTerrainBlock(render_blocks.Items[start_idx + k])^.normals[0],
(pbyte(render_batches[tex].normals) + k * VerticesPerBlock * sizeof(Tvector3_single))^,
VerticesPerBlock * sizeof(Tvector3_single));
for vidx := 0 to FacesPerBlock*3 - 1 do
render_batches[tex].face_indices[k * FacesPerBlock*3 + vidx] :=
block_face_indices[vidx] + k * VerticesPerBlock;
end;
start_idx := end_idx;
end_idx += 1;
if end_idx > render_blocks.Count - 1 then
break;
end;
end; end;
{ DrawGL { DrawGL
Renders terrain blocks. Renders terrain block batches.
Terrain textures are stored independently, and if we process the blocks in spatial order,
repeated texture binding slows this down a lot (68->30fps).
So we sort blocks by texture and render them out of order.
} }
procedure TTerrainMesh.DrawGL(opts: TRenderOpts); procedure TTerrainMesh.DrawGL(opts: TRenderOpts);
var var
i: integer; i: integer;
b: PTerrainBlock; b: TRenderBlockBatch;
last_tex_index: integer;
begin begin
if opts.wireframe then if opts.wireframe then
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
@ -256,24 +310,21 @@ begin
glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY);
last_tex_index := -1;
for i := 0 to terrain.TileHeight * terrain.TileWidth - 1 do begin
b := PTerrainBlock(render_blocks[i]);
if last_tex_index <> b^.texture_index then begin
last_tex_index := b^.texture_index;
glBindTexture(GL_TEXTURE_2D, textures_glidx[last_tex_index]);
end;
glVertexPointer(3, GL_FLOAT, sizeof(Tvector3_single), @b^.vertices[0].data[0]);
glNormalPointer(GL_FLOAT, sizeof(Tvector3_single), @b^.normals[0].data[0]);
glTexCoordPointer(2, GL_FLOAT, sizeof(Tvector2_single), @block_texcoords[0].data[0]); glTexCoordPointer(2, GL_FLOAT, sizeof(Tvector2_single), @block_texcoords[0].data[0]);
for i := 0 to Length(render_batches) - 1 do begin
b := render_batches[i];
if b.blocks = 0 then
continue;
glBindTexture(GL_TEXTURE_2D, b.texture_gl_index);
glVertexPointer(3, GL_FLOAT, sizeof(Tvector3_single), b.vertices);
glNormalPointer(GL_FLOAT, sizeof(Tvector3_single), b.normals);
if opts.points then if opts.points then
glDrawArrays(GL_POINTS, 0, 25) glDrawArrays(GL_POINTS, 0, b.blocks * VerticesPerBlock)
else else
glDrawElements(GL_TRIANGLES, 16*2*3, GL_UNSIGNED_BYTE, @block_face_indices); glDrawElements(GL_TRIANGLES, b.blocks * FacesPerBlock * 3, GL_UNSIGNED_INT, b.face_indices);
end; end;
end; end;