Reorganize ClientMap rendering code for a bit more performance
authorPerttu Ahola <celeron55@gmail.com>
Tue, 4 Sep 2012 06:48:26 +0000 (09:48 +0300)
committerPerttu Ahola <celeron55@gmail.com>
Tue, 4 Sep 2012 19:41:03 +0000 (22:41 +0300)
- Don't select blocks for drawing in every frame
- Sort meshbuffers by material before drawing

src/clientmap.cpp
src/clientmap.h
src/game.cpp
src/map.cpp
src/mapblock.cpp
src/mapblock.h

index 4d14cc1a24bfdb36acbcc7b82a87f00faf2e485b..64d5656d46135a263a4719d14d349e58cb829ed2 100644 (file)
@@ -157,43 +157,21 @@ static bool isOccluded(Map *map, v3s16 p0, v3s16 p1, float step, float stepfac,
        return false;
 }
 
-void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
+void ClientMap::updateDrawList(video::IVideoDriver* driver)
 {
-       INodeDefManager *nodemgr = m_gamedef->ndef();
-
-       //m_dout<<DTIME<<"Rendering map..."<<std::endl;
-       DSTACK(__FUNCTION_NAME);
+       ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG);
+       g_profiler->add("CM::updateDrawList() count", 1);
 
-       bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
-       
-       std::string prefix;
-       if(pass == scene::ESNRP_SOLID)
-               prefix = "CM: solid: ";
-       else
-               prefix = "CM: transparent: ";
+       INodeDefManager *nodemgr = m_gamedef->ndef();
 
-       /*
-               This is called two times per frame, reset on the non-transparent one
-       */
-       if(pass == scene::ESNRP_SOLID)
+       for(core::map<v3s16, MapBlock*>::Iterator
+                       i = m_drawlist.getIterator();
+                       i.atEnd() == false; i++)
        {
-               m_last_drawn_sectors.clear();
+               MapBlock *block = i.getNode()->getValue();
+               block->refDrop();
        }
-
-       /*
-               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_drawlist.clear();
 
        m_camera_mutex.Lock();
        v3f camera_position = m_camera_position;
@@ -201,17 +179,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
        f32 camera_fov = m_camera_fov;
        m_camera_mutex.Unlock();
 
-       /*
-               Get all blocks and draw all visible ones
-       */
+       // Use a higher fov to accomodate faster camera movements.
+       // 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 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(
@@ -223,13 +199,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
                        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;
-       
        // Number of blocks in rendering range
        u32 blocks_in_range = 0;
        // 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
        u32 blocks_drawn = 0;
        // 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
-       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);
+       //u32 blocks_without_stuff = 0;
 
        for(core::map<v2s16, MapSector*>::Iterator
                        si = m_sectors.getIterator();
@@ -380,36 +340,10 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
                                        && d > m_control.wanted_min_range * BS)
                                continue;
 
-                       // Mesh animation
-                       {
-                               //JMutexAutoLock lock(block->mesh_mutex);
-                               MapBlockMesh *mapBlockMesh = block->mesh;
-                               // Pretty random but this should work somewhat nicely
-                               bool faraway = d >= BS*50;
-                               //bool faraway = d >= m_control.wanted_range * BS;
-                               if(mapBlockMesh->isAnimationForced() ||
-                                               !faraway ||
-                                               mesh_animate_count_far < (m_control.range_all ? 200 : 50))
-                               {
-                                       bool animated = mapBlockMesh->animate(
-                                                       faraway,
-                                                       animation_time,
-                                                       crack,
-                                                       daynight_ratio);
-                                       if(animated)
-                                               mesh_animate_count++;
-                                       if(animated && faraway)
-                                               mesh_animate_count_far++;
-                               }
-                               else
-                               {
-                                       mapBlockMesh->decreaseAnimationForceTimer();
-                               }
-                       }
-
                        // Add to set
-                       drawset[block->getPos()] = block;
-                       
+                       block->refGrab();
+                       m_drawlist[block->getPos()] = block;
+
                        sector_blocks_drawn++;
                        blocks_drawn++;
 
@@ -418,8 +352,127 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
                if(sector_blocks_drawn != 0)
                        m_last_drawn_sectors[sp] = true;
        }
-       } // ScopeProfiler
        
+       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
        */
@@ -427,10 +480,90 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
        {
        ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG);
 
-       int timecheck_counter = 0;
+       MeshBufListList drawbufs;
+
        for(core::map<v3s16, MapBlock*>::Iterator
-                       i = drawset.getIterator();
+                       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
+               {
+                       //JMutexAutoLock lock(block->mesh_mutex);
+                       MapBlockMesh *mapBlockMesh = block->mesh;
+                       assert(mapBlockMesh);
+                       // Pretty random but this should work somewhat nicely
+                       bool faraway = d >= BS*50;
+                       //bool faraway = d >= m_control.wanted_range * BS;
+                       if(mapBlockMesh->isAnimationForced() ||
+                                       !faraway ||
+                                       mesh_animate_count_far < (m_control.range_all ? 200 : 50))
+                       {
+                               bool animated = mapBlockMesh->animate(
+                                               faraway,
+                                               animation_time,
+                                               crack,
+                                               daynight_ratio);
+                               if(animated)
+                                       mesh_animate_count++;
+                               if(animated && faraway)
+                                       mesh_animate_count_far++;
+                       }
+                       else
+                       {
+                               mapBlockMesh->decreaseAnimationForceTimer();
+                       }
+               }
+
+               /*
+                       Get the meshbuffers of the block
+               */
+               {
+                       //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;
+       for(core::list<MeshBufList>::Iterator i = lists.begin();
+                       i != lists.end(); i++)
        {
                {
                        timecheck_counter++;
@@ -447,9 +580,20 @@ 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
                */
@@ -494,17 +638,12 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
                        else
                                blocks_without_stuff++;
                }
+#endif
        }
        } // ScopeProfiler
        
        // Log only on solid pass because values are the same
        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 (far)", mesh_animate_count_far);
        }
index 29ebed15988d94488df41634b648a69ba2296a8b..f8a69630ec526ead34854aa1e8508e2de00e9b8a 100644 (file)
@@ -113,7 +113,8 @@ public:
        {
                return m_box;
        }
-
+       
+       void updateDrawList(video::IVideoDriver* driver);
        void renderMap(video::IVideoDriver* driver, s32 pass);
 
        int getBackgroundBrightness(float max_d, u32 daylight_factor,
@@ -141,6 +142,8 @@ private:
        v3f m_camera_direction;
        f32 m_camera_fov;
        JMutex m_camera_mutex;
+
+       core::map<v3s16, MapBlock*> m_drawlist;
        
        core::map<v2s16, bool> m_last_drawn_sectors;
 };
index 7d93e3db265f7a303aa5a15b3c69adc55017e889..a1a197219f4bad5b0c03d013aa0f6a859d17edfd 100644 (file)
@@ -1235,6 +1235,9 @@ void the_game(
        float object_hit_delay_timer = 0.0;
        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 respawn_menu_active = false;
@@ -2697,7 +2700,19 @@ void the_game(
                                item = mlist->getItem(client.getPlayerItem());
                        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
                */
index 824553b372b42db00d4389a723e21d819e7cde2e..39c6d292b3248569a0ffe382220250e24b01a440 100644 (file)
@@ -1468,8 +1468,8 @@ void Map::timerUpdate(float dtime, float unload_timeout,
                        MapBlock *block = (*i);
                        
                        block->incrementUsageTimer(dtime);
-                       
-                       if(block->getUsageTimer() > unload_timeout)
+
+                       if(block->refGet() == 0 && block->getUsageTimer() > unload_timeout)
                        {
                                v3s16 p = block->getPos();
 
index b2da768f541b14dfc38572db18f7e7df079430c6..2ae6e9bd7279259461d07788100f6e593fd0c987 100644 (file)
@@ -56,7 +56,8 @@ MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
                m_generated(false),
                m_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
                m_disk_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
-               m_usage_timer(0)
+               m_usage_timer(0),
+               m_refcount(0)
 {
        data = NULL;
        if(dummy == false)
index 7f901e5d35ec24ca48663f6556e7021310383c4e..d56d93ddae8e41c5ee0b776f0411fa9445ac9fc2 100644 (file)
@@ -430,6 +430,22 @@ public:
        {
                return m_usage_timer;
        }
+
+       /*
+               See m_refcount
+       */
+       void refGrab()
+       {
+               m_refcount++;
+       }
+       void refDrop()
+       {
+               m_refcount--;
+       }
+       int refGet()
+       {
+               return m_refcount;
+       }
        
        /*
                Node Timers
@@ -566,6 +582,12 @@ private:
                Map will unload the block when this reaches a timeout.
        */
        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)