From 2eb48d12a86c54a18648c8f7536d26196b124f11 Mon Sep 17 00:00:00 2001 From: DrSocalkwe3n Date: Wed, 9 Jul 2025 18:30:54 +0600 Subject: [PATCH] =?UTF-8?q?=D0=91=D1=83=D1=84=D0=B5=D1=80=D1=8B=20=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D1=88=D0=B8=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/Client/Vulkan/Abstract.hpp | 43 ++++ Src/Client/Vulkan/VertexPool.hpp | 283 ++++++++++++++++++++++ Src/Client/Vulkan/VulkanRenderSession.cpp | 34 ++- Src/Client/Vulkan/VulkanRenderSession.hpp | 51 +--- 4 files changed, 353 insertions(+), 58 deletions(-) create mode 100644 Src/Client/Vulkan/Abstract.hpp create mode 100644 Src/Client/Vulkan/VertexPool.hpp diff --git a/Src/Client/Vulkan/Abstract.hpp b/Src/Client/Vulkan/Abstract.hpp new file mode 100644 index 0000000..a598787 --- /dev/null +++ b/Src/Client/Vulkan/Abstract.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include + +/* + Воксели рендерятся точками, которые распаковываются в квадратные плоскости + + В чанке по оси 256 вокселей, и 257 позиций вершин (включая дальнюю границу чанка) + 9 бит на позицию *3 оси = 27 бит + Указание материала 16 бит +*/ + +namespace LV::Client::VK { + +struct VoxelVertexPoint { + uint32_t + FX : 9, FY : 9, FZ : 9, // Позиция + Place : 3, // Положение распространения xz, xy, zy, и обратные + N1 : 1, // Не занято + LS : 1, // Масштаб карты освещения (1м/16 или 1м) + TX : 8, TY : 8, // Размер+1 + VoxMtl : 16, // Материал вокселя DefVoxelId_t + LU : 14, LV : 14, // Позиция на карте освещения + N2 : 2; // Не занято +}; + +/* + Максимальный размер меша 14^3 м от центра ноды + Координатное пространство то же, что и у вокселей + 8 позиций с двух сторон + Рисуется полигонами +*/ + +struct NodeVertexStatic { + uint32_t + FX : 9, FY : 9, FZ : 9, // Позиция 15 -120 ~ 240 360 15 / 16 + N1 : 4, // Не занято + LS : 1, // Масштаб карты освещения (1м/16 или 1м) + Tex : 18, // Текстура + N2 : 14, // Не занято + TU : 16, TV : 16; // UV на текстуре +}; + +} \ No newline at end of file diff --git a/Src/Client/Vulkan/VertexPool.hpp b/Src/Client/Vulkan/VertexPool.hpp new file mode 100644 index 0000000..51a16ad --- /dev/null +++ b/Src/Client/Vulkan/VertexPool.hpp @@ -0,0 +1,283 @@ +#pragma once + +#include "Vulkan.hpp" +#include +#include + + +namespace LV::Client::VK { + +/* + Память на устройстве выделяется пулами + Для массивов вершин память выделяется блоками по PerBlock вершин в каждом + Размер пулла sizeof(Vertex)*PerBlock*PerPool + + Получаемые вершины сначала пишутся в общий буфер, потом передаются на устройство +*/ +template +class VertexPool { + static constexpr size_t HC_Buffer_Size = size_t(PerBlock)*size_t(PerPool); + + Vulkan *Inst; + + // Память, доступная для обмена с устройством + Buffer HostCoherent; + Vertex *HCPtr = nullptr; + size_t WritePos = 0; + + struct Pool { + // Память на устройстве + Buffer DeviceBuff; + // Свободные блоки + std::bitset Allocation; + + Pool(Vulkan* inst) + : DeviceBuff(inst, + sizeof(Vertex)*size_t(PerBlock)*size_t(PerPool)+4 /* Для vkCmdFillBuffer */, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) + { + Allocation.set(); + } + }; + + std::vector Pools; + + struct Task { + std::vector Data; + size_t Pos = -1; // Если данные уже записаны, то будет указана позиция в буфере общения + uint8_t PoolId; // Куда потом направить + uint16_t BlockId; // И в какой блок + }; + + /* + Перед следующим обновлением буфер общения заполняется с начала и до конца + Если место закончится, будет дослано в следующем обновлении + */ + std::queue TasksWait, TasksPostponed; + + +private: + void pushData(std::vector&& data, uint8_t poolId, uint16_t blockId) { + if(HC_Buffer_Size-WritePos >= data.size()) { + // Пишем в общий буфер, TasksWait + Vertex *ptr = HCPtr+WritePos; + std::copy(data.begin(), data.end(), ptr); + TasksWait.push({std::move(data), WritePos, poolId, blockId}); + WritePos += data.size(); + } else { + // Отложим запись на следующий такт + TasksPostponed.push(Task(std::move(data), -1, poolId, blockId)); + } + } + +public: + VertexPool(Vulkan* inst) + : Inst(inst), + HostCoherent(inst, + sizeof(Vertex)*HC_Buffer_Size+4 /* Для vkCmdFillBuffer */, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) + { + Pools.reserve(16); + HCPtr = (Vertex*) HostCoherent.mapMemory(); + } + + ~VertexPool() { + if(HCPtr) + HostCoherent.unMapMemory(); + } + + + struct Pointer { + uint32_t PoolId : 8, BlockId : 16, VertexCount = 0; + + operator bool() const { return VertexCount; } + }; + + /* + Переносит вершины на устройство, заранее передаёт указатель на область в памяти + Надеемся что к следующему кадру данные будут переданы + */ + Pointer pushVertexs(std::vector&& data) { + if(data.empty()) + return {0, 0, 0}; + + // Необходимое количество блоков + uint16_t blocks = data.size() / size_t(PerBlock); + assert(blocks <= PerPool); + + // Нужно найти пулл в котором будет свободно blocks количество блоков или создать новый + for(size_t iterPool = 0; iterPool < Pools.size(); iterPool++) { + Pool &pool = Pools[iterPool]; + size_t pos = pool.Allocation._Find_first(); + + while(true) { + int countEmpty = 1; + for(size_t pos2 = pos+1; pos2 < PerPool && pool.Allocation.test(pos2) && countEmpty < blocks; pos2++, countEmpty++); + + if(countEmpty == blocks) { + for(int block = 0; block < blocks; block++) + pool.Allocation.reset(pos+block); + + size_t count = data.size(); + pushData(std::move(data), iterPool, pos); + + return Pointer(iterPool, pos, count); + } + + pos += countEmpty; + + if(pos >= PerPool) + break; + + pos = pool.Allocation._Find_next(pos+countEmpty); + } + } + + // Не нашлось подходящего пула, создаём новый + assert(Pools.size() < 256); + + Pools.emplace_back(Inst); + Pool &last = Pools.back(); + // vkCmdFillBuffer(nullptr, last.DeviceBuff, 0, last.DeviceBuff.getSize() & ~0x3, 0); + for(int block = 0; block < blocks; block++) + last.Allocation.reset(block); + + size_t count = data.size(); + pushData(std::move(data), Pools.size()-1, 0); + + return Pointer(Pools.size()-1, 0, count); + } + + /* + Освобождает указатель + */ + void dropVertexs(const Pointer &pointer) { + if(!pointer) + return; + + assert(pointer.PoolId < Pools.size()); + assert(pointer.BlockId < PerPool); + + Pool &pool = Pools[pointer.PoolId]; + int blocks = (pointer.VertexCount+PerBlock-1) / PerBlock; + for(int indexBlock = 0; indexBlock < blocks; indexBlock++) { + assert(!pool.Allocation.test(pointer.BlockId+indexBlock)); + pool.Allocation.set(pointer.BlockId+indexBlock); + } + } + + void dropVertexs(Pointer &pointer) { + dropVertexs(const_cast(pointer)); + pointer.VertexCount = 0; + } + + /* + Перевыделяет память под новые данные + */ + void relocate(Pointer& pointer, std::vector&& data) { + if(data.empty()) { + if(!pointer) + return; + else { + dropVertexs(pointer); + pointer.VertexCount = 0; + } + } else if(!pointer) { + pointer = pushVertexs(std::move(data)); + } else { + int needBlocks = (data.size()+PerBlock-1) / PerBlock; + + if((pointer.VertexCount+PerBlock-1) / PerBlock == needBlocks) { + pointer.VertexCount = data.size(); + pushData(std::move(data), pointer.PoolId, pointer.BlockId); + } else { + dropVertexs(pointer); + pointer = pushVertexs(std::move(data)); + } + } + } + + /* + Транслирует локальный указатель в буффер и позицию вершины в нём + */ + std::pair map(const Pointer pointer) { + assert(pointer.PoolId < Pools.size()); + assert(pointer.BlockId < PerPool); + + return {Pools[pointer.PoolId].DeviceBuff.getBuffer(), pointer.BlockId*PerBlock}; + } + + /* + Должно вызываться после приёма всех данных и перед рендером + */ + void update(VkCommandPool commandPool) { + if(TasksWait.empty()) + return; + + VkCommandBufferAllocateInfo allocInfo { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + nullptr, + commandPool, + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + 1 + }; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(Inst->Graphics.Device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + nullptr, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + nullptr + }; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + while(!TasksWait.empty()) { + Task& task = TasksWait.front(); + + VkBufferCopy copyRegion { + task.Pos*sizeof(Vertex), + task.BlockId*sizeof(Vertex)*size_t(PerBlock), + task.Data.size()*sizeof(Vertex) + }; + + vkCmdCopyBuffer(commandBuffer, HostCoherent.getBuffer(), Pools[task.PoolId].DeviceBuff.getBuffer(), + 1, ©Region); + + TasksWait.pop(); + } + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo { + VK_STRUCTURE_TYPE_SUBMIT_INFO, + nullptr, + 0, nullptr, + nullptr, + 1, + &commandBuffer, + 0, + nullptr + }; + + vkQueueSubmit(Inst->Graphics.DeviceQueueGraphic, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(Inst->Graphics.DeviceQueueGraphic); + vkFreeCommandBuffers(Inst->Graphics.Device, commandPool, 1, &commandBuffer); + + std::queue postponed = std::move(TasksPostponed); + WritePos = 0; + + while(!postponed.empty()) { + Task& task = TasksWait.front(); + pushData(std::move(task.Data), task.PoolId, task.BlockId); + TasksWait.pop(); + } + } +}; + + +} \ No newline at end of file diff --git a/Src/Client/Vulkan/VulkanRenderSession.cpp b/Src/Client/Vulkan/VulkanRenderSession.cpp index 6a1421b..7e595ab 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.cpp +++ b/Src/Client/Vulkan/VulkanRenderSession.cpp @@ -625,28 +625,19 @@ void VulkanRenderSession::onChunksChange(WorldId_t worldId, const std::unordered const auto &chunk = ServerSession->Data.Worlds[worldId].Regions[rPos].Chunks[cPos.x][cPos.y][cPos.z]; if(chunk.Voxels.empty()) { - std::get<0>(buffers) = nullptr; + VKCTX->VertexPool_Voxels.dropVertexs(std::get<0>(buffers)); } else { std::vector vertexs = generateMeshForVoxelChunks(chunk.Voxels); - - if(!vertexs.empty()) { - auto &voxels = std::get<0>(buffers); - voxels = std::make_unique(VkInst, vertexs.size()*sizeof(VoxelVertexPoint)); - std::copy(vertexs.data(), vertexs.data()+vertexs.size(), (VoxelVertexPoint*) voxels->mapMemory()); - voxels->unMapMemory(); - } else { - std::get<0>(buffers) = nullptr; - } + auto &voxels = std::get<0>(buffers); + VKCTX->VertexPool_Voxels.relocate(voxels, std::move(vertexs)); } std::vector vertexs2 = generateMeshForNodeChunks(chunk.Nodes); if(vertexs2.empty()) { - std::get<1>(buffers) = nullptr; + VKCTX->VertexPool_Nodes.dropVertexs(std::get<1>(buffers)); } else { auto &nodes = std::get<1>(buffers); - nodes = std::make_unique(VkInst, vertexs2.size()*sizeof(NodeVertexStatic)); - std::copy(vertexs2.data(), vertexs2.data()+vertexs2.size(), (NodeVertexStatic*) nodes->mapMemory()); - nodes->unMapMemory(); + VKCTX->VertexPool_Nodes.relocate(nodes, std::move(vertexs2)); } if(!std::get<0>(buffers) && !std::get<1>(buffers)) { @@ -661,8 +652,11 @@ void VulkanRenderSession::onChunksChange(WorldId_t worldId, const std::unordered 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()) + 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); + } } } @@ -680,6 +674,8 @@ 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); } } @@ -742,12 +738,12 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff if(auto& voxels = std::get<0>(pair.second)) { glm::vec3 cpos(pair.first.x, pair.first.y, pair.first.z); PCO.Model = glm::translate(orig, cpos*16.f); - vkBuffer = *voxels; + auto [vkBuffer, offset] = VKCTX->VertexPool_Voxels.map(voxels); 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, voxels->getSize() / sizeof(VoxelVertexPoint), 1, 0, 0); + vkCmdDraw(drawCmd, voxels.VertexCount, 1, offset, 0); } } @@ -771,12 +767,12 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff if(auto& nodes = std::get<1>(pair.second)) { glm::vec3 cpos(pair.first.x, pair.first.y, pair.first.z); PCO.Model = glm::translate(orig, cpos*16.f); - vkBuffer = *nodes; + 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->getSize() / sizeof(NodeVertexStatic), 1, 0, 0); + vkCmdDraw(drawCmd, nodes.VertexCount, 1, offset, 0); } } diff --git a/Src/Client/Vulkan/VulkanRenderSession.hpp b/Src/Client/Vulkan/VulkanRenderSession.hpp index e6171ed..834ab94 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.hpp +++ b/Src/Client/Vulkan/VulkanRenderSession.hpp @@ -7,7 +7,8 @@ #include #include #include - +#include "Abstract.hpp" +#include "VertexPool.hpp" /* У движка есть один текстурный атлас VK_IMAGE_VIEW_TYPE_2D_ARRAY(RGBA_UINT) и к нему Storage с инфой о положении текстур @@ -28,6 +29,8 @@ */ + + namespace LV::Client::VK { struct WorldPCO { @@ -36,41 +39,6 @@ struct WorldPCO { static_assert(sizeof(WorldPCO) == 128); -/* - Воксели рендерятся точками, которые распаковываются в квадратные плоскости - - В чанке по оси 256 вокселей, и 257 позиций вершин (включая дальнюю границу чанка) - 9 бит на позицию *3 оси = 27 бит - Указание материала 16 бит -*/ - -struct VoxelVertexPoint { - uint32_t - FX : 9, FY : 9, FZ : 9, // Позиция - Place : 3, // Положение распространения xz, xy, zy, и обратные - N1 : 1, // Не занято - LS : 1, // Масштаб карты освещения (1м/16 или 1м) - TX : 8, TY : 8, // Размер+1 - VoxMtl : 16, // Материал вокселя DefVoxelId_t - LU : 14, LV : 14, // Позиция на карте освещения - N2 : 2; // Не занято -}; - -/* - Максимальный размер меша 14^3 м от центра ноды - Координатное пространство то же, что и у вокселей + 8 позиций с двух сторон - Рисуется полигонами -*/ - -struct NodeVertexStatic { - uint32_t - FX : 9, FY : 9, FZ : 9, // Позиция 15 -120 ~ 240 360 15 / 16 - N1 : 4, // Не занято - LS : 1, // Масштаб карты освещения (1м/16 или 1м) - Tex : 18, // Текстура - N2 : 14, // Не занято - TU : 16, TV : 16; // UV на текстуре -}; class VulkanRenderSession : public IRenderSession, public IVulkanDependent { VK::Vulkan *VkInst = nullptr; @@ -87,9 +55,15 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { Buffer TestQuad; std::optional TestVoxel; + + VertexPool VertexPool_Voxels; + VertexPool VertexPool_Nodes; + VulkanContext(Vulkan *vkInst) : MainTest(vkInst), LightDummy(vkInst), - TestQuad(vkInst, sizeof(NodeVertexStatic)*6) + TestQuad(vkInst, sizeof(NodeVertexStatic)*6), + VertexPool_Voxels(vkInst), + VertexPool_Nodes(vkInst) {} }; @@ -129,13 +103,12 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { NodeStaticOpaquePipeline = VK_NULL_HANDLE, NodeStaticTransparentPipeline = VK_NULL_HANDLE; - std::map ServerToAtlas; struct { std::unordered_map, std::unique_ptr> // Voxels, Nodes + std::pair::Pointer, VertexPool::Pointer> // Voxels, Nodes > > ChunkVoxelMesh; } External;