#pragma once #include "Client/Abstract.hpp" #include "Common/Abstract.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "Abstract.hpp" #include "TOSLib.hpp" #include "VertexPool.hpp" #include "glm/fwd.hpp" #include "../FrustumCull.h" #include "glm/geometric.hpp" /* У движка есть один текстурный атлас VK_IMAGE_VIEW_TYPE_2D_ARRAY(RGBA_UINT) и к нему Storage с инфой о положении текстур Это общий для всех VkDescriptorSetLayout и VkDescriptorSet Для отрисовки вокселей используется карта освещения VK_IMAGE_VIEW_TYPE_2D_ARRAY(RGB_UINT), разделённая по прямоугольным плоскостям Разрешение у этих карт 1м/16 -- Для всего остального 3д карты освещённости по чанкам в разрешении 1м. 16^3 = 4096 текселей -- */ /* Самые критичные случаи Для вокселей это чередование в шахматном порядке присутствия и отсутствия вокселей Это 8'388'608 вокселя в чанке, общей площадью на картах освещения (256^3/2 *4 *6) 201'326'592 текселей или текстура размером 16к^2 Понадобится переиспользование одинаковых участков освещённости Если чанк заполнен слоями вокселей в шахматном порядке по вертикале ((257^2+257*2*4)*128) 8'717'440 текселей или текстура размером 4к^2 */ namespace LV::Client::VK { struct WorldPCO { glm::mat4 ProjView, Model; }; static_assert(sizeof(WorldPCO) == 128); /* Объект, занимающийся генерацией меша на основе нод и вокселей Требует доступ к профилям в ServerSession (ServerSession должен быть заблокирован только на чтение) Также доступ к идентификаторам текстур в VulkanRenderSession и моделей по состояниям Очередь чанков, ожидающих перерисовку. Возвращает готовые вершинные данные. */ struct ChunkMeshGenerator { // Данные рендера чанка struct ChunkObj_t { // Идентификатор запроса (на случай если запрос просрочился и чанк уже был удалён) uint32_t RequestId = 0; // Мир WorldId_t WId; // Позиция чанка в мире Pos::GlobalChunk Pos; // Сортированный список уникальных значений std::vector VoxelDefines; // Вершины std::vector VoxelVertexs; // Ноды std::vector NodeDefines; // Вершины нод std::vector NodeVertexs; // Индексы std::variant, std::vector> NodeIndicies; }; // Очередь чанков на перерисовку TOS::SpinlockObject>> Input; // Выход TOS::SpinlockObject> Output; public: ChunkMeshGenerator(IServerSession* serverSession) : SS(serverSession) { assert(serverSession); } ~ChunkMeshGenerator() { assert(Threads.empty()); } // Меняет количество обрабатывающих потоков void changeThreadsCount(uint8_t threads) { Sync.NeedShutdown = true; std::unique_lock lock(Sync.Mutex); Sync.CV_CountInRun.wait(lock, [&]() { return Sync.CountInRun == 0; }); for(std::thread& thr : Threads) thr.join(); Sync.NeedShutdown = false; Threads.resize(threads); for(int iter = 0; iter < threads; iter++) Threads[iter] = std::thread(&ChunkMeshGenerator::run, this, iter); Sync.CV_CountInRun.wait(lock, [&]() { return Sync.CountInRun == Threads.size() || Sync.NeedShutdown; }); if(Sync.NeedShutdown) MAKE_ERROR("Ошибка обработчика вершин чанков"); } void prepareTickSync() { Sync.Stop = true; } void pushStageTickSync() { std::unique_lock lock(Sync.Mutex); Sync.CV_CountInRun.wait(lock, [&]() { return Sync.CountInRun == 0; }); } void endTickSync() { Sync.Stop = false; Sync.CV_CountInRun.notify_all(); } private: struct { std::mutex Mutex; // Если нужно остановить пул потоков, вызывается NeedShutdown volatile bool NeedShutdown = false, Stop = false; volatile uint8_t CountInRun = 0; std::condition_variable CV_CountInRun; } Sync; IServerSession *SS; // Потоки std::vector Threads; void run(uint8_t id); }; /* Модуль обрабатывает рендер чанков */ class ModuleChunkPreparator { public: struct TickSyncData { // Профили на которые повлияли изменения, по ним нужно пересчитать чанки std::vector ChangedVoxels; std::vector ChangedNodes; std::unordered_map> ChangedChunks; std::unordered_map> LostRegions; }; public: ModuleChunkPreparator(Vulkan* vkInst, IServerSession* serverSession) : VkInst(vkInst), CMG(serverSession), VertexPool_Voxels(vkInst), VertexPool_Nodes(vkInst) { assert(vkInst); assert(serverSession); CMG.changeThreadsCount(2); const VkCommandPoolCreateInfo infoCmdPool = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .pNext = nullptr, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex = VkInst->getSettings().QueueGraphics }; vkAssert(!vkCreateCommandPool(VkInst->Graphics.Device, &infoCmdPool, nullptr, &CMDPool)); } ~ModuleChunkPreparator() { CMG.changeThreadsCount(0); if(CMDPool) vkDestroyCommandPool(VkInst->Graphics.Device, CMDPool, nullptr); } void prepareTickSync() { CMG.prepareTickSync(); } void pushStageTickSync() { CMG.pushStageTickSync(); } void tickSync(const TickSyncData& data) { // Обработать изменения в чанках // Пересчёт соседних чанков // Проверить необходимость пересчёта чанков при изменении профилей CMG.endTickSync(); } // Готовность кадров определяет когда можно удалять ненужные ресурсы, которые ещё используются в рендере void pushFrame() { } // Выдаёт буферы для рендера в порядке от ближнего к дальнему. distance - радиус в регионах 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); private: static constexpr uint8_t FRAME_COUNT_RESOURCE_LATENCY = 6; Vulkan* VkInst; VkCommandPool CMDPool = nullptr; // Генератор вершин чанков ChunkMeshGenerator CMG; // Буферы для хранения вершин VertexPool VertexPool_Voxels; VertexPool VertexPool_Nodes; struct ChunkObj_t { std::vector Voxels; VertexPool::Pointer VoxelPointer; std::vector Nodes; VertexPool::Pointer NodePointer; }; // Склад указателей на вершины чанков std::unordered_map> > ChunksMesh; uint8_t FrameRoulette = 0; // Вершины, ожидающие удаления по прошествию какого-то количества кадров std::vector::Pointer> VPV_ToFree[FRAME_COUNT_RESOURCE_LATENCY]; std::vector::Pointer> VPN_ToFree[FRAME_COUNT_RESOURCE_LATENCY]; // Следующий идентификатор запроса uint32_t NextRequest = 0; // Список ожидаемых чанков. Если регион был потерян, следующая его запись получит // новый идентификатор (при отсутствии записи готовые чанки с MCMG будут проигнорированы) std::unordered_map> Requests; }; /* Модуль, рисующий то, что предоставляет IServerSession */ class VulkanRenderSession : public IRenderSession { VK::Vulkan *VkInst = nullptr; // Доступ к миру на стороне клиента IServerSession *ServerSession = nullptr; // Положение камеры WorldId_t WorldId; Pos::Object Pos; /* Графический конвейер оперирует числами с плавающей запятой Для сохранения точности матрица модели хранит смещения близкие к нулю (X64Delta) глобальные смещения на уровне региона исключаются из смещения ещё при задании матрицы модели X64Offset = позиция игрока на уровне регионов X64Delta = позиция игрока в рамках региона Внутри графического конвейера будут числа приблежённые к 0 */ // Смещение дочерних объекто на стороне хоста перед рендером Pos::Object X64Offset; glm::vec3 X64Offset_f, X64Delta; // Смещение мира относительно игрока в матрице вида (0 -> 64) glm::quat Quat; ModuleChunkPreparator MCP; AtlasImage MainTest, LightDummy; Buffer TestQuad; std::optional TestVoxel; VkDescriptorPool DescriptorPool = VK_NULL_HANDLE; /* .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, Текстурный атлас .binding = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, Данные к атласу */ VkDescriptorSetLayout MainAtlasDescLayout = VK_NULL_HANDLE; VkDescriptorSet MainAtlasDescriptor = VK_NULL_HANDLE; /* .binding = 2, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, Воксельная карта освещения .binding = 3, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, Информация о размерах карты для приведения размеров */ VkDescriptorSetLayout VoxelLightMapDescLayout = VK_NULL_HANDLE; VkDescriptorSet VoxelLightMapDescriptor = VK_NULL_HANDLE; // Для отрисовки с использованием текстурного атласа и карты освещения VkPipelineLayout MainAtlas_LightMap_PipelineLayout = VK_NULL_HANDLE; // Для отрисовки вокселей std::shared_ptr VoxelShaderVertex, VoxelShaderGeometry, VoxelShaderFragmentOpaque, VoxelShaderFragmentTransparent; VkPipeline VoxelOpaquePipeline = VK_NULL_HANDLE, // Альфа канал может быть либо 255, либо 0 VoxelTransparentPipeline = VK_NULL_HANDLE; // Допускается полупрозрачность и смешивание // Для отрисовки статичных, не анимированных нод std::shared_ptr NodeShaderVertex, NodeShaderGeometry, NodeShaderFragmentOpaque, NodeShaderFragmentTransparent; VkPipeline NodeStaticOpaquePipeline = VK_NULL_HANDLE, NodeStaticTransparentPipeline = VK_NULL_HANDLE; std::map ServerToAtlas; public: WorldPCO PCO; WorldId_t WI = 0; glm::vec3 PlayerPos = glm::vec3(0); public: VulkanRenderSession(Vulkan *vkInst, IServerSession *serverSession); virtual ~VulkanRenderSession(); virtual void prepareTickSync() override; virtual void pushStageTickSync() override; virtual void tickSync(const TickSyncData& data) override; virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) override; glm::mat4 calcViewMatrix(glm::quat quat, glm::vec3 camOffset = glm::vec3(0)) { return glm::translate(glm::mat4(quat), camOffset); } void beforeDraw(); void drawWorld(GlobalTime gTime, float dTime, VkCommandBuffer drawCmd); void pushStage(EnumRenderStage stage); static std::vector generateMeshForVoxelChunks(const std::vector& cubes); private: void updateDescriptor_MainAtlas(); void updateDescriptor_VoxelsLight(); void updateDescriptor_ChunksLight(); }; }