1363 lines
53 KiB
C++
1363 lines
53 KiB
C++
#pragma once
|
||
|
||
#include "Client/AssetsManager.hpp"
|
||
#include "Client/Abstract.hpp"
|
||
#include "Common/Abstract.hpp"
|
||
#include <Client/Vulkan/Vulkan.hpp>
|
||
#include <algorithm>
|
||
#include <bitset>
|
||
#include <condition_variable>
|
||
#include <cstring>
|
||
#include <functional>
|
||
#include <limits>
|
||
#include <memory>
|
||
#include <mutex>
|
||
#include <optional>
|
||
#include <queue>
|
||
#include <string_view>
|
||
#include <thread>
|
||
#include <unordered_map>
|
||
#include <unordered_set>
|
||
#include <utility>
|
||
#include <variant>
|
||
#include <vulkan/vulkan_core.h>
|
||
#include "Client/Vulkan/AtlasPipeline/PipelinedTextureAtlas.hpp"
|
||
#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 <execution>
|
||
#include <MaxRectsBinPack.h>
|
||
|
||
/*
|
||
У движка есть один текстурный атлас 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<std::string> TextureKeys;
|
||
// Привязка локальных ключей к глобальным
|
||
std::unordered_map<std::string, TexturePipeline> TextureMap;
|
||
// Вершины со всеми применёнными трансформациями, с CullFace
|
||
std::unordered_map<EnumFace, std::vector<Vertex>> Vertecies;
|
||
// Текстуры этой модели не будут переписаны вышестоящими
|
||
bool UniqueTextures = false;
|
||
};
|
||
|
||
public:
|
||
// Предкомпилирует модель
|
||
Model getModel(ResourceId id) {
|
||
std::vector<ResourceId> used;
|
||
return getModel(id, used);
|
||
}
|
||
|
||
// Применяет изменения, возвращая все затронутые модели
|
||
std::vector<AssetsModel> onModelChanges(std::vector<std::tuple<AssetsModel, Resource, const std::vector<uint8_t>*>> newOrChanged,
|
||
std::vector<AssetsModel> lost,
|
||
const std::unordered_map<ResourceId, AssetEntry>* modelAssets) {
|
||
std::vector<AssetsModel> result;
|
||
|
||
std::move_only_function<void(ResourceId)> 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);
|
||
}
|
||
|
||
std::unordered_map<std::string, ResourceId> modelKeyToId;
|
||
if(modelAssets) {
|
||
modelKeyToId.reserve(modelAssets->size());
|
||
for(const auto& [id, entry] : *modelAssets) {
|
||
modelKeyToId.emplace(entry.Domain + ':' + entry.Key, id);
|
||
}
|
||
}
|
||
|
||
for(const auto& [key, resource, deps] : newOrChanged) {
|
||
result.push_back(key);
|
||
|
||
makeUnready(key);
|
||
ModelObject model;
|
||
std::string type = "unknown";
|
||
std::optional<AssetsManager::ParsedHeader> header;
|
||
if(deps && !deps->empty())
|
||
header = AssetsManager::parseHeader(EnumAssets::Model, *deps);
|
||
const std::vector<uint32_t>* textureDeps = header ? &header->TextureDeps : nullptr;
|
||
auto remapTextureId = [&](uint32_t placeholder) -> uint32_t {
|
||
if(!textureDeps || placeholder >= textureDeps->size())
|
||
return 0;
|
||
return (*textureDeps)[placeholder];
|
||
};
|
||
auto remapPipeline = [&](TexturePipeline pipe) {
|
||
if(textureDeps) {
|
||
for(auto& texId : pipe.BinTextures)
|
||
texId = remapTextureId(texId);
|
||
if(!pipe.Pipeline.empty()) {
|
||
std::vector<uint8_t> code;
|
||
code.resize(pipe.Pipeline.size());
|
||
std::memcpy(code.data(), pipe.Pipeline.data(), code.size());
|
||
TexturePipelineProgram::remapTexIds(code, *textureDeps, nullptr);
|
||
pipe.Pipeline.resize(code.size());
|
||
std::memcpy(pipe.Pipeline.data(), code.data(), code.size());
|
||
}
|
||
}
|
||
return pipe;
|
||
};
|
||
|
||
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.clear();
|
||
model.TextureMap.reserve(pm.CompiledTextures.size());
|
||
for(auto& [tkey, pipe] : pm.CompiledTextures)
|
||
model.TextureMap.emplace(tkey, remapPipeline(std::move(pipe)));
|
||
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<Vertex> v;
|
||
|
||
auto addQuad = [&](const glm::vec3& p0,
|
||
const glm::vec3& p1,
|
||
const glm::vec3& p2,
|
||
const glm::vec3& p3,
|
||
const glm::vec2& uv0,
|
||
const glm::vec2& uv1,
|
||
const glm::vec2& uv2,
|
||
const glm::vec2& uv3) {
|
||
v.emplace_back(p0, uv0, texId);
|
||
v.emplace_back(p1, uv1, texId);
|
||
v.emplace_back(p2, uv2, texId);
|
||
v.emplace_back(p0, uv0, texId);
|
||
v.emplace_back(p2, uv2, texId);
|
||
v.emplace_back(p3, uv3, texId);
|
||
};
|
||
|
||
const float x0 = min.x;
|
||
const float x1 = max.x;
|
||
const float y0 = min.y;
|
||
const float y1 = max.y;
|
||
const float z0 = min.z;
|
||
const float z1 = max.z;
|
||
const float u0 = from_uv.x;
|
||
const float v0 = from_uv.y;
|
||
const float u1 = to_uv.x;
|
||
const float v1 = to_uv.y;
|
||
|
||
switch(face) {
|
||
case EnumFace::Up:
|
||
addQuad({x0, y1, z1}, {x1, y1, z1}, {x1, y1, z0}, {x0, y1, z0},
|
||
{u0, v0}, {u1, v0}, {u1, v1}, {u0, v1});
|
||
break;
|
||
case EnumFace::Down:
|
||
addQuad({x0, y0, z1}, {x0, y0, z0}, {x1, y0, z0}, {x1, y0, z1},
|
||
{u0, v0}, {u0, v1}, {u1, v1}, {u1, v0});
|
||
break;
|
||
case EnumFace::East:
|
||
addQuad({x1, y0, z1}, {x1, y0, z0}, {x1, y1, z0}, {x1, y1, z1},
|
||
{u0, v0}, {u0, v1}, {u1, v1}, {u1, v0});
|
||
break;
|
||
case EnumFace::West:
|
||
addQuad({x0, y0, z1}, {x0, y1, z1}, {x0, y1, z0}, {x0, y0, z0},
|
||
{u0, v0}, {u1, v0}, {u1, v1}, {u0, v1});
|
||
break;
|
||
case EnumFace::South:
|
||
addQuad({x0, y0, z1}, {x1, y0, z1}, {x1, y1, z1}, {x0, y1, z1},
|
||
{u0, v0}, {u1, v0}, {u1, v1}, {u0, v1});
|
||
break;
|
||
case EnumFace::North:
|
||
addQuad({x0, y0, z0}, {x0, y1, z0}, {x1, y1, z0}, {x1, y0, z0},
|
||
{u0, v0}, {u0, v1}, {u1, v1}, {u1, v0});
|
||
break;
|
||
default:
|
||
MAKE_ERROR("EnumFace::None");
|
||
}
|
||
|
||
cb.Trs.apply(v);
|
||
model.Vertecies[params.Cullface].append_range(v);
|
||
}
|
||
}
|
||
|
||
if(!pm.SubModels.empty() && modelAssets) {
|
||
model.Depends.reserve(pm.SubModels.size());
|
||
for(const auto& sub : pm.SubModels) {
|
||
auto iter = modelKeyToId.find(sub.Domain + ':' + sub.Key);
|
||
if(iter == modelKeyToId.end())
|
||
continue;
|
||
model.Depends.emplace_back(iter->second, Transformations{});
|
||
}
|
||
}
|
||
|
||
// struct Face {
|
||
// int TintIndex = -1;
|
||
// int16_t Rotation = 0;
|
||
// };
|
||
|
||
// std::vector<Transformation> 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_or_assign(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<std::tuple<ResourceId, Transformations>> Depends;
|
||
|
||
// Те кто использовали модель как зависимость в ней отметятся
|
||
std::vector<ResourceId> UpUse;
|
||
// При изменении/удалении модели убрать метки с зависимостей
|
||
std::vector<ResourceId> DownUse;
|
||
// Для постройки зависимостей
|
||
bool Ready = false;
|
||
};
|
||
|
||
Logger LOG = "Client>ModelProvider";
|
||
// Таблица моделей
|
||
std::unordered_map<ResourceId, ModelObject> Models;
|
||
std::unordered_set<ResourceId> MissingModelsLogged;
|
||
uint64_t UniqId = 0;
|
||
|
||
Model getModel(ResourceId id, std::vector<ResourceId>& used) {
|
||
auto iterModel = Models.find(id);
|
||
if(iterModel == Models.end()) {
|
||
// Нет такой модели, ну и хрен с ним
|
||
if(MissingModelsLogged.insert(id).second) {
|
||
LOG.warn() << "Missing model id=" << id;
|
||
}
|
||
return {};
|
||
}
|
||
|
||
ModelObject& model = iterModel->second;
|
||
if(!model.Ready) {
|
||
std::vector<ResourceId> 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<Model> 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<ResourceId> localRelocate;
|
||
|
||
if(subModel.UniqueTextures) {
|
||
std::string extraKey = "#" + std::to_string(UniqId++);
|
||
for(std::string& key : subModel.TextureKeys) {
|
||
key += extraKey;
|
||
}
|
||
|
||
std::unordered_map<std::string, TexturePipeline> 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 {
|
||
public:
|
||
struct TextureUpdate {
|
||
AssetsTexture Id = 0;
|
||
Resource Res;
|
||
std::string Domain;
|
||
std::string Key;
|
||
};
|
||
|
||
TextureProvider(Vulkan* inst, VkDescriptorPool descPool)
|
||
: Inst(inst), DescPool(descPool)
|
||
{
|
||
assert(inst);
|
||
|
||
{
|
||
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(
|
||
Inst->Graphics.Device, &descriptorLayout, nullptr, &DescLayout));
|
||
}
|
||
|
||
{
|
||
VkDescriptorSetAllocateInfo ciAllocInfo =
|
||
{
|
||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||
.pNext = nullptr,
|
||
.descriptorPool = DescPool,
|
||
.descriptorSetCount = 1,
|
||
.pSetLayouts = &DescLayout
|
||
};
|
||
|
||
vkAssert(!vkAllocateDescriptorSets(Inst->Graphics.Device, &ciAllocInfo, &Descriptor));
|
||
}
|
||
|
||
{
|
||
TextureAtlas::Config cfg;
|
||
cfg.MaxTextureId = 1 << 18;
|
||
AtlasStaging = std::make_shared<SharedStagingBuffer>(
|
||
Inst->Graphics.Device,
|
||
Inst->Graphics.PhysicalDevice
|
||
);
|
||
Atlas = std::make_unique<PipelinedTextureAtlas>(
|
||
TextureAtlas(Inst->Graphics.Device, Inst->Graphics.PhysicalDevice, cfg, {}, AtlasStaging)
|
||
);
|
||
}
|
||
|
||
{
|
||
const VkFenceCreateInfo info = {
|
||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||
.pNext = nullptr,
|
||
.flags = 0
|
||
};
|
||
|
||
vkAssert(!vkCreateFence(Inst->Graphics.Device, &info, nullptr, &UpdateFence));
|
||
}
|
||
|
||
NeedsUpload = true;
|
||
}
|
||
|
||
~TextureProvider() {
|
||
if(UpdateFence)
|
||
vkDestroyFence(Inst->Graphics.Device, UpdateFence, nullptr);
|
||
|
||
if(DescLayout)
|
||
vkDestroyDescriptorSetLayout(Inst->Graphics.Device, DescLayout, nullptr);
|
||
}
|
||
|
||
VkDescriptorSetLayout getDescriptorLayout() const {
|
||
return DescLayout;
|
||
}
|
||
|
||
VkDescriptorSet getDescriptorSet() const {
|
||
return Descriptor;
|
||
}
|
||
|
||
uint32_t getTextureId(const TexturePipeline& pipe) {
|
||
std::lock_guard lock(Mutex);
|
||
bool animated = isAnimatedPipeline(pipe);
|
||
if(!animated) {
|
||
auto iter = PipelineToAtlas.find(pipe);
|
||
if(iter != PipelineToAtlas.end())
|
||
return iter->second;
|
||
}
|
||
|
||
::HashedPipeline hashed = makeHashedPipeline(pipe);
|
||
uint32_t atlasId = Atlas->getByPipeline(hashed);
|
||
|
||
uint32_t result = atlasId;
|
||
if(Atlas && result >= Atlas->maxTextureId()) {
|
||
LOG.warn() << "Atlas texture id overflow: " << atlasId;
|
||
result = Atlas->reservedOverflowId();
|
||
}
|
||
|
||
if(!animated)
|
||
PipelineToAtlas.emplace(pipe, result);
|
||
NeedsUpload = true;
|
||
return result;
|
||
}
|
||
|
||
uint32_t getAtlasMaxLayers() const {
|
||
std::lock_guard lock(Mutex);
|
||
return Atlas ? Atlas->maxLayers() : 0u;
|
||
}
|
||
|
||
uint32_t getAtlasLayerId(uint32_t layer) const {
|
||
std::lock_guard lock(Mutex);
|
||
if(!Atlas)
|
||
return TextureAtlas::kOverflowId;
|
||
if(layer >= Atlas->maxLayers())
|
||
return Atlas->reservedOverflowId();
|
||
return Atlas->reservedLayerId(layer);
|
||
}
|
||
|
||
void requestAtlasLayerCount(uint32_t layers) {
|
||
std::lock_guard lock(Mutex);
|
||
if(Atlas)
|
||
Atlas->requestLayerCount(layers);
|
||
}
|
||
|
||
// Применяет изменения, возвращая все затронутые модели
|
||
std::vector<AssetsTexture> onTexturesChanges(std::vector<TextureUpdate> newOrChanged, std::vector<AssetsTexture> lost) {
|
||
std::lock_guard lock(Mutex);
|
||
std::vector<AssetsTexture> result;
|
||
|
||
for(const auto& update : newOrChanged) {
|
||
const AssetsTexture key = update.Id;
|
||
const Resource& res = update.Res;
|
||
result.push_back(key);
|
||
|
||
iResource sres((const uint8_t*) res.data(), res.size());
|
||
iBinaryStream stream = sres.makeStream();
|
||
png::image<png::rgba_pixel> img(stream.Stream);
|
||
uint32_t width = img.get_width();
|
||
uint32_t height = img.get_height();
|
||
|
||
std::vector<uint32_t> pixels;
|
||
pixels.resize(width*height);
|
||
|
||
for(uint32_t y = 0; y < height; y++) {
|
||
const auto& row = img.get_pixbuf().operator [](y);
|
||
for(uint32_t x = 0; x < width; x++) {
|
||
const auto& px = row[x];
|
||
uint32_t rgba = (uint32_t(px.alpha) << 24)
|
||
| (uint32_t(px.red) << 16)
|
||
| (uint32_t(px.green) << 8)
|
||
| uint32_t(px.blue);
|
||
pixels[x + y * width] = rgba;
|
||
}
|
||
}
|
||
|
||
Atlas->updateTexture(key, StoredTexture(
|
||
static_cast<uint16_t>(width),
|
||
static_cast<uint16_t>(height),
|
||
std::move(pixels)
|
||
));
|
||
|
||
if(auto anim = getDefaultAnimation(update.Key, width, height)) {
|
||
AnimatedSources[key] = *anim;
|
||
} else {
|
||
AnimatedSources.erase(key);
|
||
}
|
||
|
||
NeedsUpload = true;
|
||
}
|
||
|
||
for(AssetsTexture key : lost) {
|
||
result.push_back(key);
|
||
Atlas->freeTexture(key);
|
||
AnimatedSources.erase(key);
|
||
NeedsUpload = true;
|
||
}
|
||
|
||
std::sort(result.begin(), result.end());
|
||
auto eraseIter = std::unique(result.begin(), result.end());
|
||
result.erase(eraseIter, result.end());
|
||
|
||
return result;
|
||
}
|
||
|
||
void update(double timeSeconds) {
|
||
std::lock_guard lock(Mutex);
|
||
if(!Atlas)
|
||
return;
|
||
|
||
if(Atlas->updateAnimatedPipelines(timeSeconds))
|
||
NeedsUpload = true;
|
||
|
||
if(!NeedsUpload)
|
||
return;
|
||
|
||
Atlas->flushNewPipelines();
|
||
|
||
VkCommandBufferAllocateInfo allocInfo {
|
||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||
nullptr,
|
||
Inst->Graphics.Pool,
|
||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||
1
|
||
};
|
||
|
||
VkCommandBuffer commandBuffer;
|
||
vkAssert(!vkAllocateCommandBuffers(Inst->Graphics.Device, &allocInfo, &commandBuffer));
|
||
|
||
VkCommandBufferBeginInfo beginInfo {
|
||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||
nullptr,
|
||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||
nullptr
|
||
};
|
||
|
||
vkAssert(!vkBeginCommandBuffer(commandBuffer, &beginInfo));
|
||
|
||
TextureAtlas::DescriptorOut desc = Atlas->flushUploadsAndBarriers(commandBuffer);
|
||
|
||
vkAssert(!vkEndCommandBuffer(commandBuffer));
|
||
|
||
VkSubmitInfo submitInfo {
|
||
VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||
nullptr,
|
||
0, nullptr,
|
||
nullptr,
|
||
1,
|
||
&commandBuffer,
|
||
0,
|
||
nullptr
|
||
};
|
||
|
||
{
|
||
auto lockQueue = Inst->Graphics.DeviceQueueGraphic.lock();
|
||
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submitInfo, UpdateFence));
|
||
}
|
||
|
||
vkAssert(!vkWaitForFences(Inst->Graphics.Device, 1, &UpdateFence, VK_TRUE, UINT64_MAX));
|
||
vkAssert(!vkResetFences(Inst->Graphics.Device, 1, &UpdateFence));
|
||
|
||
vkFreeCommandBuffers(Inst->Graphics.Device, Inst->Graphics.Pool, 1, &commandBuffer);
|
||
|
||
Atlas->notifyGpuFinished();
|
||
updateDescriptor(desc);
|
||
|
||
NeedsUpload = false;
|
||
}
|
||
|
||
private:
|
||
struct AnimatedSource {
|
||
uint16_t FrameW = 0;
|
||
uint16_t FrameH = 0;
|
||
uint16_t FrameCount = 0;
|
||
uint16_t FpsQ = 0;
|
||
uint16_t Flags = 0;
|
||
};
|
||
|
||
static std::optional<AnimatedSource> getDefaultAnimation(std::string_view key, uint32_t width, uint32_t height) {
|
||
if(auto slash = key.find_last_of('/'); slash != std::string_view::npos)
|
||
key = key.substr(slash + 1);
|
||
|
||
if(key == "fire_0.png") {
|
||
AnimatedSource anim;
|
||
anim.FrameW = static_cast<uint16_t>(width);
|
||
anim.FrameH = static_cast<uint16_t>(width);
|
||
anim.FrameCount = static_cast<uint16_t>(width ? height / width : 0);
|
||
anim.FpsQ = static_cast<uint16_t>(12 * 256);
|
||
anim.Flags = 0;
|
||
return anim;
|
||
}
|
||
|
||
if(key == "lava_still.png") {
|
||
AnimatedSource anim;
|
||
anim.FrameW = static_cast<uint16_t>(width);
|
||
anim.FrameH = static_cast<uint16_t>(width);
|
||
anim.FrameCount = static_cast<uint16_t>(width ? height / width : 0);
|
||
anim.FpsQ = static_cast<uint16_t>(8 * 256);
|
||
anim.Flags = 0;
|
||
return anim;
|
||
}
|
||
|
||
if(key == "water_still.png") {
|
||
AnimatedSource anim;
|
||
anim.FrameW = static_cast<uint16_t>(width);
|
||
anim.FrameH = static_cast<uint16_t>(width);
|
||
anim.FrameCount = static_cast<uint16_t>(width ? height / width : 0);
|
||
anim.FpsQ = static_cast<uint16_t>(8 * 256);
|
||
anim.Flags = TexturePipelineProgram::AnimSmooth;
|
||
return anim;
|
||
}
|
||
|
||
return std::nullopt;
|
||
}
|
||
|
||
bool isAnimatedPipeline(const TexturePipeline& pipe) const {
|
||
if(!pipe.Pipeline.empty())
|
||
return false;
|
||
if(pipe.BinTextures.size() != 1)
|
||
return false;
|
||
return AnimatedSources.contains(pipe.BinTextures.front());
|
||
}
|
||
|
||
::HashedPipeline makeHashedPipeline(const TexturePipeline& pipe) const {
|
||
::Pipeline pipeline;
|
||
|
||
if(!pipe.Pipeline.empty()) {
|
||
const auto* bytes = reinterpret_cast<const ::detail::Word*>(pipe.Pipeline.data());
|
||
pipeline._Pipeline.assign(bytes, bytes + pipe.Pipeline.size());
|
||
}
|
||
|
||
if(pipeline._Pipeline.empty()) {
|
||
if(!pipe.BinTextures.empty()) {
|
||
AssetsTexture texId = pipe.BinTextures.front();
|
||
auto animIter = AnimatedSources.find(texId);
|
||
if(animIter != AnimatedSources.end()) {
|
||
const auto& anim = animIter->second;
|
||
pipeline._Pipeline.clear();
|
||
pipeline._Pipeline.reserve(1 + 1 + 3 + 2 + 2 + 2 + 2 + 1 + 1);
|
||
auto emit16 = [&](uint16_t v) {
|
||
pipeline._Pipeline.push_back(static_cast<::detail::Word>(v & 0xFFu));
|
||
pipeline._Pipeline.push_back(static_cast<::detail::Word>((v >> 8) & 0xFFu));
|
||
};
|
||
pipeline._Pipeline.push_back(static_cast<::detail::Word>(::detail::Op16::Base_Anim));
|
||
pipeline._Pipeline.push_back(static_cast<::detail::Word>(::detail::SrcKind16::TexId));
|
||
pipeline._Pipeline.push_back(static_cast<::detail::Word>(texId & 0xFFu));
|
||
pipeline._Pipeline.push_back(static_cast<::detail::Word>((texId >> 8) & 0xFFu));
|
||
pipeline._Pipeline.push_back(static_cast<::detail::Word>((texId >> 16) & 0xFFu));
|
||
emit16(anim.FrameW);
|
||
emit16(anim.FrameH);
|
||
emit16(anim.FrameCount);
|
||
emit16(anim.FpsQ);
|
||
pipeline._Pipeline.push_back(static_cast<::detail::Word>(anim.Flags & 0xFFu));
|
||
pipeline._Pipeline.push_back(static_cast<::detail::Word>(::detail::Op16::End));
|
||
} else {
|
||
pipeline = ::Pipeline(texId);
|
||
}
|
||
}
|
||
}
|
||
|
||
return ::HashedPipeline(pipeline);
|
||
}
|
||
|
||
void updateDescriptor(const TextureAtlas::DescriptorOut& desc) {
|
||
VkWriteDescriptorSet writes[2] = {};
|
||
|
||
writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||
writes[0].dstSet = Descriptor;
|
||
writes[0].dstBinding = 0;
|
||
writes[0].descriptorCount = 1;
|
||
writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||
writes[0].pImageInfo = &desc.ImageInfo;
|
||
|
||
writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||
writes[1].dstSet = Descriptor;
|
||
writes[1].dstBinding = 1;
|
||
writes[1].descriptorCount = 1;
|
||
writes[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||
writes[1].pBufferInfo = &desc.EntriesInfo;
|
||
|
||
vkUpdateDescriptorSets(Inst->Graphics.Device, 2, writes, 0, nullptr);
|
||
}
|
||
|
||
private:
|
||
Vulkan* Inst = nullptr;
|
||
VkDescriptorPool DescPool = VK_NULL_HANDLE;
|
||
VkDescriptorSetLayout DescLayout = VK_NULL_HANDLE;
|
||
VkDescriptorSet Descriptor = VK_NULL_HANDLE;
|
||
VkFence UpdateFence = VK_NULL_HANDLE;
|
||
|
||
std::shared_ptr<SharedStagingBuffer> AtlasStaging;
|
||
std::unique_ptr<PipelinedTextureAtlas> Atlas;
|
||
std::unordered_map<TexturePipeline, uint32_t> PipelineToAtlas;
|
||
std::unordered_map<AssetsTexture, AnimatedSource> AnimatedSources;
|
||
|
||
bool NeedsUpload = false;
|
||
Logger LOG = "Client>TextureProvider";
|
||
mutable std::mutex Mutex;
|
||
};
|
||
|
||
|
||
/*
|
||
Хранит информацию о моделях при различных состояниях нод
|
||
*/
|
||
class NodestateProvider {
|
||
public:
|
||
NodestateProvider(ModelProvider& mp, TextureProvider& tp)
|
||
: MP(mp), TP(tp)
|
||
{}
|
||
|
||
// Применяет изменения, возвращает изменённые описания состояний
|
||
std::vector<AssetsNodestate> onNodestateChanges(std::vector<std::tuple<AssetsNodestate, Resource, const std::vector<uint8_t>*>> newOrChanged, std::vector<AssetsNodestate> lost, std::vector<AssetsModel> changedModels) {
|
||
std::vector<AssetsNodestate> 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, deps] : 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 формате
|
||
} else {
|
||
type = "InternalBinaryLegacy";
|
||
// Старый двоичный формат без заголовка "bn"
|
||
std::u8string patched;
|
||
patched.reserve(data.size() + 2);
|
||
patched.push_back(u8'b');
|
||
patched.push_back(u8'n');
|
||
patched.append(data);
|
||
nodestate = PreparedNodeState(patched);
|
||
}
|
||
} catch(const std::exception& exc) {
|
||
LOG.warn() << "Не удалось распарсить nodestate " << type << ":\n\t" << exc.what();
|
||
continue;
|
||
}
|
||
|
||
if(deps && !deps->empty()) {
|
||
auto header = AssetsManager::parseHeader(EnumAssets::Model, *deps);
|
||
if(header && header->Type == EnumAssets::Nodestate) {
|
||
nodestate.LocalToModel.assign(header->ModelDeps.begin(), header->ModelDeps.end());
|
||
}
|
||
}
|
||
|
||
Nodestates.insert_or_assign(key, std::move(nodestate));
|
||
if(key < 64) {
|
||
auto iter = Nodestates.find(key);
|
||
if(iter != Nodestates.end()) {
|
||
LOG.debug() << "Nodestate loaded id=" << key
|
||
<< " routes=" << iter->second.Routes.size()
|
||
<< " models=" << iter->second.LocalToModel.size();
|
||
}
|
||
}
|
||
}
|
||
|
||
if(!changedModels.empty()) {
|
||
std::unordered_set<AssetsModel> changed;
|
||
changed.reserve(changedModels.size());
|
||
for(AssetsModel modelId : changedModels)
|
||
changed.insert(modelId);
|
||
|
||
for(const auto& [nodestateId, nodestate] : Nodestates) {
|
||
for(AssetsModel modelId : nodestate.LocalToModel) {
|
||
if(changed.contains(modelId)) {
|
||
result.push_back(nodestateId);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
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<std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>> getModelsForNode(AssetsNodestate id, const std::vector<NodeStateInfo>& statesInfo, const std::unordered_map<std::string, int32_t>& states) {
|
||
auto iterNodestate = Nodestates.find(id);
|
||
if(iterNodestate == Nodestates.end()) {
|
||
if(MissingNodestateLogged.insert(id).second) {
|
||
LOG.warn() << "Missing nodestate id=" << id;
|
||
}
|
||
return {};
|
||
}
|
||
|
||
PreparedNodeState& nodestate = iterNodestate->second;
|
||
std::vector<uint16_t> routes = nodestate.getModelsForState(statesInfo, states);
|
||
if(routes.empty()) {
|
||
int32_t metaValue = 0;
|
||
if(auto iterMeta = states.find("meta"); iterMeta != states.end())
|
||
metaValue = iterMeta->second;
|
||
uint64_t key = (uint64_t(id) << 32) | (uint32_t(metaValue) & 0xffffffffu);
|
||
if(EmptyRouteLogged.insert(key).second) {
|
||
LOG.warn() << "No nodestate routes id=" << id
|
||
<< " meta=" << metaValue
|
||
<< " total_routes=" << nodestate.Routes.size();
|
||
}
|
||
}
|
||
std::vector<std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>> result;
|
||
|
||
std::unordered_map<TexturePipeline, uint32_t> pipelineResolveCache;
|
||
|
||
auto appendModel = [&](AssetsModel modelId, const std::vector<Transformation>& transforms, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>& out) {
|
||
ModelProvider::Model model = MP.getModel(modelId);
|
||
Transformations trf{transforms};
|
||
|
||
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 + 16.0f) * 2.0f + 224.0f;
|
||
vert.FY = (v.Pos.y + 16.0f) * 2.0f + 224.0f;
|
||
vert.FZ = (v.Pos.z + 16.0f) * 2.0f + 224.0f;
|
||
|
||
vert.TU = std::clamp<int32_t>(v.UV.x * (1 << 11), 0, (1 << 16) - 1);
|
||
vert.TV = std::clamp<int32_t>(v.UV.y * (1 << 11), 0, (1 << 16) - 1);
|
||
|
||
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);
|
||
}
|
||
}
|
||
};
|
||
|
||
auto resolveModelId = [&](uint16_t localId, AssetsModel& outId) -> bool {
|
||
if(localId >= nodestate.LocalToModel.size())
|
||
return false;
|
||
outId = nodestate.LocalToModel[localId];
|
||
return true;
|
||
};
|
||
|
||
for(uint16_t routeId : routes) {
|
||
if(routeId >= nodestate.Routes.size())
|
||
continue;
|
||
|
||
std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>> routeModels;
|
||
const auto& route = nodestate.Routes[routeId];
|
||
for(const auto& [w, m] : route.second) {
|
||
std::unordered_map<EnumFace, std::vector<NodeVertexStatic>> out;
|
||
|
||
if(const PreparedNodeState::Model* ptr = std::get_if<PreparedNodeState::Model>(&m)) {
|
||
AssetsModel modelId;
|
||
if(resolveModelId(ptr->Id, modelId))
|
||
appendModel(modelId, ptr->Transforms, out);
|
||
} else if(const PreparedNodeState::VectorModel* ptr = std::get_if<PreparedNodeState::VectorModel>(&m)) {
|
||
for(const auto& sub : ptr->Models) {
|
||
AssetsModel modelId;
|
||
if(!resolveModelId(sub.Id, modelId))
|
||
continue;
|
||
|
||
std::vector<Transformation> transforms = sub.Transforms;
|
||
transforms.insert(transforms.end(), ptr->Transforms.begin(), ptr->Transforms.end());
|
||
appendModel(modelId, transforms, out);
|
||
}
|
||
}
|
||
|
||
/// TODO: uvlock
|
||
routeModels.emplace_back(w, std::move(out));
|
||
}
|
||
|
||
result.push_back(std::move(routeModels));
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
uint32_t getTextureId(AssetsTexture texId) {
|
||
if(texId == 0)
|
||
return 0;
|
||
|
||
TexturePipeline pipe;
|
||
pipe.BinTextures.push_back(texId);
|
||
return TP.getTextureId(pipe);
|
||
}
|
||
|
||
bool hasNodestate(AssetsNodestate id) const {
|
||
return Nodestates.contains(id);
|
||
}
|
||
|
||
private:
|
||
Logger LOG = "Client>NodestateProvider";
|
||
ModelProvider& MP;
|
||
TextureProvider& TP;
|
||
std::unordered_map<AssetsNodestate, PreparedNodeState> Nodestates;
|
||
std::unordered_set<AssetsNodestate> MissingNodestateLogged;
|
||
std::unordered_set<uint64_t> EmptyRouteLogged;
|
||
};
|
||
|
||
/*
|
||
Объект, занимающийся генерацией меша на основе нод и вокселей
|
||
Требует доступ к профилям в ServerSession (ServerSession должен быть заблокирован только на чтение)
|
||
Также доступ к идентификаторам текстур в VulkanRenderSession и моделей по состояниям
|
||
Очередь чанков, ожидающих перерисовку. Возвращает готовые вершинные данные.
|
||
*/
|
||
struct ChunkMeshGenerator {
|
||
// Данные рендера чанка
|
||
struct ChunkObj_t {
|
||
// Идентификатор запроса (на случай если запрос просрочился и чанк уже был удалён)
|
||
uint32_t RequestId = 0;
|
||
// Мир
|
||
WorldId_t WId;
|
||
// Позиция чанка в мире
|
||
Pos::GlobalChunk Pos;
|
||
// Сортированный список уникальных значений
|
||
std::vector<DefVoxelId> VoxelDefines;
|
||
// Вершины
|
||
std::vector<VoxelVertexPoint> VoxelVertexs;
|
||
// Ноды
|
||
std::vector<DefNodeId> NodeDefines;
|
||
// Вершины нод
|
||
std::vector<NodeVertexStatic> NodeVertexs;
|
||
// Индексы
|
||
std::variant<std::vector<uint16_t>, std::vector<uint32_t>> NodeIndexes;
|
||
};
|
||
|
||
// Очередь чанков на перерисовку
|
||
TOS::SpinlockObject<std::queue<std::tuple<WorldId_t, Pos::GlobalChunk, uint32_t>>> Input;
|
||
// Выход
|
||
TOS::SpinlockObject<std::vector<ChunkObj_t>> Output;
|
||
|
||
public:
|
||
ChunkMeshGenerator(IServerSession* serverSession)
|
||
: SS(serverSession)
|
||
{
|
||
assert(serverSession);
|
||
}
|
||
|
||
~ChunkMeshGenerator() {
|
||
assert(Threads.empty());
|
||
}
|
||
|
||
// Меняет количество обрабатывающих потоков
|
||
void changeThreadsCount(uint8_t threads);
|
||
|
||
void setNodestateProvider(NodestateProvider* provider) {
|
||
NSP = provider;
|
||
}
|
||
|
||
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;
|
||
NodestateProvider* NSP = nullptr;
|
||
std::vector<std::thread> Threads;
|
||
|
||
void run(uint8_t id);
|
||
};
|
||
|
||
/*
|
||
Модуль обрабатывает рендер чанков
|
||
*/
|
||
class ChunkPreparator {
|
||
public:
|
||
struct TickSyncData {
|
||
// Профили на которые повлияли изменения, по ним нужно пересчитать чанки
|
||
std::vector<DefVoxelId> ChangedVoxels;
|
||
std::vector<DefNodeId> ChangedNodes;
|
||
|
||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalChunk>> ChangedChunks;
|
||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> 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);
|
||
}
|
||
|
||
~ChunkPreparator() {
|
||
CMG.changeThreadsCount(0);
|
||
}
|
||
|
||
|
||
void prepareTickSync() {
|
||
CMG.prepareTickSync();
|
||
}
|
||
|
||
void pushStageTickSync() {
|
||
CMG.pushStageTickSync();
|
||
}
|
||
|
||
void setNodestateProvider(NodestateProvider* provider) {
|
||
CMG.setNodestateProvider(provider);
|
||
}
|
||
|
||
void tickSync(const TickSyncData& data);
|
||
void notifyGpuFinished() {
|
||
resetVertexStaging();
|
||
VertexPool_Voxels.notifyGpuFinished();
|
||
VertexPool_Nodes.notifyGpuFinished();
|
||
IndexPool_Nodes_16.notifyGpuFinished();
|
||
IndexPool_Nodes_32.notifyGpuFinished();
|
||
}
|
||
void flushUploadsAndBarriers(VkCommandBuffer commandBuffer) {
|
||
VertexPool_Voxels.flushUploadsAndBarriers(commandBuffer);
|
||
VertexPool_Nodes.flushUploadsAndBarriers(commandBuffer);
|
||
IndexPool_Nodes_16.flushUploadsAndBarriers(commandBuffer);
|
||
IndexPool_Nodes_32.flushUploadsAndBarriers(commandBuffer);
|
||
}
|
||
|
||
// Готовность кадров определяет когда можно удалять ненужные ресурсы, которые ещё используются в рендере
|
||
void pushFrame();
|
||
|
||
// Выдаёт буферы для рендера в порядке от ближнего к дальнему. distance - радиус в регионах
|
||
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>>
|
||
> 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;
|
||
|
||
// Генератор вершин чанков
|
||
ChunkMeshGenerator CMG;
|
||
|
||
// Буферы для хранения вершин
|
||
VertexPool<VoxelVertexPoint> VertexPool_Voxels;
|
||
VertexPool<NodeVertexStatic> VertexPool_Nodes;
|
||
IndexPool<uint16_t> IndexPool_Nodes_16;
|
||
IndexPool<uint32_t> IndexPool_Nodes_32;
|
||
|
||
struct ChunkObj_t {
|
||
std::vector<DefVoxelId> Voxels;
|
||
VertexPool<VoxelVertexPoint>::Pointer VoxelPointer;
|
||
std::vector<DefNodeId> Nodes;
|
||
VertexPool<NodeVertexStatic>::Pointer NodePointer;
|
||
std::variant<IndexPool<uint16_t>::Pointer, IndexPool<uint32_t>::Pointer> NodeIndexes;
|
||
};
|
||
|
||
// Склад указателей на вершины чанков
|
||
std::unordered_map<WorldId_t,
|
||
std::unordered_map<Pos::GlobalRegion, std::array<ChunkObj_t, 4*4*4>>
|
||
> ChunksMesh;
|
||
|
||
uint8_t FrameRoulette = 0;
|
||
// Вершины, ожидающие удаления по прошествию какого-то количества кадров
|
||
std::vector<VertexPool<VoxelVertexPoint>::Pointer> VPV_ToFree[FRAME_COUNT_RESOURCE_LATENCY];
|
||
std::vector<std::tuple<
|
||
VertexPool<NodeVertexStatic>::Pointer,
|
||
std::variant<IndexPool<uint16_t>::Pointer, IndexPool<uint32_t>::Pointer>
|
||
>> VPN_ToFree[FRAME_COUNT_RESOURCE_LATENCY];
|
||
|
||
// Следующий идентификатор запроса
|
||
uint32_t NextRequest = 0;
|
||
// Список ожидаемых чанков. Если регион был потерян, следующая его запись получит
|
||
// новый идентификатор (при отсутствии записи готовые чанки с MCMG будут проигнорированы)
|
||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, uint32_t>> 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;
|
||
std::unique_ptr<TextureProvider> TP;
|
||
std::unique_ptr<NodestateProvider> NSP;
|
||
|
||
AtlasImage LightDummy;
|
||
Buffer TestQuad;
|
||
std::optional<Buffer> TestVoxel;
|
||
std::optional<Buffer> AtlasLayersPreview;
|
||
uint32_t AtlasLayersPreviewCount = 0;
|
||
uint32_t EntityTextureId = 0;
|
||
bool EntityTextureReady = false;
|
||
|
||
VkDescriptorPool DescriptorPool = VK_NULL_HANDLE;
|
||
|
||
/*
|
||
.binding = 0,
|
||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, Текстурный атлас
|
||
.binding = 1,
|
||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, Данные к атласу
|
||
*/
|
||
/*
|
||
.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<ShaderModule> VoxelShaderVertex, VoxelShaderGeometry, VoxelShaderFragmentOpaque, VoxelShaderFragmentTransparent;
|
||
VkPipeline
|
||
VoxelOpaquePipeline = VK_NULL_HANDLE, // Альфа канал может быть либо 255, либо 0
|
||
VoxelTransparentPipeline = VK_NULL_HANDLE; // Допускается полупрозрачность и смешивание
|
||
|
||
// Для отрисовки статичных, не анимированных нод
|
||
std::shared_ptr<ShaderModule> NodeShaderVertex, NodeShaderGeometry, NodeShaderFragmentOpaque, NodeShaderFragmentTransparent;
|
||
VkPipeline
|
||
NodeStaticOpaquePipeline = VK_NULL_HANDLE,
|
||
NodeStaticTransparentPipeline = VK_NULL_HANDLE;
|
||
|
||
|
||
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(double timeSeconds);
|
||
void onGpuFinished();
|
||
void drawWorld(GlobalTime gTime, float dTime, VkCommandBuffer drawCmd);
|
||
void pushStage(EnumRenderStage stage);
|
||
|
||
static std::vector<VoxelVertexPoint> generateMeshForVoxelChunks(const std::vector<VoxelCube>& cubes);
|
||
|
||
private:
|
||
void updateTestQuadTexture(uint32_t texId);
|
||
void ensureEntityTexture();
|
||
void ensureAtlasLayerPreview();
|
||
void updateDescriptor_VoxelsLight();
|
||
void updateDescriptor_ChunksLight();
|
||
};
|
||
|
||
}
|