From 2933465f840c8b0f59c9683e755d79ee8b540b95 Mon Sep 17 00:00:00 2001 From: DrSocalkwe3n Date: Fri, 25 Jul 2025 14:59:45 +0600 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=D0=BE=20=D0=BE?= =?UTF-8?q?=D1=82=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B3=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BC=D0=B5=D1=88?= =?UTF-8?q?=D0=B5=D0=B9=20=D1=87=D0=B0=D0=BD=D0=BA=D0=BE=D0=B2=20=D0=B2=20?= =?UTF-8?q?=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE=D0=BC=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/Client/Vulkan/VulkanRenderSession.cpp | 313 +++++++++++++++++----- Src/Client/Vulkan/VulkanRenderSession.hpp | 137 +++++++++- Src/Common/Abstract.hpp | 4 +- 3 files changed, 373 insertions(+), 81 deletions(-) diff --git a/Src/Client/Vulkan/VulkanRenderSession.cpp b/Src/Client/Vulkan/VulkanRenderSession.cpp index 1e2dbda..161b0a5 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.cpp +++ b/Src/Client/Vulkan/VulkanRenderSession.cpp @@ -9,6 +9,7 @@ #include "glm/ext/scalar_constants.hpp" #include "glm/matrix.hpp" #include "glm/trigonometric.hpp" +#include #include #include #include @@ -17,13 +18,214 @@ namespace LV::Client::VK { +void VulkanRenderSession::ThreadVertexObj_t::run() { + Logger LOG = "ThreadVertex"; + + // Контейнеры событий + std::vector changedDefines_Voxel; + std::vector changedDefines_Node; + std::unordered_map> changedContent_Chunk; + std::unordered_map> changedContent_RegionRemove; + + + // std::vector::Pointer> ToDelete_Voxels; + // std::vector::Pointer> ToDelete_Nodes; + + try { + while(Stage != EnumStage::Shutdown) { + if(Stage != EnumStage::WorldUpdate) { + // Переносим все события в локальные хранилища + ServerSession_InUse = true; + + if(!ChangedContent_Chunk.empty()) { + for(auto& [worldId, chunks] : ChangedContent_Chunk) { + auto &list = changedContent_Chunk[worldId]; + list.insert(list.end(), chunks.begin(), chunks.end()); + } + + ChangedContent_Chunk.clear(); + } + + if(!ChangedContent_RegionRemove.empty()) { + for(auto& [worldId, chunks] : ChangedContent_RegionRemove) { + auto &list = changedContent_RegionRemove[worldId]; + list.insert(list.end(), chunks.begin(), chunks.end()); + } + + ChangedContent_RegionRemove.clear(); + } + + if(!ChangedDefines_Voxel.empty()) { + changedDefines_Voxel.insert(changedDefines_Voxel.end(), ChangedDefines_Voxel.begin(), ChangedDefines_Voxel.end()); + ChangedDefines_Voxel.clear(); + + std::sort(changedDefines_Voxel.begin(), changedDefines_Voxel.end()); + auto last = std::unique(changedDefines_Voxel.begin(), changedDefines_Voxel.end()); + changedDefines_Voxel.erase(last, changedDefines_Voxel.end()); + } + + if(!ChangedDefines_Node.empty()) { + changedDefines_Node.insert(changedDefines_Node.end(), ChangedDefines_Node.begin(), ChangedDefines_Voxel.end()); + ChangedDefines_Node.clear(); + + std::sort(changedDefines_Node.begin(), changedDefines_Node.end()); + auto last = std::unique(changedDefines_Node.begin(), changedDefines_Node.end()); + changedDefines_Node.erase(last, changedDefines_Node.end()); + } + + ServerSession_InUse = false; + + // Ищем чанки, которые нужно перерисовать + if(!changedDefines_Voxel.empty() || changedDefines_Node.empty()) { + for(auto& [worldId, regions] : ChunkMesh) { + for(auto& [regionPos, chunks] : regions) { + for(size_t index = 0; index < chunks.size(); index++) { + if(!changedDefines_Voxel.empty() && !chunks[index].VoxelDefines.empty()) { + bool hasIntersection = false; + { + size_t i = 0, j = 0; + while(i < changedDefines_Voxel.size() && j < chunks[index].VoxelDefines.size()) { + if (changedDefines_Voxel[i] == chunks[index].VoxelDefines[j]) { + hasIntersection = true; + break; + } else if (changedDefines_Voxel[i] < chunks[index].VoxelDefines[j]) + ++i; + else + ++j; + } + } + + if(hasIntersection) { + changedContent_Chunk[worldId].push_back((Pos::GlobalChunk(regionPos) << 2) + Pos::GlobalChunk(Pos::bvec4u().unpack(index))); + } + } + + if(!changedDefines_Node.empty() && !chunks[index].NodeDefines.empty()) { + bool hasIntersection = false; + { + size_t i = 0, j = 0; + while(i < changedDefines_Node.size() && j < chunks[index].NodeDefines.size()) { + if (changedDefines_Node[i] == chunks[index].NodeDefines[j]) { + hasIntersection = true; + break; + } else if (changedDefines_Node[i] < chunks[index].NodeDefines[j]) + ++i; + else + ++j; + } + } + + if(hasIntersection) { + changedContent_Chunk[worldId].push_back((Pos::GlobalChunk(regionPos) << 2) + Pos::GlobalChunk(Pos::bvec4u().unpack(index))); + } + } + } + } + } + } + + changedDefines_Voxel.clear(); + changedDefines_Node.clear(); + + // Уникализируем + for(auto& [worldId, chunks] : changedContent_Chunk) { + std::sort(chunks.begin(), chunks.end()); + auto last = std::unique(chunks.begin(), chunks.end()); + chunks.erase(last, chunks.end()); + } + + for(auto& [worldId, regions] : changedContent_RegionRemove) { + std::sort(regions.begin(), regions.end()); + auto last = std::unique(regions.begin(), regions.end()); + regions.erase(last, regions.end()); + } + } + + if(Stage == EnumStage::ComposingCommandBuffer || Stage == EnumStage::Render) { + // Здесь можно обработать события, и подготовить меши по данным с мира + // changedContent_Chunk + + ServerSession_InUse = true; + + std::vector toRemove; + for(auto& [worldId, chunks] : changedContent_Chunk) { + if(Stage != EnumStage::ComposingCommandBuffer && Stage != EnumStage::Render) + break; + + while(!chunks.empty() && Stage != EnumStage::ComposingCommandBuffer && Stage != EnumStage::Render) { + + // TODO: Сгенерировать меши + + chunks.pop_back(); + } + + if(chunks.empty()) + toRemove.push_back(worldId); + } + + for(WorldId_t worldId : toRemove) + changedContent_Chunk.erase(changedContent_Chunk.find(worldId)); + + ServerSession_InUse = false; + } + + if(Stage == EnumStage::Render || Stage == EnumStage::WorldUpdate) { + // Здесь можно выгрузить готовые данные в ChunkMesh + ChunkMesh_IsUse = true; + + // Удаляем регионы + for(auto& [worldId, regions] : changedContent_RegionRemove) { + auto iterWorld = ChunkMesh.find(worldId); + if(iterWorld == ChunkMesh.end()) + continue; + + for(Pos::GlobalRegion regionPos : regions) { + auto iterRegion = iterWorld->second.find(regionPos); + if(iterRegion == iterWorld->second.end()) + continue; + + for(size_t index = 0; index < iterRegion->second.size(); index++) { + auto &chunk = iterRegion->second[index]; + chunk.NodeDefines.clear(); + chunk.VoxelDefines.clear(); + + VertexPool_Voxels.dropVertexs(chunk.VoxelPointer); + VertexPool_Nodes.dropVertexs(chunk.NodePointer); + } + } + } + + // Добавляем обновлённые меши + + ChunkMesh_IsUse = false; + } + } + } catch(const std::exception &exc) { + LOG.error() << exc.what(); + } + + ChunkMesh_IsUse = false; + ServerSession_InUse = false; +} + +void VulkanRenderSession::VulkanContext::onUpdate() { + // { + // Сделать отдельный пул комманд? + // auto lock = ThreadVertexObj.lock(); + // lock->VertexPool_Voxels.update(VkInst->Graphics.Pool); + // lock->VertexPool_Nodes.update(VkInst->Graphics.Pool); + // } + + MainTest.atlasUpdateDynamicData(); + LightDummy.atlasUpdateDynamicData(); +} VulkanRenderSession::VulkanRenderSession() { } VulkanRenderSession::~VulkanRenderSession() { - + } void VulkanRenderSession::free(Vulkan *instance) { @@ -674,57 +876,37 @@ void VulkanRenderSession::onContentDefinesLost(std::unordered_map& changeOrAddList, const std::unordered_set& remove) { - auto &table = External.ChunkVoxelMesh[worldId]; + std::unordered_map> chunkChanges; + chunkChanges[worldId] = std::vector(changeOrAddList.begin(), changeOrAddList.end()); + std::unordered_map> regionRemove; + regionRemove[worldId] = std::vector(remove.begin(), remove.end()); + VKCTX->ThreadVertexObj.onContentChunkChange(chunkChanges, regionRemove); + + // if(chunk.Voxels.empty()) { + // VKCTX->VertexPool_Voxels.dropVertexs(std::get<0>(buffers)); + // } else { + // std::vector vertexs = generateMeshForVoxelChunks(chunk.Voxels); + // auto &voxels = std::get<0>(buffers); + // VKCTX->VertexPool_Voxels.relocate(voxels, std::move(vertexs)); + // } - for(Pos::GlobalChunk pos : changeOrAddList) { - Pos::GlobalRegion rPos = pos >> 2; - Pos::bvec4u cPos = pos & 0x3; + // std::vector vertexs2 = generateMeshForNodeChunks(chunk.Nodes.data()); - auto &buffers = table[pos]; - - const auto &chunk = ServerSession->Data.Worlds[worldId].Regions[rPos].Chunks[cPos.pack()]; - - if(chunk.Voxels.empty()) { - VKCTX->VertexPool_Voxels.dropVertexs(std::get<0>(buffers)); - } else { - std::vector vertexs = generateMeshForVoxelChunks(chunk.Voxels); - auto &voxels = std::get<0>(buffers); - VKCTX->VertexPool_Voxels.relocate(voxels, std::move(vertexs)); - } - - std::vector vertexs2 = generateMeshForNodeChunks(chunk.Nodes.data()); - - if(vertexs2.empty()) { - VKCTX->VertexPool_Nodes.dropVertexs(std::get<1>(buffers)); - } else { - auto &nodes = std::get<1>(buffers); - VKCTX->VertexPool_Nodes.relocate(nodes, std::move(vertexs2)); - } + // if(vertexs2.empty()) { + // VKCTX->VertexPool_Nodes.dropVertexs(std::get<1>(buffers)); + // } else { + // auto &nodes = std::get<1>(buffers); + // VKCTX->VertexPool_Nodes.relocate(nodes, std::move(vertexs2)); + // } - if(!std::get<0>(buffers) && !std::get<1>(buffers)) { - auto iter = table.find(pos); - if(iter != table.end()) - table.erase(iter); - } - } - - for(Pos::GlobalRegion pos : remove) { - for(int z = 0; z < 4; z++) - for(int y = 0; y < 4; y++) - for(int x = 0; x < 4; x++) { - auto iter = table.find((Pos::GlobalChunk(pos) << 2) + Pos::GlobalChunk(x, y, z)); - if(iter != table.end()) { - VKCTX->VertexPool_Voxels.dropVertexs(std::get<0>(iter->second)); - VKCTX->VertexPool_Nodes.dropVertexs(std::get<1>(iter->second)); - table.erase(iter); - } - } - } - - if(table.empty()) - External.ChunkVoxelMesh.erase( External.ChunkVoxelMesh.find(worldId)); + // if(!std::get<0>(buffers) && !std::get<1>(buffers)) { + // auto iter = table.find(pos); + // if(iter != table.end()) + // table.erase(iter); + // } } + void VulkanRenderSession::setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) { WorldId = worldId; Pos = pos; @@ -733,10 +915,7 @@ void VulkanRenderSession::setCameraPos(WorldId_t worldId, Pos::Object pos, glm:: void VulkanRenderSession::beforeDraw() { if(VKCTX) { - VKCTX->MainTest.atlasUpdateDynamicData(); - VKCTX->LightDummy.atlasUpdateDynamicData(); - VKCTX->VertexPool_Voxels.update(VkInst->Graphics.Pool); - VKCTX->VertexPool_Nodes.update(VkInst->Graphics.Pool); + VKCTX->onUpdate(); } } @@ -895,25 +1074,25 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff { Pos::GlobalChunk x64offset = X64Offset >> Pos::Object_t::BS_Bit >> 4; - auto iterWorld = External.ChunkVoxelMesh.find(WorldId); - if(iterWorld != External.ChunkVoxelMesh.end()) { - glm::mat4 orig = PCO.Model; + // auto iterWorld = External.ChunkVoxelMesh.find(WorldId); + // if(iterWorld != External.ChunkVoxelMesh.end()) { + // glm::mat4 orig = PCO.Model; - for(auto &pair : iterWorld->second) { - if(auto& nodes = std::get<1>(pair.second)) { - glm::vec3 cpos(pair.first-x64offset); - PCO.Model = glm::translate(orig, cpos*16.f); - auto [vkBuffer, offset] = VKCTX->VertexPool_Nodes.map(nodes); + // for(auto &pair : iterWorld->second) { + // if(auto& nodes = std::get<1>(pair.second)) { + // glm::vec3 cpos(pair.first-x64offset); + // PCO.Model = glm::translate(orig, cpos*16.f); + // auto [vkBuffer, offset] = VKCTX->VertexPool_Nodes.map(nodes); - vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout, - VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, offsetof(WorldPCO, Model), sizeof(WorldPCO::Model), &PCO.Model); - vkCmdBindVertexBuffers(drawCmd, 0, 1, &vkBuffer, &vkOffsets); - vkCmdDraw(drawCmd, nodes.VertexCount, 1, offset, 0); - } - } + // vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout, + // VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, offsetof(WorldPCO, Model), sizeof(WorldPCO::Model), &PCO.Model); + // vkCmdBindVertexBuffers(drawCmd, 0, 1, &vkBuffer, &vkOffsets); + // vkCmdDraw(drawCmd, nodes.VertexCount, 1, offset, 0); + // } + // } - PCO.Model = orig; - } + // PCO.Model = orig; + // } } } diff --git a/Src/Client/Vulkan/VulkanRenderSession.hpp b/Src/Client/Vulkan/VulkanRenderSession.hpp index 6084ea3..ca1dda8 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.hpp +++ b/Src/Client/Vulkan/VulkanRenderSession.hpp @@ -5,9 +5,13 @@ #include #include #include +#include +#include #include +#include #include #include "Abstract.hpp" +#include "TOSLib.hpp" #include "VertexPool.hpp" /* @@ -39,7 +43,9 @@ struct WorldPCO { static_assert(sizeof(WorldPCO) == 128); - +/* + Модуль, рисующий то, что предоставляет IServerSession +*/ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { VK::Vulkan *VkInst = nullptr; // Доступ к миру на стороне клиента @@ -63,21 +69,131 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { glm::vec3 X64Offset_f, X64Delta; // Смещение мира относительно игрока в матрице вида (0 -> 64) glm::quat Quat; + /* + Поток, занимающийся генерацией меша на основе нод и вокселей + Требует доступ к профилям в ServerSession (ServerSession должен быть заблокирован только на чтение) + Также доступ к идентификаторам текстур в VulkanRenderSession (только на чтение) + Должен оповещаться об изменениях профилей и событий чанков + Удалённые мешы хранятся в памяти N количество кадров + */ + struct ThreadVertexObj_t { + + // Здесь не хватает стадии работы с текстурами + enum class EnumStage { + // Постройка буфера команд на рисовку + // В этот период не должно быть изменений в таблице, + // хранящей указатели на данные для рендера ChunkMesh + // Можно работать с миром + // Здесь нужно дождаться завершения работы с ChunkMesh + ComposingCommandBuffer, + // В этот период можно менять ChunkMesh + // Можно работать с миром + Render, + // В этот период нельзя работать с миром + // Можно менять ChunkMesh + // Здесь нужно дождаться завершения работы с миром, только в + // этом этапе могут приходить события изменения чанков и определений + WorldUpdate, + + Shutdown + } Stage = EnumStage::Render; + + volatile bool ChunkMesh_IsUse = false, ServerSession_InUse = false; + + struct ChunkObj_t { + // Сортированный список уникальных значений + std::vector VoxelDefines; + VertexPool::Pointer VoxelPointer; + std::vector NodeDefines; + VertexPool::Pointer NodePointer; + }; + + std::unordered_map> + > ChunkMesh; + + ThreadVertexObj_t(Vulkan* vkInst) + : VertexPool_Voxels(vkInst), + VertexPool_Nodes(vkInst), + Thread(&ThreadVertexObj_t::run, this) + {} + + ~ThreadVertexObj_t() { + Stage = EnumStage::Shutdown; + Thread.join(); + } + + // Сюда входят добавленные/изменённые/удалённые определения нод и вокселей + // Чтобы перерисовать чанки, связанные с ними + void onContentDefinesChange(const std::vector& voxels, const std::vector& nodes) { + ChangedDefines_Voxel.insert(ChangedDefines_Voxel.end(), voxels.begin(), voxels.end()); + ChangedDefines_Node.insert(ChangedDefines_Node.end(), nodes.begin(), nodes.end()); + } + + // Изменение/удаление чанков + void onContentChunkChange(const std::unordered_map>& chunkChanges, const std::unordered_map>& regionRemove) { + for(auto& [worldId, chunks] : chunkChanges) { + auto &list = ChangedContent_Chunk[worldId]; + list.insert(list.end(), chunks.begin(), chunks.end()); + } + + for(auto& [worldId, regions] : regionRemove) { + auto &list = ChangedContent_RegionRemove[worldId]; + list.insert(list.end(), regions.begin(), regions.end()); + } + } + + // Синхронизация потока рендера мира + void pushStage(EnumStage stage) { + if(Stage == EnumStage::Shutdown) + MAKE_ERROR("Остановка из-за ошибки ThreadVertex"); + + assert(Stage != stage); + + Stage = stage; + if(stage == EnumStage::ComposingCommandBuffer) { + while(ChunkMesh_IsUse); + } else if(stage == EnumStage::WorldUpdate) { + while(ServerSession_InUse); + } + } + + private: + // Буферы для хранения вершин + VertexPool VertexPool_Voxels; + VertexPool VertexPool_Nodes; + // Списки изменённых определений + std::vector ChangedDefines_Voxel; + std::vector ChangedDefines_Node; + // Список чанков на перерисовку + std::unordered_map> ChangedContent_Chunk; + std::unordered_map> ChangedContent_RegionRemove; + // Внешний поток + std::thread Thread; + + void run(); + }; + struct VulkanContext { + VK::Vulkan *VkInst; AtlasImage MainTest, LightDummy; Buffer TestQuad; std::optional TestVoxel; + ThreadVertexObj_t ThreadVertexObj; - VertexPool VertexPool_Voxels; - VertexPool VertexPool_Nodes; - - VulkanContext(Vulkan *vkInst) - : MainTest(vkInst), LightDummy(vkInst), + VulkanContext(Vulkan* vkInst) + : VkInst(vkInst), + MainTest(vkInst), LightDummy(vkInst), TestQuad(vkInst, sizeof(NodeVertexStatic)*6*3*2), - VertexPool_Voxels(vkInst), - VertexPool_Nodes(vkInst) + ThreadVertexObj(vkInst) {} + + ~VulkanContext() { + ThreadVertexObj.pushStage(ThreadVertexObj_t::EnumStage::Shutdown); + } + + void onUpdate(); }; std::shared_ptr VKCTX; @@ -119,11 +235,6 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { std::map ServerToAtlas; struct { - std::unordered_map::Pointer, VertexPool::Pointer> // Voxels, Nodes - > - > ChunkVoxelMesh; } External; virtual void free(Vulkan *instance) override; diff --git a/Src/Common/Abstract.hpp b/Src/Common/Abstract.hpp index 3ad260e..c69cbc2 100644 --- a/Src/Common/Abstract.hpp +++ b/Src/Common/Abstract.hpp @@ -97,12 +97,14 @@ public: return out; } - void unpack(const Pack pack) requires (N*BitsPerComponent <= 64) { + BitVec3 unpack(const Pack pack) requires (N*BitsPerComponent <= 64) { using U = std::make_unsigned_t; for(size_t iter = 0; iter < N; iter++) { set(iter, T(U((pack >> BitsPerComponent*iter) & U((Pack(1) << BitsPerComponent)-1)))); } + + return *this; } auto operator<=>(const BitVec3&) const = default;