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

@ -17,22 +17,31 @@ type
fg_to_draw: integer;
end;
TTerrainBlock = record
texture_index: integer;
vertices: array[0..24] of Tvector3_single; //25*3*4 = 300B
normals: array[0..24] of Tvector3_single; //25*3*4 = 300B
end; //~600B per block
PTerrainBlock = ^TTerrainBlock;
TTerrainBlock = record
texture_index: integer;
vertices: array[0..24] of Tvector3_single; //25*3*4 = 300B
normals: array[0..24] of Tvector3_single; //25*3*4 = 300B
end; //~600B per block
PTerrainBlock = ^TTerrainBlock;
TRenderBlockBatch = record
texture_gl_index: integer;
blocks: integer;
vertices: PSingle;
normals: PSingle;
face_indices: PInteger;
end;
{ TTerrainMesh }
TTerrainMesh = class
const
FacesPerBlock = 4 * 4 * 2;
private
terrain: TWorld;
blocks: array of array of TTerrainBlock;
block_texcoords: array[0..24] of Tvector2_single; //static, 25*2*4 = 200B
block_face_indices: array[0..16*2*3 - 1] of byte; //static, 96B
textures_glidx: array of integer;
render_blocks: TList;
block_texcoords: array of Tvector2_single; //static, 25*2*4 = 200B
block_face_indices: array[0..FacesPerBlock*3 - 1] of byte; //static, 96B
render_batches: array of TRenderBlockBatch;
function TileToBlock(var tile: TTile; basey, basex: integer): TTerrainBlock;
procedure TransformTiles;
@ -46,6 +55,18 @@ type
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
Create single terrain block at given position using RS3D tile.
Basex/y is the position in block units for given dimension.
@ -140,6 +161,7 @@ const
VertexStride = 5;
var
x, y, i, tri_idx: integer;
blk: integer;
begin
tri_idx := 0;
//init face indices
@ -150,10 +172,12 @@ begin
SetTriData(tri_idx+1, i+1, i+VertexStride+1, i+VertexStride);
tri_idx += 2;
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 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;
@ -162,56 +186,43 @@ begin
inherited Destroy;
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);
const
LevelIds = '0123456789abcdefgh';
var
x, y: integer;
c: char;
begin
terrain := TWorld.Create;
level_idx := level_idx mod length(LevelIds);
c := LevelIds[1 + level_idx];
terrain.LoadFromFiles(
terrain := TWorld.Create;
level_idx := level_idx mod length(LevelIds);
c := LevelIds[1 + level_idx];
terrain.LoadFromFiles(
'data\hmp_' + c,
'data\lv_'+c+'.text',
'data\lv_'+c+'.tex');
TransformTiles;
InitBlockStaticData;
WriteLn(Format('terrain size: %dx%d, tris: %d',
TransformTiles;
InitBlockStaticData;
WriteLn(Format('terrain size: %dx%d, tris: %d',
[terrain.TileWidth, terrain.TileHeight,
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;
//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 GenTexture(tex_idx: integer; use_mip: boolean);
function GenTexture(tex_idx: integer; use_mip: boolean): integer;
const
TexW = 64;
TexH = 64;
var
texture_glidx: integer;
tex: pbyte;
begin
tex := terrain.heightmap.textures[tex_idx];
//pnm_save('tex'+IntToStr(tex_idx) + '.pnm', tex, TexW, TexH); //debug
glGenTextures(1, @textures_glidx[tex_idx]);
glBindTexture(GL_TEXTURE_2D, textures_glidx[tex_idx]);
glGenTextures(1, @texture_glidx);
glBindTexture(GL_TEXTURE_2D, texture_glidx);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, TexW, TexH, 0, GL_RGB, GL_UNSIGNED_BYTE, tex);
if use_mip then begin
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
@ -223,29 +234,72 @@ procedure TTerrainMesh.InitGL;
end;
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
result := texture_glidx;
end;
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
glEnable(GL_TEXTURE_2D);
SetLength(textures_glidx, terrain.heightmap.texture_count);
for i := 0 to terrain.heightmap.texture_count - 1 do
GenTexture(i, true);
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(@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;
{ DrawGL
Renders terrain blocks.
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.
Renders terrain block batches.
}
procedure TTerrainMesh.DrawGL(opts: TRenderOpts);
var
i: integer;
b: PTerrainBlock;
last_tex_index: integer;
b: TRenderBlockBatch;
begin
if opts.wireframe then
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
@ -256,24 +310,21 @@ begin
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
last_tex_index := -1;
glTexCoordPointer(2, GL_FLOAT, sizeof(Tvector2_single), @block_texcoords[0].data[0]);
for i := 0 to terrain.TileHeight * terrain.TileWidth - 1 do begin
b := PTerrainBlock(render_blocks[i]);
for i := 0 to Length(render_batches) - 1 do begin
b := render_batches[i];
if b.blocks = 0 then
continue;
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]);
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
glDrawArrays(GL_POINTS, 0, 25)
glDrawArrays(GL_POINTS, 0, b.blocks * VerticesPerBlock)
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;