diff --git a/CMakeLists.txt b/CMakeLists.txt index aaeea34..b353738 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -474,6 +474,8 @@ add_executable(space-game001 src/planet/PlanetData.h src/utils/Perlin.cpp src/utils/Perlin.h + src/utils/TaskManager.cpp + src/utils/TaskManager.h src/planet/StoneObject.cpp src/planet/StoneObject.h src/render/FrameBuffer.cpp @@ -491,6 +493,7 @@ set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT s target_include_directories(space-game001 PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" "${CMAKE_CURRENT_SOURCE_DIR}/external" + "C:/Boost/include/boost-1_84" ) set_target_properties(space-game001 PROPERTIES @@ -501,6 +504,7 @@ set_target_properties(space-game001 PROPERTIES # PNG_ENABLED – включает код PNG в TextureManager # SDL_MAIN_HANDLED – отключает переопределение main -> SDL_main target_compile_definitions(space-game001 PRIVATE + WIN32_LEAN_AND_MEAN PNG_ENABLED SDL_MAIN_HANDLED SIMPLIFIED diff --git a/Readme.md b/Readme.md index 874bf39..e712921 100644 --- a/Readme.md +++ b/Readme.md @@ -157,7 +157,7 @@ emrun --no_browser --port 8080 . # Emscripten new ``` -emcc src/main.cpp src/Game.cpp src/Environment.cpp src/BoneAnimatedModel.cpp src/TextModel.cpp src/Projectile.cpp src/SparkEmitter.cpp src/UiManager.cpp src/render/Renderer.cpp src/render/ShaderManager.cpp src/render/TextureManager.cpp src/render/FrameBuffer.cpp src/render/OpenGlExtensions.cpp src/utils/Utils.cpp src/utils/Perlin.cpp src/planet/PlanetData.cpp src/planet/PlanetObject.cpp src/planet/StoneObject.cpp -O2 -std=c++17 -pthread -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=4 -sTOTAL_MEMORY=4294967296 -sINITIAL_MEMORY=3221225472 -sMAXIMUM_MEMORY=4294967296 -sALLOW_MEMORY_GROWTH=1 -I./thirdparty1/eigen-5.0.0 -I./src -I./thirdparty/libzip-1.11.3/build-emcmake/install/include -L./thirdparty/libzip-1.11.3/build-emcmake/install/lib -lzip -lz -sUSE_SDL_IMAGE=2 -sUSE_SDL=2 -sUSE_LIBPNG=1 -DSIMPLIFIED=1 --preload-file space-game001x.zip -o space-game001.html +emcc src/main.cpp src/Game.cpp src/Environment.cpp src/BoneAnimatedModel.cpp src/TextModel.cpp src/Projectile.cpp src/SparkEmitter.cpp src/UiManager.cpp src/render/Renderer.cpp src/render/ShaderManager.cpp src/render/TextureManager.cpp src/render/FrameBuffer.cpp src/render/OpenGlExtensions.cpp src/utils/Utils.cpp src/utils/TaskManager.cpp src/utils/Perlin.cpp src/planet/PlanetData.cpp src/planet/PlanetObject.cpp src/planet/StoneObject.cpp -O2 -std=c++17 -pthread -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=4 -sTOTAL_MEMORY=4294967296 -sINITIAL_MEMORY=3221225472 -sMAXIMUM_MEMORY=4294967296 -sALLOW_MEMORY_GROWTH=1 -fexceptions -I./thirdparty1/eigen-5.0.0 -I./src -I./thirdparty/libzip-1.11.3/build-emcmake/install/include -IC:/Boost/include/boost-1_84 -L./thirdparty/libzip-1.11.3/build-emcmake/install/lib -lzip -lz -sUSE_SDL_IMAGE=2 -sUSE_SDL=2 -sUSE_LIBPNG=1 -DSIMPLIFIED=1 --preload-file space-game001.zip -o space-game001.html ``` # License diff --git a/src/Game.cpp b/src/Game.cpp index 4f948c2..1a075cf 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -13,7 +13,7 @@ namespace ZL { #ifdef EMSCRIPTEN - const char* CONST_ZIP_FILE = "space-game001a.zip"; + const char* CONST_ZIP_FILE = "space-game001.zip"; #else const char* CONST_ZIP_FILE = ""; #endif @@ -343,6 +343,8 @@ namespace ZL std::cout << "Init step 6 " << std::endl; planetObject.init(); + planetObject.taskManager = &taskManager; + std::cout << "Init step 7 " << std::endl; //rockTexture = std::make_unique(CreateTextureDataFromPng("./resources/rock.png", CONST_ZIP_FILE)); diff --git a/src/Game.h b/src/Game.h index 04e5558..3c2a86e 100644 --- a/src/Game.h +++ b/src/Game.h @@ -7,6 +7,8 @@ #include "planet/PlanetObject.h" #include "UiManager.h" #include "Projectile.h" +#include "utils/TaskManager.h" +#include namespace ZL { @@ -103,6 +105,11 @@ namespace ZL { uint64_t lastProjectileFireTime = 0; int maxProjectiles = 32; std::vector shipLocalEmissionPoints; + + TaskManager taskManager; + + + }; diff --git a/src/planet/PlanetData.cpp b/src/planet/PlanetData.cpp index 8458f95..394a7a4 100644 --- a/src/planet/PlanetData.cpp +++ b/src/planet/PlanetData.cpp @@ -51,12 +51,9 @@ namespace ZL { planetMeshLods[i].Move(PLANET_CENTER_OFFSET); } - -//#ifndef SIMPLIFIED planetAtmosphereLod = generateSphere(5, 0); planetAtmosphereLod.Scale(PLANET_RADIUS * 1.03); planetAtmosphereLod.Move(PLANET_CENTER_OFFSET); -//#endif } const LodLevel& PlanetData::getLodLevel(int level) const { diff --git a/src/planet/PlanetData.h b/src/planet/PlanetData.h index c16bddf..e496699 100644 --- a/src/planet/PlanetData.h +++ b/src/planet/PlanetData.h @@ -26,7 +26,7 @@ namespace ZL { VertexID generateEdgeID(const VertexID& id1, const VertexID& id2); #ifdef SIMPLIFIED - constexpr static int MAX_LOD_LEVELS = 1; + constexpr static int MAX_LOD_LEVELS = 6; #else constexpr static int MAX_LOD_LEVELS = 6; #endif diff --git a/src/planet/PlanetObject.cpp b/src/planet/PlanetObject.cpp index 5f4a86f..68852be 100644 --- a/src/planet/PlanetObject.cpp +++ b/src/planet/PlanetObject.cpp @@ -4,7 +4,7 @@ #include "render/OpenGlExtensions.h" #include "Environment.h" #include "StoneObject.h" - +#include "utils/TaskManager.h" namespace ZL { @@ -77,20 +77,18 @@ namespace ZL { sandTexture = std::make_unique(CreateTextureDataFromPng("./resources/sand2.png", CONST_ZIP_FILE)); stoneTexture = std::make_unique(CreateTextureDataFromPng("./resources/rock.png", CONST_ZIP_FILE)); - -//#ifndef SIMPLIFIED // Атмосфера planetAtmosphereRenderStruct.data = planetData.getAtmosphereLod().vertexData; if (planetAtmosphereRenderStruct.data.PositionData.size() > 0) { planetAtmosphereRenderStruct.RefreshVBO(); } -//#endif - planetStones = CreateStoneGroupData(778, planetData.getLodLevel(lodIndex)); - stonesToRender = planetStones.inflate(planetStones.allInstances.size()); + //stonesToRender = planetStones.inflate(planetStones.allInstances.size()); + stonesToRender.resize(planetStones.allInstances.size()); + planetStones.initStatuses(); stoneToBake = planetStones.inflateOne(0, 0.75); } @@ -98,6 +96,75 @@ namespace ZL { void PlanetObject::update(float deltaTimeMs) { + // 1. Проверка порога движения (оптимизация из текущего кода) + float movementThreshold = 1.0f; + if ((Environment::shipPosition - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold + && !triangleIndicesToDraw.empty()) { + processMainThreadTasks(); // Все равно обрабатываем очередь OpenGL задач + return; + } + lastUpdatePos = Environment::shipPosition; + + // 2. Получаем список видимых треугольников + auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipPosition); + std::sort(newIndices.begin(), newIndices.end()); + + // 3. Анализируем, что нужно загрузить + for (int triIdx : newIndices) { + if (planetStones.statuses[triIdx] == ChunkStatus::Empty) { + // Помечаем, чтобы не спамить задачами + planetStones.statuses[triIdx] = ChunkStatus::Generating; + + // Отправляем тяжелую математику в TaskManager + taskManager->EnqueueBackgroundTask([this, triIdx]() { + // Выполняется в фоновом потоке: только генерация геометрии в RAM + float scaleModifier = 1.0f; + VertexDataStruct generatedData = planetStones.inflateOneDataOnly(triIdx, scaleModifier); + + // Передаем задачу на загрузку в GPU в очередь главного потока + this->EnqueueMainThreadTask([this, triIdx, data = std::move(generatedData)]() mutable { + // Проверяем актуальность: если треугольник всё еще в списке видимых + auto it = std::find(triangleIndicesToDraw.begin(), triangleIndicesToDraw.end(), triIdx); + if (it != triangleIndicesToDraw.end()) { + stonesToRender[triIdx].data = std::move(data); + stonesToRender[triIdx].RefreshVBO(); // OpenGL вызов + planetStones.statuses[triIdx] = ChunkStatus::Live; + } + else { + // Если уже не нужен — просто сбрасываем статус + planetStones.statuses[triIdx] = ChunkStatus::Empty; + } + }); + }); + } + } + + // 4. Очистка (Unload) + // Если статус Live, но треугольника нет в новых индексах — освобождаем GPU память + for (size_t i = 0; i < planetStones.statuses.size(); ++i) { + if (planetStones.statuses[i] == ChunkStatus::Live) { + bool isVisible = std::binary_search(newIndices.begin(), newIndices.end(), (int)i); + if (!isVisible) { + // Очищаем данные и VBO (деструкторы shared_ptr в VertexRenderStruct сделают glDeleteBuffers) + stonesToRender[i].data.PositionData.clear(); + stonesToRender[i].vao.reset(); + stonesToRender[i].positionVBO.reset(); + stonesToRender[i].normalVBO.reset(); + stonesToRender[i].tangentVBO.reset(); + stonesToRender[i].binormalVBO.reset(); + stonesToRender[i].colorVBO.reset(); + stonesToRender[i].texCoordVBO.reset(); + + planetStones.statuses[i] = ChunkStatus::Empty; + } + } + } + + triangleIndicesToDraw = std::move(newIndices); + + // 5. Выполняем одну задачу из очереди OpenGL (RefreshVBO и т.д.) + processMainThreadTasks(); + /* float movementThreshold = 1.0f; if ((Environment::shipPosition - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold && !triangleIndicesToDraw.empty()) { return; @@ -111,7 +178,7 @@ namespace ZL { if (newIndices != triangleIndicesToDraw) { triangleIndicesToDraw = std::move(newIndices); - } + }*/ } @@ -205,7 +272,6 @@ namespace ZL { void PlanetObject::draw(Renderer& renderer) { -//#ifndef SIMPLIFIED { if (stoneMapFB == nullptr) { @@ -221,10 +287,6 @@ namespace ZL { stoneMapFB->GenerateMipmaps(); } -//#endif - - - //bakeStoneTexture(renderer); glViewport(0, 0, Environment::width, Environment::height); //-------------------------- @@ -232,10 +294,7 @@ namespace ZL { drawPlanet(renderer); drawStones(renderer); - -//#ifndef SIMPLIFIED drawAtmosphere(renderer); -//#endif } void PlanetObject::drawPlanet(Renderer& renderer) @@ -382,7 +441,7 @@ namespace ZL { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID()); - + /* for (int i : triangleIndicesToDraw) //for (int i = 0; i < stonesToRender.size(); i++) { @@ -390,7 +449,20 @@ namespace ZL { { renderer.DrawVertexRenderStruct(stonesToRender[i]); } + }*/ + for (int i : triangleIndicesToDraw) { + // КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ: + // Проверяем, что данные не просто существуют, а загружены в GPU + if (planetStones.statuses[i] == ChunkStatus::Live) { + // Дополнительная проверка на наличие данных (на всякий случай) + if (stonesToRender[i].data.PositionData.size() > 0) { + renderer.DrawVertexRenderStruct(stonesToRender[i]); + } + } + // Если статус Generating или Empty — мы просто пропускаем этот индекс. + // Камни появятся на экране плавно, как только отработает TaskManager и RefreshVBO. } + CheckGlError(); glDisable(GL_BLEND); glDisable(GL_CULL_FACE); diff --git a/src/planet/PlanetObject.h b/src/planet/PlanetObject.h index a1b57fc..416e5bd 100644 --- a/src/planet/PlanetObject.h +++ b/src/planet/PlanetObject.h @@ -16,8 +16,11 @@ #include "PlanetData.h" #include "StoneObject.h" #include "render/FrameBuffer.h" +#include +#include namespace ZL { + class TaskManager; class PlanetObject { public: @@ -39,6 +42,8 @@ namespace ZL { Vector3f lastUpdatePos; + TaskManager* taskManager = nullptr; + public: PlanetObject(); @@ -51,6 +56,32 @@ namespace ZL { void drawAtmosphere(Renderer& renderer); float distanceToPlanetSurface(const Vector3f& viewerPosition); + + std::queue> mainThreadTasks; + std::mutex mainThreadMutex; + + void EnqueueMainThreadTask(std::function task) { + std::lock_guard lock(mainThreadMutex); + mainThreadTasks.push(task); + } + + // Выполнение задач по одной (или пачкой) за кадр + void processMainThreadTasks() { + std::function task; + + // Извлекаем только одну задачу, чтобы не блокировать update надолго + { + std::lock_guard lock(mainThreadMutex); + if (!mainThreadTasks.empty()) { + task = std::move(mainThreadTasks.front()); + mainThreadTasks.pop(); + } + } + + if (task) { + task(); // Здесь выполняется RefreshVBO или загрузка текстуры + } + } }; } // namespace ZL \ No newline at end of file diff --git a/src/planet/StoneObject.cpp b/src/planet/StoneObject.cpp index f5522a4..6df3f27 100644 --- a/src/planet/StoneObject.cpp +++ b/src/planet/StoneObject.cpp @@ -16,13 +16,20 @@ namespace ZL { #ifdef SIMPLIFIED + const float StoneParams::BASE_SCALE = 10.0f; // Общий размер камня + const float StoneParams::MIN_AXIS_SCALE = 1.0f; // Минимальное растяжение/сжатие по оси + const float StoneParams::MAX_AXIS_SCALE = 1.0f; // Максимальное растяжение/сжатие по оси + const float StoneParams::MIN_PERTURBATION = 0.0f; // Минимальное радиальное возмущение вершины + const float StoneParams::MAX_PERTURBATION = 0.0f; // Максимальное радиальное возмущение вершины + const int StoneParams::STONES_PER_TRIANGLE = 40; + /* const float StoneParams::BASE_SCALE = 1000.0f; // Общий размер камня const float StoneParams::MIN_AXIS_SCALE = 1.0f; // Минимальное растяжение/сжатие по оси const float StoneParams::MAX_AXIS_SCALE = 1.0f; // Максимальное растяжение/сжатие по оси const float StoneParams::MIN_PERTURBATION = 0.0f; // Минимальное радиальное возмущение вершины const float StoneParams::MAX_PERTURBATION = 0.0f; // Максимальное радиальное возмущение вершины const int StoneParams::STONES_PER_TRIANGLE = 2; - + */ #else const float StoneParams::BASE_SCALE = 10.0f; // Общий размер камня const float StoneParams::MIN_AXIS_SCALE = 1.0f; // Минимальное растяжение/сжатие по оси @@ -297,34 +304,12 @@ namespace ZL { std::vector StoneGroup::inflate(int count) { - //static VertexDataStruct baseStone = CreateBaseConvexPolyhedron(1337); - std::vector result; result.resize(count); for (int tIdx = 0; tIdx < count; tIdx++) { result[tIdx] = inflateOne(tIdx, 1.0f); - /* - for (const auto& inst : allInstances[tIdx]) { - Matrix3f rotMat = QuatToMatrix(inst.rotation); - - for (size_t j = 0; j < baseStone.PositionData.size(); ++j) { - Vector3f p = baseStone.PositionData[j]; - Vector3f n = baseStone.NormalData[j]; - - p.v[0] *= inst.scale.v[0]; - p.v[1] *= inst.scale.v[1]; - p.v[2] *= inst.scale.v[2]; - - result[tIdx].data.PositionData.push_back(MultMatrixVector(rotMat, p) + inst.position); - result[tIdx].data.NormalData.push_back(MultMatrixVector(rotMat, n)); - result[tIdx].data.TexCoordData.push_back(baseStone.TexCoordData[j]); - - } - - result[tIdx].RefreshVBO(); - }*/ } return result; @@ -361,4 +346,33 @@ namespace ZL { return result; } + VertexDataStruct StoneGroup::inflateOneDataOnly(int index, float scaleModifier) + { + static VertexDataStruct baseStone = CreateBaseConvexPolyhedron(1337); + + VertexDataStruct result; + + + for (const auto& inst : allInstances[index]) { + Matrix3f rotMat = inst.rotation.toRotationMatrix(); + + for (size_t j = 0; j < baseStone.PositionData.size(); ++j) { + Vector3f p = baseStone.PositionData[j]; + Vector3f n = baseStone.NormalData[j]; + + p(0) *= inst.scale(0) * scaleModifier; + p(1) *= inst.scale(1) * scaleModifier; + p(2) *= inst.scale(2) * scaleModifier; + + result.PositionData.push_back(rotMat * p + inst.position); + result.NormalData.push_back(rotMat * n); + result.TexCoordData.push_back(baseStone.TexCoordData[j]); + + } + } + + + return result; + } + } // namespace ZL diff --git a/src/planet/StoneObject.h b/src/planet/StoneObject.h index 17a7d75..a2218b4 100644 --- a/src/planet/StoneObject.h +++ b/src/planet/StoneObject.h @@ -22,6 +22,13 @@ namespace ZL { Eigen::Quaternionf rotation; }; + enum class ChunkStatus { + Empty, // + Generating, // TaskManager (CPU) + ReadyToUpload, // , + Live // GPU + }; + struct StoneGroup { // mesh.PositionData inflate() VertexDataStruct mesh; @@ -32,6 +39,14 @@ namespace ZL { std::vector inflate(int count); VertexRenderStruct inflateOne(int index, float scaleModifier); + VertexDataStruct inflateOneDataOnly(int index, float scaleModifier); + + std::vector statuses; + + // + void initStatuses() { + statuses.assign(allInstances.size(), ChunkStatus::Empty); + } }; // , diff --git a/src/utils/TaskManager.cpp b/src/utils/TaskManager.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/TaskManager.h b/src/utils/TaskManager.h new file mode 100644 index 0000000..998bd85 --- /dev/null +++ b/src/utils/TaskManager.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace ZL { + + class TaskManager { + private: + boost::asio::io_context ioContext; + std::unique_ptr> workGuard; + std::vector workers; + + public: + //TaskManager(size_t threadCount = std::thread::hardware_concurrency()) { + TaskManager(size_t threadCount = 2) { + workGuard = std::make_unique>(ioContext.get_executor()); + + for (size_t i = 0; i < threadCount; ++i) { + workers.emplace_back([this]() { + ioContext.run(); + }); + } + } + + // + void EnqueueBackgroundTask(std::function task) { + boost::asio::post(ioContext, task); + } + + // Graceful shutdown + ~TaskManager() { + workGuard.reset(); // ioContext.run() , + ioContext.stop(); // : + for (auto& t : workers) { + if (t.joinable()) t.join(); + } + } + }; + +} // namespace ZL \ No newline at end of file