void ClientMap::updateDrawList(video::IVideoDriver* driver, float dtime, unsigned int max_cycle_ms) { ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG); //g_profiler->add("CM::updateDrawList() count", 1); TimeTaker timer_step("ClientMap::updateDrawList"); INodeDefManager *nodemgr = m_gamedef->ndef(); if (!m_drawlist_last) m_drawlist_current = !m_drawlist_current; auto & drawlist = m_drawlist_current ? m_drawlist_1 : m_drawlist_0; if (!max_cycle_ms) max_cycle_ms = 300/getControl().fps_wanted; m_camera_mutex.Lock(); v3f camera_position = m_camera_position; f32 camera_fov = m_camera_fov; //v3s16 camera_offset = m_camera_offset; m_camera_mutex.Unlock(); // 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( 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); // Number of blocks in rendering range u32 blocks_in_range = 0; // Number of blocks occlusion culled u32 blocks_occlusion_culled = 0; // Number of blocks in rendering range but don't have a mesh u32 blocks_in_range_without_mesh = 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; // Distance to farthest drawn block float farthest_drawn = 0; int m_mesh_queued = 0; bool free_move = g_settings->getBool("free_move"); float range_max = 100000 * BS; if(m_control.range_all == false) range_max = m_control.wanted_range * BS; if (draw_nearest.empty()) { //ScopeProfiler sp(g_profiler, "CM::updateDrawList() make list", SPT_AVG); TimeTaker timer_step("ClientMap::updateDrawList make list"); auto lock = m_blocks.try_lock_shared_rec(); if (!lock->owns_lock()) return; draw_nearest.clear(); for(auto & ir : m_blocks) { auto bp = ir.first; if(m_control.range_all == false) { if(bp.X < p_blocks_min.X || bp.X > p_blocks_max.X || bp.Z > p_blocks_max.Z || bp.Z < p_blocks_min.Z || bp.Y < p_blocks_min.Y || bp.Y > p_blocks_max.Y) continue; } v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE; // Block center position v3f blockpos( ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS, ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS, ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS ); f32 d = radius_box(blockpos, camera_position); //blockpos_relative.getLength(); if (d> range_max) continue; int range = d / (MAP_BLOCKSIZE * BS); draw_nearest.emplace_back(std::make_pair(bp, range)); } } const int maxq = 1000; // No occlusion culling when free_move is on and camera is // inside ground bool occlusion_culling_enabled = true; if(free_move) { MapNode n = getNodeNoEx(cam_pos_nodes); if(n.getContent() == CONTENT_IGNORE || nodemgr->get(n).solidness == 2) occlusion_culling_enabled = false; } u32 calls = 0, end_ms = porting::getTimeMs() + u32(max_cycle_ms); std::unordered_map<v3POS, bool, v3POSHash, v3POSEqual> occlude_cache; while (!draw_nearest.empty()) { auto ir = draw_nearest.back(); auto bp = ir.first; int range = ir.second; draw_nearest.pop_back(); ++calls; //auto block = getBlockNoCreateNoEx(bp); auto block = m_blocks.get(bp); if (!block) continue; int mesh_step = getFarmeshStep(m_control, getNodeBlockPos(cam_pos_nodes), bp); /* Compare block position to camera position, skip if not seen on display */ auto mesh = block->getMesh(mesh_step); if (mesh) mesh->updateCameraOffset(m_camera_offset); blocks_in_range++; auto smesh_size = block->mesh_size; /* Ignore if mesh doesn't exist */ { if(!mesh) { blocks_in_range_without_mesh++; if (m_mesh_queued < maxq || range <= 2) { m_client->addUpdateMeshTask(bp, false); ++m_mesh_queued; } continue; } if(mesh_step == mesh->step && block->getTimestamp() <= mesh->timestamp && !smesh_size) { blocks_in_range_without_mesh++; continue; } } /* Occlusion culling */ v3s16 cpn = bp * MAP_BLOCKSIZE; cpn += v3s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2); float step = BS*1; float stepfac = 1.2; float startoff = BS*1; float endoff = -BS*MAP_BLOCKSIZE; //*1.42; //*1.42; v3s16 spn = cam_pos_nodes + v3s16(0,0,0); s16 bs2 = MAP_BLOCKSIZE/2 + 1; u32 needed_count = 1; if( range > 1 && smesh_size && occlusion_culling_enabled && isOccluded(this, spn, cpn + v3s16(0,0,0), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) && isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2), step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) ) { blocks_occlusion_culled++; continue; } // This block is in range. Reset usage timer. block->resetUsageTimer(); // Limit block count in case of a sudden increase blocks_would_have_drawn++; /* if(blocks_drawn >= m_control.wanted_max_blocks && m_control.range_all == false && d > m_control.wanted_min_range * BS) continue; */ if (mesh_step != mesh->step && (m_mesh_queued < maxq*1.2 || range <= 2)) { m_client->addUpdateMeshTask(bp); ++m_mesh_queued; } if (block->getTimestamp() > mesh->timestamp + (smesh_size ? 0 : range >= 1 ? 60 : 5) && (m_mesh_queued < maxq*1.5 || range <= 2)) { m_client->addUpdateMeshTaskWithEdge(bp); ++m_mesh_queued; } if(!smesh_size) continue; mesh->incrementUsageTimer(dtime); // Add to set //block->refGrab(); block->resetUsageTimer(); drawlist.set(bp, block); blocks_drawn++; if(range * MAP_BLOCKSIZE > farthest_drawn) farthest_drawn = range * MAP_BLOCKSIZE; if (farthest_drawn > m_control.farthest_drawn) m_control.farthest_drawn = farthest_drawn; if (porting::getTimeMs() > end_ms) { break; } } m_drawlist_last = draw_nearest.size(); //if (m_drawlist_last) infostream<<"breaked UDL "<<m_drawlist_last<<" collected="<<drawlist.size()<<" calls="<<calls<<" s="<<m_blocks.size()<<" maxms="<<max_cycle_ms<<" fw="<<getControl().fps_wanted<<" morems="<<porting::getTimeMs() - end_ms<< " meshq="<<m_mesh_queued<<" occache="<<occlude_cache.size()<<std::endl; if (m_drawlist_last) return; //for (auto & ir : *m_drawlist) // ir.second->refDrop(); auto m_drawlist_old = !m_drawlist_current ? &m_drawlist_1 : &m_drawlist_0; m_drawlist = m_drawlist_current ? &m_drawlist_1 : &m_drawlist_0; m_drawlist_old->clear(); m_control.blocks_would_have_drawn = blocks_would_have_drawn; m_control.blocks_drawn = blocks_drawn; m_control.farthest_drawn = farthest_drawn; g_profiler->avg("CM: blocks total", m_blocks.size()); 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: farthest drawn", farthest_drawn); g_profiler->avg("CM: wanted max blocks", m_control.wanted_max_blocks); }
u32 Map::timerUpdate(float uptime, float unload_timeout, u32 max_loaded_blocks, unsigned int max_cycle_ms, std::vector<v3POS> *unloaded_blocks) { bool save_before_unloading = (mapType() == MAPTYPE_SERVER); // Profile modified reasons Profiler modprofiler; if (/*!m_blocks_update_last && */ m_blocks_delete->size() > 1000) { m_blocks_delete = (m_blocks_delete == &m_blocks_delete_1 ? &m_blocks_delete_2 : &m_blocks_delete_1); verbosestream << "Deleting blocks=" << m_blocks_delete->size() << std::endl; for (auto & ir : *m_blocks_delete) delete ir.first; m_blocks_delete->clear(); getBlockCacheFlush(); } u32 deleted_blocks_count = 0; u32 saved_blocks_count = 0; u32 block_count_all = 0; u32 n = 0, calls = 0, end_ms = porting::getTimeMs() + max_cycle_ms; std::vector<MapBlockP> blocks_delete; int save_started = 0; { auto lock = m_blocks.try_lock_shared_rec(); if (!lock->owns_lock()) return m_blocks_update_last; #if !ENABLE_THREADS auto lock_map = m_nothread_locker.try_lock_unique_rec(); if (!lock_map->owns_lock()) return m_blocks_update_last; #endif for(auto ir : m_blocks) { if (n++ < m_blocks_update_last) { continue; } else { m_blocks_update_last = 0; } ++calls; auto block = ir.second; if (!block) continue; { auto lock = block->try_lock_unique_rec(); if (!lock->owns_lock()) continue; if(block->getUsageTimer() > unload_timeout) { // block->refGet() <= 0 && v3POS p = block->getPos(); //infostream<<" deleting block p="<<p<<" ustimer="<<block->getUsageTimer() <<" to="<< unload_timeout<<" inc="<<(uptime - block->m_uptime_timer_last)<<" state="<<block->getModified()<<std::endl; // Save if modified if (block->getModified() != MOD_STATE_CLEAN && save_before_unloading) { //modprofiler.add(block->getModifiedReasonString(), 1); if(!save_started++) beginSave(); if (!saveBlock(block)) continue; saved_blocks_count++; } blocks_delete.push_back(block); if(unloaded_blocks) unloaded_blocks->push_back(p); deleted_blocks_count++; } else { #ifndef SERVER if (block->mesh_old) block->mesh_old = nullptr; #endif if (!block->m_uptime_timer_last) // not very good place, but minimum modifications block->m_uptime_timer_last = uptime - 0.1; block->incrementUsageTimer(uptime - block->m_uptime_timer_last); block->m_uptime_timer_last = uptime; block_count_all++; /*#ifndef SERVER if(block->refGet() == 0 && block->getUsageTimer() > g_settings->getFloat("unload_unused_meshes_timeout")) { if(block->mesh){ delete block->mesh; block->mesh = NULL; } } #endif*/ } } // block lock if (porting::getTimeMs() > end_ms) { m_blocks_update_last = n; break; } } } if(save_started) endSave(); if (!calls) m_blocks_update_last = 0; for (auto & block : blocks_delete) this->deleteBlock(block); // Finally delete the empty sectors if(deleted_blocks_count != 0) { if (m_blocks_update_last) infostream << "ServerMap: timerUpdate(): Blocks processed:" << calls << "/" << m_blocks.size() << " to " << m_blocks_update_last << std::endl; PrintInfo(infostream); // ServerMap/ClientMap: infostream << "Unloaded " << deleted_blocks_count << "/" << (block_count_all + deleted_blocks_count) << " blocks from memory"; infostream << " (deleteq1=" << m_blocks_delete_1.size() << " deleteq2=" << m_blocks_delete_2.size() << ")"; if(saved_blocks_count) infostream << ", of which " << saved_blocks_count << " were written"; /* infostream<<", "<<block_count_all<<" blocks in memory"; */ infostream << "." << std::endl; if(saved_blocks_count != 0) { PrintInfo(infostream); // ServerMap/ClientMap: //infostream<<"Blocks modified by: "<<std::endl; modprofiler.print(infostream); } } return m_blocks_update_last; }