346 lines
16 KiB
C++
346 lines
16 KiB
C++
#pragma once
|
||
|
||
#include "Client/Abstract.hpp"
|
||
#include "Common/Abstract.hpp"
|
||
#include <Client/Vulkan/Vulkan.hpp>
|
||
#include <memory>
|
||
#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"
|
||
|
||
/*
|
||
У движка есть один текстурный атлас 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);
|
||
|
||
/*
|
||
Модуль, рисующий то, что предоставляет IServerSession
|
||
*/
|
||
class VulkanRenderSession : public IRenderSession, public IVulkanDependent {
|
||
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;
|
||
|
||
/*
|
||
Поток, занимающийся генерацией меша на основе нод и вокселей
|
||
Требует доступ к профилям в ServerSession (ServerSession должен быть заблокирован только на чтение)
|
||
Также доступ к идентификаторам текстур в VulkanRenderSession (только на чтение)
|
||
Должен оповещаться об изменениях профилей и событий чанков
|
||
Удалённые мешы хранятся в памяти N количество кадров
|
||
*/
|
||
struct ThreadVertexObj_t {
|
||
// Сессия будет выдана позже
|
||
// Предполагается что события будут только после того как сессия будет установлена,
|
||
// соответственно никто не попытаеся сюда обратится без событий
|
||
IServerSession *SSession = nullptr;
|
||
Vulkan *VkInst;
|
||
|
||
// Здесь не хватает стадии работы с текстурами
|
||
struct StateObj_t {
|
||
EnumRenderStage Stage = EnumRenderStage::Render;
|
||
volatile bool ChunkMesh_IsUse = false, ServerSession_InUse = false;
|
||
};
|
||
|
||
SpinlockObject<StateObj_t> State;
|
||
|
||
struct ChunkObj_t {
|
||
// Сортированный список уникальных значений
|
||
std::vector<DefVoxelId_t> VoxelDefines;
|
||
VertexPool<VoxelVertexPoint>::Pointer VoxelPointer;
|
||
std::vector<DefNodeId_t> NodeDefines;
|
||
VertexPool<NodeVertexStatic>::Pointer NodePointer;
|
||
};
|
||
|
||
ThreadVertexObj_t(Vulkan* vkInst)
|
||
: VkInst(vkInst),
|
||
VertexPool_Voxels(vkInst),
|
||
VertexPool_Nodes(vkInst),
|
||
Thread(&ThreadVertexObj_t::run, this)
|
||
{}
|
||
|
||
~ThreadVertexObj_t() {
|
||
State.lock()->Stage = EnumRenderStage::Shutdown;
|
||
Thread.join();
|
||
}
|
||
|
||
// Сюда входят добавленные/изменённые/удалённые определения нод и вокселей
|
||
// Чтобы перерисовать чанки, связанные с ними
|
||
void onContentDefinesChange(const std::vector<DefVoxelId_t>& voxels, const std::vector<DefNodeId_t>& nodes) {
|
||
ChangedDefines_Voxel.insert(ChangedDefines_Voxel.end(), voxels.begin(), voxels.end());
|
||
ChangedDefines_Node.insert(ChangedDefines_Node.end(), nodes.begin(), nodes.end());
|
||
}
|
||
|
||
// Изменение/удаление чанков
|
||
void onContentChunkChange(const std::unordered_map<WorldId_t, std::vector<Pos::GlobalChunk>>& chunkChanges, const std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>>& regionRemove) {
|
||
for(auto& [worldId, chunks] : chunkChanges) {
|
||
auto &list = ChangedContent_Chunk[worldId];
|
||
list.insert(list.end(), chunks.begin(), chunks.end());
|
||
}
|
||
|
||
for(auto& [worldId, regions] : regionRemove) {
|
||
auto &list = ChangedContent_RegionRemove[worldId];
|
||
list.insert(list.end(), regions.begin(), regions.end());
|
||
}
|
||
}
|
||
|
||
// Синхронизация потока рендера мира
|
||
void pushStage(EnumRenderStage stage) {
|
||
auto lock = State.lock();
|
||
|
||
if(lock->Stage == EnumRenderStage::Shutdown)
|
||
MAKE_ERROR("Остановка из-за ошибки ThreadVertex");
|
||
|
||
assert(lock->Stage != stage);
|
||
|
||
lock->Stage = stage;
|
||
|
||
if(stage == EnumRenderStage::ComposingCommandBuffer) {
|
||
if(lock->ChunkMesh_IsUse) {
|
||
lock.unlock();
|
||
while(State.get_read().ChunkMesh_IsUse);
|
||
} else
|
||
lock.unlock();
|
||
|
||
VertexPool_Voxels.update(VkInst->Graphics.Pool);
|
||
VertexPool_Nodes.update(VkInst->Graphics.Pool);
|
||
} else if(stage == EnumRenderStage::WorldUpdate) {
|
||
if(lock->ServerSession_InUse) {
|
||
lock.unlock();
|
||
while(State.get_read().ServerSession_InUse);
|
||
} else
|
||
lock.unlock();
|
||
}
|
||
}
|
||
|
||
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) {
|
||
Pos::GlobalRegion center = pos >> Pos::Object_t::BS_Bit >> 4 >> 2;
|
||
|
||
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>> vertexVoxels;
|
||
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>> vertexNodes;
|
||
|
||
auto iterWorld = ChunkMesh.find(worldId);
|
||
if(iterWorld == ChunkMesh.end())
|
||
return {};
|
||
|
||
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);
|
||
|
||
bool isVisible = false;
|
||
for(int index = 0; index < 8; index++) {
|
||
glm::vec4 temp = glm::vec4((begin+glm::vec3(index&1, (index>>1)&1, (index>>2)&1))*64.f, 1) * projView;
|
||
|
||
if(temp.x >= -1 && temp.x <= 1
|
||
&& temp.y >= -1 && temp.y <= 1
|
||
&& temp.z >= 0 && temp.z <= 1
|
||
) {
|
||
isVisible = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if(!isVisible)
|
||
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++) {
|
||
auto &chunk = iterRegion->second[index];
|
||
|
||
if(chunk.VoxelPointer)
|
||
vertexVoxels.emplace_back(local+Pos::GlobalChunk(Pos::bvec4u().unpack(index)), VertexPool_Voxels.map(chunk.VoxelPointer), chunk.VoxelPointer.VertexCount);
|
||
if(chunk.NodePointer)
|
||
vertexNodes.emplace_back(local+Pos::GlobalChunk(Pos::bvec4u().unpack(index)), VertexPool_Nodes.map(chunk.NodePointer), chunk.NodePointer.VertexCount);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return std::pair{vertexVoxels, vertexNodes};
|
||
}
|
||
|
||
private:
|
||
// Буферы для хранения вершин
|
||
VertexPool<VoxelVertexPoint> VertexPool_Voxels;
|
||
VertexPool<NodeVertexStatic> VertexPool_Nodes;
|
||
// Списки изменённых определений
|
||
std::vector<DefVoxelId_t> ChangedDefines_Voxel;
|
||
std::vector<DefNodeId_t> ChangedDefines_Node;
|
||
// Список чанков на перерисовку
|
||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalChunk>> ChangedContent_Chunk;
|
||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> ChangedContent_RegionRemove;
|
||
// Меши
|
||
std::unordered_map<WorldId_t,
|
||
std::unordered_map<Pos::GlobalRegion, std::array<ChunkObj_t, 4*4*4>>
|
||
> ChunkMesh;
|
||
// Внешний поток
|
||
std::thread Thread;
|
||
|
||
void run();
|
||
};
|
||
|
||
struct VulkanContext {
|
||
VK::Vulkan *VkInst;
|
||
AtlasImage MainTest, LightDummy;
|
||
Buffer TestQuad;
|
||
std::optional<Buffer> TestVoxel;
|
||
|
||
ThreadVertexObj_t ThreadVertexObj;
|
||
|
||
VulkanContext(Vulkan* vkInst)
|
||
: VkInst(vkInst),
|
||
MainTest(vkInst), LightDummy(vkInst),
|
||
TestQuad(vkInst, sizeof(NodeVertexStatic)*6*3*2),
|
||
ThreadVertexObj(vkInst)
|
||
{}
|
||
|
||
~VulkanContext() {
|
||
ThreadVertexObj.pushStage(EnumRenderStage::Shutdown);
|
||
}
|
||
|
||
void onUpdate();
|
||
|
||
void setServerSession(IServerSession* ssession) {
|
||
ThreadVertexObj.SSession = ssession;
|
||
}
|
||
};
|
||
|
||
std::shared_ptr<VulkanContext> VKCTX;
|
||
|
||
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<BinTextureId_t, uint16_t> ServerToAtlas;
|
||
|
||
struct {
|
||
} External;
|
||
|
||
virtual void free(Vulkan *instance) override;
|
||
virtual void init(Vulkan *instance) override;
|
||
|
||
public:
|
||
WorldPCO PCO;
|
||
|
||
public:
|
||
VulkanRenderSession();
|
||
virtual ~VulkanRenderSession();
|
||
|
||
void setServerSession(IServerSession *serverSession) {
|
||
ServerSession = serverSession;
|
||
if(VKCTX)
|
||
VKCTX->setServerSession(serverSession);
|
||
assert(serverSession);
|
||
}
|
||
|
||
virtual void onBinaryResourceAdd(std::vector<Hash_t>) override;
|
||
virtual void onContentDefinesAdd(std::unordered_map<EnumDefContent, std::vector<ResourceId_t>>) override;
|
||
virtual void onContentDefinesLost(std::unordered_map<EnumDefContent, std::vector<ResourceId_t>>) override;
|
||
virtual void onChunksChange(WorldId_t worldId, const std::unordered_set<Pos::GlobalChunk>& changeOrAddList, const std::unordered_set<Pos::GlobalRegion>& remove) 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);
|
||
static std::vector<NodeVertexStatic> generateMeshForNodeChunks(const Node* nodes);
|
||
|
||
private:
|
||
void updateDescriptor_MainAtlas();
|
||
void updateDescriptor_VoxelsLight();
|
||
void updateDescriptor_ChunksLight();
|
||
};
|
||
|
||
} |