Closed bonesoul closed 11 years ago
I've further started inspecting the issue.
DotTrace 5.3 settings;
Manually inlined version - WITH OFFSET CALCULATION OPTIMIZATION
Note that: This initial version only calculates offset once in the loop, where as other tests doesn't have the optimization. I'll be also posting a manually inlined version test without the offset-calculation-trick.
BiomedTerrain.GenerateBlocks()
code: https://github.com/raistlinthewiz/voxeliq/commit/212af471920d7bbbb1dddbe9e392534d753c59e7
protected virtual void GenerateBlocks(Chunk chunk, int worldPositionX, int worldPositionZ)
{
float dirtHeight = this.GetRockHeight(worldPositionX, worldPositionZ);
var offset = BlockStorage.BlockIndexByWorldPosition(worldPositionX, worldPositionZ);
for (int y = (int)Chunk.MaxHeightIndexInBlocks; y >= 0; y--)
{
if ((float)y > dirtHeight)
{
BlockStorage.Blocks[offset + y] = new Block(BlockType.None);
if ((int)chunk.LowestEmptyBlockOffset > y)
{
chunk.LowestEmptyBlockOffset = (byte)y;
}
}
else
{
BlockStorage.Blocks[offset + y] = new Block(BlockType.Dirt);
if (y > (int)chunk.HighestSolidBlockOffset)
{
chunk.HighestSolidBlockOffset = (byte)y;
}
}
}
}
Statement Lambda Tests;
protected virtual void GenerateBlocks(Chunk chunk, int worldPositionX, int worldPositionZ)
{
var dirtHeight = this.GetRockHeight(worldPositionX, worldPositionZ);
for (int y = Chunk.MaxHeightIndexInBlocks; y >= 0; y--)
{
if (y > dirtHeight) // air
{
SetBlock(chunk, worldPositionX, y, worldPositionZ, BlockType.None);
}
else // dirt level
{
SetBlock(chunk, worldPositionX, y, worldPositionZ, BlockType.Dirt);
}
}
}
private Action<Chunk, int, int, int, BlockType> SetBlock = (Chunk chunk, int x, int y, int z, BlockType type) =>
{
var offset = BlockStorage.BlockIndexByWorldPosition(x, z);
BlockStorage.Blocks[offset + y] = new Block(type);
if ((type != BlockType.None) && (y > chunk.HighestSolidBlockOffset))
chunk.HighestSolidBlockOffset = (byte)y;
else if ((type == BlockType.None) && (chunk.LowestEmptyBlockOffset > y))
chunk.LowestEmptyBlockOffset = (byte)y;
};
Normal function tests
protected virtual void GenerateBlocks(Chunk chunk, int worldPositionX, int worldPositionZ)
{
var dirtHeight = this.GetRockHeight(worldPositionX, worldPositionZ);
for (int y = Chunk.MaxHeightIndexInBlocks; y >= 0; y--)
{
if (y > dirtHeight) // air
{
SetBlock(chunk, worldPositionX, y, worldPositionZ, BlockType.None);
}
else // dirt level
{
SetBlock(chunk, worldPositionX, y, worldPositionZ, BlockType.Dirt);
}
}
}
private void SetBlock(Chunk chunk, int x, int y, int z, BlockType type)
{
var offset = BlockStorage.BlockIndexByWorldPosition(x, z);
BlockStorage.Blocks[offset + y] = new Block(type);
if ((type != BlockType.None) && (y > chunk.HighestSolidBlockOffset))
chunk.HighestSolidBlockOffset = (byte)y;
else if ((type == BlockType.None) && (chunk.LowestEmptyBlockOffset > y))
chunk.LowestEmptyBlockOffset = (byte)y;
}
.net 4.5 / [MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual void GenerateBlocks(Chunk chunk, int worldPositionX, int worldPositionZ)
{
var dirtHeight = this.GetRockHeight(worldPositionX, worldPositionZ);
for (int y = Chunk.MaxHeightIndexInBlocks; y >= 0; y--)
{
if (y > dirtHeight) // air
{
SetBlock(chunk, worldPositionX, y, worldPositionZ, BlockType.None);
}
else // dirt level
{
SetBlock(chunk, worldPositionX, y, worldPositionZ, BlockType.Dirt);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetBlock(Chunk chunk, int x, int y, int z, BlockType type)
{
var offset = BlockStorage.BlockIndexByWorldPosition(x, z);
BlockStorage.Blocks[offset + y] = new Block(type);
if ((type != BlockType.None) && (y > chunk.HighestSolidBlockOffset))
chunk.HighestSolidBlockOffset = (byte)y;
else if ((type == BlockType.None) && (chunk.LowestEmptyBlockOffset > y))
chunk.LowestEmptyBlockOffset = (byte)y;
}
.net 4.0 / [MethodImpl(MethodImplOptions.NoInlining)]
protected virtual void GenerateBlocks(Chunk chunk, int worldPositionX, int worldPositionZ)
{
var dirtHeight = this.GetRockHeight(worldPositionX, worldPositionZ);
for (int y = Chunk.MaxHeightIndexInBlocks; y >= 0; y--)
{
if (y > dirtHeight) // air
{
SetBlock(chunk, worldPositionX, y, worldPositionZ, BlockType.None);
}
else // dirt level
{
SetBlock(chunk, worldPositionX, y, worldPositionZ, BlockType.Dirt);
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void SetBlock(Chunk chunk, int x, int y, int z, BlockType type)
{
var offset = BlockStorage.BlockIndexByWorldPosition(x, z);
BlockStorage.Blocks[offset + y] = new Block(type);
if ((type != BlockType.None) && (y > chunk.HighestSolidBlockOffset))
chunk.HighestSolidBlockOffset = (byte)y;
else if ((type == BlockType.None) && (chunk.LowestEmptyBlockOffset > y))
chunk.LowestEmptyBlockOffset = (byte)y;
}
Vanilla version without the offset-optimization
protected virtual void GenerateBlocks(Chunk chunk, int worldPositionX, int worldPositionZ)
{
float dirtHeight = this.GetRockHeight(worldPositionX, worldPositionZ);
for (int y = (int)Chunk.MaxHeightIndexInBlocks; y >= 0; y--)
{
var offset = BlockStorage.BlockIndexByWorldPosition(worldPositionX, worldPositionZ);
if ((float)y > dirtHeight)
{
BlockStorage.Blocks[offset + y] = new Block(BlockType.None);
if ((int)chunk.LowestEmptyBlockOffset > y)
{
chunk.LowestEmptyBlockOffset = (byte)y;
}
}
else
{
BlockStorage.Blocks[offset + y] = new Block(BlockType.Dirt);
if (y > (int)chunk.HighestSolidBlockOffset)
{
chunk.HighestSolidBlockOffset = (byte)y;
}
}
}
}
I've came up with a good idea; CalculateHeightIndexes() method which will be called by chunk builders before actual mesh building starts which will automatically calculate HighestSolidBlockOffset and LowestEmptyBlockOffset for the chunk. - https://github.com/raistlinthewiz/voxeliq/commit/9ced7e110ef9c5d34579f2d515c87d52a9fe1d72
I've implemented the idea and now it all works good. And this optimization was all about;
Who wants to build all the mesh for the chunks?
Voxeliq will optimize out your chunk meshes:)
Voxeliq currently uses an advanced optimization to improve rendering performance, in simple worlds while building the mesh for a chunk, instead of building the complete mesh for the chunk, it only builds it partially (basically it doesn't include blocks in the mesh where user can't seem them).
Check this graphic;
So lets say that a chunk may had landscape generated around y=50 ~ y=55.
So the ideas is that, user will not able to see blocks below the lowest-solid-block-index(y) in chunk.
As in the screenshot above, users will not be able to see blocks below the [chunk's lowest block index - 1](marked with purple).
And the optimization arrives here, where we only build the mesh between [chunks highest block index + 1] to [chunks lowest block index -1].
This optimization is all good and improves the rendering performance a lot but with a given drawback; with every set-block operation, chunk's highest block index and lowest block index values have to be updated.
In the current state of the engine, this is done in landscape-generation after every block-set operation;
The problem is that this is an engine and forcing game-developers for so, will increase the entry barrier of engine higher and will make things complex.
A solution is that using the available SetBlock methods which can find the chunk block belongs to and do the calculation on it's own.
The problem is that setting blocks in a block-engine is an operation used millions of times. The current method we use in first code snippet is automatic (or close enough being atomic at least in our context - as it just sets a byte value in our Flatten Block Storage array).
The latter solution in second snippet means calling that SetBlockAt() functions over and over millions of times, which for sure will decrease the performance a lot - here's a proof of concept test; http://www.dotnetperls.com/inline-optimization
So I've been looking for possible solutions and here's what I've came up with;
Statement Lamdas As I've discussed it here and they maybe a solution but I'm not sure yet, will check it further & profile.
The second solution is MethodImplOptions.AggressiveInlining that was introduced with .net framework 4.5. But I'm not sure if we'll be able to change our engine projects framework version to 4.5. Will also try that.
I'm looking for other possible solutions.
Interesting reads on the topic;