356 lines
14 KiB
C++
356 lines
14 KiB
C++
#pragma once
|
||
|
||
#include "Client/Abstract.hpp"
|
||
#include "Common/Abstract.hpp"
|
||
#include <Client/Vulkan/Vulkan.hpp>
|
||
#include <algorithm>
|
||
#include <condition_variable>
|
||
#include <memory>
|
||
#include <mutex>
|
||
#include <optional>
|
||
#include <queue>
|
||
#include <thread>
|
||
#include <unordered_map>
|
||
#include <unordered_set>
|
||
#include <vulkan/vulkan_core.h>
|
||
#include "Abstract.hpp"
|
||
#include "TOSLib.hpp"
|
||
#include "VertexPool.hpp"
|
||
#include "glm/fwd.hpp"
|
||
#include "../FrustumCull.h"
|
||
#include "glm/geometric.hpp"
|
||
|
||
/*
|
||
У движка есть один текстурный атлас VK_IMAGE_VIEW_TYPE_2D_ARRAY(RGBA_UINT) и к нему Storage с инфой о положении текстур
|
||
Это общий для всех VkDescriptorSetLayout и VkDescriptorSet
|
||
|
||
Для отрисовки вокселей используется карта освещения VK_IMAGE_VIEW_TYPE_2D_ARRAY(RGB_UINT), разделённая по прямоугольным плоскостям
|
||
Разрешение у этих карт 1м/16
|
||
|
||
-- Для всего остального 3д карты освещённости по чанкам в разрешении 1м. 16^3 = 4096 текселей --
|
||
*/
|
||
|
||
/*
|
||
Самые критичные случаи
|
||
Для вокселей это чередование в шахматном порядке присутствия и отсутствия вокселей
|
||
Это 8'388'608 вокселя в чанке, общей площадью на картах освещения (256^3/2 *4 *6) 201'326'592 текселей или текстура размером 16к^2
|
||
Понадобится переиспользование одинаковых участков освещённости
|
||
Если чанк заполнен слоями вокселей в шахматном порядке по вертикале ((257^2+257*2*4)*128) 8'717'440 текселей или текстура размером 4к^2
|
||
|
||
*/
|
||
|
||
|
||
namespace LV::Client::VK {
|
||
|
||
struct WorldPCO {
|
||
glm::mat4 ProjView, Model;
|
||
};
|
||
|
||
static_assert(sizeof(WorldPCO) == 128);
|
||
|
||
/*
|
||
Объект, занимающийся генерацией меша на основе нод и вокселей
|
||
Требует доступ к профилям в ServerSession (ServerSession должен быть заблокирован только на чтение)
|
||
Также доступ к идентификаторам текстур в VulkanRenderSession и моделей по состояниям
|
||
Очередь чанков, ожидающих перерисовку. Возвращает готовые вершинные данные.
|
||
*/
|
||
struct ChunkMeshGenerator {
|
||
// Данные рендера чанка
|
||
struct ChunkObj_t {
|
||
// Идентификатор запроса (на случай если запрос просрочился и чанк уже был удалён)
|
||
uint32_t RequestId = 0;
|
||
// Мир
|
||
WorldId_t WId;
|
||
// Позиция чанка в мире
|
||
Pos::GlobalChunk Pos;
|
||
// Сортированный список уникальных значений
|
||
std::vector<DefVoxelId> VoxelDefines;
|
||
// Вершины
|
||
std::vector<VoxelVertexPoint> VoxelVertexs;
|
||
// Ноды
|
||
std::vector<DefNodeId> NodeDefines;
|
||
// Вершины нод
|
||
std::vector<NodeVertexStatic> NodeVertexs;
|
||
// Индексы
|
||
std::variant<std::vector<uint16_t>, std::vector<uint32_t>> NodeIndicies;
|
||
};
|
||
|
||
// Очередь чанков на перерисовку
|
||
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) {
|
||
Sync.NeedShutdown = true;
|
||
std::unique_lock lock(Sync.Mutex);
|
||
Sync.CV_CountInRun.wait(lock, [&]() { return Sync.CountInRun == 0; });
|
||
|
||
for(std::thread& thr : Threads)
|
||
thr.join();
|
||
|
||
Sync.NeedShutdown = false;
|
||
|
||
Threads.resize(threads);
|
||
for(int iter = 0; iter < threads; iter++)
|
||
Threads[iter] = std::thread(&ChunkMeshGenerator::run, this, iter);
|
||
|
||
Sync.CV_CountInRun.wait(lock, [&]() { return Sync.CountInRun == Threads.size() || Sync.NeedShutdown; });
|
||
|
||
if(Sync.NeedShutdown)
|
||
MAKE_ERROR("Ошибка обработчика вершин чанков");
|
||
}
|
||
|
||
void prepareTickSync() {
|
||
Sync.Stop = true;
|
||
}
|
||
|
||
void pushStageTickSync() {
|
||
std::unique_lock lock(Sync.Mutex);
|
||
Sync.CV_CountInRun.wait(lock, [&]() { return Sync.CountInRun == 0; });
|
||
}
|
||
|
||
void endTickSync() {
|
||
Sync.Stop = false;
|
||
Sync.CV_CountInRun.notify_all();
|
||
}
|
||
|
||
|
||
private:
|
||
struct {
|
||
std::mutex Mutex;
|
||
// Если нужно остановить пул потоков, вызывается NeedShutdown
|
||
volatile bool NeedShutdown = false, Stop = false;
|
||
volatile uint8_t CountInRun = 0;
|
||
std::condition_variable CV_CountInRun;
|
||
} Sync;
|
||
|
||
IServerSession *SS;
|
||
|
||
// Потоки
|
||
std::vector<std::thread> Threads;
|
||
|
||
void run(uint8_t id);
|
||
};
|
||
|
||
/*
|
||
Модуль обрабатывает рендер чанков
|
||
*/
|
||
class ModuleChunkPreparator {
|
||
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:
|
||
ModuleChunkPreparator(Vulkan* vkInst, IServerSession* serverSession)
|
||
: VkInst(vkInst),
|
||
CMG(serverSession),
|
||
VertexPool_Voxels(vkInst),
|
||
VertexPool_Nodes(vkInst)
|
||
{
|
||
assert(vkInst);
|
||
assert(serverSession);
|
||
|
||
CMG.changeThreadsCount(2);
|
||
|
||
const VkCommandPoolCreateInfo infoCmdPool =
|
||
{
|
||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||
.pNext = nullptr,
|
||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||
.queueFamilyIndex = VkInst->getSettings().QueueGraphics
|
||
};
|
||
|
||
vkAssert(!vkCreateCommandPool(VkInst->Graphics.Device, &infoCmdPool, nullptr, &CMDPool));
|
||
}
|
||
|
||
~ModuleChunkPreparator() {
|
||
CMG.changeThreadsCount(0);
|
||
|
||
if(CMDPool)
|
||
vkDestroyCommandPool(VkInst->Graphics.Device, CMDPool, nullptr);
|
||
}
|
||
|
||
|
||
void prepareTickSync() {
|
||
CMG.prepareTickSync();
|
||
}
|
||
|
||
void pushStageTickSync() {
|
||
CMG.pushStageTickSync();
|
||
}
|
||
|
||
void tickSync(const TickSyncData& data) {
|
||
// Обработать изменения в чанках
|
||
// Пересчёт соседних чанков
|
||
// Проверить необходимость пересчёта чанков при изменении профилей
|
||
|
||
CMG.endTickSync();
|
||
}
|
||
|
||
// Готовность кадров определяет когда можно удалять ненужные ресурсы, которые ещё используются в рендере
|
||
void pushFrame() {
|
||
|
||
}
|
||
|
||
// Выдаёт буферы для рендера в порядке от ближнего к дальнему. distance - радиус в регионах
|
||
std::pair<
|
||
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>>,
|
||
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, 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<VoxelVertexPoint> VertexPool_Voxels;
|
||
VertexPool<NodeVertexStatic> VertexPool_Nodes;
|
||
|
||
struct ChunkObj_t {
|
||
std::vector<DefVoxelId> Voxels;
|
||
VertexPool<VoxelVertexPoint>::Pointer VoxelPointer;
|
||
std::vector<DefNodeId> Nodes;
|
||
VertexPool<NodeVertexStatic>::Pointer NodePointer;
|
||
};
|
||
|
||
// Склад указателей на вершины чанков
|
||
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<VertexPool<NodeVertexStatic>::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;
|
||
|
||
ModuleChunkPreparator MCP;
|
||
|
||
AtlasImage MainTest, LightDummy;
|
||
Buffer TestQuad;
|
||
std::optional<Buffer> 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<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;
|
||
|
||
std::map<AssetsTexture, uint16_t> 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<VoxelVertexPoint> generateMeshForVoxelChunks(const std::vector<VoxelCube>& cubes);
|
||
|
||
private:
|
||
void updateDescriptor_MainAtlas();
|
||
void updateDescriptor_VoxelsLight();
|
||
void updateDescriptor_ChunksLight();
|
||
};
|
||
|
||
} |