#include "VulkanRenderSession.hpp"
#include "Client/Abstract.hpp"
#include "Client/Vulkan/Abstract.hpp"
#include "Client/Vulkan/Vulkan.hpp"
#include "Common/Abstract.hpp"
#include "TOSLib.hpp"
#include "assets.hpp"
#include "glm/ext/matrix_transform.hpp"
#include "glm/ext/scalar_constants.hpp"
#include "glm/matrix.hpp"
#include "glm/trigonometric.hpp"
#include <atomic>
#include <algorithm>
#include <chrono>
#include <cstddef>
#include <cstring>
#include <memory>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
#include <vulkan/vulkan_core.h>
#include <fstream>

namespace std {
    template<>
    struct hash<LV::Client::VK::NodeVertexStatic> {
        size_t operator()(const LV::Client::VK::NodeVertexStatic& v) const {
            const uint32_t* ptr = reinterpret_cast<const uint32_t*>(&v);
            size_t h1 = std::hash<uint32_t>{}(ptr[0]);
            size_t h2 = std::hash<uint32_t>{}(ptr[1]);
            size_t h3 = std::hash<uint32_t>{}(ptr[2]);

            return h1 ^ (h2 << 1) ^ (h3 << 2);
        }
    };
}

namespace LV::Client::VK {

void ChunkMeshGenerator::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 ChunkMeshGenerator::run(uint8_t id) {
    Logger LOG = "ChunkMeshGenerator<"+std::to_string(id)+'>';

    {
        std::unique_lock lock(Sync.Mutex);
        Sync.CountInRun += 1;
        Sync.CV_CountInRun.notify_all();
    }

    LOG.debug() << "Старт потока верширования чанков";
    int timeWait = 1;

    try {
        while(!Sync.NeedShutdown) {
            if(Sync.Stop) {
                // Мир клиента начинает обрабатывать такты
                std::unique_lock lock(Sync.Mutex);
                if(Sync.Stop) {
                    Sync.CountInRun -= 1;
                    Sync.CV_CountInRun.notify_all();
                    Sync.CV_CountInRun.wait(lock, [&](){ return !Sync.Stop; });
                    Sync.CountInRun += 1;
                }
            }

            // Если нет входных запросов - ожидаем
            if(Input.get_read().empty()) {
                std::this_thread::sleep_for(std::chrono::milliseconds(timeWait));
                if(++timeWait > 20)
                    timeWait = 20;

                continue;
            }

            timeWait = 0;

            WorldId_t wId;
            Pos::GlobalChunk pos;
            uint32_t requestId;

            {
                auto lock = Input.lock();
                if(lock->empty())
                    continue;

                std::tuple<WorldId_t, Pos::GlobalChunk, uint32_t> v = lock->front();
                wId = std::get<0>(v);
                pos = std::get<1>(v);
                requestId = std::get<2>(v);
                lock->pop();
            }

            ChunkObj_t result;
            result.RequestId = requestId;
            result.WId = wId;
            result.Pos = pos;

            const std::array<Node, 16*16*16>* chunk;
            const std::vector<VoxelCube>* voxels;
            // Если на позиции полная нода, то она перекрывает стороны соседей
            uint8_t fullNodes[18][18][18];

            // Профиль, который используется если на стороне клиента отсутствует нужных профиль
            DefNode_t defaultProfileNode;
            // Кеш запросов профилей нод
            std::unordered_map<DefNodeId, const DefNode_t*> profilesNodeCache;
            auto getNodeProfile = [&](DefNodeId id) -> const DefNode_t* {
                auto iterCache = profilesNodeCache.find(id);
                if(iterCache == profilesNodeCache.end()) {
                    // Промах кеша
                    auto iterSS = SS->Profiles.DefNode.find(id);
                    if(iterSS != SS->Profiles.DefNode.end()) {
                        return (profilesNodeCache[id] = &iterSS->second);
                    } else {
                        // Профиль отсутствует на клиенте
                        return (profilesNodeCache[id] = &defaultProfileNode);
                    }
                } else {
                    return iterCache->second;
                }
            };

            std::vector<NodeStateInfo> metaStatesInfo;
            {
                NodeStateInfo info;
                info.Name = "meta";
                info.Variations = 256;
                metaStatesInfo.push_back(std::move(info));
            }

            // Воксели пока не рендерим
            if(auto iterWorld = SS->Content.Worlds.find(wId); iterWorld != SS->Content.Worlds.end()) {
                Pos::GlobalRegion rPos = pos >> 2;
                if(auto iterRegion = iterWorld->second.Regions.find(rPos); iterRegion != iterWorld->second.Regions.end()) {
                    auto& chunkPtr = iterRegion->second.Chunks[Pos::bvec4u(pos & 0x3).pack()];
                    chunk = &chunkPtr.Nodes;
                    voxels = &chunkPtr.Voxels;
                } else
                    goto end;

                // Собрать чанки с каждой стороны для face culling
                const std::array<Node, 16*16*16>* chunks[6] = {0};

                for(int var = 0; var < 6; var++) {
                    Pos::GlobalChunk chunkPos = pos;

                    if(var == 0)
                        chunkPos += Pos::GlobalChunk(1, 0, 0);
                    else if(var == 1)
                        chunkPos += Pos::GlobalChunk(-1, 0, 0);
                    else if(var == 2)
                        chunkPos += Pos::GlobalChunk(0, 1, 0);
                    else if(var == 3)
                        chunkPos += Pos::GlobalChunk(0, -1, 0);
                    else if(var == 4)
                        chunkPos += Pos::GlobalChunk(0, 0, 1);
                    else if(var == 5)
                        chunkPos += Pos::GlobalChunk(0, 0, -1);

                    rPos = chunkPos >> 2;

                    if(auto iterRegion = iterWorld->second.Regions.find(rPos); iterRegion != iterWorld->second.Regions.end()) {
                        auto& chunkPtr = iterRegion->second.Chunks[Pos::bvec4u(chunkPos & 0x3).pack()];
                        chunks[var] = &chunkPtr.Nodes;
                    }
                }

                std::fill(((uint8_t*) fullNodes), ((uint8_t*) fullNodes)+18*18*18, 0);

                std::unordered_map<DefNodeId, bool> nodeFullCuboidCache;
                auto nodeIsFull = [&](Node node) -> bool {
                    auto iterCache = nodeFullCuboidCache.find(node.Data);
                    if(iterCache == nodeFullCuboidCache.end()) {
                        const DefNode_t* profile = getNodeProfile(node.NodeId);
                        if(profile->TexId != 0) {
                            return (nodeFullCuboidCache[node.Data] = true);
                        }

                        if(NSP && profile->NodestateId != 0 && NSP->hasNodestate(profile->NodestateId)) {
                            std::unordered_map<std::string, int32_t> states;
                            int32_t meta = node.Meta;
                            states.emplace("meta", meta);
                            const auto routes = NSP->getModelsForNode(profile->NodestateId, metaStatesInfo, states);
                            bool isFull = !routes.empty();
                            if(isFull) {
                                for(const auto& variants : routes) {
                                    for(const auto& [weight, faces] : variants) {
                                        (void)weight;
                                        auto hasFace = [&](EnumFace face) -> bool {
                                            auto iterFace = faces.find(face);
                                            return iterFace != faces.end() && !iterFace->second.empty();
                                        };
                                        if(!hasFace(EnumFace::Up)
                                            || !hasFace(EnumFace::Down)
                                            || !hasFace(EnumFace::East)
                                            || !hasFace(EnumFace::West)
                                            || !hasFace(EnumFace::South)
                                            || !hasFace(EnumFace::North))
                                        {
                                            isFull = false;
                                            break;
                                        }
                                    }
                                    if(!isFull)
                                        break;
                                }
                            }
                            return (nodeFullCuboidCache[node.Data] = isFull);
                        }

                        return (nodeFullCuboidCache[node.Data] = false);
                    } else {
                        return iterCache->second;
                    }
                };

                {
                    const Node* n = chunk->data();
                    for(int z = 0; z < 16; z++)
                        for(int y = 0; y < 16; y++)
                            for(int x = 0; x < 16; x++) {
                                fullNodes[x+1][y+1][z+1] = (uint8_t) nodeIsFull(n[x+y*16+z*16*16]);
                        
                            }
                }

                if(chunks[0]) {
                    const Node* n = chunks[0]->data();
                    for(int z = 0; z < 16; z++)
                        for(int y = 0; y < 16; y++) {
                            fullNodes[17][y+1][z+1] = (uint8_t) nodeIsFull(n[y*16+z*16*16]);
                        }
                } else {
                    for(int z = 0; z < 16; z++)
                        for(int y = 0; y < 16; y++)
                            fullNodes[17][y+1][z+1] = 0;
                }

                if(chunks[1]) {
                    const Node* n = chunks[1]->data();
                    for(int z = 0; z < 16; z++)
                        for(int y = 0; y < 16; y++) {
                            fullNodes[0][y+1][z+1] = (uint8_t) nodeIsFull(n[15+y*16+z*16*16]);
                        }
                } else {
                    for(int z = 0; z < 16; z++)
                        for(int y = 0; y < 16; y++)
                            fullNodes[0][y+1][z+1] = 0;
                }

                if(chunks[2]) {
                    const Node* n = chunks[2]->data();
                    for(int z = 0; z < 16; z++)
                        for(int x = 0; x < 16; x++) {
                            fullNodes[x+1][17][z+1] = (uint8_t) nodeIsFull(n[x+0+z*16*16]);
                        }
                } else {
                    for(int z = 0; z < 16; z++)
                        for(int x = 0; x < 16; x++)
                            fullNodes[x+1][17][z+1] = 0;
                }

                if(chunks[3]) {
                    const Node* n = chunks[3]->data();
                    for(int z = 0; z < 16; z++)
                        for(int x = 0; x < 16; x++) {
                            fullNodes[x+1][0][z+1] = (uint8_t) nodeIsFull(n[x+15*16+z*16*16]);
                        }
                } else {
                    for(int z = 0; z < 16; z++)
                        for(int x = 0; x < 16; x++)
                            fullNodes[x+1][0][z+1] = 0;
                }

                if(chunks[4]) {
                    const Node* n = chunks[4]->data();
                    for(int y = 0; y < 16; y++)
                        for(int x = 0; x < 16; x++) {
                            fullNodes[x+1][y+1][17] = (uint8_t) nodeIsFull(n[x+y*16+0]);
                        }
                } else {
                    for(int y = 0; y < 16; y++)
                        for(int x = 0; x < 16; x++)
                            fullNodes[x+1][y+1][17] = 0;
                }

                if(chunks[5]) {
                    const Node* n = chunks[5]->data();
                    for(int y = 0; y < 16; y++)
                        for(int x = 0; x < 16; x++) {
                            fullNodes[x+1][y+1][0] = (uint8_t) nodeIsFull(n[x+y*16+15*16*16]);
                        }
                } else {
                    for(int y = 0; y < 16; y++)
                        for(int x = 0; x < 16; x++)
                            fullNodes[x+0][y+1][0] = 0;
                }
            } else 
                goto end;

            {
                result.VoxelDefines.reserve(voxels->size());
                for(const VoxelCube& cube : *voxels)
                    result.VoxelDefines.push_back(cube.VoxelId);

                std::sort(result.VoxelDefines.begin(), result.VoxelDefines.end());
                auto eraseIter = std::unique(result.VoxelDefines.begin(), result.VoxelDefines.end());
                result.VoxelDefines.erase(eraseIter, result.VoxelDefines.end());
                result.VoxelDefines.shrink_to_fit();
            }

            {
                result.NodeDefines.reserve(16*16*16);
                for(int iter = 0; iter < 16*16*16; iter++)
                    result.NodeDefines.push_back((*chunk)[iter].NodeId);
                std::sort(result.NodeDefines.begin(), result.NodeDefines.end());
                auto eraseIter = std::unique(result.NodeDefines.begin(), result.NodeDefines.end());
                result.NodeDefines.erase(eraseIter, result.NodeDefines.end());
                result.NodeDefines.shrink_to_fit();
            }

            // Генерация вершин вокселей
            {

            }

            // Генерация вершин нод
            {
                NodeVertexStatic v;
                std::memset(&v, 0, sizeof(v));

                static std::atomic<uint32_t> debugMeshWarnCount = 0;
                const bool debugMeshEnabled = debugMeshWarnCount.load() < 16;
                std::array<uint8_t, 16> expectedColumnX = {};
                std::array<uint8_t, 16> expectedColumnY = {};
                std::array<uint8_t, 16> expectedColumnZ = {};
                std::array<uint8_t, 16> generatedColumnX = {};
                std::array<uint8_t, 16> generatedColumnY = {};
                std::array<uint8_t, 16> generatedColumnZ = {};

                struct ModelCacheEntry {
                    std::vector<std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>> Routes;
                };

                std::unordered_map<uint32_t, ModelCacheEntry> modelCache;
                std::unordered_map<AssetsTexture, uint32_t> baseTextureCache;

                auto isFaceCovered = [&](EnumFace face, int covered) -> bool {
                    switch(face) {
                    case EnumFace::Up: return covered & (1 << 2);
                    case EnumFace::Down: return covered & (1 << 3);
                    case EnumFace::East: return covered & (1 << 0);
                    case EnumFace::West: return covered & (1 << 1);
                    case EnumFace::South: return covered & (1 << 4);
                    case EnumFace::North: return covered & (1 << 5);
                    default: return false;
                    }
                };

                auto pickVariant = [&](const std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>& variants, uint32_t seed)
                    -> const std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>*
                {
                    if(variants.empty())
                        return nullptr;

                    float total = 0.0f;
                    for(const auto& entry : variants)
                        total += std::max(0.0f, entry.first);

                    if(total <= 0.0f)
                        return &variants.front().second;

                    float r = (seed % 10000u) / 10000.0f * total;
                    float accum = 0.0f;
                    for(const auto& entry : variants) {
                        accum += std::max(0.0f, entry.first);
                        if(r <= accum)
                            return &entry.second;
                    }

                    return &variants.back().second;
                };

                auto appendModel = [&](const std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>& faces, int covered, int x, int y, int z) {
                    for(const auto& [face, verts] : faces) {
                        if(face != EnumFace::None && isFaceCovered(face, covered))
                            continue;

                        for(const NodeVertexStatic& baseVert : verts) {
                            NodeVertexStatic vert = baseVert;
                            vert.FX = uint32_t(vert.FX + x * 64);
                            vert.FY = uint32_t(vert.FY + y * 64);
                            vert.FZ = uint32_t(vert.FZ + z * 64);
                            result.NodeVertexs.push_back(vert);
                        }
                    }
                };

                // Сбор вершин
                for(int z = 0; z < 16; z++)
                for(int y = 0; y < 16; y++)
                for(int x = 0; x < 16; x++) {
                    const size_t vertexStart = result.NodeVertexs.size();
                    int fullCovered = 0;

                    fullCovered |= fullNodes[x+1+1][y+1][z+1];
                    fullCovered |= fullNodes[x+1-1][y+1][z+1] << 1;
                    fullCovered |= fullNodes[x+1][y+1+1][z+1] << 2;
                    fullCovered |= fullNodes[x+1][y+1-1][z+1] << 3;
                    fullCovered |= fullNodes[x+1][y+1][z+1+1] << 4;
                    fullCovered |= fullNodes[x+1][y+1][z+1-1] << 5;

                    if(fullCovered == 0b111111)
                        continue;

                    const Node& nodeData = (*chunk)[x+y*16+z*16*16];
                    const DefNode_t* node = getNodeProfile(nodeData.NodeId);

                    if(debugMeshEnabled) {
                        const bool hasRenderable = (node->NodestateId != 0) || (node->TexId != 0);
                        if(hasRenderable && fullCovered != 0b111111) {
                            expectedColumnX[x] = 1;
                            expectedColumnY[y] = 1;
                            expectedColumnZ[z] = 1;
                        }
                    }

                    bool usedModel = false;

                    if(NSP && (node->NodestateId != 0 || NSP->hasNodestate(node->NodestateId))) {
                        auto iterCache = modelCache.find(nodeData.Data);
                        if(iterCache == modelCache.end()) {
                            std::unordered_map<std::string, int32_t> states;
                            states.emplace("meta", nodeData.Meta);

                            ModelCacheEntry entry;
                            entry.Routes = NSP->getModelsForNode(node->NodestateId, metaStatesInfo, states);
                            iterCache = modelCache.emplace(nodeData.Data, std::move(entry)).first;
                        }

                        if(!iterCache->second.Routes.empty()) {
                            uint32_t seed = uint32_t(nodeData.Data) * 2654435761u;
                            seed ^= uint32_t(x) * 73856093u;
                            seed ^= uint32_t(y) * 19349663u;
                            seed ^= uint32_t(z) * 83492791u;

                            for(size_t routeIndex = 0; routeIndex < iterCache->second.Routes.size(); routeIndex++) {
                                const auto& variants = iterCache->second.Routes[routeIndex];
                                const auto* faces = pickVariant(variants, seed + uint32_t(routeIndex) * 374761393u);
                                if(faces)
                                    appendModel(*faces, fullCovered, x, y, z);
                            }

                            usedModel = true;
                        }
                    }

                    if(usedModel)
                        goto node_done;

                    if(NSP && node->TexId != 0) {
                        auto iterTex = baseTextureCache.find(node->TexId);
                        if(iterTex != baseTextureCache.end()) {
                            v.Tex = iterTex->second;
                        } else {
                            uint32_t resolvedTex = NSP->getTextureId(node->TexId);
                            v.Tex = resolvedTex;
                            baseTextureCache.emplace(node->TexId, resolvedTex);
                        }
                    } else {
                        v.Tex = node->TexId;
                    }

                    if(v.Tex == 0)
                        goto node_done;

                    // Рендерим обычный кубоид
                    // XZ+Y
                    if(!(fullCovered & 0b000100)) {
                        v.FX = 224+x*64;
                        v.FY = 224+y*64+64;
                        v.FZ = 224+z*64+64;
                        v.TU = 0;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);

                        v.FX += 64;
                        v.TU = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FZ -= 64;
                        v.TV = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FX = 224+x*64;
                        v.FZ = 224+z*64+64;
                        v.TU = 0;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);

                        v.FX += 64;
                        v.FZ -= 64;
                        v.TV = 65535;
                        v.TU = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FX -= 64;
                        v.TU = 0;
                        result.NodeVertexs.push_back(v);
                    }

                    // XZ-Y
                    if(!(fullCovered & 0b001000)) {
                        v.FX = 224+x*64;
                        v.FY = 224+y*64;
                        v.FZ = 224+z*64+64;
                        v.TU = 0;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);

                        v.FZ -= 64;
                        v.TV = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FX += 64;
                        v.TU = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FX = 224+x*64;
                        v.FZ = 224+z*64+64;
                        v.TU = 0;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);

                        v.FX += 64;
                        v.FZ -= 64;
                        v.TV = 65535;
                        v.TU = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FZ += 64;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);
                    }

                    //YZ+X
                    if(!(fullCovered & 0b000001)) {
                        v.FX = 224+x*64+64;
                        v.FY = 224+y*64;
                        v.FZ = 224+z*64+64;
                        v.TU = 0;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);

                        v.FZ -= 64;
                        v.TV = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FY += 64;
                        v.TU = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FY = 224+y*64;
                        v.FZ = 224+z*64+64;
                        v.TU = 0;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);

                        v.FY += 64;
                        v.FZ -= 64;
                        v.TV = 65535;
                        v.TU = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FZ += 64;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);
                    }

                    //YZ-X
                    if(!(fullCovered & 0b000010)) {
                        v.FX = 224+x*64;
                        v.FY = 224+y*64;
                        v.FZ = 224+z*64+64;
                        v.TU = 0;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);

                        v.FY += 64;
                        v.TU = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FZ -= 64;
                        v.TV = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FY = 224+y*64;
                        v.FZ = 224+z*64+64;
                        v.TU = 0;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);

                        v.FY += 64;
                        v.FZ -= 64;
                        v.TV = 65535;
                        v.TU = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FY -= 64;
                        v.TU = 0;
                        result.NodeVertexs.push_back(v);
                    }

                    //XY+Z
                    if(!(fullCovered & 0b010000)) {
                        v.FX = 224+x*64;
                        v.FY = 224+y*64;
                        v.FZ = 224+z*64+64;
                        v.TU = 0;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);

                        v.FX += 64;
                        v.TU = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FY += 64;
                        v.TV = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FX = 224+x*64;
                        v.FY = 224+y*64;
                        v.TU = 0;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);

                        v.FX += 64;
                        v.FY += 64;
                        v.TV = 65535;
                        v.TU = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FX -= 64;
                        v.TU = 0;
                        result.NodeVertexs.push_back(v);
                    }

                    // XY-Z
                    if(!(fullCovered & 0b100000)) {
                        v.FX = 224+x*64;
                        v.FY = 224+y*64;
                        v.FZ = 224+z*64;
                        v.TU = 0;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);

                        v.FY += 64;
                        v.TV = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FX += 64;
                        v.TU = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FX = 224+x*64;
                        v.FY = 224+y*64;
                        v.TU = 0;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);

                        v.FX += 64;
                        v.FY += 64;
                        v.TV = 65535;
                        v.TU = 65535;
                        result.NodeVertexs.push_back(v);

                        v.FY -= 64;
                        v.TV = 0;
                        result.NodeVertexs.push_back(v);
                    }

                    node_done:
                    if(debugMeshEnabled) {
                        const bool emitted = result.NodeVertexs.size() > vertexStart;
                        if(emitted) {
                            generatedColumnX[x] = 1;
                            generatedColumnY[y] = 1;
                            generatedColumnZ[z] = 1;
                        } else {
                            const bool hasRenderable = (node->NodestateId != 0) || (node->TexId != 0);
                            if(hasRenderable && fullCovered != 0b111111) {
                                uint32_t warnIndex = debugMeshWarnCount.fetch_add(1);
                                if(warnIndex < 16) {
                                    LOG.warn() << "Missing node geometry at chunk " << int(pos[0]) << ','
                                        << int(pos[1]) << ',' << int(pos[2])
                                        << " local " << x << ',' << y << ',' << z
                                        << " nodeId " << nodeData.NodeId
                                        << " meta " << int(nodeData.Meta)
                                        << " covered " << fullCovered
                                        << " tex " << node->TexId
                                        << " nodestate " << node->NodestateId;
                                }
                            }
                        }
                    }
                }

                if(debugMeshEnabled) {
                    auto collectMissing = [](const std::array<uint8_t, 16>& expected,
                        const std::array<uint8_t, 16>& generated) {
                        std::string res;
                        for(int i = 0; i < 16; i++) {
                            if(expected[i] && !generated[i]) {
                                if(!res.empty())
                                    res += ',';
                                res += std::to_string(i);
                            }
                        }
                        return res;
                    };

                    std::string missingX = collectMissing(expectedColumnX, generatedColumnX);
                    std::string missingY = collectMissing(expectedColumnY, generatedColumnY);
                    std::string missingZ = collectMissing(expectedColumnZ, generatedColumnZ);
                    if(!missingX.empty() || !missingY.empty() || !missingZ.empty()) {
                        uint32_t warnIndex = debugMeshWarnCount.fetch_add(1);
                        if(warnIndex < 16) {
                            LOG.warn() << "Missing mesh columns at chunk " << int(pos[0]) << ','
                                << int(pos[1]) << ',' << int(pos[2])
                                << " missingX[" << missingX << "]"
                                << " missingY[" << missingY << "]"
                                << " missingZ[" << missingZ << "]";
                        }
                    }
                }

                // Вычислить индексы и сократить вершины
                {
                    uint32_t nextIndex = 0;
                    std::vector<NodeVertexStatic> vertexes;
                    std::unordered_map<NodeVertexStatic, uint32_t> vertexTable;
                    std::vector<uint32_t> indexes;
                    
                    for(const NodeVertexStatic& vertex : result.NodeVertexs) {
                        auto iter = vertexTable.find(vertex);
                        if(iter == vertexTable.end()) {
                            vertexTable.insert({vertex, nextIndex});
                            vertexes.push_back(vertex);
                            indexes.push_back(nextIndex);
                            nextIndex += 1;
                        } else {
                            indexes.push_back(iter->second);
                        }
                    }

                    result.NodeVertexs = std::move(vertexes);

                    if(nextIndex <= (1 << 16)) {
                        std::vector<uint16_t> indexes16;
                        indexes16.reserve(indexes.size());
                        for(size_t iter = 0; iter < indexes.size(); iter++)
                            indexes16.push_back(indexes[iter]);

                        result.NodeIndexes = std::move(indexes16);
                    } else {
                        result.NodeIndexes = std::move(indexes);
                    }
                }
            }
            end:
            Output.lock()->emplace_back(std::move(result));

        }
    } catch(const std::exception& exc) {
        LOG.debug() << "Ошибка в работе потока:\n" << exc.what();
        Sync.NeedShutdown = true;
    }

    {
        std::unique_lock lock(Sync.Mutex);
        Sync.CountInRun -= 1;
        Sync.CV_CountInRun.notify_all();
    }

    LOG.debug() << "Завершение потока верширования чанков";
}


void ChunkPreparator::tickSync(const TickSyncData& data) {
    // Обработать изменения в чанках
    // Пересчёт соседних чанков
    // Проверить необходимость пересчёта чанков при изменении профилей

    std::unordered_map<WorldId_t, std::vector<Pos::GlobalChunk>> changedChunks = data.ChangedChunks;

    if(!data.ChangedNodes.empty()) {
        std::unordered_set<DefNodeId> changedNodes(data.ChangedNodes.begin(), data.ChangedNodes.end());

        for(const auto& [wId, regions] : ChunksMesh) {
            for(const auto& [rPos, chunks] : regions) {
                Pos::GlobalChunk base = Pos::GlobalChunk(rPos) << 2;

                for(size_t index = 0; index < chunks.size(); index++) {
                    const ChunkObj_t& chunk = chunks[index];
                    if(chunk.Nodes.empty())
                        continue;

                    bool hit = false;
                    for(DefNodeId nodeId : chunk.Nodes) {
                        if(changedNodes.contains(nodeId)) {
                            hit = true;
                            break;
                        }
                    }

                    if(!hit)
                        continue;

                    Pos::bvec4u localPos;
                    localPos.unpack(index);
                    changedChunks[wId].push_back(base + Pos::GlobalChunk(localPos));
                }
            }
        }
    }

    if(!data.ChangedVoxels.empty()) {
        std::unordered_set<DefVoxelId> changedVoxels(data.ChangedVoxels.begin(), data.ChangedVoxels.end());

        for(const auto& [wId, regions] : ChunksMesh) {
            for(const auto& [rPos, chunks] : regions) {
                Pos::GlobalChunk base = Pos::GlobalChunk(rPos) << 2;

                for(size_t index = 0; index < chunks.size(); index++) {
                    const ChunkObj_t& chunk = chunks[index];
                    if(chunk.Voxels.empty())
                        continue;

                    bool hit = false;
                    for(DefVoxelId voxelId : chunk.Voxels) {
                        if(changedVoxels.contains(voxelId)) {
                            hit = true;
                            break;
                        }
                    }

                    if(!hit)
                        continue;

                    Pos::bvec4u localPos;
                    localPos.unpack(index);
                    changedChunks[wId].push_back(base + Pos::GlobalChunk(localPos));
                }
            }
        }
    }

    // Добавляем к изменёным чанкам пересчёт соседей
    {
        std::vector<std::tuple<WorldId_t, Pos::GlobalChunk, uint32_t>> toBuild;
        for(auto& [wId, chunks] : changedChunks) {
            std::vector<Pos::GlobalChunk> list;
            for(const Pos::GlobalChunk& pos : chunks) {
                list.push_back(pos);
                list.push_back(pos+Pos::GlobalChunk(1, 0, 0));
                list.push_back(pos+Pos::GlobalChunk(-1, 0, 0));
                list.push_back(pos+Pos::GlobalChunk(0, 1, 0));
                list.push_back(pos+Pos::GlobalChunk(0, -1, 0));
                list.push_back(pos+Pos::GlobalChunk(0, 0, 1));
                list.push_back(pos+Pos::GlobalChunk(0, 0, -1));
            }

            std::sort(list.begin(), list.end());
            auto eraseIter = std::unique(list.begin(), list.end());
            list.erase(eraseIter, list.end());


            for(Pos::GlobalChunk& pos : list) {
                Pos::GlobalRegion rPos = pos >> 2;
                auto iterRegion = Requests[wId].find(rPos);
                if(iterRegion != Requests[wId].end())
                    toBuild.emplace_back(wId, pos, iterRegion->second);
                else
                    toBuild.emplace_back(wId, pos, Requests[wId][rPos] = NextRequest++);
            }
        }

        CMG.Input.lock()->push_range(toBuild);
    }

    // Чистим запросы и чанки
    {
        uint8_t frameRetirement = (FrameRoulette+FRAME_COUNT_RESOURCE_LATENCY) % FRAME_COUNT_RESOURCE_LATENCY;
        for(auto& [wId, regions] : data.LostRegions) {
            if(auto iterWorld = Requests.find(wId); iterWorld != Requests.end()) {
                for(const Pos::GlobalRegion& rPos : regions)
                    if(auto iterRegion = iterWorld->second.find(rPos); iterRegion != iterWorld->second.end())
                        iterWorld->second.erase(iterRegion);
            }

            if(auto iterWorld = ChunksMesh.find(wId); iterWorld != ChunksMesh.end()) {
                for(const Pos::GlobalRegion& rPos : regions)
                    if(auto iterRegion = iterWorld->second.find(rPos); iterRegion != iterWorld->second.end()) {
                        for(int iter = 0; iter < 4*4*4; iter++) {
                            auto& chunk = iterRegion->second[iter];
                            if(chunk.VoxelPointer)
                                VPV_ToFree[frameRetirement].emplace_back(std::move(chunk.VoxelPointer));
                            if(chunk.NodePointer) {
                                VPN_ToFree[frameRetirement].emplace_back(std::move(chunk.NodePointer), std::move(chunk.NodeIndexes));
                            }
                        }
                        
                        iterWorld->second.erase(iterRegion);
                    }
            }
        }
    }

    // Получаем готовые чанки
    {
        std::vector<ChunkMeshGenerator::ChunkObj_t> chunks = std::move(*CMG.Output.lock());
        for(auto& chunk : chunks) {
            auto iterWorld = Requests.find(chunk.WId);
            if(iterWorld == Requests.end())
                continue;

            auto iterRegion = iterWorld->second.find(chunk.Pos >> 2);
            if(iterRegion == iterWorld->second.end())
                continue;

            if(iterRegion->second != chunk.RequestId)
                continue;

            // Чанк ожидаем
            auto& rChunk = ChunksMesh[chunk.WId][chunk.Pos >> 2][Pos::bvec4u(chunk.Pos & 0x3).pack()];
            rChunk.Voxels = std::move(chunk.VoxelDefines);
            if(!chunk.VoxelVertexs.empty())
                rChunk.VoxelPointer = VertexPool_Voxels.pushVertexs(std::move(chunk.VoxelVertexs));
            rChunk.Nodes = std::move(chunk.NodeDefines);
            if(!chunk.NodeVertexs.empty())
                rChunk.NodePointer = VertexPool_Nodes.pushVertexs(std::move(chunk.NodeVertexs));

            if(std::vector<uint16_t>* ptr = std::get_if<std::vector<uint16_t>>(&chunk.NodeIndexes)) {
                if(!ptr->empty())
                    rChunk.NodeIndexes = IndexPool_Nodes_16.pushVertexs(std::move(*ptr));
            } else if(std::vector<uint32_t>* ptr = std::get_if<std::vector<uint32_t>>(&chunk.NodeIndexes)) {
                if(!ptr->empty())
                    rChunk.NodeIndexes = IndexPool_Nodes_32.pushVertexs(std::move(*ptr));
            }
        }
    }

    CMG.endTickSync();
}

void ChunkPreparator::pushFrame() {
    FrameRoulette = (FrameRoulette+1) % FRAME_COUNT_RESOURCE_LATENCY;

    for(auto pointer : VPV_ToFree[FrameRoulette]) {
        VertexPool_Voxels.dropVertexs(pointer);
    }

    VPV_ToFree[FrameRoulette].clear();

    for(auto& pointer : VPN_ToFree[FrameRoulette]) {
        VertexPool_Nodes.dropVertexs(std::get<0>(pointer));
        if(IndexPool<uint16_t>::Pointer* ind = std::get_if<IndexPool<uint16_t>::Pointer>(&std::get<1>(pointer))) {
            IndexPool_Nodes_16.dropVertexs(*ind);
        } else if(IndexPool<uint32_t>::Pointer* ind = std::get_if<IndexPool<uint32_t>::Pointer>(&std::get<1>(pointer))) {
            IndexPool_Nodes_32.dropVertexs(*ind);
        }
    }

    VPN_ToFree[FrameRoulette].clear();
}

std::pair<
    std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>>,
    std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, std::pair<VkBuffer, int>, bool, uint32_t>>
> ChunkPreparator::getChunksForRender(
    WorldId_t worldId, Pos::Object pos, uint8_t distance, glm::mat4 projView, Pos::GlobalRegion x64offset
) {
    Pos::GlobalChunk playerChunk = pos >> Pos::Object_t::BS_Bit >> 4;
    Pos::GlobalRegion center = playerChunk >> 2;

    std::vector<std::tuple<float, Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>> vertexVoxels;
    std::vector<std::tuple<float, Pos::GlobalChunk, std::pair<VkBuffer, int>, std::pair<VkBuffer, int>, bool, uint32_t>> vertexNodes;

    auto iterWorld = ChunksMesh.find(worldId);
    if(iterWorld == ChunksMesh.end())
        return {};

    Frustum fr(projView);

    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) * 64.f;
                glm::vec3 end = begin + glm::vec3(64.f);

                if(!fr.IsBoxVisible(begin, end))
                    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++) {
                    Pos::bvec4u localPos;
                    localPos.unpack(index);

                    glm::vec3 chunkPos = begin+glm::vec3(localPos)*16.f;
                    if(!fr.IsBoxVisible(chunkPos, chunkPos+glm::vec3(16)))
                        continue;

                    auto &chunk = iterRegion->second[index];
                    
                    float distance;

                    if(chunk.VoxelPointer || chunk.NodePointer) {
                        Pos::GlobalChunk cp = local+Pos::GlobalChunk(localPos)-playerChunk;
                        distance = cp.x*cp.x+cp.y*cp.y+cp.z*cp.z;
                    }

                    if(chunk.VoxelPointer) {
                        vertexVoxels.emplace_back(distance, local+Pos::GlobalChunk(localPos), VertexPool_Voxels.map(chunk.VoxelPointer), chunk.VoxelPointer.VertexCount);
                    }

                    if(chunk.NodePointer) {
                        vertexNodes.emplace_back(
                            distance, local+Pos::GlobalChunk(localPos), 
                            VertexPool_Nodes.map(chunk.NodePointer), 
                            chunk.NodeIndexes.index() == 0
                                ? IndexPool_Nodes_16.map(std::get<0>(chunk.NodeIndexes))
                                : IndexPool_Nodes_32.map(std::get<1>(chunk.NodeIndexes))
                            , chunk.NodeIndexes.index() == 0, 
                            std::visit<uint32_t>([](const auto& val) -> uint32_t { return val.VertexCount; }, chunk.NodeIndexes));
                    }
                }
            }
        }
    }

    {
        auto sortByDistance = []
        (
            const std::tuple<float, Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>& a, 
            const std::tuple<float, Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>& b
        ) {
            return std::get<0>(a) < std::get<0>(b);
        };
        
        std::sort(vertexVoxels.begin(), vertexVoxels.end(), sortByDistance);
    }

    {
        auto sortByDistance = []
        (
            const std::tuple<float, Pos::GlobalChunk, std::pair<VkBuffer, int>, std::pair<VkBuffer, int>, bool, uint32_t>& a, 
            const std::tuple<float, Pos::GlobalChunk, std::pair<VkBuffer, int>, std::pair<VkBuffer, int>, bool, uint32_t>& b
        ) {
            return std::get<0>(a) < std::get<0>(b);
        };
        std::sort(vertexNodes.begin(), vertexNodes.end(), sortByDistance);
    }

    std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>> resVertexVoxels;
    std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, std::pair<VkBuffer, int>, bool, uint32_t>> resVertexNodes;

    resVertexVoxels.reserve(vertexVoxels.size());
    resVertexNodes.reserve(vertexNodes.size());

    for(auto& [d, pos, ptr, count] : vertexVoxels)
        resVertexVoxels.emplace_back(pos, std::move(ptr), count);

    for(auto& [d, pos, ptr, ptr2, type, count] : vertexNodes)
        resVertexNodes.emplace_back(pos, std::move(ptr), std::move(ptr2), type, count);

    return std::pair{std::move(resVertexVoxels), std::move(resVertexNodes)};
}

VulkanRenderSession::VulkanRenderSession(Vulkan *vkInst, IServerSession *serverSession)
    :   VkInst(vkInst),
        ServerSession(serverSession),
        CP(vkInst, serverSession),
        LightDummy(vkInst),
        TestQuad(vkInst, sizeof(NodeVertexStatic)*6*3*2)
{
    assert(vkInst);
    assert(serverSession);

    {
        std::vector<VkDescriptorPoolSize> poolSizes {
            {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3},
            {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 3},
            {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3}
        };

        VkDescriptorPoolCreateInfo descriptorPool = {
            .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
            .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
            .maxSets = 8,
            .poolSizeCount = (uint32_t) poolSizes.size(),
            .pPoolSizes = poolSizes.data()
        };

        vkAssert(!vkCreateDescriptorPool(VkInst->Graphics.Device, &descriptorPool, nullptr,
                                        &DescriptorPool));
    }

    TP = std::make_unique<TextureProvider>(VkInst, DescriptorPool);
    NSP = std::make_unique<NodestateProvider>(MP, *TP);
    CP.setNodestateProvider(NSP.get());

    {
	    std::vector<VkDescriptorSetLayoutBinding> shaderLayoutBindings =
        {
            {
                .binding = 0,
                .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                .descriptorCount = 1,
                .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
                .pImmutableSamplers = nullptr
            }, {
                .binding = 1,
                .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
                .descriptorCount = 1,
                .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
                .pImmutableSamplers = nullptr
            }
        };

        const VkDescriptorSetLayoutCreateInfo descriptorLayout =
		{
			.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
			.pNext = nullptr,
			.flags = 0,
			.bindingCount = (uint32_t) shaderLayoutBindings.size(),
			.pBindings = shaderLayoutBindings.data()
		};

        vkAssert(!vkCreateDescriptorSetLayout(VkInst->Graphics.Device, &descriptorLayout, nullptr, &VoxelLightMapDescLayout));
    }

    {
        VkDescriptorSetAllocateInfo ciAllocInfo =
        {
                .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
                .pNext = nullptr,
                .descriptorPool = DescriptorPool,
                .descriptorSetCount = 1,
                .pSetLayouts = &VoxelLightMapDescLayout
        };

        vkAssert(!vkAllocateDescriptorSets(VkInst->Graphics.Device, &ciAllocInfo, &VoxelLightMapDescriptor));
    }

    
    LightDummy.atlasAddCallbackOnUniformChange([this]() -> bool {
        updateDescriptor_VoxelsLight();
        return true;
    });

    /*
    x left -1 ~ right 1
    y up 1 ~ down -1
    z near 0 ~ far -1

    glm

    */

    {
        NodeVertexStatic *array = (NodeVertexStatic*) TestQuad.mapMemory();
        array[0] = {224, 224, 0, 224, 0, 0, 0, 65535, 0};
        array[1] = {224, 224+64, 0, 224, 0, 0, 0, 0, 65535};
        array[2] = {224+64, 224+64, 0, 224, 0, 0, 0, 0, 65535};
        array[3] = {224, 224, 0, 224, 0, 0, 0, 65535, 0};
        array[4] = {224+64, 224+64, 0, 224, 0, 0, 0, 0, 65535};
        array[5] = {224+64, 224, 0, 224, 0, 0, 0, 0, 0};

        array[6] = {224, 224, 0, 224+64, 0, 0, 0, 0, 0};
        array[7] = {224+64, 224, 0, 224+64, 0, 0, 0, 65535, 0};
        array[8] = {224+64, 224+64, 0, 224+64, 0, 0, 0, 65535, 65535};
        array[9] = {224, 224, 0, 224+64, 0, 0, 0, 0, 0};
        array[10] = {224+64, 224+64, 0, 224+64, 0, 0, 0, 65535, 65535};
        array[11] = {224, 224+64, 0, 224+64, 0, 0, 0, 0, 65535};

        array[12] = {224, 224, 0, 224, 0, 0, 0, 0, 0};
        array[13] = {224, 224, 0, 224+64, 0, 0, 0, 65535, 0};
        array[14] = {224, 224+64, 0, 224+64, 0, 0, 0, 65535, 65535};
        array[15] = {224, 224, 0, 224, 0, 0, 0, 0, 0};
        array[16] = {224, 224+64, 0, 224+64, 0, 0, 0, 65535, 65535};
        array[17] = {224, 224+64, 0, 224, 0, 0, 0, 0, 65535};

        array[18] = {224+64, 224, 0, 224+64, 0, 0, 0, 0, 0};
        array[19] = {224+64, 224, 0, 224, 0, 0, 0, 65535, 0};
        array[20] = {224+64, 224+64, 0, 224, 0, 0, 0, 65535, 65535};
        array[21] = {224+64, 224, 0, 224+64, 0, 0, 0, 0, 0};
        array[22] = {224+64, 224+64, 0, 224, 0, 0, 0, 65535, 65535};
        array[23] = {224+64, 224+64, 0, 224+64, 0, 0, 0, 0, 65535};

        array[24] = {224, 224, 0, 224, 0, 0, 0, 0, 0};
        array[25] = {224+64, 224, 0, 224, 0, 0, 0, 65535, 0};
        array[26] = {224+64, 224, 0, 224+64, 0, 0, 0, 65535, 65535};
        array[27] = {224, 224, 0, 224, 0, 0, 0, 0, 0};
        array[28] = {224+64, 224, 0, 224+64, 0, 0, 0, 65535, 65535};
        array[29] = {224, 224, 0, 224+64, 0, 0, 0, 0, 65535};

        array[30] = {224, 224+64, 0, 224+64, 0, 0, 0, 0, 0};
        array[31] = {224+64, 224+64, 0, 224+64, 0, 0, 0, 65535, 0};
        array[32] = {224+64, 224+64, 0, 224, 0, 0, 0, 65535, 65535};
        array[33] = {224, 224+64, 0, 224+64, 0, 0, 0, 0, 0};
        array[34] = {224+64, 224+64, 0, 224, 0, 0, 0, 65535, 65535};
        array[35] = {224, 224+64, 0, 224, 0, 0, 0, 0, 65535};

        for(int iter = 0; iter < 36; iter++) {
            array[iter].Tex = 6;
            if(array[iter].FX == 135)
                array[iter].FX--;
            else
                array[iter].FX++;

            if(array[iter].FY == 135)
                array[iter].FY--;
            else
                array[iter].FY++;

            if(array[iter].FZ == 135)
                array[iter].FZ--;
            else
                array[iter].FZ++;
        }

        TestQuad.unMapMemory();
    }

    {
        std::vector<VoxelCube> cubes;

        cubes.push_back({0, 0, Pos::bvec256u{0, 0, 0}, Pos::bvec256u{0, 0, 0}});
        cubes.push_back({1, 0, Pos::bvec256u{255, 0, 0}, Pos::bvec256u{0, 0, 0}});
        cubes.push_back({1, 0, Pos::bvec256u{0, 255, 0}, Pos::bvec256u{0, 0, 0}});
        cubes.push_back({1, 0, Pos::bvec256u{0, 0, 255}, Pos::bvec256u{0, 0, 0}});
        cubes.push_back({2, 0, Pos::bvec256u{255, 255, 0}, Pos::bvec256u{0, 0, 0}});
        cubes.push_back({2, 0, Pos::bvec256u{0, 255, 255}, Pos::bvec256u{0, 0, 0}});
        cubes.push_back({2, 0, Pos::bvec256u{255, 0, 255}, Pos::bvec256u{0, 0, 0}});
        cubes.push_back({3, 0, Pos::bvec256u{255, 255, 255}, Pos::bvec256u{0, 0, 0}});

        cubes.push_back({4, 0, Pos::bvec256u{64, 64, 64}, Pos::bvec256u{127, 127, 127}});

        std::vector<VoxelVertexPoint> vertexs = generateMeshForVoxelChunks(cubes);

        if(!vertexs.empty()) {
            TestVoxel.emplace(VkInst, vertexs.size()*sizeof(VoxelVertexPoint));
            std::copy(vertexs.data(), vertexs.data()+vertexs.size(), (VoxelVertexPoint*) TestVoxel->mapMemory());
            TestVoxel->unMapMemory();
        }
    }

    updateDescriptor_VoxelsLight();
    updateDescriptor_ChunksLight();

    // Разметка графических конвейеров
    {
        std::vector<VkPushConstantRange> worldWideShaderPushConstants =
        {
            {
                .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT,
                .offset = 0,
                .size = uint32_t(sizeof(WorldPCO))
            }
        };

        std::vector<VkDescriptorSetLayout> layouts =
        {
            TP ? TP->getDescriptorLayout() : VK_NULL_HANDLE,
            VoxelLightMapDescLayout
        };

		const VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
		{
			.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
			.pNext = nullptr,
			.flags = 0,
			.setLayoutCount = (uint32_t) layouts.size(),
			.pSetLayouts = layouts.data(),
			.pushConstantRangeCount = (uint32_t) worldWideShaderPushConstants.size(),
			.pPushConstantRanges = worldWideShaderPushConstants.data()
		};
        
		vkAssert(!vkCreatePipelineLayout(VkInst->Graphics.Device, &pPipelineLayoutCreateInfo, nullptr, &MainAtlas_LightMap_PipelineLayout));
    }

    // Настройка мультисемплинга
    // Может нужно будет в будущем связать с настройками главного буфера
    VkPipelineMultisampleStateCreateInfo multisample =
    {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
        .pNext = nullptr,
        .flags = 0,
        .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
        .sampleShadingEnable = false,
        .minSampleShading = 0.0f,
        .pSampleMask = nullptr,
        .alphaToCoverageEnable = false,
        .alphaToOneEnable = false
    };


    // Конвейеры для вокселей и нод
    {
        VkPipelineCacheCreateInfo infoPipelineCache {
            .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .initialDataSize = 0,
            .pInitialData = nullptr
        };
        
        // Для статичных непрозрачных и полупрозрачных вокселей
        if(!VoxelShaderVertex)
            VoxelShaderVertex = VkInst->createShader(getResource("shaders/chunk/voxel.vert.bin")->makeView());

        if(!VoxelShaderGeometry)
            VoxelShaderGeometry = VkInst->createShader(getResource("shaders/chunk/voxel.geom.bin")->makeView());

        if(!VoxelShaderFragmentOpaque)
            VoxelShaderFragmentOpaque = VkInst->createShader(getResource("shaders/chunk/voxel_opaque.frag.bin")->makeView());

        if(!VoxelShaderFragmentTransparent)
            VoxelShaderFragmentTransparent = VkInst->createShader(getResource("shaders/chunk/voxel_transparent.frag.bin")->makeView());

		// Конвейер шейдеров
		std::vector<VkPipelineShaderStageCreateInfo> shaderStages =
        {
            {
                .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
                .pNext = nullptr,
                .flags = 0,
                .stage = VK_SHADER_STAGE_VERTEX_BIT,
                .module = *VoxelShaderVertex,
                .pName = "main",
                .pSpecializationInfo = nullptr
            }, {
                .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
                .pNext = nullptr,
                .flags = 0,
                .stage = VK_SHADER_STAGE_GEOMETRY_BIT,
                .module = *VoxelShaderGeometry,
                .pName = "main",
                .pSpecializationInfo = nullptr
            }, {
                .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
                .pNext = nullptr,
                .flags = 0,
                .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
                .module = *VoxelShaderFragmentOpaque,
                .pName = "main",
                .pSpecializationInfo = nullptr
            }
        };

        // Вершины шейдера
        // Настройка формата вершин шейдера
        std::vector<VkVertexInputBindingDescription> shaderVertexBindings =
        {
            {
                .binding = 0,
                .stride = sizeof(VoxelVertexPoint),
                .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
            }
        };

		std::vector<VkVertexInputAttributeDescription> shaderVertexAttribute =
        {
            {
                .location = 0,
                .binding = 0,
                .format = VK_FORMAT_R32G32B32_UINT,
                .offset = 0
            }
        };

        VkPipelineVertexInputStateCreateInfo createInfoVertexInput =
        {
            .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .vertexBindingDescriptionCount = (uint32_t) shaderVertexBindings.size(),
            .pVertexBindingDescriptions = shaderVertexBindings.data(),
            .vertexAttributeDescriptionCount = (uint32_t) shaderVertexAttribute.size(),
            .pVertexAttributeDescriptions = shaderVertexAttribute.data()
        };

	    // Топология вершин на входе (треугольники, линии, точки)
        VkPipelineInputAssemblyStateCreateInfo ia =
        {
            .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST,
            .primitiveRestartEnable = false
        };

        VkPipelineViewportStateCreateInfo vp =
        {
            .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .viewportCount = 1,
            .pViewports = nullptr,
            .scissorCount = 1,
            .pScissors = nullptr
        };

		// Настройки растеризатора
		VkPipelineRasterizationStateCreateInfo rasterization =
        {
            .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .depthClampEnable = false,
            .rasterizerDiscardEnable = false,
            .polygonMode = VK_POLYGON_MODE_FILL,
            .cullMode = VK_CULL_MODE_BACK_BIT,
            .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
            .depthBiasEnable = false,
            .depthBiasConstantFactor = 0.0f,
            .depthBiasClamp = 0.0f,
            .depthBiasSlopeFactor = 0.0f,
            .lineWidth = 1.0f
        };

		// Тест буфера глубины и трафарета
		VkPipelineDepthStencilStateCreateInfo depthStencil =
        {
            .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .depthTestEnable = true,
            .depthWriteEnable = true,
            .depthCompareOp = VK_COMPARE_OP_LESS,
            .depthBoundsTestEnable = false,
            .stencilTestEnable = false,
            .front = VkStencilOpState
            {
                .failOp = VK_STENCIL_OP_KEEP,
                .passOp = VK_STENCIL_OP_KEEP,
                .depthFailOp = VK_STENCIL_OP_KEEP,
                .compareOp = VK_COMPARE_OP_ALWAYS,
                .compareMask = 0x0,
                .writeMask = 0x0,
                .reference = 0x0
            },
            .back = VkStencilOpState
            {
                .failOp = VK_STENCIL_OP_KEEP,
                .passOp = VK_STENCIL_OP_KEEP,
                .depthFailOp = VK_STENCIL_OP_KEEP,
                .compareOp = VK_COMPARE_OP_ALWAYS,
                .compareMask = 0x0,
                .writeMask = 0x0,
                .reference = 0x0
            },
            .minDepthBounds = 0.0f,
            .maxDepthBounds = 0.0f
        };

        // Логика смешивания цветов
		std::vector<VkPipelineColorBlendAttachmentState> colorBlend =
        {
            {
                .blendEnable = false,
                .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO,
                .dstColorBlendFactor = VK_BLEND_FACTOR_ONE,
                .colorBlendOp = VK_BLEND_OP_ADD,
                .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
                .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
                .alphaBlendOp = VK_BLEND_OP_ADD,
                .colorWriteMask = 0xf
            }
        };

        VkPipelineColorBlendStateCreateInfo cb =
        {
            .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .logicOpEnable = VK_FALSE,
            .logicOp = VK_LOGIC_OP_CLEAR,
            .attachmentCount = (uint32_t) colorBlend.size(),
            .pAttachments = colorBlend.data(),
            .blendConstants = {0.f, 0.f, 0.f, 0.f}
        };

        // Настройки конвейера, которые могут быть изменены без пересоздания конвейера
		std::vector<VkDynamicState> dynamicStates =
        {
            VK_DYNAMIC_STATE_VIEWPORT,
            VK_DYNAMIC_STATE_SCISSOR
        };

        VkPipelineDynamicStateCreateInfo dynamicState =
        {
            .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .dynamicStateCount = (uint32_t) dynamicStates.size(),
            .pDynamicStates = dynamicStates.data(),
        };

        VkGraphicsPipelineCreateInfo pipeline =
        {
            .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .stageCount = (uint32_t) shaderStages.size(),
            .pStages = shaderStages.data(),
            .pVertexInputState = &createInfoVertexInput,
            .pInputAssemblyState = &ia,
            .pTessellationState = nullptr,
            .pViewportState = &vp,
            .pRasterizationState = &rasterization,
            .pMultisampleState = &multisample,
            .pDepthStencilState = &depthStencil,
            .pColorBlendState = &cb,
            .pDynamicState = &dynamicState,
            .layout = MainAtlas_LightMap_PipelineLayout,
            .renderPass = VkInst->Graphics.RenderPass,
            .subpass = 0,
            .basePipelineHandle = nullptr,
            .basePipelineIndex = 0
        };

        if(!VoxelOpaquePipeline)
            vkAssert(!vkCreateGraphicsPipelines(VkInst->Graphics.Device, VK_NULL_HANDLE, 1, &pipeline, nullptr, &VoxelOpaquePipeline));

        if(!VoxelTransparentPipeline) {
            shaderStages[2].module = *VoxelShaderFragmentTransparent,

            colorBlend[0] =
            {
                .blendEnable = VK_TRUE,
                .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
                .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
                .colorBlendOp = VK_BLEND_OP_ADD,
                .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
                .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
                .alphaBlendOp = VK_BLEND_OP_ADD,
                .colorWriteMask = 0xf
            };

           vkAssert(!vkCreateGraphicsPipelines(VkInst->Graphics.Device, VK_NULL_HANDLE, 1, &pipeline, nullptr, &VoxelTransparentPipeline));
        }

        // Для статичных непрозрачных и полупрозрачных нод
        if(!NodeShaderVertex)
            NodeShaderVertex = VkInst->createShader(getResource("shaders/chunk/node.vert.bin")->makeView());

        if(!NodeShaderGeometry)
            NodeShaderGeometry = VkInst->createShader(getResource("shaders/chunk/node.geom.bin")->makeView());

        if(!NodeShaderFragmentOpaque)
            NodeShaderFragmentOpaque = VkInst->createShader(getResource("shaders/chunk/node_opaque.frag.bin")->makeView());

        if(!NodeShaderFragmentTransparent)
            NodeShaderFragmentTransparent = VkInst->createShader(getResource("shaders/chunk/node_transparent.frag.bin")->makeView());

        ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,

        shaderStages[0].module = *NodeShaderVertex;
        shaderStages[1].module = *NodeShaderGeometry;
        shaderStages[2].module = *NodeShaderFragmentOpaque;

        colorBlend[0] =
        {
            .blendEnable = false,
            .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO,
            .dstColorBlendFactor = VK_BLEND_FACTOR_ONE,
            .colorBlendOp = VK_BLEND_OP_ADD,
            .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
            .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
            .alphaBlendOp = VK_BLEND_OP_ADD,
            .colorWriteMask = 0xf
        };

        if(!NodeStaticOpaquePipeline) {
            vkAssert(!vkCreateGraphicsPipelines(VkInst->Graphics.Device, VK_NULL_HANDLE,
                 1, &pipeline, nullptr, &NodeStaticOpaquePipeline));
        }

        if(!NodeStaticTransparentPipeline) {
            shaderStages[2].module = *NodeShaderFragmentTransparent;

            colorBlend[0] =
            {
                .blendEnable = VK_TRUE,
                .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
                .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
                .colorBlendOp = VK_BLEND_OP_ADD,
                .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
                .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
                .alphaBlendOp = VK_BLEND_OP_ADD,
                .colorWriteMask = 0xf
            };

            vkAssert(!vkCreateGraphicsPipelines(VkInst->Graphics.Device, VK_NULL_HANDLE, 
                1, &pipeline, nullptr, &NodeStaticTransparentPipeline));
        }
    }
}

VulkanRenderSession::~VulkanRenderSession() {
    

    if(VoxelOpaquePipeline)
        vkDestroyPipeline(VkInst->Graphics.Device, VoxelOpaquePipeline, nullptr);
    if(VoxelTransparentPipeline)
        vkDestroyPipeline(VkInst->Graphics.Device, VoxelTransparentPipeline, nullptr);
    if(NodeStaticOpaquePipeline)
        vkDestroyPipeline(VkInst->Graphics.Device, NodeStaticOpaquePipeline, nullptr);
    if(NodeStaticTransparentPipeline)
        vkDestroyPipeline(VkInst->Graphics.Device, NodeStaticTransparentPipeline, nullptr);

    if(MainAtlas_LightMap_PipelineLayout)
        vkDestroyPipelineLayout(VkInst->Graphics.Device, MainAtlas_LightMap_PipelineLayout, nullptr);

    if(VoxelLightMapDescLayout)
        vkDestroyDescriptorSetLayout(VkInst->Graphics.Device, VoxelLightMapDescLayout, nullptr);

    if(DescriptorPool)
        vkDestroyDescriptorPool(VkInst->Graphics.Device, DescriptorPool, nullptr);
}

void VulkanRenderSession::prepareTickSync() {
    CP.prepareTickSync();
}

void VulkanRenderSession::pushStageTickSync() {
    CP.pushStageTickSync();
}

void VulkanRenderSession::tickSync(TickSyncData& data) {
    // Изменение ассетов
    // Профили
    // Чанки

    ChunkPreparator::TickSyncData mcpData;
    mcpData.ChangedChunks = data.Chunks_ChangeOrAdd;
    mcpData.LostRegions = data.Chunks_Lost;

    if(auto iter = data.Profiles_ChangeOrAdd.find(EnumDefContent::Node); iter != data.Profiles_ChangeOrAdd.end())
        mcpData.ChangedNodes.insert(mcpData.ChangedNodes.end(), iter->second.begin(), iter->second.end());
    if(auto iter = data.Profiles_Lost.find(EnumDefContent::Node); iter != data.Profiles_Lost.end())
        mcpData.ChangedNodes.insert(mcpData.ChangedNodes.end(), iter->second.begin(), iter->second.end());
    if(auto iter = data.Profiles_ChangeOrAdd.find(EnumDefContent::Voxel); iter != data.Profiles_ChangeOrAdd.end())
        mcpData.ChangedVoxels.insert(mcpData.ChangedVoxels.end(), iter->second.begin(), iter->second.end());
    if(auto iter = data.Profiles_Lost.find(EnumDefContent::Voxel); iter != data.Profiles_Lost.end())
        mcpData.ChangedVoxels.insert(mcpData.ChangedVoxels.end(), iter->second.begin(), iter->second.end());

    std::vector<AssetsModel> changedModels;
    if(!data.AssetsModels.empty())
        changedModels = MP.onModelChanges(std::move(data.AssetsModels));

    if(TP && !data.AssetsTextures.empty())
        TP->onTexturesChanges(std::move(data.AssetsTextures));

    std::vector<AssetsNodestate> changedNodestates;
    if(NSP && (!data.AssetsNodestates.empty() || !changedModels.empty())) {
        changedNodestates = NSP->onNodestateChanges(std::move(data.AssetsNodestates), std::move(changedModels));
    }

    if(!changedNodestates.empty()) {
        std::unordered_set<AssetsNodestate> changed;
        changed.reserve(changedNodestates.size());
        for(AssetsNodestate id : changedNodestates)
            changed.insert(id);

        for(const auto& [nodeId, def] : ServerSession->Profiles.DefNode) {
            if(changed.contains(def.NodestateId))
                mcpData.ChangedNodes.push_back(nodeId);
        }
    }

    CP.tickSync(mcpData);
}

void VulkanRenderSession::setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) {
    WorldId = worldId;
    Pos = pos;
    Quat = quat;

    WI = worldId;
    PlayerPos = pos;
    PlayerPos /= float(Pos::Object_t::BS);
}

void VulkanRenderSession::beforeDraw(double timeSeconds) {
    if(TP)
        TP->update(timeSeconds);
    LightDummy.atlasUpdateDynamicData();
    CP.flushUploadsAndBarriers(VkInst->Graphics.CommandBufferRender);
}

void VulkanRenderSession::onGpuFinished() {
    CP.notifyGpuFinished();
}

void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuffer drawCmd) {
    {
        X64Offset = Pos & ~((1 << Pos::Object_t::BS_Bit << 4 << 2)-1);
        X64Offset_f = glm::vec3(X64Offset >> Pos::Object_t::BS_Bit);
        X64Delta = glm::vec3(Pos-X64Offset) / float(Pos::Object_t::BS);
    }


    // vkCmdBindPipeline(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, NodeStaticOpaquePipeline);
	// vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout, 
    //     VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(WorldPCO), &PCO);
    // vkCmdBindDescriptorSets(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, 
    //     MainAtlas_LightMap_PipelineLayout,  0, 2, 
    //     (const VkDescriptorSet[]) {MainAtlasDescriptor, VoxelLightMapDescriptor}, 0, nullptr);

    // VkDeviceSize vkOffsets = 0;
    // VkBuffer vkBuffer = VKCTX->TestQuad;
    // vkCmdBindVertexBuffers(drawCmd, 0, 1, &vkBuffer, &vkOffsets);

    // for(int i = 0; i < 16; i++) {
    //     PCO.Model = glm::rotate(PCO.Model, glm::half_pi<float>()/4, glm::vec3(0, 1, 0));
    //     vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout, 
    //         VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(WorldPCO), &PCO);
    //     vkCmdDraw(drawCmd, 6, 1, 0, 0);
    // }

    // PCO.Model = glm::mat4(1);

    // // Проба рендера вокселей
    // vkCmdBindPipeline(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, VoxelOpaquePipeline);
	// vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout, 
    //     VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(WorldPCO), &PCO);
    // vkCmdBindDescriptorSets(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, 
    //     MainAtlas_LightMap_PipelineLayout,  0, 2, 
    //     (const VkDescriptorSet[]) {MainAtlasDescriptor, VoxelLightMapDescriptor}, 0, nullptr);

    // if(VKCTX->TestVoxel) {
    //     vkBuffer = *VKCTX->TestVoxel;
    //     vkCmdBindVertexBuffers(drawCmd, 0, 1, &vkBuffer, &vkOffsets);
    //     vkCmdDraw(drawCmd, VKCTX->TestVoxel->getSize() / sizeof(VoxelVertexPoint), 1, 0, 0);
    // }

    // {
    //     auto iterWorld = External.ChunkVoxelMesh.find(WorldId);
    //     if(iterWorld != External.ChunkVoxelMesh.end()) {
    //         glm::mat4 orig = PCO.Model;

    //         for(auto &pair : iterWorld->second) {
    //             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);
    //                 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.VertexCount, 1, offset, 0);
    //             }
    //         }

    //         PCO.Model = orig;
    //     }
    // }

    // vkCmdBindPipeline(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, NodeStaticOpaquePipeline);
	// vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout, 
    //     VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(WorldPCO), &PCO);
    // vkCmdBindDescriptorSets(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, 
    //     MainAtlas_LightMap_PipelineLayout,  0, 2, 
    //     (const VkDescriptorSet[]) {MainAtlasDescriptor, VoxelLightMapDescriptor}, 0, nullptr);

    // {
    //     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.z, pair.first.y, pair.first.x);
    //                 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);
    //             }
    //         }

    //         PCO.Model = orig;
    //     }
    // }

    static float Delta = 0;
    Delta += dTime;

    PCO.Model = glm::mat4(1);
    //PCO.Model = glm::translate(PCO.Model, -X64Offset_f);
    // glm::quat quat = glm::inverse(Quat);

    {

        // auto *srv = (class ServerSession*) ServerSession;

        glm::vec4 v = glm::mat4(glm::inverse(Quat))*glm::vec4(0, 0, -6, 1);

        Pos::GlobalNode pos = (Pos::GlobalNode) (glm::vec3) v;

        pos += (Pos-X64Offset) >> Pos::Object_t::BS_Bit;
        PCO.Model = glm::translate(PCO.Model, glm::vec3(pos));
    }
    

    {
        glm::mat4 proj = glm::perspective<float>(glm::radians(75.f), float(VkInst->Screen.Width)/float(VkInst->Screen.Height), 0.5, std::pow(2, 17));
        proj[1][1] *= -1;

        // Получили область рендера от левого верхнего угла
        // x -1 -> 1; y 1 -> -1; z 0 -> -1
        // Правило левой руки
        // Перед полигонов определяется обходом против часовой стрелки

        glm::mat4 view = glm::mat4(1);
        // Смещаем мир относительно позиции игрока, чтобы игрок в пространстве рендера оказался в нулевых координатах
        view = glm::translate(view, -X64Delta);
        // Поворачиваем мир обратно взгляду игрока, чтобы его взгляд стал по направлению оси -z
        view = glm::mat4(Quat)*view;

        // Сначала применяется матрица вида, потом проекции
        PCO.ProjView = proj*view;
    }

    vkCmdBindPipeline(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, NodeStaticOpaquePipeline);
	vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout, 
        VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(WorldPCO), &PCO);
    vkCmdBindDescriptorSets(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, 
        MainAtlas_LightMap_PipelineLayout,  0, 2, 
        (const VkDescriptorSet[]) {TP ? TP->getDescriptorSet() : VK_NULL_HANDLE, VoxelLightMapDescriptor}, 0, nullptr);

    {
        // glm::vec4 offset = glm::inverse(Quat)*glm::vec4(0, 0, -64, 1);
        // PCO.Model = glm::translate(glm::mat4(1), glm::vec3(offset));
        PCO.Model = glm::mat4(1);
    }
    VkBuffer vkBuffer = TestQuad;
    VkDeviceSize vkOffsets = 0;

    vkCmdBindVertexBuffers(drawCmd, 0, 1, &vkBuffer, &vkOffsets);
    vkCmdDraw(drawCmd, 6*3*2, 1, 0, 0);

    {
        PCO.Model = glm::mat4(1.0f);
        Pos::GlobalChunk x64offset = X64Offset >> Pos::Object_t::BS_Bit >> 4;
        Pos::GlobalRegion x64offset_region = x64offset >> 2;

        auto [voxelVertexs, nodeVertexs] = CP.getChunksForRender(WorldId, Pos, 1, PCO.ProjView, x64offset_region);

        // {
        //     static uint32_t l = TOS::Time::getSeconds();
        //     if(l != TOS::Time::getSeconds()) {
        //         l = TOS::Time::getSeconds();
        //         TOS::Logger("Test").debug() << nodeVertexs.size();
        //     }
        // }

        size_t count = 0;

        glm::mat4 orig = PCO.Model;
        for(auto& [chunkPos, vertexs, indexes, type, vertexCount] : nodeVertexs) {
            count += vertexCount;
            
            glm::vec3 cpos(chunkPos-x64offset);
            PCO.Model = glm::translate(orig, cpos*16.f);
            auto [vkBufferV, offsetV] = vertexs;
            auto [vkBufferI, offsetI] = indexes;

            vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout, 
                VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, offsetof(WorldPCO, Model), sizeof(WorldPCO::Model), &PCO.Model);
            
            VkDeviceSize offset = offsetV*sizeof(NodeVertexStatic);
            vkCmdBindVertexBuffers(drawCmd, 0, 1, &vkBufferV, &offset);
            offset = offsetI * (type ? 2 : 4);
            vkCmdBindIndexBuffer(drawCmd, vkBufferI, offset, type ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32);

            vkCmdDrawIndexed(drawCmd, vertexCount, 1, 0, 0, 0);
            //vkCmdDraw(drawCmd, vertexCount, 1, 0, 0);
        }

        PCO.Model = orig;
    }

    vkCmdBindPipeline(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, NodeStaticTransparentPipeline);
	vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout, 
        VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(WorldPCO), &PCO);
    vkCmdBindDescriptorSets(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, 
        MainAtlas_LightMap_PipelineLayout,  0, 2, 
        (const VkDescriptorSet[]) {TP ? TP->getDescriptorSet() : VK_NULL_HANDLE, VoxelLightMapDescriptor}, 0, nullptr);

    ensureAtlasLayerPreview();
    if(AtlasLayersPreview) {
        glm::mat4 previewModel = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 64.0f, 0.0f)-glm::vec3(X64Offset >> Pos::Object_t::BS_Bit));
        vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout,
            VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, offsetof(WorldPCO, Model), sizeof(WorldPCO::Model), &previewModel);
        VkBuffer previewBuffer = *AtlasLayersPreview;
        VkDeviceSize previewOffset = 0;
        vkCmdBindVertexBuffers(drawCmd, 0, 1, &previewBuffer, &previewOffset);
        vkCmdDraw(drawCmd, AtlasLayersPreviewCount * 6, 1, 0, 0);
    }

    if(false) {
        ensureEntityTexture();

        if(!ServerSession->Content.Entityes.empty()) {
            VkBuffer entityBuffer = TestQuad;
            VkDeviceSize entityOffset = 0;
            vkCmdBindVertexBuffers(drawCmd, 0, 1, &entityBuffer, &entityOffset);

            glm::mat4 orig = PCO.Model;
            for(const auto& pair : ServerSession->Content.Entityes) {
                const auto& info = pair.second;
                if(info.WorldId != WorldId)
                    continue;

                glm::vec3 entityPos = Pos::Object_t::asFloatVec(info.Pos - X64Offset);
                entityPos.y -= 1.6f; // Camera position arrives as eye height.

                glm::mat4 model = glm::translate(glm::mat4(1.0f), entityPos);
                model = model * glm::mat4(info.Quat);
                model = glm::scale(model, glm::vec3(0.6f, 1.8f, 0.6f));

                PCO.Model = model;
                vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout,
                    VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, offsetof(WorldPCO, Model), sizeof(WorldPCO::Model), &PCO.Model);
                vkCmdDraw(drawCmd, 6*3*2, 1, 0, 0);
            }

            PCO.Model = orig;
        }
    }

    CP.pushFrame();
}

void VulkanRenderSession::updateTestQuadTexture(uint32_t texId) {
    if(EntityTextureReady && EntityTextureId == texId)
        return;

    auto *array = reinterpret_cast<NodeVertexStatic*>(TestQuad.mapMemory());
    const size_t vertexCount = TestQuad.getSize() / sizeof(NodeVertexStatic);
    for(size_t iter = 0; iter < vertexCount; ++iter)
        array[iter].Tex = texId;
    TestQuad.unMapMemory();

    EntityTextureId = texId;
    EntityTextureReady = true;
}

void VulkanRenderSession::ensureEntityTexture() {
    if(EntityTextureReady || !TP || !NSP)
        return;

    return;
}

void VulkanRenderSession::ensureAtlasLayerPreview() {
    if(!TP)
        return;

    const uint32_t maxLayers = TP->getAtlasMaxLayers();
    if(maxLayers == 0)
        return;

    if(AtlasLayersPreview && AtlasLayersPreviewCount == maxLayers)
        return;

    TP->requestAtlasLayerCount(maxLayers);

    const uint32_t vertsPerQuad = 6;
    const uint32_t totalVerts = maxLayers * vertsPerQuad;
    std::vector<NodeVertexStatic> verts(totalVerts);

    const uint32_t columns = 4;
    const uint32_t base = 224;
    const uint32_t step = 320;
    const uint32_t size = 256;
    const uint32_t z = base;

    auto makeVert = [&](uint32_t fx, uint32_t fy, uint32_t tex, uint32_t tu, uint32_t tv) -> NodeVertexStatic {
        NodeVertexStatic v{};
        v.FX = fx;
        v.FY = fy;
        v.FZ = z;
        v.LS = 0;
        v.Tex = tex;
        v.TU = tu;
        v.TV = tv;
        return v;
    };

    for(uint32_t layer = 0; layer < maxLayers; ++layer) {
        const uint32_t col = layer % columns;
        const uint32_t row = layer / columns;
        const uint32_t x0 = base + col * step;
        const uint32_t y0 = base + row * step;
        const uint32_t x1 = x0 + size;
        const uint32_t y1 = y0 + size;
        const uint32_t tex = TP->getAtlasLayerId(layer);

        const size_t start = static_cast<size_t>(layer) * vertsPerQuad;
        verts[start + 0] = makeVert(x0, y0, tex, 0, 0);
        verts[start + 1] = makeVert(x0, y1, tex, 0, 65535);
        verts[start + 2] = makeVert(x1, y1, tex, 65535, 65535);
        verts[start + 3] = makeVert(x0, y0, tex, 0, 0);
        verts[start + 4] = makeVert(x1, y1, tex, 65535, 65535);
        verts[start + 5] = makeVert(x1, y0, tex, 65535, 0);
    }

    AtlasLayersPreview.emplace(VkInst, verts.size() * sizeof(NodeVertexStatic));
    std::memcpy(AtlasLayersPreview->mapMemory(), verts.data(), verts.size() * sizeof(NodeVertexStatic));
    AtlasLayersPreview->unMapMemory();
    AtlasLayersPreviewCount = maxLayers;
}

void VulkanRenderSession::pushStage(EnumRenderStage stage) {
}

std::vector<VoxelVertexPoint> VulkanRenderSession::generateMeshForVoxelChunks(const std::vector<VoxelCube>& cubes) {
    std::vector<VoxelVertexPoint> out;
    out.reserve(cubes.size()*6);
    
    for(const VoxelCube &cube : cubes) {
        out.emplace_back(
            cube.Pos.x,
            cube.Pos.y,
            cube.Pos.z,
            0,
            0, 0,
            cube.Size.x,
            cube.Size.z,
            cube.VoxelId,
            0, 0,
            0
        );

        out.emplace_back(
            cube.Pos.x,
            cube.Pos.y,
            cube.Pos.z,
            1,
            0, 0,
            cube.Size.x,
            cube.Size.y,
            cube.VoxelId,
            0, 0,
            0
        );

        out.emplace_back(
            cube.Pos.x,
            cube.Pos.y,
            cube.Pos.z,
            2,
            0, 0,
            cube.Size.z,
            cube.Size.y,
            cube.VoxelId,
            0, 0,
            0
        );

        out.emplace_back(
            cube.Pos.x,
            cube.Pos.y+cube.Size.y+1,
            cube.Pos.z,
            3,
            0, 0,
            cube.Size.x,
            cube.Size.z,
            cube.VoxelId,
            0, 0,
            0
        );

        out.emplace_back(
            cube.Pos.x,
            cube.Pos.y,
            cube.Pos.z+cube.Size.z+1,
            4,
            0, 0,
            cube.Size.x,
            cube.Size.y,
            cube.VoxelId,
            0, 0,
            0
        );

        out.emplace_back(
            cube.Pos.x+cube.Size.x+1,
            cube.Pos.y,
            cube.Pos.z,
            5,
            0, 0,
            cube.Size.z,
            cube.Size.y,
            cube.VoxelId,
            0, 0,
            0
        );
    }

    return out;
}

void VulkanRenderSession::updateDescriptor_VoxelsLight() {
    VkDescriptorBufferInfo bufferInfo = LightDummy;
    VkDescriptorImageInfo imageInfo = LightDummy;

    std::vector<VkWriteDescriptorSet> ciDescriptorSet =
    {
        {
            .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
            .pNext = nullptr,
            .dstSet = VoxelLightMapDescriptor,
            .dstBinding = 0,
            .dstArrayElement = 0,
            .descriptorCount = 1,
            .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
            .pImageInfo = &imageInfo
        }, {
            .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
            .pNext = nullptr,
            .dstSet = VoxelLightMapDescriptor,
            .dstBinding = 1,
            .dstArrayElement = 0,
            .descriptorCount = 1,
            .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
            .pBufferInfo = &bufferInfo
        }
    };

    vkUpdateDescriptorSets(VkInst->Graphics.Device, ciDescriptorSet.size(), ciDescriptorSet.data(), 0, nullptr);
}

void VulkanRenderSession::updateDescriptor_ChunksLight() {

}

}
