#pragma once #include "Client/Abstract.hpp" #include "Common/Abstract.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Abstract.hpp" #include "TOSLib.hpp" #include "VertexPool.hpp" #include "assets.hpp" #include "glm/common.hpp" #include "glm/fwd.hpp" #include "../FrustumCull.h" #include "glm/geometric.hpp" #include "png++/image.hpp" #include #include /* У движка есть один текстурный атлас 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); /* Хранит модели и предоставляет их конечные варианты */ class ModelProvider { public: struct Model { // В вершинах текущей модели TexId ссылается на локальный текстурный ключ // 0 -> default_texture -> luavox:grass.png std::vector TextureKeys; // Привязка локальных ключей к глобальным std::unordered_map TextureMap; // Вершины со всеми применёнными трансформациями, с CullFace std::unordered_map> Vertecies; // Текстуры этой модели не будут переписаны вышестоящими bool UniqueTextures = false; }; public: // Предкомпилирует модель Model getModel(ResourceId id) { std::vector used; return getModel(id, used); } // Применяет изменения, возвращая все затронутые модели std::vector onModelChanges(std::vector> newOrChanged, std::vector lost) { std::vector result; std::move_only_function makeUnready; makeUnready = [&](ResourceId id) { auto iterModel = Models.find(id); if(iterModel == Models.end()) return; if(!iterModel->second.Ready) return; result.push_back(id); for(ResourceId downId : iterModel->second.DownUse) { auto iterModel = Models.find(downId); if(iterModel == Models.end()) return; auto iter = std::find(iterModel->second.UpUse.begin(), iterModel->second.UpUse.end(), id); assert(iter != iterModel->second.UpUse.end()); iterModel->second.UpUse.erase(iter); } for(ResourceId upId : iterModel->second.UpUse) { makeUnready(upId); } assert(iterModel->second.UpUse.empty()); iterModel->second.Ready = false; }; for(ResourceId lostId : lost) { makeUnready(lostId); } for(ResourceId lostId : lost) { auto iterModel = Models.find(lostId); if(iterModel == Models.end()) continue; Models.erase(iterModel); } for(const auto& [key, resource] : newOrChanged) { result.push_back(key); makeUnready(key); ModelObject model; std::string type = "unknown"; try { std::u8string_view data((const char8_t*) resource.data(), resource.size()); if(data.starts_with((const char8_t*) "bm")) { type = "InternalBinary"; // Компилированная модель внутреннего формата LV::PreparedModel pm((std::u8string) data); model.TextureMap = pm.CompiledTextures; model.TextureKeys = {}; for(const PreparedModel::Cuboid& cb : pm.Cuboids) { glm::vec3 min = glm::min(cb.From, cb.To), max = glm::max(cb.From, cb.To); for(const auto& [face, params] : cb.Faces) { glm::vec2 from_uv = {params.UV[0], params.UV[1]}, to_uv = {params.UV[2], params.UV[3]}; uint32_t texId; { auto iter = std::find(model.TextureKeys.begin(), model.TextureKeys.end(), params.Texture); if(iter == model.TextureKeys.end()) { texId = model.TextureKeys.size(); model.TextureKeys.push_back(params.Texture); } else { texId = iter-model.TextureKeys.begin(); } } std::vector v; switch(face) { case EnumFace::Down: v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId); v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId); v.emplace_back(glm::vec3{max.x, min.y, min.z}, to_uv, texId); v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId); v.emplace_back(glm::vec3{min.x, min.y, max.z}, to_uv, texId); v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{to_uv.x, from_uv.y}, texId); break; case EnumFace::Up: v.emplace_back(glm::vec3{min.x, max.y, min.z}, from_uv, texId); v.emplace_back(glm::vec3{max.x, max.y, min.z}, glm::vec2{from_uv.x, to_uv.y}, texId); v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId); v.emplace_back(glm::vec3{min.x, max.y, min.z}, from_uv, texId); v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId); v.emplace_back(glm::vec3{min.x, max.y, max.z}, glm::vec2{to_uv.x, from_uv.y}, texId); break; case EnumFace::North: v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId); v.emplace_back(glm::vec3{max.x, min.y, min.z}, glm::vec2{from_uv.x, to_uv.y}, texId); v.emplace_back(glm::vec3{max.x, max.y, min.z}, to_uv, texId); v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId); v.emplace_back(glm::vec3{max.x, max.y, min.z}, to_uv, texId); v.emplace_back(glm::vec3{min.x, max.y, min.z}, glm::vec2{to_uv.x, from_uv.y}, texId); break; case EnumFace::South: v.emplace_back(glm::vec3{min.x, min.y, max.z}, from_uv, texId); v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId); v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId); v.emplace_back(glm::vec3{min.x, min.y, max.z}, from_uv, texId); v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId); v.emplace_back(glm::vec3{min.x, max.y, max.z}, glm::vec2{to_uv.x, from_uv.y}, texId); break; case EnumFace::West: v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId); v.emplace_back(glm::vec3{min.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId); v.emplace_back(glm::vec3{min.x, max.y, max.z}, to_uv, texId); v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId); v.emplace_back(glm::vec3{min.x, max.y, max.z}, to_uv, texId); v.emplace_back(glm::vec3{min.x, max.y, min.z}, glm::vec2{to_uv.x, from_uv.y}, texId); break; case EnumFace::East: v.emplace_back(glm::vec3{max.x, min.y, min.z}, from_uv, texId); v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId); v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId); v.emplace_back(glm::vec3{max.x, min.y, min.z}, from_uv, texId); v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId); v.emplace_back(glm::vec3{max.x, max.y, min.z}, glm::vec2{to_uv.x, from_uv.y}, texId); break; default: MAKE_ERROR("EnumFace::None"); } cb.Trs.apply(v); model.Vertecies[params.Cullface].append_range(v); } } // struct Face { // int TintIndex = -1; // int16_t Rotation = 0; // }; // std::vector Transformations; } else if(data.starts_with((const char8_t*) "glTF")) { type = "glb"; } else if(data.starts_with((const char8_t*) "bgl")) { type = "InternalGLTF"; } else if(data.starts_with((const char8_t*) "{")) { type = "InternalJson или glTF"; // Модель внутреннего формата или glTF } } catch(const std::exception& exc) { LOG.warn() << "Не удалось распарсить модель " << type << ":\n\t" << exc.what(); continue; } Models.insert({key, std::move(model)}); } std::sort(result.begin(), result.end()); auto eraseIter = std::unique(result.begin(), result.end()); result.erase(eraseIter, result.end()); return result; } private: struct ModelObject : public Model { // Зависимости, их трансформации (здесь может повторятся одна и таже модель) // и перезаписи идентификаторов текстур std::vector> Depends; // Те кто использовали модель как зависимость в ней отметятся std::vector UpUse; // При изменении/удалении модели убрать метки с зависимостей std::vector DownUse; // Для постройки зависимостей bool Ready = false; }; Logger LOG = "Client>ModelProvider"; // Таблица моделей std::unordered_map Models; uint64_t UniqId = 0; Model getModel(ResourceId id, std::vector& used) { auto iterModel = Models.find(id); if(iterModel == Models.end()) { // Нет такой модели, ну и хрен с ним return {}; } ModelObject& model = iterModel->second; if(!model.Ready) { std::vector deps; for(const auto&[id, _] : model.Depends) { deps.push_back(id); } std::sort(deps.begin(), deps.end()); auto eraseIter = std::unique(deps.begin(), deps.end()); deps.erase(eraseIter, deps.end()); // Отмечаемся в зависимостях for(ResourceId subId : deps) { auto iterModel = Models.find(subId); if(iterModel == Models.end()) continue; iterModel->second.UpUse.push_back(id); } model.Ready = true; } // Собрать зависимости std::vector subModels; used.push_back(id); for(const auto&[id, trans] : model.Depends) { if(std::find(used.begin(), used.end(), id) != used.end()) { // Цикл зависимостей continue; } Model model = getModel(id, used); for(auto& [face, vertecies] : model.Vertecies) trans.apply(vertecies); subModels.emplace_back(std::move(model)); } subModels.push_back(model); used.pop_back(); // Собрать всё воедино Model result; for(Model& subModel : subModels) { std::vector localRelocate; if(subModel.UniqueTextures) { std::string extraKey = "#" + std::to_string(UniqId++); for(std::string& key : subModel.TextureKeys) { key += extraKey; } std::unordered_map newTable; for(auto& [key, _] : subModel.TextureMap) { newTable[key + extraKey] = _; } subModel.TextureMap = std::move(newTable); } for(const std::string& key : subModel.TextureKeys) { auto iterKey = std::find(result.TextureKeys.begin(), result.TextureKeys.end(), key); if(iterKey == result.TextureKeys.end()) { localRelocate.push_back(result.TextureKeys.size()); result.TextureKeys.push_back(key); } else { localRelocate.push_back(iterKey-result.TextureKeys.begin()); } } for(const auto& [face, vertecies] : subModel.Vertecies) { auto& resVerts = result.Vertecies[face]; for(Vertex v : vertecies) { v.TexId = localRelocate[v.TexId]; resVerts.push_back(v); } } for(auto& [key, dk] : subModel.TextureMap) { result.TextureMap[key] = dk; } } return result; } }; /** Обработчик текстурных атласов для моделей Функция выделения места в полном объёме. Перед отрисовкой кадра выставить команды ожидания завершения передачи данных на GPU, до вызова стадии FRAGMENT_STAGE Одновременно может проходить два рендера (двойные копии ресурсов) Когда одна копия освободилась, в неё начинается запись Два атласа, постоянно редактируются меж кадрами 2D_ARRAY и одну текстуру на стороне хоста для обмена информацией Ужатие hd текстур Хранить все текстуры в оперативке */ class TextureProvider { // Хедер для атласа перед описанием текстур struct alignas(16) UniformInfo { uint32_t // Количество текстур SubsCount, // Счётчик времени с разрешением 8 бит в секунду Counter, // Размер атласа Size; // Дальше в шейдере массив на описания текстур // std::vector SubsInfo; }; // Описание текстуры на стороне шейдера struct alignas(16) InfoSubTexture { uint32_t isExist = 0; uint32_t // Точная позиция в атласе PosX = 0, PosY = 0, PosZ = 0, // Размер текстуры в атласе Width = 0, Height = 0; struct { uint16_t Enabled : 1 = 0, Frames : 15 = 0; uint16_t TimePerFrame = 0; } Animation; }; public: TextureProvider(Vulkan* inst, VkDescriptorPool descPool) : Inst(inst), DescPool(descPool) { { const VkSamplerCreateInfo ciSampler = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = nullptr, .flags = 0, .magFilter = VK_FILTER_NEAREST, .minFilter = VK_FILTER_NEAREST, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, .mipLodBias = 0.0f, .anisotropyEnable = VK_FALSE, .maxAnisotropy = 1, .compareEnable = 0, .compareOp = VK_COMPARE_OP_NEVER, .minLod = 0.0f, .maxLod = 0.0f, .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, .unnormalizedCoordinates = VK_FALSE }; vkAssert(!vkCreateSampler(inst->Graphics.Device, &ciSampler, nullptr, &Sampler)); } { std::vector 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( Inst->Graphics.Device, &descriptorLayout, nullptr, &DescLayout)); } { Atlases.resize(BackupAtlasCount); VkDescriptorSetAllocateInfo ciAllocInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .pNext = nullptr, .descriptorPool = descPool, .descriptorSetCount = (uint32_t) Atlases.size(), .pSetLayouts = &DescLayout }; std::vector descriptors; descriptors.resize(Atlases.size()); vkAssert(!vkAllocateDescriptorSets(inst->Graphics.Device, &ciAllocInfo, descriptors.data())); for(auto& atlas : Atlases) { atlas.recreate(Inst, true); atlas.Descriptor = descriptors.back(); descriptors.pop_back(); } } { VkSemaphoreCreateInfo semaphoreCreateInfo = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, .pNext = nullptr, .flags = 0 }; vkAssert(!vkCreateSemaphore(Inst->Graphics.Device, &semaphoreCreateInfo, nullptr, &SendChanges)); } { const VkCommandBufferAllocateInfo infoCmd = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .pNext = nullptr, .commandPool = Inst->Graphics.Pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1 }; vkAssert(!vkAllocateCommandBuffers(Inst->Graphics.Device, &infoCmd, &CMD)); } Cache.recreate(Inst, false); AtlasTextureUnusedId.all(); } ~TextureProvider() { if(DescLayout) vkDestroyDescriptorSetLayout(Inst->Graphics.Device, DescLayout, nullptr); if(Sampler) { vkDestroySampler(Inst->Graphics.Device, Sampler, nullptr); Sampler = nullptr; } for(auto& atlas : Atlases) { atlas.destroy(Inst); } Atlases.clear(); Cache.destroy(Inst); Cache.unMap(Inst); if(SendChanges) { vkDestroySemaphore(Inst->Graphics.Device, SendChanges, nullptr); SendChanges = nullptr; } if(CMD) { vkFreeCommandBuffers(Inst->Graphics.Device, Inst->Graphics.Pool, 1, &CMD); CMD = nullptr; } } uint16_t getTextureId(const TexturePipeline& pipe) { return 0; } // Устанавливает новый размер единицы в массиве текстур атласа enum class EnumAtlasSize { _2048 = 2048, _4096 = 4096, _8192 = 8192, _16_384 = 16'384 }; void setAtlasSize(EnumAtlasSize size) { ReferenceSize = size; } // Максимальный размер выделенный под атласы в памяти устройства void setDeviceMemorySize(size_t size) { std::unreachable(); } // Применяет изменения, возвращая все затронутые модели std::vector onTexturesChanges(std::vector> newOrChanged, std::vector lost) { std::vector result; for(const auto& [key, res] : newOrChanged) { result.push_back(key); ChangedOrAdded.push_back(key); TextureEntry entry; iResource sres((const uint8_t*) res.data(), res.size()); iBinaryStream stream = sres.makeStream(); png::image img(stream.Stream); entry.Width = img.get_width(); entry.Height = img.get_height(); entry.RGBA.resize(4*entry.Width*entry.Height); for(int i = 0; i < entry.Height; i++) { std::copy( ((const uint32_t*) &img.get_pixbuf().operator [](i)[0]), ((const uint32_t*) &img.get_pixbuf().operator [](i)[0])+entry.Width, ((uint32_t*) entry.RGBA.data())+entry.Width*(false ? entry.Height-i-1 : i) ); } Textures[key] = std::move(entry); } for(AssetsTexture key : lost) { result.push_back(key); Lost.push_back(key); } { std::sort(result.begin(), result.end()); auto eraseIter = std::unique(result.begin(), result.end()); result.erase(eraseIter, result.end()); } { std::sort(ChangedOrAdded.begin(), ChangedOrAdded.end()); auto eraseIter = std::unique(ChangedOrAdded.begin(), ChangedOrAdded.end()); ChangedOrAdded.erase(eraseIter, ChangedOrAdded.end()); } { std::sort(Lost.begin(), Lost.end()); auto eraseIter = std::unique(Lost.begin(), Lost.end()); Lost.erase(eraseIter, Lost.end()); } return result; } void update() { // Подготовить обновления атласа // Если предыдущий освободился, то записать изменения в него // Держать на стороне хоста полную версию атласа и все изменения писать туда // Когерентная память сама разберётся что отсылать на устройство // Синхронизировать всё из внутреннего буфера в атлас // При пересоздании хостового буфера, скопировать всё из старого. // Оптимизации копирования при указании конкретных изменённых слоёв? } VkDescriptorSet getDescriptor() { return Atlases[ActiveAtlas].Descriptor; } void pushFrame() { for(auto& atlas : Atlases) if(atlas.NotUsedFrames < 100) atlas.NotUsedFrames++; Atlases[ActiveAtlas].NotUsedFrames = 0; // Если есть новые текстуры или они поменялись // } private: Vulkan* Inst = nullptr; VkDescriptorPool DescPool = VK_NULL_HANDLE; VkDescriptorSetLayout DescLayout = VK_NULL_HANDLE; // Для всех атласов VkSampler Sampler = VK_NULL_HANDLE; // Ожидание завершения работы с хостовым буфером VkSemaphore SendChanges = VK_NULL_HANDLE; // VkCommandBuffer CMD = VK_NULL_HANDLE; // Размер, которому должны соответствовать все атласы EnumAtlasSize ReferenceSize = EnumAtlasSize::_2048; struct TextureEntry { uint16_t Width, Height; std::vector RGBA; // Идентификатор текстуры в атласе uint16_t InAtlasId = uint16_t(-1); }; // Текстуры, загруженные с файлов std::unordered_map Textures; struct TextureFromPipeline { }; std::unordered_map Pipelines; struct AtlasTextureEntry { uint16_t PosX, PosY, PosZ, Width, Height; }; std::bitset<1 << 16> AtlasTextureUnusedId; std::unordered_map AtlasTextureInfo; std::vector ChangedOrAdded, Lost; struct VkAtlasInfo { VkImage Image = VK_NULL_HANDLE; VkImageLayout ImageLayout = VK_IMAGE_LAYOUT_MAX_ENUM; VkDeviceMemory Memory = VK_NULL_HANDLE; VkImageView View = VK_NULL_HANDLE; VkDescriptorSet Descriptor; EnumAtlasSize Size = EnumAtlasSize::_2048; uint16_t Depth = 1; // Сколько кадров уже не используется атлас int NotUsedFrames = 0; void destroy(Vulkan* inst) { if(View) { vkDestroyImageView(inst->Graphics.Device, View, nullptr); View = nullptr; } if(Image) { vkDestroyImage(inst->Graphics.Device, Image, nullptr); Image = nullptr; } if(Memory) { vkFreeMemory(inst->Graphics.Device, Memory, nullptr); Memory = nullptr; } } void recreate(Vulkan* inst, bool deviceLocal) { // Уничтожаем то, что не понадобится if(View) { vkDestroyImageView(inst->Graphics.Device, View, nullptr); View = nullptr; } if(Image) { vkDestroyImage(inst->Graphics.Device, Image, nullptr); Image = nullptr; } if(Memory) { vkFreeMemory(inst->Graphics.Device, Memory, nullptr); Memory = nullptr; } // Создаём атлас uint32_t size = uint32_t(Size); VkImageCreateInfo infoImageCreate = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = nullptr, .flags = 0, .imageType = VK_IMAGE_TYPE_2D, .format = VK_FORMAT_B8G8R8A8_UNORM, .extent = { size, size, 1 }, .mipLevels = 1, .arrayLayers = Depth, .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_MAX_ENUM, .usage = static_cast(deviceLocal ? VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT : VK_IMAGE_USAGE_TRANSFER_SRC_BIT), .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = 0, .initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED }; VkFormatProperties props; vkGetPhysicalDeviceFormatProperties(inst->Graphics.PhysicalDevice, infoImageCreate.format, &props); if (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) infoImageCreate.tiling = VK_IMAGE_TILING_OPTIMAL; else if (props.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) infoImageCreate.tiling = VK_IMAGE_TILING_LINEAR; else vkAssert(!"No support for B8G8R8A8_UNORM as texture image format"); vkAssert(!vkCreateImage(inst->Graphics.Device, &infoImageCreate, nullptr, &Image)); // Выделяем память VkMemoryRequirements memoryReqs; vkGetImageMemoryRequirements(inst->Graphics.Device, Image, &memoryReqs); VkMemoryAllocateInfo memoryAlloc { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .pNext = nullptr, .allocationSize = memoryReqs.size, .memoryTypeIndex = inst->memoryTypeFromProperties(memoryReqs.memoryTypeBits, deviceLocal ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ) }; vkAssert(!vkAllocateMemory(inst->Graphics.Device, &memoryAlloc, nullptr, &Memory)); vkAssert(!vkBindImageMemory(inst->Graphics.Device, Image, Memory, 0)); // Порядок пикселей и привязка к картинке VkImageViewCreateInfo ciView = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = nullptr, .flags = 0, .image = Image, .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY, .format = infoImageCreate.format, .components = { VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_A }, .subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 } }; vkAssert(!vkCreateImageView(inst->Graphics.Device, &ciView, nullptr, &View)); } }; struct HostCache : public VkAtlasInfo { std::vector Layers; std::vector Layouts; std::vector Packs; void map(Vulkan* inst) { Layers.resize(Depth); Layouts.resize(Depth); for(uint32_t layer = 0; layer < Depth; layer++) { const VkImageSubresource memorySubres = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .arrayLayer = layer, }; vkGetImageSubresourceLayout(inst->Graphics.Device, Image, &memorySubres, &Layouts[layer]); vkAssert(!vkMapMemory(inst->Graphics.Device, Memory, Layouts[layer].offset, Layouts[layer].size, 0, (void**) &Layers[layer])); } } void unMap(Vulkan* inst) { vkUnmapMemory(inst->Graphics.Device, Memory); Layers.clear(); Layouts.clear(); } }; HostCache Cache; static constexpr size_t BackupAtlasCount = 2; // Атласы, используемые в кадре. // Изменения пишутся в не используемый в данный момент атлас // и изменённый атлас становится активным. Новые изменения // можно писать по прошествии нескольких кадров. std::vector Atlases; int ActiveAtlas = 0; }; /* Хранит информацию о моделях при различных состояниях нод */ class NodestateProvider { public: NodestateProvider(ModelProvider& mp, TextureProvider& tp) : MP(mp), TP(tp) {} // Применяет изменения, возвращает изменённые описания состояний std::vector onNodestateChanges(std::vector> newOrChanged, std::vector lost, std::vector changedModels) { std::vector result; for(ResourceId lostId : lost) { auto iterNodestate = Nodestates.find(lostId); if(iterNodestate == Nodestates.end()) continue; result.push_back(lostId); Nodestates.erase(iterNodestate); } for(const auto& [key, resource] : newOrChanged) { result.push_back(key); PreparedNodeState nodestate; std::string type = "unknown"; try { std::u8string_view data((const char8_t*) resource.data(), resource.size()); if(data.starts_with((const char8_t*) "bn")) { type = "InternalBinary"; // Компилированный nodestate внутреннего формата nodestate = PreparedNodeState(data); } else if(data.starts_with((const char8_t*) "{")) { type = "InternalJson"; // nodestate в json формате } } catch(const std::exception& exc) { LOG.warn() << "Не удалось распарсить nodestate " << type << ":\n\t" << exc.what(); continue; } Nodestates.insert({key, std::move(nodestate)}); } std::sort(result.begin(), result.end()); auto eraseIter = std::unique(result.begin(), result.end()); result.erase(eraseIter, result.end()); return result; } // Выдаёт модели в зависимости от состояний // statesInfo - Описание состояний ноды // states - Текущие значения состояний ноды std::vector>>>> getModelsForNode(AssetsNodestate id, const std::vector& statesInfo, const std::unordered_map& states) { auto iterNodestate = Nodestates.find(id); if(iterNodestate == Nodestates.end()) return {}; std::vector routes = iterNodestate->second.getModelsForState(statesInfo, states); std::vector>>>> result; std::unordered_map pipelineResolveCache; for(uint16_t routeId : routes) { std::vector>>> routeModels; const auto& route = iterNodestate->second.Routes[routeId]; for(const auto& [w, m] : route.second) { if(const PreparedNodeState::Model* ptr = std::get_if(&m)) { ModelProvider::Model model = MP.getModel(ptr->Id); Transformations trf(ptr->Transforms); std::unordered_map> out; for(auto& [l, r] : model.Vertecies) { trf.apply(r); // Позиция -224 ~ 288; 64 позиций в одной ноде, 7.5 метров в ряд for(const Vertex& v : r) { NodeVertexStatic vert; vert.FX = (v.Pos.x+0.5)*64+224; vert.FY = (v.Pos.y+0.5)*64+224; vert.FZ = (v.Pos.z+0.5)*64+224; vert.TU = std::clamp(v.UV.x * (1 << 16), 0, (1 << 16)); vert.TV = std::clamp(v.UV.y * (1 << 16), 0, (1 << 16)); const TexturePipeline& pipe = model.TextureMap[model.TextureKeys[v.TexId]]; if(auto iterPipe = pipelineResolveCache.find(pipe); iterPipe != pipelineResolveCache.end()) { vert.Tex = iterPipe->second; } else { vert.Tex = TP.getTextureId(pipe); pipelineResolveCache[pipe] = vert.Tex; } out[l].push_back(vert); } } /// TODO: uvlock routeModels.emplace_back(w, std::move(out)); } } result.push_back(std::move(routeModels)); } return result; } private: Logger LOG = "Client>NodestateProvider"; ModelProvider& MP; TextureProvider& TP; std::unordered_map Nodestates; }; /* Объект, занимающийся генерацией меша на основе нод и вокселей Требует доступ к профилям в 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> NodeIndexes; }; // Очередь чанков на перерисовку TOS::SpinlockObject>> Input; // Выход TOS::SpinlockObject> Output; public: ChunkMeshGenerator(IServerSession* serverSession) : SS(serverSession) { assert(serverSession); } ~ChunkMeshGenerator() { assert(Threads.empty()); } // Меняет количество обрабатывающих потоков void changeThreadsCount(uint8_t threads); 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 ChunkPreparator { public: struct TickSyncData { // Профили на которые повлияли изменения, по ним нужно пересчитать чанки std::vector ChangedVoxels; std::vector ChangedNodes; std::unordered_map> ChangedChunks; std::unordered_map> LostRegions; }; public: ChunkPreparator(Vulkan* vkInst, IServerSession* serverSession) : VkInst(vkInst), CMG(serverSession), VertexPool_Voxels(vkInst), VertexPool_Nodes(vkInst), IndexPool_Nodes_16(vkInst), IndexPool_Nodes_32(vkInst) { assert(vkInst); assert(serverSession); CMG.changeThreadsCount(1); 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)); } ~ChunkPreparator() { CMG.changeThreadsCount(0); if(CMDPool) vkDestroyCommandPool(VkInst->Graphics.Device, CMDPool, nullptr); } void prepareTickSync() { CMG.prepareTickSync(); } void pushStageTickSync() { CMG.pushStageTickSync(); } void tickSync(const TickSyncData& data); // Готовность кадров определяет когда можно удалять ненужные ресурсы, которые ещё используются в рендере void pushFrame(); // Выдаёт буферы для рендера в порядке от ближнего к дальнему. distance - радиус в регионах std::pair< std::vector, uint32_t>>, std::vector, std::pair, bool, 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; IndexPool IndexPool_Nodes_16; IndexPool IndexPool_Nodes_32; struct ChunkObj_t { std::vector Voxels; VertexPool::Pointer VoxelPointer; std::vector Nodes; VertexPool::Pointer NodePointer; std::variant::Pointer, IndexPool::Pointer> NodeIndexes; }; // Склад указателей на вершины чанков std::unordered_map> > ChunksMesh; uint8_t FrameRoulette = 0; // Вершины, ожидающие удаления по прошествию какого-то количества кадров std::vector::Pointer> VPV_ToFree[FRAME_COUNT_RESOURCE_LATENCY]; std::vector::Pointer, std::variant::Pointer, IndexPool::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; ChunkPreparator CP; ModelProvider MP; 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(); }; }