diff --git a/.gitignore b/.gitignore index 9308498..9de2b7f 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ /gmon.out /log.raw +/Cache diff --git a/Src/Client/Vulkan/Vulkan.cpp b/Src/Client/Vulkan/Vulkan.cpp index 03524b0..081758d 100644 --- a/Src/Client/Vulkan/Vulkan.cpp +++ b/Src/Client/Vulkan/Vulkan.cpp @@ -148,10 +148,14 @@ void Vulkan::run() double prevTime = glfwGetTime(); while(!NeedShutdown) { + float dTime = glfwGetTime()-prevTime; prevTime += dTime; Screen.State = DrawState::Begin; + if(Game.RSession) + Game.RSession->pushStage(EnumRenderStage::ComposingCommandBuffer); + { std::lock_guard lock(Screen.BeforeDrawMtx); while(!Screen.BeforeDraw.empty()) @@ -452,7 +456,8 @@ void Vulkan::run() // vkCmdBeginRenderPass(Graphics.CommandBufferRender, &rp_begin, VK_SUBPASS_CONTENTS_INLINE); // } - + if(Game.RSession) + Game.RSession->pushStage(EnumRenderStage::Render); #ifdef HAS_IMGUI ImGui_ImplVulkan_NewFrame(); @@ -539,6 +544,9 @@ void Vulkan::run() vkAssert(err == VK_TIMEOUT); if(Game.Session) { + if(Game.RSession) + Game.RSession->pushStage(EnumRenderStage::WorldUpdate); + Game.Session->atFreeDrawTime(gTime, dTime); } diff --git a/Src/Client/Vulkan/Vulkan.hpp b/Src/Client/Vulkan/Vulkan.hpp index d7a6016..57f4ff7 100644 --- a/Src/Client/Vulkan/Vulkan.hpp +++ b/Src/Client/Vulkan/Vulkan.hpp @@ -1021,5 +1021,25 @@ public: virtual ~PipelineVGF(); }; + +enum class EnumRenderStage { + // Постройка буфера команд на рисовку + // В этот период не должно быть изменений в таблице, + // хранящей указатели на данные для рендера ChunkMesh + // Можно работать с миром + // Здесь нужно дождаться завершения работы с ChunkMesh + ComposingCommandBuffer, + // В этот период можно менять ChunkMesh + // Можно работать с миром + Render, + // В этот период нельзя работать с миром + // Можно менять ChunkMesh + // Здесь нужно дождаться завершения работы с миром, только в + // этом этапе могут приходить события изменения чанков и определений + WorldUpdate, + + Shutdown +}; + } /* namespace TOS::Navie::VK */ diff --git a/Src/Client/Vulkan/VulkanRenderSession.cpp b/Src/Client/Vulkan/VulkanRenderSession.cpp index 161b0a5..08f7d20 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.cpp +++ b/Src/Client/Vulkan/VulkanRenderSession.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -27,15 +28,22 @@ void VulkanRenderSession::ThreadVertexObj_t::run() { std::unordered_map> changedContent_Chunk; std::unordered_map> changedContent_RegionRemove; + // Контейнер новых мешей + std::unordered_map>> chunksUpdate; // std::vector::Pointer> ToDelete_Voxels; // std::vector::Pointer> ToDelete_Nodes; try { - while(Stage != EnumStage::Shutdown) { - if(Stage != EnumStage::WorldUpdate) { + + while(State.get_read().Stage != EnumRenderStage::Shutdown) { + bool hasWork = false; + auto lock = State.lock(); + + if(lock->Stage != EnumRenderStage::WorldUpdate) { // Переносим все события в локальные хранилища - ServerSession_InUse = true; + lock->ServerSession_InUse = true; + lock.unlock(); if(!ChangedContent_Chunk.empty()) { for(auto& [worldId, chunks] : ChangedContent_Chunk) { @@ -44,6 +52,7 @@ void VulkanRenderSession::ThreadVertexObj_t::run() { } ChangedContent_Chunk.clear(); + hasWork = true; } if(!ChangedContent_RegionRemove.empty()) { @@ -53,6 +62,7 @@ void VulkanRenderSession::ThreadVertexObj_t::run() { } ChangedContent_RegionRemove.clear(); + hasWork = true; } if(!ChangedDefines_Voxel.empty()) { @@ -62,6 +72,7 @@ void VulkanRenderSession::ThreadVertexObj_t::run() { 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()); + hasWork = true; } if(!ChangedDefines_Node.empty()) { @@ -71,12 +82,13 @@ void VulkanRenderSession::ThreadVertexObj_t::run() { 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()); + hasWork = true; } - ServerSession_InUse = false; + State.lock()->ServerSession_InUse = false; // Ищем чанки, которые нужно перерисовать - if(!changedDefines_Voxel.empty() || changedDefines_Node.empty()) { + 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++) { @@ -139,22 +151,73 @@ void VulkanRenderSession::ThreadVertexObj_t::run() { auto last = std::unique(regions.begin(), regions.end()); regions.erase(last, regions.end()); } + + lock = State.lock(); } - if(Stage == EnumStage::ComposingCommandBuffer || Stage == EnumStage::Render) { + if(lock->Stage == EnumRenderStage::ComposingCommandBuffer || lock->Stage == EnumRenderStage::Render) { // Здесь можно обработать события, и подготовить меши по данным с мира + lock->ServerSession_InUse = true; + lock.unlock(); // changedContent_Chunk - - ServerSession_InUse = true; + if(!changedContent_Chunk.empty()) + hasWork = 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) { + while(!chunks.empty() && State.get_read().Stage != EnumRenderStage::ComposingCommandBuffer && State.get_read().Stage != EnumRenderStage::Render) { + auto& chunkPos = chunks.back(); + Pos::GlobalRegion regionPos = chunkPos >> 2; + Pos::bvec4u localPos = chunkPos & 0x3; - // TODO: Сгенерировать меши + auto& drawChunk = chunksUpdate[worldId][regionPos][localPos]; + { + auto iterWorld = SSession->Data.Worlds.find(worldId); + if(iterWorld == SSession->Data.Worlds.end()) + goto skip; + + auto iterRegion = iterWorld->second.Regions.find(regionPos); + if(iterRegion == iterWorld->second.Regions.end()) + goto skip; + + auto& chunk = iterRegion->second.Chunks[localPos.pack()]; + + { + drawChunk.VoxelDefines.resize(chunk.Voxels.size()); + for(size_t index = 0; index < chunk.Voxels.size(); index++) + drawChunk.VoxelDefines[index] = chunk.Voxels[index].VoxelId; + std::sort(drawChunk.VoxelDefines.begin(), drawChunk.VoxelDefines.end()); + auto last = std::unique(drawChunk.VoxelDefines.begin(), drawChunk.VoxelDefines.end()); + drawChunk.VoxelDefines.erase(last, drawChunk.VoxelDefines.end()); + drawChunk.VoxelDefines.shrink_to_fit(); + } + + { + drawChunk.NodeDefines.resize(chunk.Nodes.size()); + for(size_t index = 0; index < chunk.Nodes.size(); index++) + drawChunk.NodeDefines[index] = chunk.Nodes[index].NodeId; + std::sort(drawChunk.NodeDefines.begin(), drawChunk.NodeDefines.end()); + auto last = std::unique(drawChunk.NodeDefines.begin(), drawChunk.NodeDefines.end()); + drawChunk.NodeDefines.erase(last, drawChunk.NodeDefines.end()); + drawChunk.NodeDefines.shrink_to_fit(); + } + + { + std::vector voxels = generateMeshForVoxelChunks(chunk.Voxels); + if(!voxels.empty()) { + drawChunk.VoxelPointer = VertexPool_Voxels.pushVertexs(std::move(voxels)); + } + } + + { + std::vector nodes = generateMeshForNodeChunks(chunk.Nodes.data()); + if(!nodes.empty()) { + drawChunk.NodePointer = VertexPool_Nodes.pushVertexs(std::move(nodes)); + } + } + } + + skip: chunks.pop_back(); } @@ -166,14 +229,20 @@ void VulkanRenderSession::ThreadVertexObj_t::run() { for(WorldId_t worldId : toRemove) changedContent_Chunk.erase(changedContent_Chunk.find(worldId)); - ServerSession_InUse = false; + lock = State.lock(); + lock->ServerSession_InUse = false; } - if(Stage == EnumStage::Render || Stage == EnumStage::WorldUpdate) { + if(lock->Stage == EnumRenderStage::Render || lock->Stage == EnumRenderStage::WorldUpdate) { // Здесь можно выгрузить готовые данные в ChunkMesh - ChunkMesh_IsUse = true; + lock->ChunkMesh_IsUse = true; + lock.unlock(); + + if(!changedContent_RegionRemove.empty()) + hasWork = true; // Удаляем регионы + std::vector toRemove; for(auto& [worldId, regions] : changedContent_RegionRemove) { auto iterWorld = ChunkMesh.find(worldId); if(iterWorld == ChunkMesh.end()) @@ -192,20 +261,55 @@ void VulkanRenderSession::ThreadVertexObj_t::run() { VertexPool_Voxels.dropVertexs(chunk.VoxelPointer); VertexPool_Nodes.dropVertexs(chunk.NodePointer); } + + iterWorld->second.erase(iterRegion); + } + + if(iterWorld->second.empty()) + toRemove.push_back(worldId); + } + + for(WorldId_t worldId : toRemove) + ChunkMesh.erase(ChunkMesh.find(worldId)); + + if(!chunksUpdate.empty()) + hasWork = true; + + // Добавляем обновлённые меши + for(auto& [worldId, regions] : chunksUpdate) { + auto &world = ChunkMesh[worldId]; + + for(auto& [regionPos, chunks] : regions) { + auto ®ion = world[regionPos]; + + for(auto& [chunkPos, chunk] : chunks) { + auto &drawChunk = region[chunkPos.pack()]; + VertexPool_Voxels.dropVertexs(drawChunk.VoxelPointer); + VertexPool_Nodes.dropVertexs(drawChunk.NodePointer); + drawChunk = std::move(chunk); + } } } - // Добавляем обновлённые меши + chunksUpdate.clear(); - ChunkMesh_IsUse = false; + lock = State.lock(); + lock->ChunkMesh_IsUse = false; } + + lock.unlock(); + + if(!hasWork) + Time::sleep3(20); } } catch(const std::exception &exc) { LOG.error() << exc.what(); } - ChunkMesh_IsUse = false; - ServerSession_InUse = false; + auto lock = State.lock(); + lock->Stage = EnumRenderStage::Shutdown; + lock->ChunkMesh_IsUse = false; + lock->ServerSession_InUse = false; } void VulkanRenderSession::VulkanContext::onUpdate() { @@ -521,6 +625,8 @@ void VulkanRenderSession::init(Vulkan *instance) { } } + VKCTX->setServerSession(ServerSession); + updateDescriptor_MainAtlas(); updateDescriptor_VoxelsLight(); updateDescriptor_ChunksLight(); @@ -877,9 +983,11 @@ void VulkanRenderSession::onContentDefinesLost(std::unordered_map& changeOrAddList, const std::unordered_set& remove) { std::unordered_map> chunkChanges; - chunkChanges[worldId] = std::vector(changeOrAddList.begin(), changeOrAddList.end()); + if(!changeOrAddList.empty()) + chunkChanges[worldId] = std::vector(changeOrAddList.begin(), changeOrAddList.end()); std::unordered_map> regionRemove; - regionRemove[worldId] = std::vector(remove.begin(), remove.end()); + if(!remove.empty()) + regionRemove[worldId] = std::vector(remove.begin(), remove.end()); VKCTX->ThreadVertexObj.onContentChunkChange(chunkChanges, regionRemove); // if(chunk.Voxels.empty()) { @@ -1073,6 +1181,24 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff { Pos::GlobalChunk x64offset = X64Offset >> Pos::Object_t::BS_Bit >> 4; + Pos::GlobalRegion x64offset_region = x64offset >> 2; + + auto [voxelVertexs, nodeVertexs] = VKCTX->ThreadVertexObj.getChunksForRender(WorldId, Pos, 2, PCO.ProjView, x64offset_region); + + glm::mat4 orig = PCO.Model; + for(auto& [chunkPos, vertexs, vertexCount] : nodeVertexs) { + glm::vec3 cpos(chunkPos-x64offset); + PCO.Model = glm::translate(orig, cpos*16.f); + auto [vkBuffer, offset] = vertexs; + + 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, vertexCount, 1, offset, 0); + + } + + PCO.Model = orig; // auto iterWorld = External.ChunkVoxelMesh.find(WorldId); // if(iterWorld != External.ChunkVoxelMesh.end()) { @@ -1096,6 +1222,13 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff } } +void VulkanRenderSession::pushStage(EnumRenderStage stage) { + if(!VKCTX) + return; + + VKCTX->ThreadVertexObj.pushStage(stage); +} + std::vector VulkanRenderSession::generateMeshForVoxelChunks(const std::vector cubes) { std::vector out; out.reserve(cubes.size()*6); diff --git a/Src/Client/Vulkan/VulkanRenderSession.hpp b/Src/Client/Vulkan/VulkanRenderSession.hpp index ca1dda8..1cf3f36 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.hpp +++ b/Src/Client/Vulkan/VulkanRenderSession.hpp @@ -77,28 +77,19 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { Удалённые мешы хранятся в памяти N количество кадров */ struct ThreadVertexObj_t { + // Сессия будет выдана позже + // Предполагается что события будут только после того как сессия будет установлена, + // соответственно никто не попытаеся сюда обратится без событий + IServerSession *SSession = nullptr; + Vulkan *VkInst; // Здесь не хватает стадии работы с текстурами - enum class EnumStage { - // Постройка буфера команд на рисовку - // В этот период не должно быть изменений в таблице, - // хранящей указатели на данные для рендера ChunkMesh - // Можно работать с миром - // Здесь нужно дождаться завершения работы с ChunkMesh - ComposingCommandBuffer, - // В этот период можно менять ChunkMesh - // Можно работать с миром - Render, - // В этот период нельзя работать с миром - // Можно менять ChunkMesh - // Здесь нужно дождаться завершения работы с миром, только в - // этом этапе могут приходить события изменения чанков и определений - WorldUpdate, + struct StateObj_t { + EnumRenderStage Stage = EnumRenderStage::Render; + volatile bool ChunkMesh_IsUse = false, ServerSession_InUse = false; + }; - Shutdown - } Stage = EnumStage::Render; - - volatile bool ChunkMesh_IsUse = false, ServerSession_InUse = false; + SpinlockObject State; struct ChunkObj_t { // Сортированный список уникальных значений @@ -108,18 +99,15 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { VertexPool::Pointer NodePointer; }; - std::unordered_map> - > ChunkMesh; - ThreadVertexObj_t(Vulkan* vkInst) - : VertexPool_Voxels(vkInst), + : VkInst(vkInst), + VertexPool_Voxels(vkInst), VertexPool_Nodes(vkInst), Thread(&ThreadVertexObj_t::run, this) {} ~ThreadVertexObj_t() { - Stage = EnumStage::Shutdown; + State.lock()->Stage = EnumRenderStage::Shutdown; Thread.join(); } @@ -144,20 +132,90 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { } // Синхронизация потока рендера мира - void pushStage(EnumStage stage) { - if(Stage == EnumStage::Shutdown) + void pushStage(EnumRenderStage stage) { + auto lock = State.lock(); + + if(lock->Stage == EnumRenderStage::Shutdown) MAKE_ERROR("Остановка из-за ошибки ThreadVertex"); - assert(Stage != stage); + assert(lock->Stage != stage); - Stage = stage; - if(stage == EnumStage::ComposingCommandBuffer) { - while(ChunkMesh_IsUse); - } else if(stage == EnumStage::WorldUpdate) { - while(ServerSession_InUse); + lock->Stage = stage; + + if(stage == EnumRenderStage::ComposingCommandBuffer) { + if(lock->ChunkMesh_IsUse) { + lock.unlock(); + while(State.get_read().ChunkMesh_IsUse); + } else + lock.unlock(); + + VertexPool_Voxels.update(VkInst->Graphics.Pool); + VertexPool_Nodes.update(VkInst->Graphics.Pool); + } else if(stage == EnumRenderStage::WorldUpdate) { + if(lock->ServerSession_InUse) { + lock.unlock(); + while(State.get_read().ServerSession_InUse); + } else + lock.unlock(); } } + std::pair< + std::vector, uint32_t>>, + std::vector, uint32_t>> + > getChunksForRender(WorldId_t worldId, Pos::Object pos, uint8_t distance, glm::mat4 projView, Pos::GlobalRegion x64offset) { + Pos::GlobalRegion center = pos >> Pos::Object_t::BS_Bit >> 4 >> 2; + + std::vector, uint32_t>> vertexVoxels; + std::vector, uint32_t>> vertexNodes; + + auto iterWorld = ChunkMesh.find(worldId); + if(iterWorld == ChunkMesh.end()) + return {}; + + for(int z = -distance; z <= distance; z++) { + for(int y = -distance; y <= distance; y++) { + for(int x = -distance; x <= distance; x++) { + Pos::GlobalRegion region = center + Pos::GlobalRegion(x, y, z); + glm::vec3 begin = glm::vec3(region - x64offset); + + bool isVisible = false; + for(int index = 0; index < 8; index++) { + glm::vec4 temp = glm::vec4((begin+glm::vec3(index&1, (index>>1)&1, (index>>2)&1))*64.f, 1) * projView; + + if(temp.x >= -1 && temp.x <= 1 + && temp.y >= -1 && temp.y <= 1 + && temp.z >= 0 && temp.z <= 1 + ) { + isVisible = true; + break; + } + } + + if(!isVisible) + continue; + + auto iterRegion = iterWorld->second.find(region); + if(iterRegion == iterWorld->second.end()) + continue; + + Pos::GlobalChunk local = Pos::GlobalChunk(region) << 2; + + for(size_t index = 0; index < iterRegion->second.size(); index++) { + auto &chunk = iterRegion->second[index]; + + if(chunk.VoxelPointer) + vertexVoxels.emplace_back(local+Pos::GlobalChunk(Pos::bvec4u().unpack(index)), VertexPool_Voxels.map(chunk.VoxelPointer), chunk.VoxelPointer.VertexCount); + if(chunk.NodePointer) + vertexNodes.emplace_back(local+Pos::GlobalChunk(Pos::bvec4u().unpack(index)), VertexPool_Nodes.map(chunk.NodePointer), chunk.NodePointer.VertexCount); + } + } + } + } + + return std::pair{vertexVoxels, vertexNodes}; + } + private: // Буферы для хранения вершин VertexPool VertexPool_Voxels; @@ -168,6 +226,10 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { // Список чанков на перерисовку std::unordered_map> ChangedContent_Chunk; std::unordered_map> ChangedContent_RegionRemove; + // Меши + std::unordered_map> + > ChunkMesh; // Внешний поток std::thread Thread; @@ -190,10 +252,14 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { {} ~VulkanContext() { - ThreadVertexObj.pushStage(ThreadVertexObj_t::EnumStage::Shutdown); + ThreadVertexObj.pushStage(EnumRenderStage::Shutdown); } void onUpdate(); + + void setServerSession(IServerSession* ssession) { + ThreadVertexObj.SSession = ssession; + } }; std::shared_ptr VKCTX; @@ -249,6 +315,8 @@ public: void setServerSession(IServerSession *serverSession) { ServerSession = serverSession; + if(VKCTX) + VKCTX->setServerSession(serverSession); assert(serverSession); } @@ -264,6 +332,7 @@ public: void beforeDraw(); void drawWorld(GlobalTime gTime, float dTime, VkCommandBuffer drawCmd); + void pushStage(EnumRenderStage stage); static std::vector generateMeshForVoxelChunks(const std::vector cubes); static std::vector generateMeshForNodeChunks(const Node* nodes); diff --git a/Src/TOSLib.hpp b/Src/TOSLib.hpp index 598a055..8e837b1 100644 --- a/Src/TOSLib.hpp +++ b/Src/TOSLib.hpp @@ -111,25 +111,47 @@ public: class Lock { public: - Lock(SpinlockObject* obj, std::atomic_flag& lock) - : obj(obj), lock(lock) { - while (lock.test_and_set(std::memory_order_acquire)); + Lock(SpinlockObject* obj, std::atomic_flag& flag) + : Obj(obj), Flag(&flag) { + while (flag.test_and_set(std::memory_order_acquire)); } ~Lock() { - if(obj) - lock.clear(std::memory_order_release); + if(Obj) + Flag->clear(std::memory_order_release); } - T& get() const { assert(obj); return obj->value; } - T* operator->() const { assert(obj); return &obj->value; } - T& operator*() const { assert(obj); return obj->value; } + Lock(const Lock&) = delete; + Lock(Lock&& obj) + : Obj(obj.Obj), Flag(obj.Flag) + { + obj.Obj = nullptr; + } - void unlock() { obj = nullptr; lock.clear(std::memory_order_release);} + Lock& operator=(const Lock&) = delete; + Lock& operator=(Lock&& obj) { + if(this == &obj) + return *this; + + if(Obj) + unlock(); + + Obj = obj.Obj; + obj.Obj = nullptr; + Flag = obj.Flag; + + return *this; + } + + T& get() const { assert(Obj); return Obj->value; } + T* operator->() const { assert(Obj); return &Obj->value; } + T& operator*() const { assert(Obj); return Obj->value; } + + void unlock() { assert(Obj); Obj = nullptr; Flag->clear(std::memory_order_release);} private: - SpinlockObject* obj; - std::atomic_flag& lock; + SpinlockObject *Obj; + std::atomic_flag *Flag; }; Lock lock() {