Closed steelswing closed 9 months ago
При запуске игры в течение минуты или 2 мало фпс - 90-120. Если подождать поднимается в несколько раз из-за подгрузки чанков, потому что прорисовка в 22 чанка стоит. Подождал пока все чанки отрисуются - фпс поднялся в 3 раза.
Разрешение экрана 1920x1080, прорисовка чанков 22, играю на Windows в оконном режиме, вертикальная синхронизация выключена, AMD Ryzen 5 4600H, NVIDIA GeForce GTX 1650 Ti, 8 ГБ Оперативной памяти.
Тесты проводил когда все чанки загрузились. В 17 версии при 1350 мешах 294 фпс; В 16 версии при 1350 мешах 287 фпс. В 15 версии при 1583 мешах 269 фпс. Все в пределах погрешности. Где-то видимых чанков побольше где-то поменьше но фпс везде одинаковый +-. Чтобы повысить фпс можно было бы добавить не просто Frustum Culling но и Occlusion Culling но это видимо не так просто, раз Unity только в своей новой версии Unity 6 которая выйдет через полгода, собираются добавить Occlusion Culling на видеокарте в реальном времени.
При запуске игры в течение минуты или 2 мало фпс - 90-120. Если подождать поднимается в несколько раз из-за подгрузки чанков, потому что прорисовка в 22 чанка стоит. Подождал пока все чанки отрисуются - фпс поднялся в 3 раза.
Разрешение экрана 1920x1080, прорисовка чанков 22, играю на Windows в оконном режиме, вертикальная синхронизация выключена, AMD Ryzen 5 4600H, NVIDIA GeForce GTX 1650 Ti, 8 ГБ Оперативной памяти.
Тесты проводил когда все чанки загрузились. В 17 версии при 1350 мешах 294 фпс; В 16 версии при 1350 мешах 287 фпс. В 15 версии при 1583 мешах 269 фпс. Все в пределах погрешности. Где-то видимых чанков побольше где-то поменьше но фпс везде одинаковый +-. Чтобы повысить фпс можно было бы добавить не просто Frustum Culling но и Occlusion Culling но это видимо не так просто, раз Unity только в своей новой версии Unity 6 которая выйдет через полгода, собираются добавить Occlusion Culling на видеокарте в реальном времени.
В майне в старых версиях был Occlusion Culling, но его решили заменить на свой "Occlusion Culling" который считается при построении чанка потому что он работает быстрее
Я тоже нашёл, что игра сильно медленнее того же майна на джаве. Я попробовал сделать профилировку, и получил примерно следующие данные:
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
17.24 0.65 0.65 2336 0.28 0.28 ChunksStorage::getVoxels(VoxelsVolume*, bool) const
12.07 1.11 0.46 226661748 0.00 0.00 BlocksRenderer::isOpen(int, int, int, unsigned char) const
11.94 1.56 0.45 29072517 0.00 0.00 Chunks::getLight(int, int, int, int)
7.96 1.86 0.30 21628101 0.00 0.00 Chunks::get(int, int, int)
5.31 2.06 0.20 2658 0.08 0.59 ChunksController::loadVisible()
4.24 2.22 0.16 21842280 0.00 0.00 Chunks::getLight(int, int, int)
3.98 2.37 0.15 312151 0.00 0.00 std::_Hashtable<glm::vec<2, int, (glm::qualifier)0>, std::pair<glm::vec<2, int, (glm::qualifier)0> const, std::shared_ptr<Mesh> >, std::allocator<std::pair<glm::vec<2, int, (glm::qualifier)0> const, std::shared_ptr<Mesh> > >, std::__detail::_Select1st, std::equal_to<glm::vec<2, int, (glm::qualifier)0> >, std::hash<glm::vec<2, int, (glm::qualifier)0> >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::find(glm::vec<2, int, (glm::qualifier)0> const&)
3.85 2.51 0.15 37776958 0.00 0.00 BlocksRenderer::blockCube(int, int, int, UVRegion const (&) [6], Block const*, unsigned char, bool)
3.18 2.63 0.12 5590256 0.00 0.00 BlocksRenderer::pickSoftLight(glm::vec<3, int, (glm::qualifier)0> const&, glm::vec<3, int, (glm::qualifier)0> const&, glm::vec<3, int, (glm::qualifier)0> const&) const
2.92 2.74 0.11 2336 0.05 0.40 BlocksRenderer::render(voxel const*)
2.92 2.85 0.11 1314 0.08 0.08 verifyLoadedChunk(ContentIndices*, Chunk*)
2.92 2.96 0.11 99 1.11 1.41 WorldGenerator::generate(voxel*, int, int, int)
2.12 3.04 0.08 334 0.24 0.24 void std::__introsort_loop<__gnu_cxx::__normal_iterator<unsigned long*, std::vector<unsigned long, std::allocator<unsigned long> > >, long, __gnu_cxx::__ops::_Iter_comp_iter<WorldRenderer::drawChunks(Chunks*, Camera*, Shader*)::{lambda(unsigned long, unsigned long)#1}> >(__gnu_cxx::__normal_iterator<unsigned long*, std::vector<unsigned long, std::allocator<unsigned long> > >, __gnu_cxx::__normal_iterator<unsigned long*, std::vector<unsigned long, std::allocator<unsigned long> > >, long, __gnu_cxx::__ops::_Iter_comp_iter<WorldRenderer::drawChunks(Chunks*, Camera*, Shader*)::{lambda(unsigned long, unsigned long)#1}>)
1.86 3.11 0.07 333 0.21 0.21 BlocksController::randomTick(int, int)
1.59 3.17 0.06 1245 0.05 0.67 Lighting::onChunkLoaded(int, int)
1.46 3.23 0.06 22361024 0.00 0.00 BlocksRenderer::pickLight(int, int, int) const
1.33 3.28 0.05 359766 0.00 0.01 WorldRenderer::drawChunk(unsigned long, Camera*, Shader*, bool)
1.33 3.33 0.05 5027 0.01 0.11 LightSolver::solve()
1.33 3.38 0.05 1314 0.04 0.04 Lightmap::decode(unsigned char*)
1.33 3.43 0.05 334 0.15 5.79 WorldRenderer::drawChunks(Chunks*, Camera*, Shader*)
1.19 3.47 0.05 BlocksRenderer::face(glm::vec<3, float, (glm::qualifier)0> const&, float, float, float, glm::vec<3, float, (glm::qualifier)0> const&, glm::vec<3, float, (glm::qualifier)0> const&, glm::vec<3, float, (glm::qualifier)0> const&, UVRegion const&, glm::vec<4, float, (glm::qualifier)0> const (&) [4], glm::vec<4, float, (glm::qualifier)0> const&)
1.06 3.51 0.04 1338432 0.00 0.00 BlocksRenderer::vertex(glm::vec<3, float, (glm::qualifier)0> const&, float, float, glm::vec<4, float, (glm::qualifier)0> const&, glm::vec<3, float, (glm::qualifier)0> const&, glm::vec<3, float, (glm::qualifier)0> const&, glm::vec<3, float, (glm::qualifier)0> const&)
0.80 3.54 0.03 11962968 0.00 0.00 LightSolver::add(int, int, int)
0.80 3.57 0.03 1314 0.02 0.02 Chunk::decode(unsigned char*)
0.53 3.59 0.02 4433170 0.00 0.00 Chunks::isObstacleAt(float, float, float)
0.53 3.61 0.02 1115136 0.00 0.00 _fnlGenNoiseSingle2D
0.53 3.63 0.02 312151 0.00 0.01 ChunksRenderer::getOrRender(Chunk*)
0.53 3.65 0.02 1413 0.01 0.01 Chunk::Chunk(int, int)
0.53 3.67 0.02 Chunk::convert(unsigned char*, ContentLUT const*)
0.27 3.68 0.01 19938409 0.00 0.00 Chunks::getChunkByVoxel(int, int, int)
0.27 3.69 0.01 13415016 0.00 0.00 Chunks::getChunk(int, int)
0.27 3.70 0.01 5590256 0.00 0.00 BlocksRenderer::vertex(glm::vec<3, float, (glm::qualifier)0> const&, float, float, glm::vec<4, float, (glm::qualifier)0> const&)
0.27 3.71 0.01 1115136 0.00 0.00 fnlGetNoise2D
0.27 3.72 0.01 33302 0.00 0.00 PhysicsSolver::colisionCalc(Chunks*, Hitbox*, glm::vec<3, float, (glm::qualifier)0>&, glm::vec<3, float, (glm::qualifier)0>&, glm::vec<3, float, (glm::qualifier)0>, float)
0.27 3.73 0.01 99 0.10 0.10 Lighting::prebuildSkyLight(int, int)
0.27 3.74 0.01 64 0.16 0.16 extrle::encode(unsigned char const*, unsigned long, unsigned char*)
0.27 3.75 0.01 Chunks::isObstacleBlock(int, int, int)
0.27 3.76 0.01 lj_str_new
0.13 3.77 0.01 BlocksRenderer::blockAABB(glm::vec<3, int, (glm::qualifier)0> const&, UVRegion const (&) [6], Block const*, unsigned char, bool)
0.13 3.77 0.01 BlocksRenderer::isOpenForLight(int, int, int) const
0.00 3.77 0.00 105672 0.00 0.00 Shader::uniformMatrix(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, glm::mat<4, 4, float, (glm::qualifier)0>)
0.00 3.77 0.00 103450 0.00 0.00 Mesh::draw()
Вдруг кто захочет улучшить производительность.
Данные упорядочены от самых затратных вызовов к самым менее затратным, так же учитываются количество вызвов каждого метода (% = время метода * количество вызовов). Собственно данные собирались обычным gprof
так что это его данные. Я привёл лишь часть самых затратных вызовов, иначе бы это была огромная простыня вызовов :D
Решил ещё добавить скрин, показывающий загрузку проца, там примерно 250% (2.5 ядра молотят), так что я думаю, что упор идёт в него а не в видюху:
Релиз-сборка?
Да, релиз сборка, правда с модификацией (профайлер работает фонорм). Собственно я попробовал подождать 10 минут, и ничего не изменилось, фпс всё падает и падает. Вот лог профайлера после этого долгого запуска
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
11.84 8.76 8.76 579081902 0.00 0.00 Chunks::getLight(int, int, int, int)
10.92 16.84 8.08 7234 1.12 1.12 void std::__introsort_loop<__gnu_cxx::__normal_iterator<unsigned long*, std::vector<unsigned long, std::allocator<unsigned long> > >, long, __gnu_cxx::__ops::_Iter_comp_iter<WorldRenderer::drawChunks(Chunks*, Camera*, Shader*)::{lambda(unsigned long, unsigned long)#1}> >(__gnu_cxx::__normal_iterator<unsigned long*, std::vector<unsigned long, std::allocator<unsigned long> > >, __gnu_cxx::__normal_iterator<unsigned long*, std::vector<unsigned long, std::allocator<unsigned long> > >, long, __gnu_cxx::__ops::_Iter_comp_iter<WorldRenderer::drawChunks(Chunks*, Camera*, Shader*)::{lambda(unsigned long, unsigned long)#1}>)
8.71 23.28 6.44 21668432 0.00 0.00 std::_Hashtable<glm::vec<2, int, (glm::qualifier)0>, std::pair<glm::vec<2, int, (glm::qualifier)0> const, std::shared_ptr<Mesh> >, std::allocator<std::pair<glm::vec<2, int, (glm::qualifier)0> const, std::shared_ptr<Mesh> > >, std::__detail::_Select1st, std::equal_to<glm::vec<2, int, (glm::qualifier)0> >, std::hash<glm::vec<2, int, (glm::qualifier)0> >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::find(glm::vec<2, int, (glm::qualifier)0> const&)
8.40 29.49 6.21 20132 0.31 0.31 ChunksStorage::getVoxels(VoxelsVolume*, bool) const
6.57 34.35 4.86 7233 0.67 0.67 BlocksController::randomTick(int, int)
6.34 39.04 4.69 2171951160 0.00 0.00 BlocksRenderer::isOpen(int, int, int, unsigned char) const
5.84 43.36 4.32 324669043 0.00 0.00 Chunks::get(int, int, int)
5.02 47.07 3.71 7234 0.51 6.00 WorldRenderer::drawChunks(Chunks*, Camera*, Shader*)
4.90 50.70 3.63 21668432 0.00 0.00 ChunksRenderer::getOrRender(Chunk*)
4.74 54.20 3.51 9915 0.35 2.28 ChunksController::loadVisible()
4.48 57.51 3.31 23474614 0.00 0.00 WorldRenderer::drawChunk(unsigned long, Camera*, Shader*, bool)
3.25 59.91 2.40 3227 0.74 0.85 WorldGenerator::generate(voxel*, int, int, int)
2.79 61.98 2.07 361991860 0.00 0.00 BlocksRenderer::blockCube(int, int, int, UVRegion const (&) [6], Block const*, unsigned char, bool)
2.22 63.62 1.64 20132 0.08 0.54 BlocksRenderer::render(voxel const*)
1.61 64.81 1.19 84158568 0.00 0.00 Chunks::getLight(int, int, int)
1.46 65.89 1.08 6593247 0.00 0.00 Mesh::draw()
1.11 66.71 0.82 12183115 0.00 0.00 BlocksRenderer::vertex(glm::vec<3, float, (glm::qualifier)0> const&, float, float, glm::vec<4, float, (glm::qualifier)0> const&, glm::vec<3, float, (glm::qualifier)0> const&, glm::vec<3, float, (glm::qualifier)0> const&, glm::vec<3, float, (glm::qualifier)0> const&)
1.09 67.51 0.81 204699328 0.00 0.00 BlocksRenderer::pickLight(int, int, int) const
1.00 68.25 0.74 51174832 0.00 0.00 BlocksRenderer::pickSoftLight(glm::vec<3, int, (glm::qualifier)0> const&, glm::vec<3, int, (glm::qualifier)0> const&, glm::vec<3, int, (glm::qualifier)0> const&) const
0.81 68.85 0.60 24223 0.02 0.42 LightSolver::solve()
0.66 69.34 0.49 366891152 0.00 0.00 Chunks::getChunkByVoxel(int, int, int)
0.66 69.83 0.49 261429044 0.00 0.00 LightSolver::add(int, int, int)
0.57 70.25 0.42 6360 0.07 0.07 extrle::encode(unsigned char const*, unsigned long, unsigned char*)
0.57 70.67 0.42 4797 0.09 2.22 Lighting::onChunkLoaded(int, int)
0.53 71.06 0.39 BlocksRenderer::face(glm::vec<3, float, (glm::qualifier)0> const&, float, float, float, glm::vec<3, float, (glm::qualifier)0> const&, glm::vec<3, float, (glm::qualifier)0> const&, glm::vec<3, float, (glm::qualifier)0> const&, UVRegion const&, glm::vec<4, float, (glm::qualifier)0> const (&) [4], glm::vec<4, float, (glm::qualifier)0> const&)
0.45 71.39 0.33 3264 0.10 0.10 Lighting::prebuildSkyLight(int, int)
0.37 71.66 0.27 36348928 0.00 0.00 _fnlGenNoiseSingle2D
0.30 71.88 0.22 Chunks::isObstacleBlock(int, int, int)
0.24 72.06 0.18 174185367 0.00 0.00 Chunks::getChunk(int, int)
0.20 72.20 0.15 51174832 0.00 0.00 BlocksRenderer::vertex(glm::vec<3, float, (glm::qualifier)0> const&, float, float, glm::vec<4, float, (glm::qualifier)0> const&)
0.18 72.33 0.13 1891 0.07 0.07 verifyLoadedChunk(ContentIndices*, Chunk*)
0.16 72.45 0.12 3180 0.04 0.04 Chunk::encode() const
0.15 72.56 0.11 Chunk::convert(unsigned char*, ContentLUT const*)
0.15 72.67 0.11 2472663 0.00 0.00 Chunks::isSolidBlock(int, int, int)
0.15 72.78 0.11 5118 0.02 0.02 Chunk::updateHeights()
0.12 72.87 0.09 1854 0.05 0.05 Lightmap::decode(unsigned char*)
0.11 72.95 0.08 6645024 0.00 0.00 l_get_block(lua_State*)
0.11 73.03 0.08 2943 0.03 0.51 Lighting::buildSkyLight(int, int)
0.09 73.10 0.07 5118 0.01 0.01 Chunk::Chunk(int, int)
0.09 73.17 0.07 7505 0.01 0.02 gui::Container::draw(Batch2D*, Assets*)
0.07 73.22 0.06 BlocksRenderer::isOpenForLight(int, int, int) const
0.07 73.27 0.05 7505 0.01 0.01 gui::Container::act(float)
0.05 73.31 0.04 3304448 0.00 0.00 calc_height(fnl_state*, int, int)
0.05 73.35 0.04 471972 0.00 0.00 gui::UINode::visible() const
0.05 73.39 0.04 3180 0.01 0.01 Lightmap::encode() const
0.05 73.43 0.04 BlocksRenderer::blockAABB(glm::vec<3, int, (glm::qualifier)0> const&, UVRegion const (&) [6], Block const*, unsigned char, bool)
0.04 73.46 0.03 7505 0.00 0.01 gui::GUI::act(float)
0.04 73.49 0.03 36348928 0.00 0.00 fnlGetNoise2D
0.04 73.52 0.03 lj_vm_exit_interp
0.03 73.54 0.02 272307 0.00 0.00 Mesh::draw(unsigned int)
0.03 73.56 0.02 16596 0.00 0.00 std::_Hashtable<glm::vec<2, int, (glm::qualifier)0>, std::pair<glm::vec<2, int, (glm::qualifier)0> const, std::unique_ptr<WorldRegion, std::default_delete<WorldRegion> > >, std::allocator<std::pair<glm::vec<2, int, (glm::qualifier)0> const, std::unique_ptr<WorldRegion, std::default_delete<WorldRegion> > > >, std::__detail::_Select1st, std::equal_to<glm::vec<2, int, (glm::qualifier)0> >, std::hash<glm::vec<2, int, (glm::qualifier)0> >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::find(glm::vec<2, int, (glm::qualifier)0> const&)
0.03 73.58 0.02 7234 0.00 6.01 WorldRenderer::draw(GfxContext const&, Camera*, bool)
0.03 73.60 0.02 7234 0.00 0.00 Skybox::refresh(float, float, unsigned int)
Оптимизации точно включены? в прикрепленном выводе есть функции, которые благополучно инлайнятся в релизе
CPU: Intel® Core™ i5-1035G1 CPU @ 1.00GHz × 8 GPU: Mesa Intel® UHD Graphics (ICL GT1)
Minecraft 1.19.4 OptiFine на быстрых настройках графики 22 чанка прорисовки, чанки уже прогружены: 60/12 fps без V-Sync VoxelEngine 0.17 22 чанка прорисовки, чанки прогружены: 86/82 fps без V-Sync
Честно, я не уверен, что оптимизации включены, я собирал с единственным флагом cmake -DCMAKE_BUILD_TYPE=Release
В самом CMakeLists.txt я не нашёл никаких флагов оптимизации для linux, я пробовал добавить -O2, особого прироста в скорости я не заметил.
Есть заметная разница, если поставить Debug?
В режиме Debug как буд-то замедлилась прогрузка чанков, в остальном производительность на том же уровне, только теперь надо гораздо дольше ждать пока чанки прогружаются
Я разобрался в чём косяк с производительностью на моей системе: я запускал приложение используя докер контейнер, и как-то криво пробросил видеокарту (так и не разобрался как её нормально прокинуть, возможно, что интеловскую карту особо никак нельзя прокинуть в докер), собственно из-за чего использовалась программная отрисовки графики и игра выдавала 7фпс. Я установил библиотеки glfw-x11 и glew на свою хостовую систему (arch), после чего игра спокойно завелась выдавая в районе 50-70 фпс (без вертикальной синхронизации) на встройке от intel.
@MihailRis Я до этого писал, что не смог найти чтобы оптимизация компилятора была включена, так вот, я попробовал скомпилировать проект с флагом -O2 и о чудо, вместо 50-70 фпс он взлетел до 100-300фпс (правда он теперь стал гораздо менее стабилен). Я бы рекомендовал включить этот флаг оптимизации для linux, т.к. для Windows он уже был включён. Вот если что изменения:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4f9a4be..096f22d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,7 +38,7 @@ if(MSVC)
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /source-charset:UTF-8")
else()
- target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -lstdc++fs
+ target_compile_options(${PROJECT_NAME} PRIVATE -O2 -Wall -Wextra -lstdc++fs
# additional warnings
-Wformat-nonliteral -Wcast-align
-Wpointer-arith -Wundef
@@ -71,7 +71,7 @@ if(UNIX)
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -no-pie")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -no-pie -O2")
endif()
include_directories(${LUA_INCLUDE_DIR})
Странно, у меня при запуске cmake с флагом -DCMAKE_BUILD_TYPE=Release
само подставляет флаги оптимизации
@MihailRis Я только что перепроверил, оказывается ты прав, когда используется -DCMAKE_BUILD_TYPE=Release
, то он автоматом подтягивает флаг -O3
, только вот производительность в 5-6 раз хуже чем при ручном указании флага -O2
. Рекомендую попробовать у себя.
@MihailRis Я тут начинаю понемного верить в магию, в идеале бы мне показать тебе как-то как выглядит ускорение программы в 5-6 раз, причём одного -O2
флага там недостаточно, но и отыскать все зависимости не так просто. Если в двух словах, то добавление небольшого отладочного кода + использование O2 + включение профилировки (-pg флаг) приводит к такому ускорению программы от того, что она показыавет в main ветке.
Я создал тестовую ветку, которую ты можешь скомпилировать и запустить у себя, собственно там же ты увидишь все изменения: https://github.com/AlexXZero/VoxelEngine-Cpp/tree/test/105/random-speed-up вот что именно изменилось: https://github.com/MihailRis/VoxelEngine-Cpp/commit/dac040d92731646e917e5ae80f835e8e54e1a1d9
Вот скриншоты
P.S. на отладочный вывод можешь не обращать внимания, он поидее должен ещё и замедлять модифицированную ветку + там ещё профилировщик в фоне работает, что тоже её должно замедлять а не ускорять :D
Upd: Я обнаружил, что у меня стоит слишком высокая дальность прорисовки, так что данное поведение характерно только для этапа прогрузки чанков, после прогрузки чанков main ветка начинает работать так же быстро как и тестовая ветка, однако это занимает значительное время ожидаения, а тестовая ветка работает быстро на всём протяжении.
В общем, я докопался до сути. И даже понял почему магический код работает с разной скоростью, когда его пытаешься оптимизировать или замедлять. Всё дело в методе ChunksController::update
который подгружает чанки исходя из maxDuration
. Таким образом по умолчанию стоит принудительное понижение фпс до ~60 в момент, пока подгружаются чанки, именно из-за этого берутся все эти лаги в начале игры. Так же я обнаружил, что если поиграться с настройкой "Load Speed", то можно увеличить ФПС до приемлемых 150-200, при этом я не заметил особого замедления прогрузки чанков (т.к. чем больше ФПС, тем больше раз будет вызываться подгрузчик чанков, даже если за раз он подгружает меньшее количество чанков).
Собственно я бы рекомендовал поставить значение по умолчанию для "Load Speed" на 4 (или хотя бы на 5). Это позволяет получать 144фпс+ что достаточно для современных мониторов. Думаю что тикет можно закрывать.
В новой версии(17) всё лагает, раньше было лучше.