Доработка пайплайн машины (требуется пересмотр технологии)
This commit is contained in:
@@ -13,8 +13,8 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections -DGL
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") # -rdynamic
|
||||
|
||||
# gprof
|
||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
|
||||
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
|
||||
|
||||
# sanitizer
|
||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
||||
|
||||
@@ -112,7 +112,6 @@ struct DefPortalInfo {
|
||||
};
|
||||
|
||||
struct DefEntityInfo {
|
||||
|
||||
};
|
||||
|
||||
struct DefFuncEntityInfo {
|
||||
@@ -138,7 +137,10 @@ struct PortalInfo {
|
||||
};
|
||||
|
||||
struct EntityInfo {
|
||||
|
||||
DefEntityId DefId = 0;
|
||||
WorldId_t WorldId = 0;
|
||||
Pos::Object Pos = Pos::Object(0);
|
||||
glm::quat Quat = glm::quat(1.f, 0.f, 0.f, 0.f);
|
||||
};
|
||||
|
||||
struct FuncEntityInfo {
|
||||
|
||||
@@ -606,10 +606,12 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
||||
std::vector<DefWorldId> profile_World_Lost;
|
||||
std::unordered_map<DefPortalId, void*> profile_Portal_AddOrChange;
|
||||
std::vector<DefPortalId> profile_Portal_Lost;
|
||||
std::unordered_map<DefEntityId, void*> profile_Entity_AddOrChange;
|
||||
std::unordered_map<DefEntityId, DefEntityInfo> profile_Entity_AddOrChange;
|
||||
std::vector<DefEntityId> profile_Entity_Lost;
|
||||
std::unordered_map<DefItemId, void*> profile_Item_AddOrChange;
|
||||
std::vector<DefItemId> profile_Item_Lost;
|
||||
std::unordered_map<EntityId_t, EntityInfo> entity_AddOrChange;
|
||||
std::vector<EntityId_t> entity_Lost;
|
||||
|
||||
{
|
||||
for(TickData& data : ticks) {
|
||||
@@ -726,6 +728,25 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
||||
auto eraseIter = std::unique(profile_Item_Lost.begin(), profile_Item_Lost.end());
|
||||
profile_Item_Lost.erase(eraseIter, profile_Item_Lost.end());
|
||||
}
|
||||
|
||||
{
|
||||
for(auto& [id, info] : data.Entity_AddOrChange) {
|
||||
auto iter = std::lower_bound(entity_Lost.begin(), entity_Lost.end(), id);
|
||||
if(iter != entity_Lost.end() && *iter == id)
|
||||
entity_Lost.erase(iter);
|
||||
|
||||
entity_AddOrChange[id] = info;
|
||||
}
|
||||
|
||||
for(EntityId_t id : data.Entity_Lost) {
|
||||
entity_AddOrChange.erase(id);
|
||||
}
|
||||
|
||||
entity_Lost.insert(entity_Lost.end(), data.Entity_Lost.begin(), data.Entity_Lost.end());
|
||||
std::sort(entity_Lost.begin(), entity_Lost.end());
|
||||
auto eraseIter = std::unique(entity_Lost.begin(), entity_Lost.end());
|
||||
entity_Lost.erase(eraseIter, entity_Lost.end());
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& [id, _] : profile_Voxel_AddOrChange)
|
||||
@@ -829,6 +850,11 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
||||
auto& c = chunks_Changed[wId];
|
||||
|
||||
for(auto& [pos, val] : list) {
|
||||
auto& sizes = VisibleChunkCompressed[wId][pos];
|
||||
VisibleChunkCompressedBytes -= sizes.Voxels;
|
||||
sizes.Voxels = val.size();
|
||||
VisibleChunkCompressedBytes += sizes.Voxels;
|
||||
|
||||
caocvr[pos] = unCompressVoxels(val);
|
||||
c.push_back(pos);
|
||||
}
|
||||
@@ -839,6 +865,11 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
||||
auto& c = chunks_Changed[wId];
|
||||
|
||||
for(auto& [pos, val] : list) {
|
||||
auto& sizes = VisibleChunkCompressed[wId][pos];
|
||||
VisibleChunkCompressedBytes -= sizes.Nodes;
|
||||
sizes.Nodes = val.size();
|
||||
VisibleChunkCompressedBytes += sizes.Nodes;
|
||||
|
||||
auto& chunkNodes = caocvr[pos];
|
||||
unCompressNodes(val, chunkNodes.data());
|
||||
debugCheckGeneratedChunkNodes(wId, pos, chunkNodes);
|
||||
@@ -985,15 +1016,17 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
||||
for(auto& [resId, def] : profile_Node_AddOrChange) {
|
||||
Profiles.DefNode[resId] = def;
|
||||
}
|
||||
for(auto& [resId, def] : profile_Entity_AddOrChange) {
|
||||
Profiles.DefEntity[resId] = def;
|
||||
}
|
||||
}
|
||||
|
||||
// Чанки
|
||||
{
|
||||
for(auto& [wId, lost] : regions_Lost_Result) {
|
||||
auto iterWorld = Content.Worlds.find(wId);
|
||||
if(iterWorld == Content.Worlds.end())
|
||||
continue;
|
||||
|
||||
auto iterSizesWorld = VisibleChunkCompressed.find(wId);
|
||||
if(iterWorld != Content.Worlds.end()) {
|
||||
for(const Pos::GlobalRegion& rPos : lost) {
|
||||
auto iterRegion = iterWorld->second.Regions.find(rPos);
|
||||
if(iterRegion != iterWorld->second.Regions.end())
|
||||
@@ -1001,6 +1034,25 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
||||
}
|
||||
}
|
||||
|
||||
if(iterSizesWorld == VisibleChunkCompressed.end())
|
||||
continue;
|
||||
|
||||
for(const Pos::GlobalRegion& rPos : lost) {
|
||||
for(auto iter = iterSizesWorld->second.begin(); iter != iterSizesWorld->second.end(); ) {
|
||||
if(Pos::GlobalRegion(iter->first >> 2) == rPos) {
|
||||
VisibleChunkCompressedBytes -= iter->second.Voxels;
|
||||
VisibleChunkCompressedBytes -= iter->second.Nodes;
|
||||
iter = iterSizesWorld->second.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(iterSizesWorld->second.empty())
|
||||
VisibleChunkCompressed.erase(iterSizesWorld);
|
||||
}
|
||||
|
||||
for(auto& [wId, voxels] : chunks_AddOrChange_Voxel_Result) {
|
||||
auto& regions = Content.Worlds[wId].Regions;
|
||||
|
||||
@@ -1019,6 +1071,43 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
||||
|
||||
}
|
||||
|
||||
// Сущности
|
||||
{
|
||||
for(auto& [entityId, info] : entity_AddOrChange) {
|
||||
auto iter = Content.Entityes.find(entityId);
|
||||
if(iter != Content.Entityes.end() && iter->second.WorldId != info.WorldId) {
|
||||
auto iterWorld = Content.Worlds.find(iter->second.WorldId);
|
||||
if(iterWorld != Content.Worlds.end()) {
|
||||
auto &list = iterWorld->second.Entitys;
|
||||
list.erase(std::remove(list.begin(), list.end(), entityId), list.end());
|
||||
}
|
||||
}
|
||||
|
||||
Content.Entityes[entityId] = info;
|
||||
|
||||
auto &list = Content.Worlds[info.WorldId].Entitys;
|
||||
if(std::find(list.begin(), list.end(), entityId) == list.end())
|
||||
list.push_back(entityId);
|
||||
}
|
||||
|
||||
for(EntityId_t entityId : entity_Lost) {
|
||||
auto iter = Content.Entityes.find(entityId);
|
||||
if(iter != Content.Entityes.end()) {
|
||||
auto iterWorld = Content.Worlds.find(iter->second.WorldId);
|
||||
if(iterWorld != Content.Worlds.end()) {
|
||||
auto &list = iterWorld->second.Entitys;
|
||||
list.erase(std::remove(list.begin(), list.end(), entityId), list.end());
|
||||
}
|
||||
Content.Entityes.erase(iter);
|
||||
} else {
|
||||
for(auto& [wId, worldInfo] : Content.Worlds) {
|
||||
auto &list = worldInfo.Entitys;
|
||||
list.erase(std::remove(list.begin(), list.end(), entityId), list.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(RS)
|
||||
RS->tickSync(result);
|
||||
}
|
||||
@@ -1402,11 +1491,18 @@ coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) {
|
||||
|
||||
co_return;
|
||||
case ToClient::L2Definition::Entity:
|
||||
|
||||
{
|
||||
DefEntityId id = co_await sock.read<DefEntityId>();
|
||||
DefEntityInfo def;
|
||||
AsyncContext.ThisTickEntry.Profile_Entity_AddOrChange.emplace_back(id, def);
|
||||
co_return;
|
||||
}
|
||||
case ToClient::L2Definition::FreeEntity:
|
||||
|
||||
{
|
||||
DefEntityId id = co_await sock.read<DefEntityId>();
|
||||
AsyncContext.ThisTickEntry.Profile_Entity_Lost.push_back(id);
|
||||
co_return;
|
||||
}
|
||||
default:
|
||||
protocolError();
|
||||
}
|
||||
@@ -1433,11 +1529,35 @@ coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) {
|
||||
|
||||
co_return;
|
||||
case ToClient::L2Content::Entity:
|
||||
{
|
||||
EntityId_t id = co_await sock.read<EntityId_t>();
|
||||
DefEntityId defId = co_await sock.read<DefEntityId>();
|
||||
WorldId_t worldId = co_await sock.read<WorldId_t>();
|
||||
|
||||
Pos::Object pos;
|
||||
pos.x = co_await sock.read<decltype(pos.x)>();
|
||||
pos.y = co_await sock.read<decltype(pos.y)>();
|
||||
pos.z = co_await sock.read<decltype(pos.z)>();
|
||||
|
||||
ToServer::PacketQuat q;
|
||||
for(int iter = 0; iter < 5; iter++)
|
||||
q.Data[iter] = co_await sock.read<uint8_t>();
|
||||
|
||||
EntityInfo info;
|
||||
info.DefId = defId;
|
||||
info.WorldId = worldId;
|
||||
info.Pos = pos;
|
||||
info.Quat = q.toQuat();
|
||||
|
||||
AsyncContext.ThisTickEntry.Entity_AddOrChange.emplace_back(id, info);
|
||||
co_return;
|
||||
}
|
||||
case ToClient::L2Content::RemoveEntity:
|
||||
|
||||
{
|
||||
EntityId_t id = co_await sock.read<EntityId_t>();
|
||||
AsyncContext.ThisTickEntry.Entity_Lost.push_back(id);
|
||||
co_return;
|
||||
}
|
||||
case ToClient::L2Content::ChunkVoxels:
|
||||
{
|
||||
WorldId_t wcId = co_await sock.read<WorldId_t>();
|
||||
|
||||
@@ -42,6 +42,10 @@ public:
|
||||
return Socket->isAlive() && IsConnected;
|
||||
}
|
||||
|
||||
uint64_t getVisibleCompressedChunksBytes() const {
|
||||
return VisibleChunkCompressedBytes;
|
||||
}
|
||||
|
||||
// ISurfaceEventListener
|
||||
|
||||
virtual void onResize(uint32_t width, uint32_t height) override;
|
||||
@@ -99,7 +103,7 @@ private:
|
||||
std::vector<DefWorldId> Profile_World_Lost;
|
||||
std::vector<std::pair<DefPortalId, void*>> Profile_Portal_AddOrChange;
|
||||
std::vector<DefPortalId> Profile_Portal_Lost;
|
||||
std::vector<std::pair<DefEntityId, void*>> Profile_Entity_AddOrChange;
|
||||
std::vector<std::pair<DefEntityId, DefEntityInfo>> Profile_Entity_AddOrChange;
|
||||
std::vector<DefEntityId> Profile_Entity_Lost;
|
||||
std::vector<std::pair<DefItemId, void*>> Profile_Item_AddOrChange;
|
||||
std::vector<DefItemId> Profile_Item_Lost;
|
||||
@@ -110,6 +114,13 @@ private:
|
||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, std::u8string>> Chunks_AddOrChange_Voxel;
|
||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, std::u8string>> Chunks_AddOrChange_Node;
|
||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Regions_Lost;
|
||||
std::vector<std::pair<EntityId_t, EntityInfo>> Entity_AddOrChange;
|
||||
std::vector<EntityId_t> Entity_Lost;
|
||||
};
|
||||
|
||||
struct ChunkCompressedSize {
|
||||
uint32_t Voxels = 0;
|
||||
uint32_t Nodes = 0;
|
||||
};
|
||||
|
||||
struct AssetsBindsChange {
|
||||
@@ -169,6 +180,9 @@ private:
|
||||
|
||||
GlobalTime LastSendPYR_POS;
|
||||
|
||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, ChunkCompressedSize>> VisibleChunkCompressed;
|
||||
uint64_t VisibleChunkCompressedBytes = 0;
|
||||
|
||||
// Приём данных с сокета
|
||||
coro<> run(AsyncUseControl::Lock);
|
||||
void protocolError();
|
||||
|
||||
@@ -14,6 +14,34 @@ PipelinedTextureAtlas::AtlasTextureId PipelinedTextureAtlas::getByPipeline(const
|
||||
_AddictedTextures[texId].push_back(pipeline);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<TexturePipelineProgram::AnimSpec> animMeta =
|
||||
TexturePipelineProgram::extractAnimationSpecs(pipeline._Pipeline.data(), pipeline._Pipeline.size());
|
||||
if (!animMeta.empty()) {
|
||||
AnimatedPipelineState entry;
|
||||
entry.Specs.reserve(animMeta.size());
|
||||
for (const auto& spec : animMeta) {
|
||||
detail::AnimSpec16 outSpec{};
|
||||
outSpec.TexId = spec.HasTexId ? spec.TexId : TextureAtlas::kOverflowId;
|
||||
outSpec.FrameW = spec.FrameW;
|
||||
outSpec.FrameH = spec.FrameH;
|
||||
outSpec.FrameCount = spec.FrameCount;
|
||||
outSpec.FpsQ = spec.FpsQ;
|
||||
outSpec.Flags = spec.Flags;
|
||||
entry.Specs.push_back(outSpec);
|
||||
}
|
||||
entry.LastFrames.resize(entry.Specs.size(), std::numeric_limits<uint32_t>::max());
|
||||
entry.Smooth = false;
|
||||
for (const auto& spec : entry.Specs) {
|
||||
if (spec.Flags & detail::AnimSmooth) {
|
||||
entry.Smooth = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_AnimatedPipelines.emplace(pipeline, std::move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
return atlasTexId;
|
||||
}
|
||||
|
||||
@@ -37,6 +65,7 @@ void PipelinedTextureAtlas::freeByPipeline(const HashedPipeline& pipeline) {
|
||||
Super.removeTexture(iter->second);
|
||||
_AtlasCpuTextures.erase(iter->second);
|
||||
_PipeToTexId.erase(iter);
|
||||
_AnimatedPipelines.erase(pipeline);
|
||||
}
|
||||
|
||||
void PipelinedTextureAtlas::updateTexture(uint32_t texId, const StoredTexture& texture) {
|
||||
@@ -90,7 +119,7 @@ StoredTexture PipelinedTextureAtlas::_generatePipelineTexture(const HashedPipeli
|
||||
}
|
||||
|
||||
TexturePipelineProgram program;
|
||||
program.fromWords(std::move(words));
|
||||
program.fromBytes(std::move(words));
|
||||
|
||||
TexturePipelineProgram::OwnedTexture baked;
|
||||
auto provider = [this](uint32_t texId) -> std::optional<Texture> {
|
||||
@@ -109,7 +138,7 @@ StoredTexture PipelinedTextureAtlas::_generatePipelineTexture(const HashedPipeli
|
||||
return tex;
|
||||
};
|
||||
|
||||
if (!program.bake(provider, baked, nullptr)) {
|
||||
if (!program.bake(provider, baked, _AnimTimeSeconds, nullptr)) {
|
||||
if (auto tex = tryCopyFirstDependencyTexture(pipeline)) {
|
||||
return *tex;
|
||||
}
|
||||
@@ -135,6 +164,7 @@ StoredTexture PipelinedTextureAtlas::_generatePipelineTexture(const HashedPipeli
|
||||
|
||||
void PipelinedTextureAtlas::flushNewPipelines() {
|
||||
std::vector<uint32_t> changedTextures = std::move(_ChangedTextures);
|
||||
_ChangedTextures.clear();
|
||||
|
||||
std::sort(changedTextures.begin(), changedTextures.end());
|
||||
changedTextures.erase(std::unique(changedTextures.begin(), changedTextures.end()), changedTextures.end());
|
||||
@@ -150,6 +180,7 @@ void PipelinedTextureAtlas::flushNewPipelines() {
|
||||
}
|
||||
|
||||
changedPipelineTextures.append_range(std::move(_ChangedPipelines));
|
||||
_ChangedPipelines.clear();
|
||||
changedTextures.clear();
|
||||
|
||||
std::sort(changedPipelineTextures.begin(), changedPipelineTextures.end());
|
||||
@@ -165,6 +196,18 @@ void PipelinedTextureAtlas::flushNewPipelines() {
|
||||
auto& stored = _AtlasCpuTextures[atlasTexId];
|
||||
stored = std::move(texture);
|
||||
if (!stored._Pixels.empty()) {
|
||||
// Смена порядка пикселей
|
||||
for (uint32_t& pixel : stored._Pixels) {
|
||||
union {
|
||||
struct { uint8_t r, g, b, a; } color;
|
||||
uint32_t data;
|
||||
};
|
||||
|
||||
data = pixel;
|
||||
std::swap(color.r, color.b);
|
||||
pixel = data;
|
||||
}
|
||||
|
||||
Super.setTextureData(atlasTexId,
|
||||
stored._Widht,
|
||||
stored._Height,
|
||||
@@ -182,6 +225,72 @@ void PipelinedTextureAtlas::notifyGpuFinished() {
|
||||
Super.notifyGpuFinished();
|
||||
}
|
||||
|
||||
bool PipelinedTextureAtlas::updateAnimatedPipelines(double timeSeconds) {
|
||||
_AnimTimeSeconds = timeSeconds;
|
||||
if (_AnimatedPipelines.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
for (auto& [pipeline, entry] : _AnimatedPipelines) {
|
||||
if (entry.Specs.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.Smooth) {
|
||||
_ChangedPipelines.push_back(pipeline);
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.LastFrames.size() != entry.Specs.size())
|
||||
entry.LastFrames.assign(entry.Specs.size(), std::numeric_limits<uint32_t>::max());
|
||||
|
||||
bool pipelineChanged = false;
|
||||
for (size_t i = 0; i < entry.Specs.size(); ++i) {
|
||||
const auto& spec = entry.Specs[i];
|
||||
|
||||
uint32_t fpsQ = spec.FpsQ ? spec.FpsQ : TexturePipelineProgram::DefaultAnimFpsQ;
|
||||
double fps = double(fpsQ) / 256.0;
|
||||
double frameTime = timeSeconds * fps;
|
||||
if (frameTime < 0.0)
|
||||
frameTime = 0.0;
|
||||
|
||||
uint32_t frameCount = spec.FrameCount;
|
||||
// Авторасчёт количества кадров
|
||||
if (frameCount == 0) {
|
||||
auto iterTex = _ResToTexture.find(spec.TexId);
|
||||
if (iterTex != _ResToTexture.end()) {
|
||||
uint32_t fw = spec.FrameW ? spec.FrameW : iterTex->second._Widht;
|
||||
uint32_t fh = spec.FrameH ? spec.FrameH : iterTex->second._Widht;
|
||||
if (fw > 0 && fh > 0) {
|
||||
if (spec.Flags & detail::AnimHorizontal)
|
||||
frameCount = iterTex->second._Widht / fw;
|
||||
else
|
||||
frameCount = iterTex->second._Height / fh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frameCount == 0)
|
||||
frameCount = 1;
|
||||
|
||||
uint32_t frameIndex = frameCount ? (uint32_t(frameTime) % frameCount) : 0u;
|
||||
if (entry.LastFrames[i] != frameIndex) {
|
||||
entry.LastFrames[i] = frameIndex;
|
||||
pipelineChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (pipelineChanged) {
|
||||
_ChangedPipelines.push_back(pipeline);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
std::optional<StoredTexture> PipelinedTextureAtlas::tryCopyFirstDependencyTexture(const HashedPipeline& pipeline) const {
|
||||
auto deps = pipeline.getDependencedTextures();
|
||||
if (!deps.empty()) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
@@ -22,6 +23,7 @@ enum class Op16 : Word {
|
||||
End = 0,
|
||||
Base_Tex = 1,
|
||||
Base_Fill = 2,
|
||||
Base_Anim = 3,
|
||||
Resize = 10,
|
||||
Transform = 11,
|
||||
Opacity = 12,
|
||||
@@ -33,6 +35,7 @@ enum class Op16 : Word {
|
||||
Multiply = 18,
|
||||
Screen = 19,
|
||||
Colorize = 20,
|
||||
Anim = 21,
|
||||
Overlay = 30,
|
||||
Mask = 31,
|
||||
LowPart = 32,
|
||||
@@ -43,13 +46,24 @@ enum class SrcKind16 : Word { TexId = 0, Sub = 1 };
|
||||
|
||||
struct SrcRef16 {
|
||||
SrcKind16 kind{SrcKind16::TexId};
|
||||
Word a = 0;
|
||||
Word b = 0;
|
||||
uint32_t TexId = 0;
|
||||
uint32_t Off = 0;
|
||||
uint32_t Len = 0;
|
||||
};
|
||||
|
||||
inline uint32_t makeU32(Word lo, Word hi) {
|
||||
return uint32_t(lo) | (uint32_t(hi) << 16);
|
||||
}
|
||||
enum AnimFlags16 : Word {
|
||||
AnimSmooth = 1 << 0,
|
||||
AnimHorizontal = 1 << 1
|
||||
};
|
||||
|
||||
struct AnimSpec16 {
|
||||
uint32_t TexId = 0;
|
||||
uint16_t FrameW = 0;
|
||||
uint16_t FrameH = 0;
|
||||
uint16_t FrameCount = 0;
|
||||
uint16_t FpsQ = 0;
|
||||
uint16_t Flags = 0;
|
||||
};
|
||||
|
||||
inline void addUniqueDep(boost::container::small_vector<uint32_t, 8>& deps, uint32_t id) {
|
||||
if (id == TextureAtlas::kOverflowId) {
|
||||
@@ -60,16 +74,52 @@ inline void addUniqueDep(boost::container::small_vector<uint32_t, 8>& deps, uint
|
||||
}
|
||||
}
|
||||
|
||||
inline bool readSrc(const std::vector<Word>& words, size_t end, size_t& ip, SrcRef16& out) {
|
||||
inline bool read16(const std::vector<Word>& words, size_t end, size_t& ip, uint16_t& out) {
|
||||
if (ip + 1 >= end) {
|
||||
return false;
|
||||
}
|
||||
out = uint16_t(words[ip]) | (uint16_t(words[ip + 1]) << 8);
|
||||
ip += 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool read24(const std::vector<Word>& words, size_t end, size_t& ip, uint32_t& out) {
|
||||
if (ip + 2 >= end) {
|
||||
return false;
|
||||
}
|
||||
out.kind = static_cast<SrcKind16>(words[ip++]);
|
||||
out.a = words[ip++];
|
||||
out.b = words[ip++];
|
||||
out = uint32_t(words[ip]) |
|
||||
(uint32_t(words[ip + 1]) << 8) |
|
||||
(uint32_t(words[ip + 2]) << 16);
|
||||
ip += 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool read32(const std::vector<Word>& words, size_t end, size_t& ip, uint32_t& out) {
|
||||
if (ip + 3 >= end) {
|
||||
return false;
|
||||
}
|
||||
out = uint32_t(words[ip]) |
|
||||
(uint32_t(words[ip + 1]) << 8) |
|
||||
(uint32_t(words[ip + 2]) << 16) |
|
||||
(uint32_t(words[ip + 3]) << 24);
|
||||
ip += 4;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool readSrc(const std::vector<Word>& words, size_t end, size_t& ip, SrcRef16& out) {
|
||||
if (ip >= end) {
|
||||
return false;
|
||||
}
|
||||
out.kind = static_cast<SrcKind16>(words[ip++]);
|
||||
if (out.kind == SrcKind16::TexId) {
|
||||
return read24(words, end, ip, out.TexId);
|
||||
}
|
||||
if (out.kind == SrcKind16::Sub) {
|
||||
return read24(words, end, ip, out.Off) && read24(words, end, ip, out.Len);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void extractPipelineDependencies(const std::vector<Word>& words,
|
||||
size_t start,
|
||||
size_t end,
|
||||
@@ -88,12 +138,12 @@ inline void extractPipelineDependencies(const std::vector<Word>& words,
|
||||
auto need = [&](size_t n) { return ip + n <= end; };
|
||||
auto handleSrc = [&](const SrcRef16& src) {
|
||||
if (src.kind == SrcKind16::TexId) {
|
||||
addUniqueDep(deps, makeU32(src.a, src.b));
|
||||
addUniqueDep(deps, src.TexId);
|
||||
return;
|
||||
}
|
||||
if (src.kind == SrcKind16::Sub) {
|
||||
size_t subStart = static_cast<size_t>(src.a);
|
||||
size_t subEnd = subStart + static_cast<size_t>(src.b);
|
||||
size_t subStart = static_cast<size_t>(src.Off);
|
||||
size_t subEnd = subStart + static_cast<size_t>(src.Len);
|
||||
if (subStart < subEnd && subEnd <= words.size()) {
|
||||
extractPipelineDependencies(words, subStart, subEnd, deps, visited);
|
||||
}
|
||||
@@ -108,37 +158,54 @@ inline void extractPipelineDependencies(const std::vector<Word>& words,
|
||||
return;
|
||||
|
||||
case Op16::Base_Tex: {
|
||||
if (!need(3)) return;
|
||||
SrcRef16 src{};
|
||||
if (!readSrc(words, end, ip, src)) return;
|
||||
handleSrc(src);
|
||||
} break;
|
||||
|
||||
case Op16::Base_Fill:
|
||||
if (!need(4)) return;
|
||||
ip += 4;
|
||||
break;
|
||||
case Op16::Base_Anim: {
|
||||
SrcRef16 src{};
|
||||
if (!readSrc(words, end, ip, src)) return;
|
||||
handleSrc(src);
|
||||
uint16_t tmp16 = 0;
|
||||
uint8_t tmp8 = 0;
|
||||
if (!read16(words, end, ip, tmp16)) return;
|
||||
if (!read16(words, end, ip, tmp16)) return;
|
||||
if (!read16(words, end, ip, tmp16)) return;
|
||||
if (!read16(words, end, ip, tmp16)) return;
|
||||
if (!need(1)) return;
|
||||
tmp8 = words[ip++];
|
||||
(void)tmp8;
|
||||
} break;
|
||||
|
||||
case Op16::Base_Fill: {
|
||||
uint16_t tmp16 = 0;
|
||||
uint32_t tmp32 = 0;
|
||||
if (!read16(words, end, ip, tmp16)) return;
|
||||
if (!read16(words, end, ip, tmp16)) return;
|
||||
if (!read32(words, end, ip, tmp32)) return;
|
||||
} break;
|
||||
|
||||
case Op16::Overlay:
|
||||
case Op16::Mask: {
|
||||
if (!need(3)) return;
|
||||
SrcRef16 src{};
|
||||
if (!readSrc(words, end, ip, src)) return;
|
||||
handleSrc(src);
|
||||
} break;
|
||||
|
||||
case Op16::LowPart: {
|
||||
if (!need(1 + 3)) return;
|
||||
if (!need(1)) return;
|
||||
ip += 1; // percent
|
||||
SrcRef16 src{};
|
||||
if (!readSrc(words, end, ip, src)) return;
|
||||
handleSrc(src);
|
||||
} break;
|
||||
|
||||
case Op16::Resize:
|
||||
if (!need(2)) return;
|
||||
ip += 2;
|
||||
break;
|
||||
case Op16::Resize: {
|
||||
uint16_t tmp16 = 0;
|
||||
if (!read16(words, end, ip, tmp16)) return;
|
||||
if (!read16(words, end, ip, tmp16)) return;
|
||||
} break;
|
||||
|
||||
case Op16::Transform:
|
||||
case Op16::Opacity:
|
||||
@@ -151,8 +218,8 @@ inline void extractPipelineDependencies(const std::vector<Word>& words,
|
||||
break;
|
||||
|
||||
case Op16::MakeAlpha:
|
||||
if (!need(2)) return;
|
||||
ip += 2;
|
||||
if (!need(3)) return;
|
||||
ip += 3;
|
||||
break;
|
||||
|
||||
case Op16::Invert:
|
||||
@@ -166,27 +233,42 @@ inline void extractPipelineDependencies(const std::vector<Word>& words,
|
||||
break;
|
||||
|
||||
case Op16::Multiply:
|
||||
case Op16::Screen:
|
||||
if (!need(2)) return;
|
||||
ip += 2;
|
||||
break;
|
||||
case Op16::Screen: {
|
||||
uint32_t tmp32 = 0;
|
||||
if (!read32(words, end, ip, tmp32)) return;
|
||||
} break;
|
||||
|
||||
case Op16::Colorize:
|
||||
if (!need(3)) return;
|
||||
ip += 3;
|
||||
break;
|
||||
case Op16::Colorize: {
|
||||
uint32_t tmp32 = 0;
|
||||
if (!read32(words, end, ip, tmp32)) return;
|
||||
if (!need(1)) return;
|
||||
ip += 1;
|
||||
} break;
|
||||
|
||||
case Op16::Anim: {
|
||||
uint16_t tmp16 = 0;
|
||||
if (!read16(words, end, ip, tmp16)) return;
|
||||
if (!read16(words, end, ip, tmp16)) return;
|
||||
if (!read16(words, end, ip, tmp16)) return;
|
||||
if (!read16(words, end, ip, tmp16)) return;
|
||||
if (!need(1)) return;
|
||||
ip += 1;
|
||||
} break;
|
||||
|
||||
case Op16::Combine: {
|
||||
if (!need(3)) return;
|
||||
ip += 2; // skip w,h
|
||||
uint32_t n = words[ip++];
|
||||
uint16_t w = 0, h = 0, n = 0;
|
||||
if (!read16(words, end, ip, w)) return;
|
||||
if (!read16(words, end, ip, h)) return;
|
||||
if (!read16(words, end, ip, n)) return;
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
if (!need(2 + 3)) return;
|
||||
ip += 2; // x, y
|
||||
uint16_t tmp16 = 0;
|
||||
if (!read16(words, end, ip, tmp16)) return; // x
|
||||
if (!read16(words, end, ip, tmp16)) return; // y
|
||||
SrcRef16 src{};
|
||||
if (!readSrc(words, end, ip, src)) return;
|
||||
handleSrc(src);
|
||||
}
|
||||
(void)w; (void)h;
|
||||
} break;
|
||||
|
||||
default:
|
||||
@@ -227,8 +309,9 @@ struct Pipeline {
|
||||
_Pipeline = {
|
||||
static_cast<detail::Word>(detail::Op16::Base_Tex),
|
||||
static_cast<detail::Word>(detail::SrcKind16::TexId),
|
||||
static_cast<detail::Word>(texId & 0xFFFFu),
|
||||
static_cast<detail::Word>((texId >> 16) & 0xFFFFu),
|
||||
static_cast<detail::Word>(texId & 0xFFu),
|
||||
static_cast<detail::Word>((texId >> 8) & 0xFFu),
|
||||
static_cast<detail::Word>((texId >> 16) & 0xFFu),
|
||||
static_cast<detail::Word>(detail::Op16::End)
|
||||
};
|
||||
}
|
||||
@@ -253,9 +336,7 @@ struct HashedPipeline {
|
||||
constexpr std::size_t prime = 1099511628211ull;
|
||||
|
||||
for(detail::Word w : _Pipeline) {
|
||||
hash ^= static_cast<uint8_t>(w & 0xFF);
|
||||
hash *= prime;
|
||||
hash ^= static_cast<uint8_t>((w >> 8) & 0xFF);
|
||||
hash ^= static_cast<uint8_t>(w);
|
||||
hash *= prime;
|
||||
}
|
||||
|
||||
@@ -328,6 +409,13 @@ private:
|
||||
std::vector<uint32_t> _ChangedTextures;
|
||||
// Необходимые к созданию/обновлению пайплайны
|
||||
std::vector<HashedPipeline> _ChangedPipelines;
|
||||
struct AnimatedPipelineState {
|
||||
std::vector<detail::AnimSpec16> Specs;
|
||||
std::vector<uint32_t> LastFrames;
|
||||
bool Smooth = false;
|
||||
};
|
||||
std::unordered_map<HashedPipeline, AnimatedPipelineState, HashedPipelineKeyHash, HashedPipelineKeyEqual> _AnimatedPipelines;
|
||||
double _AnimTimeSeconds = 0.0;
|
||||
|
||||
public:
|
||||
PipelinedTextureAtlas(TextureAtlas&& tk);
|
||||
@@ -348,6 +436,26 @@ public:
|
||||
return atlasLayers();
|
||||
}
|
||||
|
||||
uint32_t maxLayers() const {
|
||||
return Super.maxLayers();
|
||||
}
|
||||
|
||||
uint32_t maxTextureId() const {
|
||||
return Super.maxTextureId();
|
||||
}
|
||||
|
||||
TextureAtlas::TextureId reservedOverflowId() const {
|
||||
return Super.reservedOverflowId();
|
||||
}
|
||||
|
||||
TextureAtlas::TextureId reservedLayerId(uint32_t layer) const {
|
||||
return Super.reservedLayerId(layer);
|
||||
}
|
||||
|
||||
void requestLayerCount(uint32_t layers) {
|
||||
Super.requestLayerCount(layers);
|
||||
}
|
||||
|
||||
// Должны всегда бронировать идентификатор, либо отдавать kOverflowId. При этом запись tex+pipeline остаётся
|
||||
// Выдаёт стабильный идентификатор, привязанный к пайплайну
|
||||
AtlasTextureId getByPipeline(const HashedPipeline& pipeline);
|
||||
@@ -373,6 +481,8 @@ public:
|
||||
|
||||
void notifyGpuFinished();
|
||||
|
||||
bool updateAnimatedPipelines(double timeSeconds);
|
||||
|
||||
private:
|
||||
std::optional<StoredTexture> tryCopyFirstDependencyTexture(const HashedPipeline& pipeline) const;
|
||||
|
||||
|
||||
@@ -29,11 +29,13 @@ TextureAtlas::TextureAtlas(VkDevice device,
|
||||
|
||||
EntriesCpu_.resize(Cfg_.MaxTextureId);
|
||||
std::memset(EntriesCpu_.data(), 0, EntriesCpu_.size() * sizeof(Entry));
|
||||
_initReservedEntries();
|
||||
EntriesDirty_ = true;
|
||||
|
||||
Slots_.resize(Cfg_.MaxTextureId);
|
||||
FreeIds_.reserve(Cfg_.MaxTextureId);
|
||||
PendingInQueue_.assign(Cfg_.MaxTextureId, false);
|
||||
NextId_ = _allocatableStart();
|
||||
|
||||
if(Cfg_.ExternalSampler != VK_NULL_HANDLE) {
|
||||
Sampler_ = Cfg_.ExternalSampler;
|
||||
@@ -70,13 +72,19 @@ TextureAtlas::TextureId TextureAtlas::registerTexture() {
|
||||
_ensureAliveOrThrow();
|
||||
|
||||
TextureId id = kOverflowId;
|
||||
if(NextId_ < _allocatableStart()) {
|
||||
NextId_ = _allocatableStart();
|
||||
}
|
||||
while(!FreeIds_.empty() && isReservedId(FreeIds_.back())) {
|
||||
FreeIds_.pop_back();
|
||||
}
|
||||
if(!FreeIds_.empty()) {
|
||||
id = FreeIds_.back();
|
||||
FreeIds_.pop_back();
|
||||
} else if(NextId_ < Cfg_.MaxTextureId) {
|
||||
} else if(NextId_ < _allocatableLimit()) {
|
||||
id = NextId_++;
|
||||
} else {
|
||||
return kOverflowId;
|
||||
return reservedOverflowId();
|
||||
}
|
||||
|
||||
Slot& s = Slots_[id];
|
||||
@@ -96,7 +104,7 @@ void TextureAtlas::setTextureData(TextureId id,
|
||||
const void* pixelsRGBA8,
|
||||
uint32_t rowPitchBytes) {
|
||||
_ensureAliveOrThrow();
|
||||
if(id == kOverflowId) return;
|
||||
if(isInvalidId(id)) return;
|
||||
_ensureRegisteredIdOrThrow(id);
|
||||
|
||||
if(w == 0 || h == 0) {
|
||||
@@ -151,7 +159,7 @@ void TextureAtlas::setTextureData(TextureId id,
|
||||
|
||||
void TextureAtlas::clearTextureData(TextureId id) {
|
||||
_ensureAliveOrThrow();
|
||||
if(id == kOverflowId) return;
|
||||
if(isInvalidId(id)) return;
|
||||
_ensureRegisteredIdOrThrow(id);
|
||||
|
||||
Slot& s = Slots_[id];
|
||||
@@ -172,7 +180,7 @@ void TextureAtlas::clearTextureData(TextureId id) {
|
||||
|
||||
void TextureAtlas::removeTexture(TextureId id) {
|
||||
_ensureAliveOrThrow();
|
||||
if(id == kOverflowId) return;
|
||||
if(isInvalidId(id)) return;
|
||||
_ensureRegisteredIdOrThrow(id);
|
||||
|
||||
Slot& s = Slots_[id];
|
||||
@@ -217,7 +225,7 @@ TextureAtlas::DescriptorOut TextureAtlas::flushUploadsAndBarriers(VkCommandBuffe
|
||||
while (!queue.empty()) {
|
||||
TextureId id = queue.front();
|
||||
queue.pop_front();
|
||||
if(id == kOverflowId || id >= inQueue.size()) {
|
||||
if(isInvalidId(id) || id >= inQueue.size()) {
|
||||
continue;
|
||||
}
|
||||
if(!inQueue[id]) {
|
||||
@@ -253,7 +261,7 @@ TextureAtlas::DescriptorOut TextureAtlas::flushUploadsAndBarriers(VkCommandBuffe
|
||||
|
||||
bool outOfSpace = false;
|
||||
for(TextureId id : pendingNow) {
|
||||
if(id == kOverflowId) continue;
|
||||
if(isInvalidId(id)) continue;
|
||||
if(id >= Slots_.size()) continue;
|
||||
Slot& s = Slots_[id];
|
||||
if(!s.InUse || !s.HasCpuData) continue;
|
||||
@@ -310,7 +318,7 @@ TextureAtlas::DescriptorOut TextureAtlas::flushUploadsAndBarriers(VkCommandBuffe
|
||||
};
|
||||
|
||||
for(TextureId id : pendingNow) {
|
||||
if(id == kOverflowId) continue;
|
||||
if(isInvalidId(id)) continue;
|
||||
Slot& s = Slots_[id];
|
||||
if(!s.InUse || !s.HasCpuData || !s.HasPlacement) continue;
|
||||
if(!uploadTextureIntoAtlas(s, s.Place, Atlas_, false)) {
|
||||
|
||||
@@ -10,7 +10,7 @@ TextureAtlas — как пользоваться (кратко)
|
||||
|
||||
2) Зарегистрируйте текстуру и получите стабильный ID:
|
||||
TextureId id = atlas.registerTexture();
|
||||
if(id == TextureAtlas::kOverflowId) { ... } // нет свободных ID
|
||||
if(id == TextureAtlas::kOverflowId || id == atlas.reservedOverflowId()) { ... } // нет свободных ID
|
||||
|
||||
3) Задайте данные (RGBA8), можно много раз — ID не меняется:
|
||||
atlas.setTextureData(id, w, h, pixels, rowPitchBytes);
|
||||
@@ -32,7 +32,11 @@ TextureAtlas — как пользоваться (кратко)
|
||||
atlas.removeTexture(id); // освободить ID (после этого использовать нельзя)
|
||||
|
||||
Примечания:
|
||||
- Вызовы API с kOverflowId игнорируются (no-op).
|
||||
- Вызовы API с kOverflowId и зарезервированными ID игнорируются (no-op).
|
||||
- ID из начала диапазона зарезервированы под служебные нужды:
|
||||
reservedOverflowId() == 0,
|
||||
reservedLayerId(0) == 1 (первый слой),
|
||||
reservedLayerId(1) == 2 (второй слой) и т.д.
|
||||
- Ошибки ресурсов (нет места/стейджинга/oom) НЕ бросают исключения — дают события.
|
||||
- Исключения только за неверный ввод/неверное использование (см. ТЗ).
|
||||
- Класс не thread-safe: синхронизацию обеспечивает пользователь.
|
||||
@@ -192,11 +196,60 @@ public:
|
||||
/// Текущее число слоёв атласа.
|
||||
uint32_t atlasLayers() const { return Atlas_.Layers; }
|
||||
|
||||
uint32_t maxLayers() const { return Cfg_.MaxLayers; }
|
||||
uint32_t maxTextureId() const { return Cfg_.MaxTextureId; }
|
||||
|
||||
TextureId reservedOverflowId() const { return 0; }
|
||||
TextureId reservedLayerId(uint32_t layer) const { return 1u + layer; }
|
||||
|
||||
bool isReservedId(TextureId id) const {
|
||||
if(id >= Cfg_.MaxTextureId) return false;
|
||||
return id < _reservedCount();
|
||||
}
|
||||
|
||||
bool isReservedLayerId(TextureId id) const {
|
||||
if(id >= Cfg_.MaxTextureId) return false;
|
||||
return id != reservedOverflowId() && id < _reservedCount();
|
||||
}
|
||||
|
||||
bool isInvalidId(TextureId id) const {
|
||||
return id == kOverflowId || isReservedId(id);
|
||||
}
|
||||
|
||||
void requestLayerCount(uint32_t layers) {
|
||||
_ensureAliveOrThrow();
|
||||
_scheduleLayerGrow(layers);
|
||||
}
|
||||
|
||||
/// Общий staging-буфер (может быть задан извне).
|
||||
std::shared_ptr<SharedStagingBuffer> getStagingBuffer() const { return Staging_; }
|
||||
|
||||
private:
|
||||
void _moveFrom(TextureAtlas&& other) noexcept;
|
||||
uint32_t _reservedCount() const { return Cfg_.MaxLayers + 1; }
|
||||
uint32_t _reservedStart() const { return 0; }
|
||||
uint32_t _allocatableStart() const { return _reservedCount(); }
|
||||
uint32_t _allocatableLimit() const { return Cfg_.MaxTextureId; }
|
||||
|
||||
void _initReservedEntries() {
|
||||
if(Cfg_.MaxTextureId <= _reservedCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_setEntryInvalid(reservedOverflowId(), /*diagPending*/false, /*diagTooLarge*/false);
|
||||
for(uint32_t layer = 0; layer < Cfg_.MaxLayers; ++layer) {
|
||||
TextureId id = reservedLayerId(layer);
|
||||
Entry& e = EntriesCpu_[id];
|
||||
e.UVMinMax[0] = 0.0f;
|
||||
e.UVMinMax[1] = 0.0f;
|
||||
e.UVMinMax[2] = 1.0f;
|
||||
e.UVMinMax[3] = 1.0f;
|
||||
e.Layer = layer;
|
||||
e.Flags = ENTRY_VALID;
|
||||
}
|
||||
EntriesDirty_ = true;
|
||||
}
|
||||
|
||||
// ============================= Ошибки/валидация =============================
|
||||
|
||||
struct InputError : std::runtime_error {
|
||||
@@ -225,6 +278,9 @@ private:
|
||||
if(Cfg_.MaxTextureId == 0) {
|
||||
throw _inputError("Config.MaxTextureId must be > 0");
|
||||
}
|
||||
if(Cfg_.MaxTextureId <= (Cfg_.MaxLayers + 1)) {
|
||||
throw _inputError("Config.MaxTextureId must be > MaxLayers + 1 (reserved ids)");
|
||||
}
|
||||
|
||||
if(Cfg_.MaxTextureSize != 2048) {
|
||||
/// TODO:
|
||||
@@ -246,7 +302,7 @@ private:
|
||||
}
|
||||
|
||||
void _ensureRegisteredIdOrThrow(TextureId id) const {
|
||||
if(id >= Cfg_.MaxTextureId) {
|
||||
if(id >= Cfg_.MaxTextureId || isReservedId(id)) {
|
||||
throw _inputError("TextureId out of range");
|
||||
}
|
||||
if(!Slots_[id].InUse || Slots_[id].StateValue == State::REMOVED) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -311,7 +311,7 @@ void Vulkan::run()
|
||||
}
|
||||
|
||||
if(Game.RSession) {
|
||||
Game.RSession->beforeDraw();
|
||||
Game.RSession->beforeDraw(double(gTime));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -624,12 +624,6 @@ void Vulkan::run()
|
||||
err = vkQueuePresentKHR(*lockQueue, &present);
|
||||
}
|
||||
|
||||
{
|
||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
||||
vkDeviceWaitIdle(Graphics.Device);
|
||||
lockQueue.unlock();
|
||||
}
|
||||
|
||||
if (err == VK_ERROR_OUT_OF_DATE_KHR)
|
||||
{
|
||||
freeSwapchains();
|
||||
@@ -651,12 +645,6 @@ void Vulkan::run()
|
||||
Screen.State = DrawState::End;
|
||||
}
|
||||
|
||||
{
|
||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
||||
vkDeviceWaitIdle(Graphics.Device);
|
||||
lockQueue.unlock();
|
||||
}
|
||||
|
||||
for(int iter = 0; iter < 4; iter++) {
|
||||
vkDestroySemaphore(Graphics.Device, SemaphoreImageAcquired[iter], nullptr);
|
||||
vkDestroySemaphore(Graphics.Device, SemaphoreDrawComplete[iter], nullptr);
|
||||
@@ -688,8 +676,6 @@ uint32_t Vulkan::memoryTypeFromProperties(uint32_t bitsOfAcceptableTypes, VkFlag
|
||||
|
||||
void Vulkan::freeSwapchains()
|
||||
{
|
||||
//vkDeviceWaitIdle(Screen.Device);
|
||||
|
||||
if(Graphics.Instance && Graphics.Device)
|
||||
{
|
||||
std::vector<VkImageView> oldViews;
|
||||
@@ -2302,6 +2288,9 @@ void Vulkan::gui_ConnectedToServer() {
|
||||
(int) Game.RSession->PlayerPos.x >> 6, (int) Game.RSession->PlayerPos.y >> 6, (int) Game.RSession->PlayerPos.z >> 6
|
||||
);
|
||||
|
||||
double chunksKb = double(Game.Session->getVisibleCompressedChunksBytes()) / 1024.0;
|
||||
ImGui::Text("chunks compressed: %.1f KB", chunksKb);
|
||||
|
||||
if(ImGui::Button("Delimeter"))
|
||||
LOG.debug();
|
||||
|
||||
|
||||
@@ -323,7 +323,7 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
||||
};
|
||||
|
||||
std::unordered_map<uint32_t, ModelCacheEntry> modelCache;
|
||||
std::unordered_map<AssetsTexture, uint16_t> baseTextureCache;
|
||||
std::unordered_map<AssetsTexture, uint32_t> baseTextureCache;
|
||||
|
||||
std::vector<NodeStateInfo> metaStatesInfo;
|
||||
{
|
||||
@@ -451,7 +451,7 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
||||
if(iterTex != baseTextureCache.end()) {
|
||||
v.Tex = iterTex->second;
|
||||
} else {
|
||||
uint16_t resolvedTex = NSP->getTextureId(node->TexId);
|
||||
uint32_t resolvedTex = NSP->getTextureId(node->TexId);
|
||||
v.Tex = resolvedTex;
|
||||
baseTextureCache.emplace(node->TexId, resolvedTex);
|
||||
}
|
||||
@@ -1670,7 +1670,7 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) {
|
||||
}
|
||||
|
||||
if(TP) {
|
||||
std::vector<std::tuple<AssetsTexture, Resource>> textureResources;
|
||||
std::vector<TextureProvider::TextureUpdate> textureResources;
|
||||
std::vector<AssetsTexture> textureLost;
|
||||
|
||||
if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Texture); iter != data.Assets_ChangeOrAdd.end()) {
|
||||
@@ -1680,7 +1680,12 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) {
|
||||
if(entryIter == list.end())
|
||||
continue;
|
||||
|
||||
textureResources.emplace_back(id, entryIter->second.Res);
|
||||
textureResources.push_back({
|
||||
.Id = id,
|
||||
.Res = entryIter->second.Res,
|
||||
.Domain = entryIter->second.Domain,
|
||||
.Key = entryIter->second.Key
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1739,9 +1744,9 @@ void VulkanRenderSession::setCameraPos(WorldId_t worldId, Pos::Object pos, glm::
|
||||
PlayerPos /= float(Pos::Object_t::BS);
|
||||
}
|
||||
|
||||
void VulkanRenderSession::beforeDraw() {
|
||||
void VulkanRenderSession::beforeDraw(double timeSeconds) {
|
||||
if(TP)
|
||||
TP->update();
|
||||
TP->update(timeSeconds);
|
||||
LightDummy.atlasUpdateDynamicData();
|
||||
CP.flushUploadsAndBarriers(VkInst->Graphics.CommandBufferRender);
|
||||
}
|
||||
@@ -1901,6 +1906,7 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff
|
||||
vkCmdDraw(drawCmd, 6*3*2, 1, 0, 0);
|
||||
|
||||
{
|
||||
PCO.Model = glm::mat4(1.0f);
|
||||
Pos::GlobalChunk x64offset = X64Offset >> Pos::Object_t::BS_Bit >> 4;
|
||||
Pos::GlobalRegion x64offset_region = x64offset >> 2;
|
||||
|
||||
@@ -1940,9 +1946,160 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff
|
||||
PCO.Model = orig;
|
||||
}
|
||||
|
||||
vkCmdBindPipeline(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, NodeStaticTransparentPipeline);
|
||||
vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout,
|
||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(WorldPCO), &PCO);
|
||||
vkCmdBindDescriptorSets(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
MainAtlas_LightMap_PipelineLayout, 0, 2,
|
||||
(const VkDescriptorSet[]) {TP ? TP->getDescriptorSet() : VK_NULL_HANDLE, VoxelLightMapDescriptor}, 0, nullptr);
|
||||
|
||||
ensureAtlasLayerPreview();
|
||||
if(AtlasLayersPreview) {
|
||||
glm::mat4 previewModel = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 64.0f, 0.0f)-glm::vec3(X64Offset >> Pos::Object_t::BS_Bit));
|
||||
vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout,
|
||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, offsetof(WorldPCO, Model), sizeof(WorldPCO::Model), &previewModel);
|
||||
VkBuffer previewBuffer = *AtlasLayersPreview;
|
||||
VkDeviceSize previewOffset = 0;
|
||||
vkCmdBindVertexBuffers(drawCmd, 0, 1, &previewBuffer, &previewOffset);
|
||||
vkCmdDraw(drawCmd, AtlasLayersPreviewCount * 6, 1, 0, 0);
|
||||
}
|
||||
|
||||
if(false) {
|
||||
ensureEntityTexture();
|
||||
|
||||
if(!ServerSession->Content.Entityes.empty()) {
|
||||
VkBuffer entityBuffer = TestQuad;
|
||||
VkDeviceSize entityOffset = 0;
|
||||
vkCmdBindVertexBuffers(drawCmd, 0, 1, &entityBuffer, &entityOffset);
|
||||
|
||||
glm::mat4 orig = PCO.Model;
|
||||
for(const auto& pair : ServerSession->Content.Entityes) {
|
||||
const auto& info = pair.second;
|
||||
if(info.WorldId != WorldId)
|
||||
continue;
|
||||
|
||||
glm::vec3 entityPos = Pos::Object_t::asFloatVec(info.Pos - X64Offset);
|
||||
entityPos.y -= 1.6f; // Camera position arrives as eye height.
|
||||
|
||||
glm::mat4 model = glm::translate(glm::mat4(1.0f), entityPos);
|
||||
model = model * glm::mat4(info.Quat);
|
||||
model = glm::scale(model, glm::vec3(0.6f, 1.8f, 0.6f));
|
||||
|
||||
PCO.Model = model;
|
||||
vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout,
|
||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, offsetof(WorldPCO, Model), sizeof(WorldPCO::Model), &PCO.Model);
|
||||
vkCmdDraw(drawCmd, 6*3*2, 1, 0, 0);
|
||||
}
|
||||
|
||||
PCO.Model = orig;
|
||||
}
|
||||
}
|
||||
|
||||
CP.pushFrame();
|
||||
}
|
||||
|
||||
void VulkanRenderSession::updateTestQuadTexture(uint32_t texId) {
|
||||
if(EntityTextureReady && EntityTextureId == texId)
|
||||
return;
|
||||
|
||||
auto *array = reinterpret_cast<NodeVertexStatic*>(TestQuad.mapMemory());
|
||||
const size_t vertexCount = TestQuad.getSize() / sizeof(NodeVertexStatic);
|
||||
for(size_t iter = 0; iter < vertexCount; ++iter)
|
||||
array[iter].Tex = texId;
|
||||
TestQuad.unMapMemory();
|
||||
|
||||
EntityTextureId = texId;
|
||||
EntityTextureReady = true;
|
||||
}
|
||||
|
||||
void VulkanRenderSession::ensureEntityTexture() {
|
||||
if(EntityTextureReady || !TP || !NSP)
|
||||
return;
|
||||
|
||||
auto iter = ServerSession->Assets.find(EnumAssets::Texture);
|
||||
if(iter == ServerSession->Assets.end() || iter->second.empty())
|
||||
return;
|
||||
|
||||
const AssetEntry* picked = nullptr;
|
||||
for(const auto& [id, entry] : iter->second) {
|
||||
if(entry.Key == "default.png") {
|
||||
picked = &entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!picked) {
|
||||
for(const auto& [id, entry] : iter->second) {
|
||||
if(entry.Key == "grass.png") {
|
||||
picked = &entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!picked)
|
||||
picked = &iter->second.begin()->second;
|
||||
|
||||
updateTestQuadTexture(NSP->getTextureId(picked->Id));
|
||||
}
|
||||
|
||||
void VulkanRenderSession::ensureAtlasLayerPreview() {
|
||||
if(!TP)
|
||||
return;
|
||||
|
||||
const uint32_t maxLayers = TP->getAtlasMaxLayers();
|
||||
if(maxLayers == 0)
|
||||
return;
|
||||
|
||||
if(AtlasLayersPreview && AtlasLayersPreviewCount == maxLayers)
|
||||
return;
|
||||
|
||||
TP->requestAtlasLayerCount(maxLayers);
|
||||
|
||||
const uint32_t vertsPerQuad = 6;
|
||||
const uint32_t totalVerts = maxLayers * vertsPerQuad;
|
||||
std::vector<NodeVertexStatic> verts(totalVerts);
|
||||
|
||||
const uint32_t columns = 4;
|
||||
const uint32_t base = 224;
|
||||
const uint32_t step = 320;
|
||||
const uint32_t size = 256;
|
||||
const uint32_t z = base;
|
||||
|
||||
auto makeVert = [&](uint32_t fx, uint32_t fy, uint32_t tex, uint32_t tu, uint32_t tv) -> NodeVertexStatic {
|
||||
NodeVertexStatic v{};
|
||||
v.FX = fx;
|
||||
v.FY = fy;
|
||||
v.FZ = z;
|
||||
v.LS = 0;
|
||||
v.Tex = tex;
|
||||
v.TU = tu;
|
||||
v.TV = tv;
|
||||
return v;
|
||||
};
|
||||
|
||||
for(uint32_t layer = 0; layer < maxLayers; ++layer) {
|
||||
const uint32_t col = layer % columns;
|
||||
const uint32_t row = layer / columns;
|
||||
const uint32_t x0 = base + col * step;
|
||||
const uint32_t y0 = base + row * step;
|
||||
const uint32_t x1 = x0 + size;
|
||||
const uint32_t y1 = y0 + size;
|
||||
const uint32_t tex = TP->getAtlasLayerId(layer);
|
||||
|
||||
const size_t start = static_cast<size_t>(layer) * vertsPerQuad;
|
||||
verts[start + 0] = makeVert(x0, y0, tex, 0, 0);
|
||||
verts[start + 1] = makeVert(x0, y1, tex, 0, 65535);
|
||||
verts[start + 2] = makeVert(x1, y1, tex, 65535, 65535);
|
||||
verts[start + 3] = makeVert(x0, y0, tex, 0, 0);
|
||||
verts[start + 4] = makeVert(x1, y1, tex, 65535, 65535);
|
||||
verts[start + 5] = makeVert(x1, y0, tex, 65535, 0);
|
||||
}
|
||||
|
||||
AtlasLayersPreview.emplace(VkInst, verts.size() * sizeof(NodeVertexStatic));
|
||||
std::memcpy(AtlasLayersPreview->mapMemory(), verts.data(), verts.size() * sizeof(NodeVertexStatic));
|
||||
AtlasLayersPreview->unMapMemory();
|
||||
AtlasLayersPreviewCount = maxLayers;
|
||||
}
|
||||
|
||||
void VulkanRenderSession::pushStage(EnumRenderStage stage) {
|
||||
}
|
||||
|
||||
|
||||
@@ -418,6 +418,13 @@ private:
|
||||
*/
|
||||
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)
|
||||
{
|
||||
@@ -508,32 +515,58 @@ public:
|
||||
return Descriptor;
|
||||
}
|
||||
|
||||
uint16_t getTextureId(const TexturePipeline& pipe) {
|
||||
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);
|
||||
|
||||
uint16_t result = 0;
|
||||
if(atlasId <= std::numeric_limits<uint16_t>::max())
|
||||
result = static_cast<uint16_t>(atlasId);
|
||||
else
|
||||
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<std::tuple<AssetsTexture, Resource>> newOrChanged, std::vector<AssetsTexture> lost) {
|
||||
std::vector<AssetsTexture> onTexturesChanges(std::vector<TextureUpdate> newOrChanged, std::vector<AssetsTexture> lost) {
|
||||
std::lock_guard lock(Mutex);
|
||||
std::vector<AssetsTexture> result;
|
||||
|
||||
for(const auto& [key, res] : newOrChanged) {
|
||||
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());
|
||||
@@ -563,12 +596,19 @@ public:
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -579,9 +619,15 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
void update() {
|
||||
void update(double timeSeconds) {
|
||||
std::lock_guard lock(Mutex);
|
||||
if(!NeedsUpload || !Atlas)
|
||||
if(!Atlas)
|
||||
return;
|
||||
|
||||
if(Atlas->updateAnimatedPipelines(timeSeconds))
|
||||
NeedsUpload = true;
|
||||
|
||||
if(!NeedsUpload)
|
||||
return;
|
||||
|
||||
Atlas->flushNewPipelines();
|
||||
@@ -638,24 +684,94 @@ public:
|
||||
}
|
||||
|
||||
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() && (pipe.Pipeline.size() % 2u) == 0u) {
|
||||
std::vector<TexturePipelineProgram::Word> words;
|
||||
words.reserve(pipe.Pipeline.size() / 2u);
|
||||
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(pipe.Pipeline.data());
|
||||
for(size_t i = 0; i < pipe.Pipeline.size(); i += 2) {
|
||||
uint16_t lo = bytes[i];
|
||||
uint16_t hi = bytes[i + 1];
|
||||
words.push_back(static_cast<TexturePipelineProgram::Word>(lo | (hi << 8)));
|
||||
}
|
||||
pipeline._Pipeline.assign(words.begin(), words.end());
|
||||
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())
|
||||
pipeline = ::Pipeline(pipe.BinTextures.front());
|
||||
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);
|
||||
@@ -690,7 +806,8 @@ private:
|
||||
|
||||
std::shared_ptr<SharedStagingBuffer> AtlasStaging;
|
||||
std::unique_ptr<PipelinedTextureAtlas> Atlas;
|
||||
std::unordered_map<TexturePipeline, uint16_t> PipelineToAtlas;
|
||||
std::unordered_map<TexturePipeline, uint32_t> PipelineToAtlas;
|
||||
std::unordered_map<AssetsTexture, AnimatedSource> AnimatedSources;
|
||||
|
||||
bool NeedsUpload = false;
|
||||
Logger LOG = "Client>TextureProvider";
|
||||
@@ -811,7 +928,7 @@ public:
|
||||
}
|
||||
std::vector<std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>> result;
|
||||
|
||||
std::unordered_map<TexturePipeline, uint16_t> pipelineResolveCache;
|
||||
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);
|
||||
@@ -886,7 +1003,7 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
uint16_t getTextureId(AssetsTexture texId) {
|
||||
uint32_t getTextureId(AssetsTexture texId) {
|
||||
if(texId == 0)
|
||||
return 0;
|
||||
|
||||
@@ -1134,6 +1251,10 @@ class VulkanRenderSession : public IRenderSession {
|
||||
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;
|
||||
|
||||
@@ -1187,7 +1308,7 @@ public:
|
||||
return glm::translate(glm::mat4(quat), camOffset);
|
||||
}
|
||||
|
||||
void beforeDraw();
|
||||
void beforeDraw(double timeSeconds);
|
||||
void onGpuFinished();
|
||||
void drawWorld(GlobalTime gTime, float dTime, VkCommandBuffer drawCmd);
|
||||
void pushStage(EnumRenderStage stage);
|
||||
@@ -1195,6 +1316,9 @@ public:
|
||||
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();
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "Abstract.hpp"
|
||||
#include "Client/Vulkan/AtlasPipeline/TexturePipelineProgram.hpp"
|
||||
#include "Common/Net.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include <boost/interprocess/file_mapping.hpp>
|
||||
@@ -6,6 +7,8 @@
|
||||
#include "boost/json.hpp"
|
||||
#include "sha2.hpp"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <boost/iostreams/filtering_streambuf.hpp>
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
#include <boost/iostreams/filter/zlib.hpp>
|
||||
@@ -15,6 +18,7 @@
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
|
||||
@@ -22,6 +26,50 @@ namespace LV {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
PrecompiledTexturePipeline compileTexturePipeline(const std::string &cmd, std::string_view defaultDomain) {
|
||||
PrecompiledTexturePipeline result;
|
||||
|
||||
std::string_view view(cmd);
|
||||
const size_t trimPos = view.find_first_not_of(" \t\r\n");
|
||||
if(trimPos == std::string_view::npos)
|
||||
MAKE_ERROR("Пустая текстурная команда");
|
||||
|
||||
view = view.substr(trimPos);
|
||||
|
||||
const bool isPipeline = view.size() >= 3
|
||||
&& view.compare(0, 3, "tex") == 0
|
||||
&& (view.size() == 3 || std::isspace(static_cast<unsigned char>(view[3])));
|
||||
|
||||
if(!isPipeline) {
|
||||
auto [domain, key] = parseDomainKey(std::string(view), defaultDomain);
|
||||
result.Assets.emplace_back(std::move(domain), std::move(key));
|
||||
return result;
|
||||
}
|
||||
|
||||
TexturePipelineProgram program;
|
||||
std::string err;
|
||||
if(!program.compile(std::string(view), &err)) {
|
||||
MAKE_ERROR("Ошибка разбора pipeline: " << err);
|
||||
}
|
||||
|
||||
result.IsSource = true;
|
||||
result.Pipeline.assign(reinterpret_cast<const char8_t*>(view.data()), view.size());
|
||||
|
||||
std::unordered_set<std::string> seen;
|
||||
for(const auto& patch : program.patches()) {
|
||||
auto [domain, key] = parseDomainKey(patch.Name, defaultDomain);
|
||||
std::string token;
|
||||
token.reserve(domain.size() + key.size() + 1);
|
||||
token.append(domain);
|
||||
token.push_back(':');
|
||||
token.append(key);
|
||||
if(seen.insert(token).second)
|
||||
result.Assets.emplace_back(std::move(domain), std::move(key));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
CompressedVoxels compressVoxels_byte(const std::vector<VoxelCube>& voxels) {
|
||||
std::u8string compressed;
|
||||
@@ -1089,6 +1137,10 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
||||
};
|
||||
|
||||
std::vector<std::variant<EnumTokenKind, std::string_view, int, uint16_t>> tokens;
|
||||
|
||||
if(expression.empty())
|
||||
tokens.push_back(int(1));
|
||||
|
||||
ssize_t pos = 0;
|
||||
auto skipWS = [&](){ while(pos<expression.size() && std::isspace((unsigned char) expression[pos])) ++pos; };
|
||||
|
||||
|
||||
@@ -516,6 +516,8 @@ struct PrecompiledTexturePipeline {
|
||||
std::vector<std::pair<std::string, std::string>> Assets;
|
||||
// Чистый код текстурных преобразований, локальные идентификаторы связаны с Assets
|
||||
std::u8string Pipeline;
|
||||
// Pipeline содержит исходный текст (tex ...), нужен для компиляции на сервере
|
||||
bool IsSource = false;
|
||||
};
|
||||
|
||||
struct TexturePipeline {
|
||||
@@ -530,15 +532,7 @@ struct TexturePipeline {
|
||||
};
|
||||
|
||||
// Компилятор текстурных потоков
|
||||
inline PrecompiledTexturePipeline compileTexturePipeline(const std::string &cmd, const std::string_view defaultDomain = "core") {
|
||||
PrecompiledTexturePipeline result;
|
||||
|
||||
auto [domain, key] = parseDomainKey(cmd, defaultDomain);
|
||||
|
||||
result.Assets.emplace_back(domain, key);
|
||||
|
||||
return result;
|
||||
}
|
||||
PrecompiledTexturePipeline compileTexturePipeline(const std::string &cmd, std::string_view defaultDomain = "core");
|
||||
|
||||
struct NodestateEntry {
|
||||
std::string Name;
|
||||
|
||||
253
Src/Common/AssetsPreloader.hpp
Normal file
253
Src/Common/AssetsPreloader.hpp
Normal file
@@ -0,0 +1,253 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include "Common/Async.hpp"
|
||||
#include "TOSAsync.hpp"
|
||||
#include "boost/asio/executor.hpp"
|
||||
#include "boost/asio/experimental/channel.hpp"
|
||||
#include "boost/asio/this_coro.hpp"
|
||||
#include "sha2.hpp"
|
||||
|
||||
/*
|
||||
Класс отвечает за отслеживание изменений и подгрузки медиаресурсов в указанных директориях.
|
||||
Медиаресурсы, собранные из папки assets или зарегистрированные модами.
|
||||
Хранит все данные в оперативной памяти.
|
||||
*/
|
||||
|
||||
|
||||
enum class EnumAssets : int {
|
||||
Nodestate, Particle, Animation, Model, Texture, Sound, Font, MAX_ENUM
|
||||
};
|
||||
|
||||
using AssetsNodestate = uint32_t;
|
||||
using AssetsParticle = uint32_t;
|
||||
using AssetsAnimation = uint32_t;
|
||||
using AssetsModel = uint32_t;
|
||||
using AssetsTexture = uint32_t;
|
||||
using AssetsSound = uint32_t;
|
||||
using AssetsFont = uint32_t;
|
||||
|
||||
static constexpr const char* EnumAssetsToDirectory(EnumAssets value) {
|
||||
switch(value) {
|
||||
case EnumAssets::Nodestate: return "nodestate";
|
||||
case EnumAssets::Particle: return "particles";
|
||||
case EnumAssets::Animation: return "animations";
|
||||
case EnumAssets::Model: return "models";
|
||||
case EnumAssets::Texture: return "textures";
|
||||
case EnumAssets::Sound: return "sounds";
|
||||
case EnumAssets::Font: return "fonts";
|
||||
default:
|
||||
}
|
||||
|
||||
assert(!"Неизвестный тип медиаресурса");
|
||||
}
|
||||
|
||||
namespace LV {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
struct ResourceFile {
|
||||
using Hash_t = sha2::sha256_hash; // boost::uuids::detail::sha1::digest_type;
|
||||
|
||||
Hash_t Hash;
|
||||
std::vector<std::byte> Data;
|
||||
|
||||
void calcHash() {
|
||||
Hash = sha2::sha256((const uint8_t*) Data.data(), Data.size());
|
||||
}
|
||||
};
|
||||
|
||||
class AssetsPreloader : public TOS::IAsyncDestructible {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<AssetsPreloader>;
|
||||
|
||||
//
|
||||
struct ReloadResult {
|
||||
};
|
||||
|
||||
struct ReloadStatus {
|
||||
/// TODO: callback'и для обновления статусов
|
||||
/// TODO: многоуровневый статус std::vector<std::string>. Этапы/Шаги/Объекты
|
||||
};
|
||||
|
||||
public:
|
||||
static coro<Ptr> Create(asio::io_context& ioc);
|
||||
~AssetsPreloader() = default;
|
||||
|
||||
AssetsPreloader(const AssetsPreloader&) = delete;
|
||||
AssetsPreloader(AssetsPreloader&&) = delete;
|
||||
AssetsPreloader& operator=(const AssetsPreloader&) = delete;
|
||||
AssetsPreloader& operator=(AssetsPreloader&&) = delete;
|
||||
|
||||
// Пересматривает ресурсы и выдаёт изменения.
|
||||
// Одновременно можно работать только один такой вызов.
|
||||
// instances -> пути к директории с assets или архивы с assets внутри. От низшего приоритета к высшему.
|
||||
// status -> обратный отклик о процессе обновления ресурсов.
|
||||
// ReloadStatus <- новые и потерянные ресурсы.
|
||||
coro<ReloadResult> reloadResources(const std::vector<fs::path>& instances, ReloadStatus* status = nullptr) {
|
||||
bool expected = false;
|
||||
assert(Reloading_.compare_exchange_strong(expected, true) && "Двойной вызов reloadResources");
|
||||
|
||||
try {
|
||||
ReloadStatus secondStatus;
|
||||
co_return _reloadResources(instances, status ? *status : secondStatus);
|
||||
} catch(...) {
|
||||
assert(!"reloadResources: здесь не должно быть ошибок");
|
||||
}
|
||||
|
||||
Reloading_.exchange(false);
|
||||
}
|
||||
|
||||
private:
|
||||
struct ResourceFirstStageInfo {
|
||||
// Путь к архиву (если есть), и путь до ресурса
|
||||
fs::path ArchivePath, Path;
|
||||
// Время изменения файла
|
||||
fs::file_time_type Timestamp;
|
||||
};
|
||||
|
||||
struct ResourceSecondStageInfo : public ResourceFirstStageInfo {
|
||||
// Обезличенный ресурс
|
||||
std::shared_ptr<std::vector<uint8_t>> Resource;
|
||||
ResourceFile::Hash_t Hash;
|
||||
// Сырой заголовок
|
||||
std::vector<std::string> Dependencies;
|
||||
};
|
||||
|
||||
/*
|
||||
Ресурс имеет бинарную часть, из который вырезаны все зависимости.
|
||||
Вторая часть это заголовок, которые всегда динамично передаётся с сервера.
|
||||
В заголовке хранятся зависимости от ресурсов.
|
||||
*/
|
||||
struct MediaResource {
|
||||
std::string Domain, Key;
|
||||
|
||||
fs::file_time_type Timestamp;
|
||||
// Обезличенный ресурс
|
||||
std::shared_ptr<std::vector<uint8_t>> Resource;
|
||||
// Хэш ресурса
|
||||
ResourceFile::Hash_t Hash;
|
||||
|
||||
// Скомпилированный заголовок
|
||||
std::vector<uint8_t> Dependencies;
|
||||
};
|
||||
|
||||
AssetsPreloader(asio::io_context& ioc)
|
||||
: TOS::IAsyncDestructible(ioc)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Текущее состояние reloadResources
|
||||
std::atomic<bool> Reloading_ = false;
|
||||
|
||||
// Пересмотр ресурсов
|
||||
coro<ReloadResult> _reloadResources(const std::vector<fs::path>& instances, ReloadStatus& status) const {
|
||||
// 1) Поиск всех ресурсов и построение конечной карты ресурсов (timestamps, path, name, size)
|
||||
// Карта найденных ресурсов
|
||||
std::unordered_map<
|
||||
EnumAssets, // Тип ресурса
|
||||
std::unordered_map<
|
||||
std::string, // Domain
|
||||
std::unordered_map<
|
||||
std::string, // Key
|
||||
ResourceFirstStageInfo // ResourceInfo
|
||||
>
|
||||
>
|
||||
> resourcesFirstStage;
|
||||
|
||||
for (const fs::path& instance : instances) {
|
||||
try {
|
||||
if (fs::is_regular_file(instance)) {
|
||||
// Может архив
|
||||
/// TODO: пока не поддерживается
|
||||
} else if (fs::is_directory(instance)) {
|
||||
// Директория
|
||||
fs::path assets = instance / "assets";
|
||||
if (fs::exists(assets) && fs::is_directory(assets)) {
|
||||
// Директорию assets существует, перебираем домены в ней
|
||||
for (auto begin = fs::directory_iterator(assets), end = fs::directory_iterator(); begin != end; begin++) {
|
||||
if (!begin->is_directory())
|
||||
continue;
|
||||
|
||||
/// TODO: выглядит всё не очень асинхронно
|
||||
co_await asio::post(co_await asio::this_coro::executor);
|
||||
|
||||
fs::path domainPath = begin->path();
|
||||
std::string domain = domainPath.filename();
|
||||
|
||||
// Перебираем по типу ресурса
|
||||
for (EnumAssets assetType = EnumAssets(0); assetType < EnumAssets::MAX_ENUM; ((int&) assetType)++) {
|
||||
fs::path assetPath = domainPath / EnumAssetsToDirectory(assetType);
|
||||
|
||||
std::unordered_map<
|
||||
std::string, // Key
|
||||
ResourceFirstStageInfo // ResourceInfo
|
||||
>& firstStage = resourcesFirstStage[assetType][domain];
|
||||
|
||||
// Исследуем все ресурсы одного типа
|
||||
for (auto begin = fs::recursive_directory_iterator(assetPath), end = fs::recursive_directory_iterator(); begin != end; begin++) {
|
||||
if (begin->is_directory())
|
||||
continue;
|
||||
|
||||
fs::path file = begin->path();
|
||||
std::string key = fs::relative(file, domainPath).string();
|
||||
|
||||
// Работаем с ресурсом
|
||||
firstStage[key] = ResourceFirstStageInfo{
|
||||
.Path = file,
|
||||
.Timestamp = fs::last_write_time(file)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("Неизвестный тип инстанса медиаресурсов");
|
||||
}
|
||||
} catch (const std::exception& exc) {
|
||||
/// TODO: Логгировать в статусе
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Обрабатываться будут только изменённые (новый timestamp) или новые ресурсы
|
||||
// .meta
|
||||
|
||||
// Текстуры, шрифты, звуки хранить как есть
|
||||
// У моделей, состояний нод, анимации, частиц обналичить зависимости
|
||||
// Мета влияет только на хедер
|
||||
|
||||
/// TODO: реализовать реформатирование новых и изменённых ресурсов во внутренний обезличенный формат
|
||||
|
||||
co_await asio::post(co_await asio::this_coro::executor);
|
||||
|
||||
asio::experimental::channel<void()> ch(IOC, 8);
|
||||
|
||||
co_return ReloadResult{};
|
||||
}
|
||||
|
||||
std::unordered_map<
|
||||
EnumAssets, // Тип ресурса
|
||||
std::unordered_map<
|
||||
std::string, // Domain
|
||||
std::unordered_map<
|
||||
std::string, // Key
|
||||
uint32_t // ResourceId
|
||||
>
|
||||
>
|
||||
> DKToId;
|
||||
|
||||
std::unordered_map<
|
||||
EnumAssets, // Тип ресурса
|
||||
std::unordered_map<
|
||||
uint32_t,
|
||||
MediaResource // ResourceInfo
|
||||
>
|
||||
> MediaResources;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -4,6 +4,18 @@
|
||||
|
||||
namespace LV::Server {
|
||||
|
||||
Entity::Entity(DefEntityId defId)
|
||||
: DefId(defId)
|
||||
{
|
||||
ABBOX = {Pos::Object_t::BS, Pos::Object_t::BS, Pos::Object_t::BS};
|
||||
WorldId = 0;
|
||||
Pos = Pos::Object(0);
|
||||
Speed = Pos::Object(0);
|
||||
Acceleration = Pos::Object(0);
|
||||
Quat = glm::quat(1.f, 0.f, 0.f, 0.f);
|
||||
InRegionPos = Pos::GlobalRegion(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace std {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "AssetsManager.hpp"
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "Client/Vulkan/AtlasPipeline/TexturePipelineProgram.hpp"
|
||||
#include "boost/json.hpp"
|
||||
#include "png++/rgb_pixel.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <png.h>
|
||||
@@ -560,10 +562,34 @@ AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const
|
||||
|
||||
// Ресолвим текстуры
|
||||
std::variant<LV::PreparedModel, PreparedGLTF> model = _model;
|
||||
std::visit([&lock](auto& val) {
|
||||
std::visit([&lock, &domain](auto& val) {
|
||||
for(const auto& [key, pipeline] : val.Textures) {
|
||||
TexturePipeline pipe;
|
||||
if(pipeline.IsSource) {
|
||||
std::string source(reinterpret_cast<const char*>(pipeline.Pipeline.data()), pipeline.Pipeline.size());
|
||||
TexturePipelineProgram program;
|
||||
std::string err;
|
||||
if(!program.compile(source, &err)) {
|
||||
MAKE_ERROR("Ошибка компиляции pipeline: " << err);
|
||||
}
|
||||
|
||||
auto resolver = [&](std::string_view name) -> std::optional<uint32_t> {
|
||||
auto [texDomain, texKey] = parseDomainKey(std::string(name), domain);
|
||||
return lock->getId(EnumAssets::Texture, texDomain, texKey);
|
||||
};
|
||||
|
||||
if(!program.link(resolver, &err)) {
|
||||
MAKE_ERROR("Ошибка линковки pipeline: " << err);
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> bytes = program.toBytes();
|
||||
pipe.Pipeline.resize(bytes.size());
|
||||
if(!bytes.empty()) {
|
||||
std::memcpy(pipe.Pipeline.data(), bytes.data(), bytes.size());
|
||||
}
|
||||
} else {
|
||||
pipe.Pipeline = pipeline.Pipeline;
|
||||
}
|
||||
|
||||
for(const auto& [domain, key] : pipeline.Assets) {
|
||||
ResourceId texId = lock->getId(EnumAssets::Texture, domain, key);
|
||||
|
||||
@@ -80,6 +80,16 @@ void ContentManager::registerBase_World(ResourceId id, const std::string& domain
|
||||
world.emplace();
|
||||
}
|
||||
|
||||
void ContentManager::registerBase_Entity(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile) {
|
||||
std::optional<DefEntity>& entity = getEntry_Entity(id);
|
||||
if(!entity)
|
||||
entity.emplace();
|
||||
|
||||
DefEntity& def = *entity;
|
||||
def.Domain = domain;
|
||||
def.Key = key;
|
||||
}
|
||||
|
||||
void ContentManager::registerBase(EnumDefContent type, const std::string& domain, const std::string& key, const sol::table& profile)
|
||||
{
|
||||
ResourceId id = getId(type, domain, key);
|
||||
@@ -89,6 +99,8 @@ void ContentManager::registerBase(EnumDefContent type, const std::string& domain
|
||||
registerBase_Node(id, domain, key, profile);
|
||||
else if(type == EnumDefContent::World)
|
||||
registerBase_World(id, domain, key, profile);
|
||||
else if(type == EnumDefContent::Entity)
|
||||
registerBase_Entity(id, domain, key, profile);
|
||||
else
|
||||
MAKE_ERROR("Не реализовано");
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@ class ContentManager {
|
||||
|
||||
void registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
||||
void registerBase_World(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
||||
void registerBase_Entity(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
||||
|
||||
public:
|
||||
ContentManager(AssetsManager &am);
|
||||
@@ -208,6 +209,10 @@ public:
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ResourceId getContentId(EnumDefContent type, const std::string& domain, const std::string& key) {
|
||||
return getId(type, domain, key);
|
||||
}
|
||||
|
||||
private:
|
||||
TOS::Logger LOG = "Server>ContentManager";
|
||||
AssetsManager& AM;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <glm/geometric.hpp>
|
||||
#include <glm/gtc/noise.hpp>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
@@ -853,30 +854,158 @@ void GameServer::BackingAsyncLua_t::run(int id) {
|
||||
lock->pop();
|
||||
}
|
||||
|
||||
//if(key.RegionPos == Pos::GlobalRegion(0, 0, 0))
|
||||
out.Voxels.clear();
|
||||
out.Entityes.clear();
|
||||
|
||||
{
|
||||
float *ptr = noise.data();
|
||||
for(int z = 0; z < 64; z++)
|
||||
for(int y = 0; y < 64; y++)
|
||||
for(int x = 0; x < 64; x++, ptr++) {
|
||||
DefVoxelId id = std::clamp(*ptr, 0.f, 1.f) * 3; //> 0.9 ? 1 : 0;
|
||||
constexpr DefNodeId kNodeAir = 0;
|
||||
constexpr DefNodeId kNodeGrass = 2;
|
||||
constexpr uint8_t kMetaGrass = 1;
|
||||
constexpr DefNodeId kNodeDirt = 3;
|
||||
constexpr DefNodeId kNodeStone = 4;
|
||||
constexpr DefNodeId kNodeWood = 1;
|
||||
constexpr DefNodeId kNodeLeaves = 5;
|
||||
constexpr DefNodeId kNodeLava = 7;
|
||||
constexpr DefNodeId kNodeWater = 8;
|
||||
constexpr DefNodeId kNodeFire = 9;
|
||||
|
||||
auto hash32 = [](uint32_t x) {
|
||||
x ^= x >> 16;
|
||||
x *= 0x7feb352dU;
|
||||
x ^= x >> 15;
|
||||
x *= 0x846ca68bU;
|
||||
x ^= x >> 16;
|
||||
return x;
|
||||
};
|
||||
|
||||
Pos::GlobalNode regionBase = key.RegionPos;
|
||||
regionBase <<= 6;
|
||||
|
||||
std::array<int, 64*64> heights;
|
||||
for(int z = 0; z < 64; z++) {
|
||||
for(int x = 0; x < 64; x++) {
|
||||
int32_t gx = regionBase.x + x;
|
||||
int32_t gz = regionBase.z + z;
|
||||
float fx = float(gx);
|
||||
float fz = float(gz);
|
||||
|
||||
float base = glm::perlin(glm::vec2(fx * 0.005f, fz * 0.005f));
|
||||
float detail = glm::perlin(glm::vec2(fx * 0.02f, fz * 0.02f)) * 0.35f;
|
||||
float ridge = glm::perlin(glm::vec2(fx * 0.0015f, fz * 0.0015f));
|
||||
float ridged = 1.f - std::abs(ridge);
|
||||
float mountains = ridged * ridged;
|
||||
float noiseDetail = noise[(z * 64) + x];
|
||||
|
||||
float height = 18.f + (base + detail) * 8.f + mountains * 32.f + noiseDetail * 3.f;
|
||||
int h = std::clamp<int>(int(height + 0.5f), -256, 256);
|
||||
heights[z * 64 + x] = h;
|
||||
}
|
||||
}
|
||||
|
||||
for(int z = 0; z < 64; z++) {
|
||||
for(int x = 0; x < 64; x++) {
|
||||
int surface = heights[z * 64 + x];
|
||||
int32_t gx = regionBase.x + x;
|
||||
int32_t gz = regionBase.z + z;
|
||||
uint32_t seed = hash32(uint32_t(gx) * 73856093u ^ uint32_t(gz) * 19349663u);
|
||||
|
||||
for(int y = 0; y < 64; y++) {
|
||||
int32_t gy = regionBase.y + y;
|
||||
Pos::bvec64u nodePos(x, y, z);
|
||||
auto &node = out.Nodes[Pos::bvec4u(nodePos >> 4).pack()][Pos::bvec16u(nodePos & 0xf).pack()];
|
||||
|
||||
if(gy <= surface) {
|
||||
if(gy == surface) {
|
||||
node.NodeId = kNodeGrass;
|
||||
node.Meta = kMetaGrass;
|
||||
} else if(gy >= surface - 3) {
|
||||
node.NodeId = kNodeDirt;
|
||||
node.Meta = uint8_t((seed + gy) & 0x3);
|
||||
} else {
|
||||
node.NodeId = kNodeStone;
|
||||
node.Meta = uint8_t((seed + gy + 1) & 0x3);
|
||||
}
|
||||
} else {
|
||||
node.Data = kNodeAir;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto setNode = [&](int x, int y, int z, DefNodeId id, uint8_t meta, bool onlyAir) {
|
||||
if(x < 0 || x >= 64 || y < 0 || y >= 64 || z < 0 || z >= 64)
|
||||
return;
|
||||
|
||||
Pos::bvec64u nodePos(x, y, z);
|
||||
auto &node = out.Nodes[Pos::bvec4u(nodePos >> 4).pack()][Pos::bvec16u(nodePos & 0xf).pack()];
|
||||
if(onlyAir && node.Data != 0)
|
||||
return;
|
||||
|
||||
node.NodeId = id;
|
||||
node.Meta = meta;
|
||||
};
|
||||
|
||||
if(x == 0 && z == 0)
|
||||
node.NodeId = 1;
|
||||
else if(y == 0 && z == 0)
|
||||
node.NodeId = 2;
|
||||
else if(x == 0 && y == 0)
|
||||
node.NodeId = 3;
|
||||
for(int z = 1; z < 63; z++) {
|
||||
for(int x = 1; x < 63; x++) {
|
||||
int surface = heights[z * 64 + x];
|
||||
int localY = surface - regionBase.y;
|
||||
if(localY < 1 || localY >= 63)
|
||||
continue;
|
||||
|
||||
if(y == 1 && z == 0)
|
||||
node.NodeId = 0;
|
||||
else if(x == 0 && y == 1)
|
||||
node.NodeId = 0;
|
||||
int32_t gx = regionBase.x + x;
|
||||
int32_t gz = regionBase.z + z;
|
||||
uint32_t seed = hash32(uint32_t(gx) * 83492791u ^ uint32_t(gz) * 2971215073u);
|
||||
|
||||
node.Meta = uint8_t((x + y + z + int(node.NodeId)) & 0x3);
|
||||
int treeHeight = 4 + int(seed % 3);
|
||||
if(localY + treeHeight + 2 >= 64)
|
||||
continue;
|
||||
|
||||
if((seed % 97) >= 2)
|
||||
continue;
|
||||
|
||||
int diff = surface - heights[z * 64 + (x - 1)];
|
||||
if(diff > 2 || diff < -2)
|
||||
continue;
|
||||
diff = surface - heights[z * 64 + (x + 1)];
|
||||
if(diff > 2 || diff < -2)
|
||||
continue;
|
||||
diff = surface - heights[(z - 1) * 64 + x];
|
||||
if(diff > 2 || diff < -2)
|
||||
continue;
|
||||
diff = surface - heights[(z + 1) * 64 + x];
|
||||
if(diff > 2 || diff < -2)
|
||||
continue;
|
||||
|
||||
uint8_t woodMeta = uint8_t((seed >> 2) & 0x3);
|
||||
uint8_t leafMeta = uint8_t((seed >> 4) & 0x3);
|
||||
|
||||
for(int i = 1; i <= treeHeight; i++) {
|
||||
setNode(x, localY + i, z, kNodeWood, woodMeta, false);
|
||||
}
|
||||
|
||||
int topY = localY + treeHeight;
|
||||
for(int dy = -2; dy <= 2; dy++) {
|
||||
for(int dz = -2; dz <= 2; dz++) {
|
||||
for(int dx = -2; dx <= 2; dx++) {
|
||||
int dist2 = dx * dx + dz * dz + dy * dy;
|
||||
if(dist2 > 5)
|
||||
continue;
|
||||
|
||||
setNode(x + dx, topY + dy, z + dz, kNodeLeaves, leafMeta, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(regionBase.x == 0 && regionBase.z == 0) {
|
||||
constexpr int kTestGlobalY = 64;
|
||||
if(regionBase.y <= kTestGlobalY && (regionBase.y + 63) >= kTestGlobalY) {
|
||||
int localY = kTestGlobalY - regionBase.y;
|
||||
setNode(2, localY, 2, kNodeLava, 0, false);
|
||||
setNode(4, localY, 2, kNodeWater, 0, false);
|
||||
setNode(6, localY, 2, kNodeFire, 0, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// else {
|
||||
@@ -1447,6 +1576,8 @@ void GameServer::init(fs::path worldPath) {
|
||||
sol::table t = LuaMainState.create_table();
|
||||
// Content.CM.registerBase(EnumDefContent::Node, "core", "none", t);
|
||||
Content.CM.registerBase(EnumDefContent::World, "test", "devel_world", t);
|
||||
Content.CM.registerBase(EnumDefContent::Entity, "core", "player", t);
|
||||
PlayerEntityDefId = Content.CM.getContentId(EnumDefContent::Entity, "core", "player");
|
||||
}
|
||||
|
||||
initLuaPre();
|
||||
@@ -1706,7 +1837,27 @@ void GameServer::stepConnections() {
|
||||
auto wIter = Expanse.Worlds.find(wPair.first);
|
||||
assert(wIter != Expanse.Worlds.end());
|
||||
|
||||
wIter->second->onRemoteClient_RegionsLost(cec, wPair.second);
|
||||
wIter->second->onRemoteClient_RegionsLost(wPair.first, cec, wPair.second);
|
||||
}
|
||||
|
||||
if(cec->PlayerEntity) {
|
||||
ServerEntityId_t entityId = *cec->PlayerEntity;
|
||||
auto [worldId, regionPos, entityIndex] = entityId;
|
||||
auto iterWorld = Expanse.Worlds.find(worldId);
|
||||
if(iterWorld != Expanse.Worlds.end()) {
|
||||
auto iterRegion = iterWorld->second->Regions.find(regionPos);
|
||||
if(iterRegion != iterWorld->second->Regions.end()) {
|
||||
Region& region = *iterRegion->second;
|
||||
if(entityIndex < region.Entityes.size())
|
||||
region.Entityes[entityIndex].IsRemoved = true;
|
||||
|
||||
std::vector<ServerEntityId_t> removed = {entityId};
|
||||
for(const std::shared_ptr<RemoteClient>& observer : region.RMs) {
|
||||
observer->prepareEntitiesRemove(removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
cec->clearPlayerEntity();
|
||||
}
|
||||
|
||||
std::string username = cec->Username;
|
||||
@@ -1811,7 +1962,7 @@ IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
|
||||
auto iterWorld = Expanse.Worlds.find(worldId);
|
||||
assert(iterWorld != Expanse.Worlds.end());
|
||||
|
||||
std::vector<Pos::GlobalRegion> notLoaded = iterWorld->second->onRemoteClient_RegionsEnter(remoteClient, regions);
|
||||
std::vector<Pos::GlobalRegion> notLoaded = iterWorld->second->onRemoteClient_RegionsEnter(worldId, remoteClient, regions);
|
||||
if(!notLoaded.empty()) {
|
||||
// Добавляем к списку на загрузку
|
||||
std::vector<Pos::GlobalRegion> &tl = toDB.Load[worldId];
|
||||
@@ -1824,7 +1975,7 @@ IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
|
||||
auto iterWorld = Expanse.Worlds.find(worldId);
|
||||
assert(iterWorld != Expanse.Worlds.end());
|
||||
|
||||
iterWorld->second->onRemoteClient_RegionsLost(remoteClient, regions);
|
||||
iterWorld->second->onRemoteClient_RegionsLost(worldId, remoteClient, regions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1929,13 +2080,116 @@ void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db
|
||||
|
||||
iterWorld->second->pushRegions(std::move(regions));
|
||||
for(auto& [cec, poses] : toSubscribe) {
|
||||
iterWorld->second->onRemoteClient_RegionsEnter(cec, poses);
|
||||
iterWorld->second->onRemoteClient_RegionsEnter(worldId, cec, poses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::stepPlayerProceed() {
|
||||
auto iterWorld = Expanse.Worlds.find(0);
|
||||
if(iterWorld == Expanse.Worlds.end())
|
||||
return;
|
||||
|
||||
World& world = *iterWorld->second;
|
||||
|
||||
for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
|
||||
if(!remoteClient)
|
||||
continue;
|
||||
|
||||
Pos::Object pos = remoteClient->CameraPos;
|
||||
Pos::GlobalRegion regionPos = Pos::Object_t::asRegionsPos(pos);
|
||||
glm::quat quat = remoteClient->CameraQuat.toQuat();
|
||||
|
||||
if(!remoteClient->PlayerEntity) {
|
||||
auto iterRegion = world.Regions.find(regionPos);
|
||||
if(iterRegion == world.Regions.end())
|
||||
continue;
|
||||
|
||||
Entity entity(PlayerEntityDefId);
|
||||
entity.WorldId = iterWorld->first;
|
||||
entity.Pos = pos;
|
||||
entity.Quat = quat;
|
||||
entity.InRegionPos = regionPos;
|
||||
|
||||
Region& region = *iterRegion->second;
|
||||
RegionEntityId_t entityIndex = region.pushEntity(entity);
|
||||
if(entityIndex == RegionEntityId_t(-1))
|
||||
continue;
|
||||
|
||||
ServerEntityId_t entityId = {iterWorld->first, regionPos, entityIndex};
|
||||
remoteClient->setPlayerEntity(entityId);
|
||||
|
||||
std::vector<std::tuple<ServerEntityId_t, const Entity*>> updates;
|
||||
updates.emplace_back(entityId, ®ion.Entityes[entityIndex]);
|
||||
for(const std::shared_ptr<RemoteClient>& observer : region.RMs) {
|
||||
observer->prepareEntitiesUpdate(updates);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ServerEntityId_t entityId = *remoteClient->PlayerEntity;
|
||||
auto [worldId, prevRegion, entityIndex] = entityId;
|
||||
auto iterRegion = world.Regions.find(prevRegion);
|
||||
if(iterRegion == world.Regions.end()) {
|
||||
remoteClient->clearPlayerEntity();
|
||||
continue;
|
||||
}
|
||||
|
||||
Region& region = *iterRegion->second;
|
||||
if(entityIndex >= region.Entityes.size() || region.Entityes[entityIndex].IsRemoved) {
|
||||
remoteClient->clearPlayerEntity();
|
||||
continue;
|
||||
}
|
||||
|
||||
Entity& entity = region.Entityes[entityIndex];
|
||||
Pos::GlobalRegion nextRegion = Pos::Object_t::asRegionsPos(pos);
|
||||
if(nextRegion != prevRegion) {
|
||||
entity.IsRemoved = true;
|
||||
std::vector<ServerEntityId_t> removed = {entityId};
|
||||
for(const std::shared_ptr<RemoteClient>& observer : region.RMs) {
|
||||
observer->prepareEntitiesRemove(removed);
|
||||
}
|
||||
|
||||
remoteClient->clearPlayerEntity();
|
||||
|
||||
auto iterNewRegion = world.Regions.find(nextRegion);
|
||||
if(iterNewRegion == world.Regions.end())
|
||||
continue;
|
||||
|
||||
Entity nextEntity(PlayerEntityDefId);
|
||||
nextEntity.WorldId = iterWorld->first;
|
||||
nextEntity.Pos = pos;
|
||||
nextEntity.Quat = quat;
|
||||
nextEntity.InRegionPos = nextRegion;
|
||||
|
||||
Region& newRegion = *iterNewRegion->second;
|
||||
RegionEntityId_t nextIndex = newRegion.pushEntity(nextEntity);
|
||||
if(nextIndex == RegionEntityId_t(-1))
|
||||
continue;
|
||||
|
||||
ServerEntityId_t nextId = {iterWorld->first, nextRegion, nextIndex};
|
||||
remoteClient->setPlayerEntity(nextId);
|
||||
|
||||
std::vector<std::tuple<ServerEntityId_t, const Entity*>> updates;
|
||||
updates.emplace_back(nextId, &newRegion.Entityes[nextIndex]);
|
||||
for(const std::shared_ptr<RemoteClient>& observer : newRegion.RMs) {
|
||||
observer->prepareEntitiesUpdate(updates);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
entity.Pos = pos;
|
||||
entity.Quat = quat;
|
||||
entity.WorldId = iterWorld->first;
|
||||
entity.InRegionPos = prevRegion;
|
||||
|
||||
std::vector<std::tuple<ServerEntityId_t, const Entity*>> updates;
|
||||
updates.emplace_back(entityId, &entity);
|
||||
for(const std::shared_ptr<RemoteClient>& observer : region.RMs) {
|
||||
observer->prepareEntitiesUpdate(updates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::stepWorldPhysic() {
|
||||
|
||||
@@ -267,6 +267,7 @@ class GameServer : public AsyncObject {
|
||||
// Идентификатор текущегго мода, находящевося в обработке
|
||||
std::string CurrentModId;
|
||||
AssetsManager::AssetsRegister AssetsInit;
|
||||
DefEntityId PlayerEntityDefId = 0;
|
||||
|
||||
public:
|
||||
GameServer(asio::io_context &ioc, fs::path worldPath);
|
||||
|
||||
@@ -366,8 +366,35 @@ void RemoteClient::NetworkAndResource_t::prepareEntitiesUpdate(const std::vector
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: отправить клиенту
|
||||
ResUses_t::RefEntity_t refEntity;
|
||||
refEntity.Profile = entity->getDefId();
|
||||
if(!ResUses.RefDefEntity.contains(refEntity.Profile))
|
||||
ResUses.RefDefEntity[refEntity.Profile] = {};
|
||||
|
||||
checkPacketBorder(32);
|
||||
NextPacket << (uint8_t) ToClient::L1::Content
|
||||
<< (uint8_t) ToClient::L2Content::Entity
|
||||
<< ceId
|
||||
<< (uint32_t) refEntity.Profile
|
||||
<< (uint32_t) entity->WorldId
|
||||
<< entity->Pos.x
|
||||
<< entity->Pos.y
|
||||
<< entity->Pos.z;
|
||||
|
||||
{
|
||||
ToServer::PacketQuat q;
|
||||
q.fromQuat(entity->Quat);
|
||||
for(int iter = 0; iter < 5; iter++)
|
||||
NextPacket << q.Data[iter];
|
||||
}
|
||||
|
||||
ResUses.RefEntity[entityId] = std::move(refEntity);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::NetworkAndResource_t::prepareEntitiesUpdate_Dynamic(const std::vector<std::tuple<ServerEntityId_t, const Entity*>>& entities)
|
||||
{
|
||||
prepareEntitiesUpdate(entities);
|
||||
}
|
||||
|
||||
void RemoteClient::NetworkAndResource_t::prepareEntitySwap(ServerEntityId_t prev, ServerEntityId_t next)
|
||||
@@ -400,6 +427,8 @@ void RemoteClient::NetworkAndResource_t::prepareEntitiesRemove(const std::vector
|
||||
ResUses.RefDefEntity.erase(iterProfileRef);
|
||||
ResUses.DefEntity.erase(iterProfile);
|
||||
}
|
||||
|
||||
ResUses.RefEntity.erase(iterEntity);
|
||||
}
|
||||
|
||||
checkPacketBorder(16);
|
||||
@@ -489,7 +518,12 @@ void RemoteClient::NetworkAndResource_t::prepareWorldRemove(WorldId_t worldId)
|
||||
// void RemoteClient::NetworkAndResource_t::preparePortalRemove(PortalId portalId) {}
|
||||
|
||||
void RemoteClient::prepareCameraSetEntity(ServerEntityId_t entityId) {
|
||||
|
||||
auto lock = NetworkAndResource.lock();
|
||||
ClientEntityId_t cId = lock->ReMapEntities.toClient(entityId);
|
||||
lock->checkPacketBorder(8);
|
||||
lock->NextPacket << (uint8_t) ToClient::L1::System
|
||||
<< (uint8_t) ToClient::L2System::LinkCameraToEntity
|
||||
<< cId;
|
||||
}
|
||||
|
||||
ResourceRequest RemoteClient::pushPreparedPackets() {
|
||||
@@ -679,15 +713,19 @@ void RemoteClient::NetworkAndResource_t::informateDefPortal(const std::vector<st
|
||||
|
||||
void RemoteClient::NetworkAndResource_t::informateDefEntity(const std::vector<std::pair<DefEntityId, DefEntity*>>& entityes)
|
||||
{
|
||||
// for(auto pair : entityes) {
|
||||
// DefEntityId_t id = pair.first;
|
||||
// if(!ResUses.DefEntity.contains(id))
|
||||
// continue;
|
||||
for(auto pair : entityes) {
|
||||
DefEntityId id = pair.first;
|
||||
if(!ResUses.DefEntity.contains(id))
|
||||
continue;
|
||||
|
||||
// NextPacket << (uint8_t) ToClient::L1::Definition
|
||||
// << (uint8_t) ToClient::L2Definition::Entity
|
||||
// << id;
|
||||
// }
|
||||
checkPacketBorder(8);
|
||||
NextPacket << (uint8_t) ToClient::L1::Definition
|
||||
<< (uint8_t) ToClient::L2Definition::Entity
|
||||
<< id;
|
||||
|
||||
if(!ResUses.RefDefEntity.contains(id))
|
||||
ResUses.RefDefEntity[id] = {};
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::NetworkAndResource_t::informateDefItem(const std::vector<std::pair<DefItemId, DefItem*>>& items)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <Common/Abstract.hpp>
|
||||
#include <bitset>
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
@@ -336,6 +337,7 @@ public:
|
||||
// Если игрок пересекал границы региона (для перерасчёта ContentViewState)
|
||||
bool CrossedRegion = true;
|
||||
std::queue<Pos::GlobalNode> Build, Break;
|
||||
std::optional<ServerEntityId_t> PlayerEntity;
|
||||
|
||||
public:
|
||||
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, GameServer* server)
|
||||
@@ -347,6 +349,9 @@ public:
|
||||
coro<> run();
|
||||
void shutdown(EnumDisconnect type, const std::string reason);
|
||||
bool isConnected() { return IsConnected; }
|
||||
void setPlayerEntity(ServerEntityId_t id) { PlayerEntity = id; }
|
||||
std::optional<ServerEntityId_t> getPlayerEntity() const { return PlayerEntity; }
|
||||
void clearPlayerEntity() { PlayerEntity.reset(); }
|
||||
|
||||
void pushPackets(std::vector<Net::Packet> *simplePackets, std::vector<Net::SmartPacket> *smartPackets = nullptr) {
|
||||
if(IsGoingShutdown)
|
||||
|
||||
@@ -16,7 +16,7 @@ World::~World() {
|
||||
|
||||
}
|
||||
|
||||
std::vector<Pos::GlobalRegion> World::onRemoteClient_RegionsEnter(std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion>& enter) {
|
||||
std::vector<Pos::GlobalRegion> World::onRemoteClient_RegionsEnter(WorldId_t worldId, std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion>& enter) {
|
||||
std::vector<Pos::GlobalRegion> out;
|
||||
|
||||
for(const Pos::GlobalRegion &pos : enter) {
|
||||
@@ -43,18 +43,49 @@ std::vector<Pos::GlobalRegion> World::onRemoteClient_RegionsEnter(std::shared_pt
|
||||
nodes[Pos::bvec4u(x, y, z)] = region.Nodes[Pos::bvec4u(x, y, z).pack()].data();
|
||||
}
|
||||
|
||||
if(!region.Entityes.empty()) {
|
||||
std::vector<std::tuple<ServerEntityId_t, const Entity*>> updates;
|
||||
updates.reserve(region.Entityes.size());
|
||||
|
||||
for(size_t iter = 0; iter < region.Entityes.size(); iter++) {
|
||||
const Entity& entity = region.Entityes[iter];
|
||||
if(entity.IsRemoved)
|
||||
continue;
|
||||
|
||||
ServerEntityId_t entityId = {worldId, pos, static_cast<RegionEntityId_t>(iter)};
|
||||
updates.emplace_back(entityId, &entity);
|
||||
}
|
||||
|
||||
if(!updates.empty())
|
||||
cec->prepareEntitiesUpdate(updates);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void World::onRemoteClient_RegionsLost(std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion> &lost) {
|
||||
void World::onRemoteClient_RegionsLost(WorldId_t worldId, std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion> &lost) {
|
||||
for(const Pos::GlobalRegion &pos : lost) {
|
||||
auto region = Regions.find(pos);
|
||||
if(region == Regions.end())
|
||||
continue;
|
||||
|
||||
if(!region->second->Entityes.empty()) {
|
||||
std::vector<ServerEntityId_t> removed;
|
||||
removed.reserve(region->second->Entityes.size());
|
||||
|
||||
for(size_t iter = 0; iter < region->second->Entityes.size(); iter++) {
|
||||
const Entity& entity = region->second->Entityes[iter];
|
||||
if(entity.IsRemoved)
|
||||
continue;
|
||||
|
||||
removed.emplace_back(worldId, pos, static_cast<RegionEntityId_t>(iter));
|
||||
}
|
||||
|
||||
if(!removed.empty())
|
||||
cec->prepareEntitiesRemove(removed);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<RemoteClient>> &CECs = region->second->RMs;
|
||||
for(size_t iter = 0; iter < CECs.size(); iter++) {
|
||||
if(CECs[iter] == cec) {
|
||||
@@ -74,6 +105,7 @@ void World::pushRegions(std::vector<std::pair<Pos::GlobalRegion, RegionIn>> regi
|
||||
Region ®ion = *(Regions[key] = std::make_unique<Region>());
|
||||
region.Voxels = std::move(value.Voxels);
|
||||
region.Nodes = value.Nodes;
|
||||
region.Entityes = std::move(value.Entityes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -146,8 +146,8 @@ public:
|
||||
Возвращает список не загруженных регионов, на которые соответственно игрока не получилось подписать
|
||||
При подписи происходит отправка всех чанков и сущностей региона
|
||||
*/
|
||||
std::vector<Pos::GlobalRegion> onRemoteClient_RegionsEnter(std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion> &enter);
|
||||
void onRemoteClient_RegionsLost(std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion>& lost);
|
||||
std::vector<Pos::GlobalRegion> onRemoteClient_RegionsEnter(WorldId_t worldId, std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion> &enter);
|
||||
void onRemoteClient_RegionsLost(WorldId_t worldId, std::shared_ptr<RemoteClient> cec, const std::vector<Pos::GlobalRegion>& lost);
|
||||
struct SaveUnloadInfo {
|
||||
std::vector<Pos::GlobalRegion> ToUnload;
|
||||
std::vector<std::pair<Pos::GlobalRegion, SB_Region_In>> ToSave;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "boost/asio/awaitable.hpp"
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <Client/Vulkan/Vulkan.hpp>
|
||||
#include <thread>
|
||||
|
||||
namespace LV {
|
||||
|
||||
@@ -37,6 +40,4 @@ int main() {
|
||||
|
||||
std::cout << "Hello world!" << std::endl;
|
||||
return LV::main();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#version 460
|
||||
|
||||
// layout(early_fragment_tests) in;
|
||||
layout(early_fragment_tests) in;
|
||||
|
||||
layout(location = 0) in FragmentObj {
|
||||
vec3 GeoPos; // Реальная позиция в мире
|
||||
|
||||
Binary file not shown.
@@ -44,4 +44,7 @@ vec4 atlasColor(uint texId, vec2 uv)
|
||||
void main() {
|
||||
Frame = atlasColor(Fragment.Texture, Fragment.UV);
|
||||
Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8))));
|
||||
|
||||
if(Frame.w == 0)
|
||||
discard;
|
||||
}
|
||||
|
||||
Binary file not shown.
161
docs/assets_definitions.md
Normal file
161
docs/assets_definitions.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# Определение ресурсов (assets)
|
||||
|
||||
Документ описывает формат файлов ресурсов и правила их адресации на стороне сервера.
|
||||
Описание основано на загрузчиках из `Src/Server/AssetsManager.hpp` и связанных структурах
|
||||
подготовки (`PreparedNodeState`, `PreparedModel`, `PreparedGLTF`).
|
||||
|
||||
## Общая схема
|
||||
- Ресурсы берутся из списка папок `AssetsRegister::Assets` (от последнего мода к первому).
|
||||
Первый найденный ресурс по пути имеет приоритет.
|
||||
- Переопределения через `AssetsRegister::Custom` имеют более высокий приоритет.
|
||||
- Адрес ресурса состоит из `domain` и `key`.
|
||||
`domain` — имя папки в assets, `key` — относительный путь внутри папки типа ресурса.
|
||||
- Обработанные ресурсы сохраняются в `server_cache/assets`.
|
||||
|
||||
## Дерево папок
|
||||
```
|
||||
assets/
|
||||
<domain>/
|
||||
nodestate/ *.json
|
||||
model/ *.json | *.gltf | *.glb
|
||||
texture/ *.png | *.jpg (jpeg)
|
||||
particle/ (загрузка из файлов пока не реализована)
|
||||
animation/ (загрузка из файлов пока не реализована)
|
||||
sound/ (загрузка из файлов пока не реализована)
|
||||
font/ (загрузка из файлов пока не реализована)
|
||||
```
|
||||
|
||||
Пример: `assets/core/nodestate/stone.json` имеет `domain=core`, `key=stone.json`.
|
||||
При обращении к nodestate из логики нод используется ключ без суффикса `.json`
|
||||
(сервер дописывает расширение автоматически).
|
||||
|
||||
## Nodestate (JSON)
|
||||
Файл nodestate — это JSON-объект, где ключи — условия, а значения — описание модели
|
||||
или список вариантов моделей.
|
||||
|
||||
### Условия
|
||||
Условие — строковое выражение. Поддерживаются:
|
||||
- числа, `true`, `false`
|
||||
- переменные: `state` или `state:value` (двоеточие — часть имени)
|
||||
- операторы: `+ - * / %`, `!`, `&`, `|`, `< <= > >= == !=`
|
||||
- скобки
|
||||
|
||||
Пустая строка условия трактуется как `true`.
|
||||
|
||||
### Формат варианта модели
|
||||
Объект варианта:
|
||||
- `model`: строка `domain:key` **или** массив объектов моделей
|
||||
- `weight`: число (вес при случайном выборе), по умолчанию `1`
|
||||
- `uvlock`: bool (используется для векторных моделей; для одиночной модели игнорируется)
|
||||
- `transformations`: массив строк `"key=value"` для трансформаций
|
||||
|
||||
Если `model` — строка, это одиночная модель.
|
||||
Если `model` — массив, это векторная модель: набор объектов вида:
|
||||
```
|
||||
{ "model": "domain:key", "uvlock": false, "transformations": ["x=0", "ry=1.57"] }
|
||||
```
|
||||
Для векторной модели также могут задаваться `uvlock` и `transformations` на верхнем уровне
|
||||
(они применяются к группе).
|
||||
|
||||
Трансформации поддерживают ключи:
|
||||
`x`, `y`, `z`, `rx`, `ry`, `rz` (сдвиг и поворот).
|
||||
|
||||
Домен в строке `domain:key` можно опустить — тогда используется домен файла nodestate.
|
||||
|
||||
### Пример
|
||||
```json
|
||||
{
|
||||
"": { "model": "core:stone" },
|
||||
"variant == 1": [
|
||||
{ "model": "core:stone_alt", "weight": 2 },
|
||||
{ "model": "core:stone_alt_2", "weight": 1, "transformations": ["ry=1.57"] }
|
||||
],
|
||||
"facing:north": {
|
||||
"model": [
|
||||
{ "model": "core:stone", "transformations": ["ry=3.14"] },
|
||||
{ "model": "core:stone_detail", "transformations": ["x=0.5"] }
|
||||
],
|
||||
"uvlock": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Model (JSON)
|
||||
Формат описывает геометрию и текстуры.
|
||||
|
||||
### Верхний уровень
|
||||
- `gui_light`: строка (сейчас используется только `default`)
|
||||
- `ambient_occlusion`: bool
|
||||
- `display`: объект с наборами `rotation`/`translation`/`scale` (все — массивы из 3 чисел)
|
||||
- `textures`: объект `name -> string` (ссылка на текстуру или pipeline)
|
||||
- `cuboids`: массив геометрических блоков
|
||||
- `sub_models`: массив подмоделей
|
||||
|
||||
### Текстуры
|
||||
В `textures` значение:
|
||||
- либо строка `domain:key` (прямая ссылка на текстуру),
|
||||
- либо pipeline-строка, начинающаяся с `tex` (компилируется `TexturePipelineProgram`).
|
||||
|
||||
Если домен не указан, используется домен файла модели.
|
||||
|
||||
### Cuboids
|
||||
Элемент `cuboids`:
|
||||
- `shade`: bool (по умолчанию `true`)
|
||||
- `from`: `[x, y, z]`
|
||||
- `to`: `[x, y, z]`
|
||||
- `faces`: объект граней (`down|up|north|south|west|east`)
|
||||
- `transformations`: массив `"key=value"` (ключи как у nodestate)
|
||||
|
||||
Грань (`faces.<name>`) может содержать:
|
||||
- `uv`: `[u0, v0, u1, v1]`
|
||||
- `texture`: строка (ключ из `textures`)
|
||||
- `cullface`: `down|up|north|south|west|east`
|
||||
- `tintindex`: int
|
||||
- `rotation`: int16
|
||||
|
||||
### Sub-models
|
||||
`sub_models` допускает:
|
||||
- строку `domain:key`
|
||||
- объект `{ "model": "domain:key", "scene": 0 }`
|
||||
- объект `{ "path": "domain:key", "scene": 0 }`
|
||||
|
||||
Поле `scene` опционально.
|
||||
|
||||
### Пример
|
||||
```json
|
||||
{
|
||||
"ambient_occlusion": true,
|
||||
"textures": {
|
||||
"all": "core:stone"
|
||||
},
|
||||
"cuboids": [
|
||||
{
|
||||
"from": [0, 0, 0],
|
||||
"to": [16, 16, 16],
|
||||
"faces": {
|
||||
"north": { "uv": [0, 0, 16, 16], "texture": "#all" }
|
||||
}
|
||||
}
|
||||
],
|
||||
"sub_models": [
|
||||
"core:stone_detail",
|
||||
{ "model": "core:stone_variant", "scene": 1 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Model (glTF / GLB)
|
||||
Файлы моделей могут быть:
|
||||
- `.gltf` (JSON glTF)
|
||||
- `.glb` (binary glTF)
|
||||
|
||||
Оба формата конвертируются в `PreparedGLTF`.
|
||||
|
||||
## Texture
|
||||
Поддерживаются только PNG и JPEG.
|
||||
Формат определяется по сигнатуре файла.
|
||||
|
||||
## Прочие типы ресурсов
|
||||
Для `particle`, `animation`, `sound`, `font` загрузка из файловой системы
|
||||
в серверном загрузчике пока не реализована (`std::unreachable()`), но возможна
|
||||
регистрация из Lua через `path` (сырые бинарные данные).
|
||||
Reference in New Issue
Block a user