Reorganize ClientMap rendering code for a bit more performance

- Don't select blocks for drawing in every frame
- Sort meshbuffers by material before drawing
This commit is contained in:
Perttu Ahola 2012-09-04 09:48:26 +03:00
parent 0e6f7a21c6
commit ee2d9d973a
6 changed files with 281 additions and 101 deletions

@ -157,43 +157,21 @@ static bool isOccluded(Map *map, v3s16 p0, v3s16 p1, float step, float stepfac,
return false; return false;
} }
void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) void ClientMap::updateDrawList(video::IVideoDriver* driver)
{ {
ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG);
g_profiler->add("CM::updateDrawList() count", 1);
INodeDefManager *nodemgr = m_gamedef->ndef(); INodeDefManager *nodemgr = m_gamedef->ndef();
//m_dout<<DTIME<<"Rendering map..."<<std::endl; for(core::map<v3s16, MapBlock*>::Iterator
DSTACK(__FUNCTION_NAME); i = m_drawlist.getIterator();
i.atEnd() == false; i++)
bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
std::string prefix;
if(pass == scene::ESNRP_SOLID)
prefix = "CM: solid: ";
else
prefix = "CM: transparent: ";
/*
This is called two times per frame, reset on the non-transparent one
*/
if(pass == scene::ESNRP_SOLID)
{ {
m_last_drawn_sectors.clear(); MapBlock *block = i.getNode()->getValue();
block->refDrop();
} }
m_drawlist.clear();
/*
Get time for measuring timeout.
Measuring time is very useful for long delays when the
machine is swapping a lot.
*/
int time1 = time(0);
/*
Get animation parameters
*/
float animation_time = m_client->getAnimationTime();
int crack = m_client->getCrackLevel();
u32 daynight_ratio = m_client->getEnv().getDayNightRatio();
m_camera_mutex.Lock(); m_camera_mutex.Lock();
v3f camera_position = m_camera_position; v3f camera_position = m_camera_position;
@ -201,17 +179,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
f32 camera_fov = m_camera_fov; f32 camera_fov = m_camera_fov;
m_camera_mutex.Unlock(); m_camera_mutex.Unlock();
/* // Use a higher fov to accomodate faster camera movements.
Get all blocks and draw all visible ones // Blocks are cropped better when they are drawn.
*/ // Or maybe they aren't? Well whatever.
camera_fov *= 1.2;
v3s16 cam_pos_nodes = floatToInt(camera_position, BS); v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1); v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1);
v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d; v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d; v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
// Take a fair amount as we will be dropping more out later // Take a fair amount as we will be dropping more out later
// Umm... these additions are a bit strange but they are needed. // Umm... these additions are a bit strange but they are needed.
v3s16 p_blocks_min( v3s16 p_blocks_min(
@ -223,13 +199,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
p_nodes_max.Y / MAP_BLOCKSIZE + 1, p_nodes_max.Y / MAP_BLOCKSIZE + 1,
p_nodes_max.Z / MAP_BLOCKSIZE + 1); p_nodes_max.Z / MAP_BLOCKSIZE + 1);
u32 vertex_count = 0;
u32 meshbuffer_count = 0;
// For limiting number of mesh animations per frame
u32 mesh_animate_count = 0;
u32 mesh_animate_count_far = 0;
// Number of blocks in rendering range // Number of blocks in rendering range
u32 blocks_in_range = 0; u32 blocks_in_range = 0;
// Number of blocks occlusion culled // Number of blocks occlusion culled
@ -242,18 +211,9 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
// Blocks that were drawn and had a mesh // Blocks that were drawn and had a mesh
u32 blocks_drawn = 0; u32 blocks_drawn = 0;
// Blocks which had a corresponding meshbuffer for this pass // Blocks which had a corresponding meshbuffer for this pass
u32 blocks_had_pass_meshbuf = 0; //u32 blocks_had_pass_meshbuf = 0;
// Blocks from which stuff was actually drawn // Blocks from which stuff was actually drawn
u32 blocks_without_stuff = 0; //u32 blocks_without_stuff = 0;
/*
Collect a set of blocks for drawing
*/
core::map<v3s16, MapBlock*> drawset;
{
ScopeProfiler sp(g_profiler, prefix+"collecting blocks for drawing", SPT_AVG);
for(core::map<v2s16, MapSector*>::Iterator for(core::map<v2s16, MapSector*>::Iterator
si = m_sectors.getIterator(); si = m_sectors.getIterator();
@ -380,10 +340,171 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
&& d > m_control.wanted_min_range * BS) && d > m_control.wanted_min_range * BS)
continue; continue;
// Add to set
block->refGrab();
m_drawlist[block->getPos()] = block;
sector_blocks_drawn++;
blocks_drawn++;
} // foreach sectorblocks
if(sector_blocks_drawn != 0)
m_last_drawn_sectors[sp] = true;
}
g_profiler->avg("CM: blocks in range", blocks_in_range);
g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled);
if(blocks_in_range != 0)
g_profiler->avg("CM: blocks in range without mesh (frac)",
(float)blocks_in_range_without_mesh/blocks_in_range);
g_profiler->avg("CM: blocks drawn", blocks_drawn);
}
struct MeshBufList
{
video::SMaterial m;
core::list<scene::IMeshBuffer*> bufs;
};
struct MeshBufListList
{
core::list<MeshBufList> lists;
void clear()
{
lists.clear();
}
void add(scene::IMeshBuffer *buf)
{
for(core::list<MeshBufList>::Iterator i = lists.begin();
i != lists.end(); i++){
MeshBufList &l = *i;
if(l.m == buf->getMaterial()){
l.bufs.push_back(buf);
return;
}
}
MeshBufList l;
l.m = buf->getMaterial();
l.bufs.push_back(buf);
lists.push_back(l);
}
};
void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
{
DSTACK(__FUNCTION_NAME);
bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
std::string prefix;
if(pass == scene::ESNRP_SOLID)
prefix = "CM: solid: ";
else
prefix = "CM: transparent: ";
/*
This is called two times per frame, reset on the non-transparent one
*/
if(pass == scene::ESNRP_SOLID)
{
m_last_drawn_sectors.clear();
}
/*
Get time for measuring timeout.
Measuring time is very useful for long delays when the
machine is swapping a lot.
*/
int time1 = time(0);
/*
Get animation parameters
*/
float animation_time = m_client->getAnimationTime();
int crack = m_client->getCrackLevel();
u32 daynight_ratio = m_client->getEnv().getDayNightRatio();
m_camera_mutex.Lock();
v3f camera_position = m_camera_position;
v3f camera_direction = m_camera_direction;
f32 camera_fov = m_camera_fov;
m_camera_mutex.Unlock();
/*
Get all blocks and draw all visible ones
*/
v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1);
v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
// Take a fair amount as we will be dropping more out later
// Umm... these additions are a bit strange but they are needed.
v3s16 p_blocks_min(
p_nodes_min.X / MAP_BLOCKSIZE - 3,
p_nodes_min.Y / MAP_BLOCKSIZE - 3,
p_nodes_min.Z / MAP_BLOCKSIZE - 3);
v3s16 p_blocks_max(
p_nodes_max.X / MAP_BLOCKSIZE + 1,
p_nodes_max.Y / MAP_BLOCKSIZE + 1,
p_nodes_max.Z / MAP_BLOCKSIZE + 1);
u32 vertex_count = 0;
u32 meshbuffer_count = 0;
// For limiting number of mesh animations per frame
u32 mesh_animate_count = 0;
u32 mesh_animate_count_far = 0;
// Blocks that had mesh that would have been drawn according to
// rendering range (if max blocks limit didn't kick in)
u32 blocks_would_have_drawn = 0;
// Blocks that were drawn and had a mesh
u32 blocks_drawn = 0;
// Blocks which had a corresponding meshbuffer for this pass
u32 blocks_had_pass_meshbuf = 0;
// Blocks from which stuff was actually drawn
u32 blocks_without_stuff = 0;
/*
Draw the selected MapBlocks
*/
{
ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG);
MeshBufListList drawbufs;
for(core::map<v3s16, MapBlock*>::Iterator
i = m_drawlist.getIterator();
i.atEnd() == false; i++)
{
MapBlock *block = i.getNode()->getValue();
// If the mesh of the block happened to get deleted, ignore it
if(block->mesh == NULL)
continue;
float d = 0.0;
if(isBlockInSight(block->getPos(), camera_position,
camera_direction, camera_fov,
100000*BS, &d) == false)
{
continue;
}
// Mesh animation // Mesh animation
{ {
//JMutexAutoLock lock(block->mesh_mutex); //JMutexAutoLock lock(block->mesh_mutex);
MapBlockMesh *mapBlockMesh = block->mesh; MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
// Pretty random but this should work somewhat nicely // Pretty random but this should work somewhat nicely
bool faraway = d >= BS*50; bool faraway = d >= BS*50;
//bool faraway = d >= m_control.wanted_range * BS; //bool faraway = d >= m_control.wanted_range * BS;
@ -407,30 +528,42 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
} }
} }
// Add to set
drawset[block->getPos()] = block;
sector_blocks_drawn++;
blocks_drawn++;
} // foreach sectorblocks
if(sector_blocks_drawn != 0)
m_last_drawn_sectors[sp] = true;
}
} // ScopeProfiler
/* /*
Draw the selected MapBlocks Get the meshbuffers of the block
*/ */
{ {
ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG); //JMutexAutoLock lock(block->mesh_mutex);
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
scene::SMesh *mesh = mapBlockMesh->getMesh();
assert(mesh);
u32 c = mesh->getMeshBufferCount();
for(u32 i=0; i<c; i++)
{
scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
const video::SMaterial& material = buf->getMaterial();
video::IMaterialRenderer* rnd =
driver->getMaterialRenderer(material.MaterialType);
bool transparent = (rnd && rnd->isTransparent());
if(transparent == is_transparent_pass)
{
if(buf->getVertexCount() == 0)
errorstream<<"Block ["<<analyze_block(block)
<<"] contains an empty meshbuf"<<std::endl;
drawbufs.add(buf);
}
}
}
}
core::list<MeshBufList> &lists = drawbufs.lists;
int timecheck_counter = 0; int timecheck_counter = 0;
for(core::map<v3s16, MapBlock*>::Iterator for(core::list<MeshBufList>::Iterator i = lists.begin();
i = drawset.getIterator(); i != lists.end(); i++)
i.atEnd() == false; i++)
{ {
{ {
timecheck_counter++; timecheck_counter++;
@ -448,8 +581,19 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
} }
} }
MapBlock *block = i.getNode()->getValue(); MeshBufList &list = *i;
driver->setMaterial(list.m);
for(core::list<scene::IMeshBuffer*>::Iterator j = list.bufs.begin();
j != list.bufs.end(); j++)
{
scene::IMeshBuffer *buf = *j;
driver->drawMeshBuffer(buf);
vertex_count += buf->getVertexCount();
meshbuffer_count++;
}
#if 0
/* /*
Draw the faces of the block Draw the faces of the block
*/ */
@ -494,17 +638,12 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
else else
blocks_without_stuff++; blocks_without_stuff++;
} }
#endif
} }
} // ScopeProfiler } // ScopeProfiler
// Log only on solid pass because values are the same // Log only on solid pass because values are the same
if(pass == scene::ESNRP_SOLID){ if(pass == scene::ESNRP_SOLID){
g_profiler->avg("CM: blocks in range", blocks_in_range);
g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled);
if(blocks_in_range != 0)
g_profiler->avg("CM: blocks in range without mesh (frac)",
(float)blocks_in_range_without_mesh/blocks_in_range);
g_profiler->avg("CM: blocks drawn", blocks_drawn);
g_profiler->avg("CM: animated meshes", mesh_animate_count); g_profiler->avg("CM: animated meshes", mesh_animate_count);
g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far); g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far);
} }

@ -114,6 +114,7 @@ public:
return m_box; return m_box;
} }
void updateDrawList(video::IVideoDriver* driver);
void renderMap(video::IVideoDriver* driver, s32 pass); void renderMap(video::IVideoDriver* driver, s32 pass);
int getBackgroundBrightness(float max_d, u32 daylight_factor, int getBackgroundBrightness(float max_d, u32 daylight_factor,
@ -142,6 +143,8 @@ private:
f32 m_camera_fov; f32 m_camera_fov;
JMutex m_camera_mutex; JMutex m_camera_mutex;
core::map<v3s16, MapBlock*> m_drawlist;
core::map<v2s16, bool> m_last_drawn_sectors; core::map<v2s16, bool> m_last_drawn_sectors;
}; };

@ -1235,6 +1235,9 @@ void the_game(
float object_hit_delay_timer = 0.0; float object_hit_delay_timer = 0.0;
float time_from_last_punch = 10; float time_from_last_punch = 10;
float update_draw_list_timer = 0.0;
v3f update_draw_list_last_cam_dir;
bool invert_mouse = g_settings->getBool("invert_mouse"); bool invert_mouse = g_settings->getBool("invert_mouse");
bool respawn_menu_active = false; bool respawn_menu_active = false;
@ -2698,6 +2701,18 @@ void the_game(
camera.wield(item); camera.wield(item);
} }
/*
Update block draw list every 200ms or when camera direction has
changed much
*/
update_draw_list_timer += dtime;
if(update_draw_list_timer >= 0.2 ||
update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2){
update_draw_list_timer = 0;
client.getEnv().getClientMap().updateDrawList(driver);
update_draw_list_last_cam_dir = camera_direction;
}
/* /*
Drawing begins Drawing begins
*/ */

@ -1469,7 +1469,7 @@ void Map::timerUpdate(float dtime, float unload_timeout,
block->incrementUsageTimer(dtime); block->incrementUsageTimer(dtime);
if(block->getUsageTimer() > unload_timeout) if(block->refGet() == 0 && block->getUsageTimer() > unload_timeout)
{ {
v3s16 p = block->getPos(); v3s16 p = block->getPos();

@ -56,7 +56,8 @@ MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
m_generated(false), m_generated(false),
m_timestamp(BLOCK_TIMESTAMP_UNDEFINED), m_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
m_disk_timestamp(BLOCK_TIMESTAMP_UNDEFINED), m_disk_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
m_usage_timer(0) m_usage_timer(0),
m_refcount(0)
{ {
data = NULL; data = NULL;
if(dummy == false) if(dummy == false)

@ -431,6 +431,22 @@ public:
return m_usage_timer; return m_usage_timer;
} }
/*
See m_refcount
*/
void refGrab()
{
m_refcount++;
}
void refDrop()
{
m_refcount--;
}
int refGet()
{
return m_refcount;
}
/* /*
Node Timers Node Timers
*/ */
@ -566,6 +582,12 @@ private:
Map will unload the block when this reaches a timeout. Map will unload the block when this reaches a timeout.
*/ */
float m_usage_timer; float m_usage_timer;
/*
Reference count; currently used for determining if this block is in
the list of blocks to be drawn.
*/
int m_refcount;
}; };
inline bool blockpos_over_limit(v3s16 p) inline bool blockpos_over_limit(v3s16 p)