Compare commits
2 Commits
d47a5cc090
...
f56b46f669
| Author | SHA1 | Date | |
|---|---|---|---|
| f56b46f669 | |||
| 4aa7c6f41a |
@@ -8,6 +8,7 @@
|
|||||||
#include "glm/ext/quaternion_geometric.hpp"
|
#include "glm/ext/quaternion_geometric.hpp"
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
#include <boost/asio/deadline_timer.hpp>
|
#include <boost/asio/deadline_timer.hpp>
|
||||||
#include <boost/asio/this_coro.hpp>
|
#include <boost/asio/this_coro.hpp>
|
||||||
#include <boost/date_time/posix_time/posix_time_duration.hpp>
|
#include <boost/date_time/posix_time/posix_time_duration.hpp>
|
||||||
@@ -15,12 +16,93 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <Common/Packets.hpp>
|
#include <Common/Packets.hpp>
|
||||||
#include <glm/ext.hpp>
|
#include <glm/ext.hpp>
|
||||||
|
#include <optional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Client {
|
namespace LV::Client {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const char* assetTypeName(EnumAssets type) {
|
||||||
|
switch(type) {
|
||||||
|
case EnumAssets::Nodestate: return "nodestate";
|
||||||
|
case EnumAssets::Model: return "model";
|
||||||
|
case EnumAssets::Texture: return "texture";
|
||||||
|
case EnumAssets::Particle: return "particle";
|
||||||
|
case EnumAssets::Animation: return "animation";
|
||||||
|
case EnumAssets::Sound: return "sound";
|
||||||
|
case EnumAssets::Font: return "font";
|
||||||
|
default: return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DefNodeId> debugExpectedGeneratedNodeId(int rx, int ry, int rz) {
|
||||||
|
if(ry == 1 && rz == 0)
|
||||||
|
return DefNodeId(0);
|
||||||
|
if(rx == 0 && ry == 1)
|
||||||
|
return DefNodeId(0);
|
||||||
|
if(rx == 0 && rz == 0)
|
||||||
|
return DefNodeId(1);
|
||||||
|
if(ry == 0 && rz == 0)
|
||||||
|
return DefNodeId(2);
|
||||||
|
if(rx == 0 && ry == 0)
|
||||||
|
return DefNodeId(3);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void debugCheckGeneratedChunkNodes(WorldId_t worldId,
|
||||||
|
Pos::GlobalChunk chunkPos,
|
||||||
|
const std::array<Node, 16 * 16 * 16>& chunk)
|
||||||
|
{
|
||||||
|
if(chunkPos[0] != 0 && chunkPos[1] != 0 && chunkPos[2] != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
static std::atomic<uint32_t> warnCount = 0;
|
||||||
|
if(warnCount.load() >= 16)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Pos::bvec4u localChunk = chunkPos & 0x3;
|
||||||
|
const int baseX = int(localChunk[0]) * 16;
|
||||||
|
const int baseY = int(localChunk[1]) * 16;
|
||||||
|
const int baseZ = int(localChunk[2]) * 16;
|
||||||
|
const int globalBaseX = int(chunkPos[0]) * 16;
|
||||||
|
const int globalBaseY = int(chunkPos[1]) * 16;
|
||||||
|
const int globalBaseZ = int(chunkPos[2]) * 16;
|
||||||
|
|
||||||
|
for(int z = 0; z < 16; z++)
|
||||||
|
for(int y = 0; y < 16; y++)
|
||||||
|
for(int x = 0; x < 16; x++) {
|
||||||
|
int rx = baseX + x;
|
||||||
|
int ry = baseY + y;
|
||||||
|
int rz = baseZ + z;
|
||||||
|
int gx = globalBaseX + x;
|
||||||
|
int gy = globalBaseY + y;
|
||||||
|
int gz = globalBaseZ + z;
|
||||||
|
std::optional<DefNodeId> expected = debugExpectedGeneratedNodeId(rx, ry, rz);
|
||||||
|
if(!expected)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const Node& node = chunk[x + y * 16 + z * 16 * 16];
|
||||||
|
if(node.NodeId != *expected) {
|
||||||
|
uint32_t index = warnCount.fetch_add(1);
|
||||||
|
if(index < 16) {
|
||||||
|
TOS::Logger("Client>WorldDebug").warn()
|
||||||
|
<< "Generated node mismatch world " << worldId
|
||||||
|
<< " chunk " << int(chunkPos[0]) << ',' << int(chunkPos[1]) << ',' << int(chunkPos[2])
|
||||||
|
<< " at local " << rx << ',' << ry << ',' << rz
|
||||||
|
<< " global " << gx << ',' << gy << ',' << gz
|
||||||
|
<< " expected " << *expected
|
||||||
|
<< " got " << node.NodeId;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
ServerSession::ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket>&& socket)
|
ServerSession::ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket>&& socket)
|
||||||
: IAsyncDestructible(ioc), Socket(std::move(socket)) //, NetInputPackets(1024)
|
: IAsyncDestructible(ioc), Socket(std::move(socket)) //, NetInputPackets(1024)
|
||||||
{
|
{
|
||||||
@@ -170,6 +252,18 @@ void ServerSession::shutdown(EnumDisconnect type) {
|
|||||||
LOG.info() << "Отключение от сервера: " << reason;
|
LOG.info() << "Отключение от сервера: " << reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServerSession::requestModsReload() {
|
||||||
|
if(!Socket || !isConnected())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Net::Packet packet;
|
||||||
|
packet << (uint8_t) ToServer::L1::System
|
||||||
|
<< (uint8_t) ToServer::L2System::ReloadMods;
|
||||||
|
|
||||||
|
Socket->pushPacket(std::move(packet));
|
||||||
|
LOG.info() << "Запрос на перезагрузку модов отправлен";
|
||||||
|
}
|
||||||
|
|
||||||
void ServerSession::onResize(uint32_t width, uint32_t height) {
|
void ServerSession::onResize(uint32_t width, uint32_t height) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -284,10 +378,50 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
|
|
||||||
// Получить ресурсы с AssetsManager
|
// Получить ресурсы с AssetsManager
|
||||||
{
|
{
|
||||||
|
static std::atomic<uint32_t> debugAssetReadLogCount = 0;
|
||||||
std::vector<std::pair<AssetsManager::ResourceKey, std::optional<Resource>>> resources = AM->pullReads();
|
std::vector<std::pair<AssetsManager::ResourceKey, std::optional<Resource>>> resources = AM->pullReads();
|
||||||
std::vector<Hash_t> needRequest;
|
std::vector<Hash_t> needRequest;
|
||||||
|
|
||||||
for(auto& [key, res] : resources) {
|
for(auto& [key, res] : resources) {
|
||||||
|
{
|
||||||
|
auto& waitingByDomain = AsyncContext.ResourceWait[(int) key.Type];
|
||||||
|
auto iterDomain = waitingByDomain.find(key.Domain);
|
||||||
|
if(iterDomain != waitingByDomain.end()) {
|
||||||
|
auto& entries = iterDomain->second;
|
||||||
|
entries.erase(std::remove_if(entries.begin(), entries.end(),
|
||||||
|
[&](const std::pair<std::string, Hash_t>& entry) {
|
||||||
|
return entry.first == key.Key && entry.second == key.Hash;
|
||||||
|
}),
|
||||||
|
entries.end());
|
||||||
|
if(entries.empty())
|
||||||
|
waitingByDomain.erase(iterDomain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(key.Domain == "test"
|
||||||
|
&& (key.Type == EnumAssets::Nodestate
|
||||||
|
|| key.Type == EnumAssets::Model
|
||||||
|
|| key.Type == EnumAssets::Texture))
|
||||||
|
{
|
||||||
|
uint32_t idx = debugAssetReadLogCount.fetch_add(1);
|
||||||
|
if(idx < 128) {
|
||||||
|
if(res) {
|
||||||
|
LOG.debug() << "Cache hit type=" << assetTypeName(key.Type)
|
||||||
|
<< " id=" << key.Id
|
||||||
|
<< " key=" << key.Domain << ':' << key.Key
|
||||||
|
<< " size=" << res->size();
|
||||||
|
} else {
|
||||||
|
LOG.debug() << "Cache miss type=" << assetTypeName(key.Type)
|
||||||
|
<< " id=" << key.Id
|
||||||
|
<< " key=" << key.Domain << ':' << key.Key
|
||||||
|
<< " hash=" << int(key.Hash[0]) << '.'
|
||||||
|
<< int(key.Hash[1]) << '.'
|
||||||
|
<< int(key.Hash[2]) << '.'
|
||||||
|
<< int(key.Hash[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!res) {
|
if(!res) {
|
||||||
// Проверить не был ли уже отправлен запрос на получение этого хеша
|
// Проверить не был ли уже отправлен запрос на получение этого хеша
|
||||||
auto iter = std::lower_bound(AsyncContext.AlreadyLoading.begin(), AsyncContext.AlreadyLoading.end(), key.Hash);
|
auto iter = std::lower_bound(AsyncContext.AlreadyLoading.begin(), AsyncContext.AlreadyLoading.end(), key.Hash);
|
||||||
@@ -311,6 +445,11 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
if(!needRequest.empty()) {
|
if(!needRequest.empty()) {
|
||||||
assert(needRequest.size() < (1 << 16));
|
assert(needRequest.size() < (1 << 16));
|
||||||
|
|
||||||
|
uint32_t idx = debugAssetReadLogCount.fetch_add(1);
|
||||||
|
if(idx < 128) {
|
||||||
|
LOG.debug() << "Send ResourceRequest count=" << needRequest.size();
|
||||||
|
}
|
||||||
|
|
||||||
Net::Packet p;
|
Net::Packet p;
|
||||||
p << (uint8_t) ToServer::L1::System << (uint8_t) ToServer::L2System::ResourceRequest;
|
p << (uint8_t) ToServer::L1::System << (uint8_t) ToServer::L2System::ResourceRequest;
|
||||||
p << (uint16_t) needRequest.size();
|
p << (uint16_t) needRequest.size();
|
||||||
@@ -416,8 +555,33 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Отправляем запрос на получение ресурсов
|
// Отправляем запрос на получение ресурсов
|
||||||
if(!needToLoad.empty())
|
if(!needToLoad.empty()) {
|
||||||
|
static std::atomic<uint32_t> debugReadRequestLogCount = 0;
|
||||||
|
AssetsManager::ResourceKey firstDebug;
|
||||||
|
bool hasDebug = false;
|
||||||
|
for(const auto& entry : needToLoad) {
|
||||||
|
if(entry.Domain == "test"
|
||||||
|
&& (entry.Type == EnumAssets::Nodestate
|
||||||
|
|| entry.Type == EnumAssets::Model
|
||||||
|
|| entry.Type == EnumAssets::Texture))
|
||||||
|
{
|
||||||
|
firstDebug = entry;
|
||||||
|
hasDebug = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(hasDebug && debugReadRequestLogCount.fetch_add(1) < 64) {
|
||||||
|
LOG.debug() << "Queue asset read count=" << needToLoad.size()
|
||||||
|
<< " type=" << assetTypeName(firstDebug.Type)
|
||||||
|
<< " id=" << firstDebug.Id
|
||||||
|
<< " key=" << firstDebug.Domain << ':' << firstDebug.Key
|
||||||
|
<< " hash=" << int(firstDebug.Hash[0]) << '.'
|
||||||
|
<< int(firstDebug.Hash[1]) << '.'
|
||||||
|
<< int(firstDebug.Hash[2]) << '.'
|
||||||
|
<< int(firstDebug.Hash[3]);
|
||||||
|
}
|
||||||
AM->pushReads(std::move(needToLoad));
|
AM->pushReads(std::move(needToLoad));
|
||||||
|
}
|
||||||
|
|
||||||
AsyncContext.Binds.push_back(std::move(abc));
|
AsyncContext.Binds.push_back(std::move(abc));
|
||||||
}
|
}
|
||||||
@@ -675,7 +839,9 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
auto& c = chunks_Changed[wId];
|
auto& c = chunks_Changed[wId];
|
||||||
|
|
||||||
for(auto& [pos, val] : list) {
|
for(auto& [pos, val] : list) {
|
||||||
unCompressNodes(val, caocvr[pos].data());
|
auto& chunkNodes = caocvr[pos];
|
||||||
|
unCompressNodes(val, chunkNodes.data());
|
||||||
|
debugCheckGeneratedChunkNodes(wId, pos, chunkNodes);
|
||||||
c.push_back(pos);
|
c.push_back(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -940,6 +1106,19 @@ void ServerSession::setRenderSession(IRenderSession* session) {
|
|||||||
RS = session;
|
RS = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServerSession::resetResourceSyncState() {
|
||||||
|
AsyncContext.AssetsLoading.clear();
|
||||||
|
AsyncContext.AlreadyLoading.clear();
|
||||||
|
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++)
|
||||||
|
AsyncContext.ResourceWait[type].clear();
|
||||||
|
AsyncContext.Binds.clear();
|
||||||
|
AsyncContext.LoadedResources.clear();
|
||||||
|
AsyncContext.ThisTickEntry = {};
|
||||||
|
AsyncContext.LoadedAssets.lock()->clear();
|
||||||
|
AsyncContext.AssetsBinds.lock()->clear();
|
||||||
|
AsyncContext.TickSequence.lock()->clear();
|
||||||
|
}
|
||||||
|
|
||||||
coro<> ServerSession::run(AsyncUseControl::Lock) {
|
coro<> ServerSession::run(AsyncUseControl::Lock) {
|
||||||
try {
|
try {
|
||||||
while(!IsGoingShutdown && IsConnected) {
|
while(!IsGoingShutdown && IsConnected) {
|
||||||
@@ -950,6 +1129,7 @@ coro<> ServerSession::run(AsyncUseControl::Lock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IsConnected = false;
|
IsConnected = false;
|
||||||
|
resetResourceSyncState();
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
@@ -1009,6 +1189,7 @@ coro<> ServerSession::rP_System(Net::AsyncSocket &sock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
||||||
|
static std::atomic<uint32_t> debugResourceLogCount = 0;
|
||||||
uint8_t second = co_await sock.read<uint8_t>();
|
uint8_t second = co_await sock.read<uint8_t>();
|
||||||
|
|
||||||
switch((ToClient::L2Resource) second) {
|
switch((ToClient::L2Resource) second) {
|
||||||
@@ -1035,6 +1216,23 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
(EnumAssets) type, (ResourceId) id, std::move(domain),
|
(EnumAssets) type, (ResourceId) id, std::move(domain),
|
||||||
std::move(key), hash
|
std::move(key), hash
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if(binds.back().Domain == "test"
|
||||||
|
&& (binds.back().Type == EnumAssets::Nodestate
|
||||||
|
|| binds.back().Type == EnumAssets::Model
|
||||||
|
|| binds.back().Type == EnumAssets::Texture))
|
||||||
|
{
|
||||||
|
uint32_t idx = debugResourceLogCount.fetch_add(1);
|
||||||
|
if(idx < 128) {
|
||||||
|
LOG.debug() << "Bind asset type=" << assetTypeName(binds.back().Type)
|
||||||
|
<< " id=" << binds.back().Id
|
||||||
|
<< " key=" << binds.back().Domain << ':' << binds.back().Key
|
||||||
|
<< " hash=" << int(binds.back().Hash[0]) << '.'
|
||||||
|
<< int(binds.back().Hash[1]) << '.'
|
||||||
|
<< int(binds.back().Hash[2]) << '.'
|
||||||
|
<< int(binds.back().Hash[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncContext.AssetsBinds.lock()->push_back(AssetsBindsChange(binds, {}));
|
AsyncContext.AssetsBinds.lock()->push_back(AssetsBindsChange(binds, {}));
|
||||||
@@ -1072,6 +1270,20 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
std::string domain = co_await sock.read<std::string>();
|
std::string domain = co_await sock.read<std::string>();
|
||||||
std::string key = co_await sock.read<std::string>();
|
std::string key = co_await sock.read<std::string>();
|
||||||
|
|
||||||
|
if(domain == "test"
|
||||||
|
&& (type == EnumAssets::Nodestate
|
||||||
|
|| type == EnumAssets::Model
|
||||||
|
|| type == EnumAssets::Texture))
|
||||||
|
{
|
||||||
|
uint32_t idx = debugResourceLogCount.fetch_add(1);
|
||||||
|
if(idx < 128) {
|
||||||
|
LOG.debug() << "InitResSend type=" << assetTypeName(type)
|
||||||
|
<< " id=" << id
|
||||||
|
<< " key=" << domain << ':' << key
|
||||||
|
<< " size=" << size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AsyncContext.AssetsLoading[hash] = AssetLoading{
|
AsyncContext.AssetsLoading[hash] = AssetLoading{
|
||||||
type, id, std::move(domain), std::move(key),
|
type, id, std::move(domain), std::move(key),
|
||||||
std::u8string(size, '\0'), 0
|
std::u8string(size, '\0'), 0
|
||||||
@@ -1095,6 +1307,20 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
|
|
||||||
if(al.Offset == al.Data.size()) {
|
if(al.Offset == al.Data.size()) {
|
||||||
// Ресурс полностью загружен
|
// Ресурс полностью загружен
|
||||||
|
if(al.Domain == "test"
|
||||||
|
&& (al.Type == EnumAssets::Nodestate
|
||||||
|
|| al.Type == EnumAssets::Model
|
||||||
|
|| al.Type == EnumAssets::Texture))
|
||||||
|
{
|
||||||
|
uint32_t idx = debugResourceLogCount.fetch_add(1);
|
||||||
|
if(idx < 128) {
|
||||||
|
LOG.debug() << "Resource loaded type=" << assetTypeName(al.Type)
|
||||||
|
<< " id=" << al.Id
|
||||||
|
<< " key=" << al.Domain << ':' << al.Key
|
||||||
|
<< " size=" << al.Data.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AsyncContext.LoadedAssets.lock()->emplace_back(
|
AsyncContext.LoadedAssets.lock()->emplace_back(
|
||||||
al.Type, al.Id, std::move(al.Domain), std::move(al.Key), std::move(al.Data)
|
al.Type, al.Id, std::move(al.Domain), std::move(al.Key), std::move(al.Data)
|
||||||
);
|
);
|
||||||
@@ -1118,6 +1344,7 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) {
|
coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) {
|
||||||
|
static std::atomic<uint32_t> debugDefLogCount = 0;
|
||||||
uint8_t second = co_await sock.read<uint8_t>();
|
uint8_t second = co_await sock.read<uint8_t>();
|
||||||
|
|
||||||
switch((ToClient::L2Definition) second) {
|
switch((ToClient::L2Definition) second) {
|
||||||
@@ -1148,6 +1375,15 @@ coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) {
|
|||||||
def.NodestateId = co_await sock.read<uint32_t>();
|
def.NodestateId = co_await sock.read<uint32_t>();
|
||||||
def.TexId = id;
|
def.TexId = id;
|
||||||
|
|
||||||
|
if(id < 32) {
|
||||||
|
uint32_t idx = debugDefLogCount.fetch_add(1);
|
||||||
|
if(idx < 64) {
|
||||||
|
LOG.debug() << "DefNode id=" << id
|
||||||
|
<< " nodestate=" << def.NodestateId
|
||||||
|
<< " tex=" << def.TexId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AsyncContext.ThisTickEntry.Profile_Node_AddOrChange.emplace_back(id, def);
|
AsyncContext.ThisTickEntry.Profile_Node_AddOrChange.emplace_back(id, def);
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public:
|
|||||||
static coro<std::unique_ptr<Net::AsyncSocket>> asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, std::function<void(const std::string&)> onProgress = nullptr);
|
static coro<std::unique_ptr<Net::AsyncSocket>> asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, std::function<void(const std::string&)> onProgress = nullptr);
|
||||||
|
|
||||||
void shutdown(EnumDisconnect type);
|
void shutdown(EnumDisconnect type);
|
||||||
|
void requestModsReload();
|
||||||
|
|
||||||
bool isConnected() {
|
bool isConnected() {
|
||||||
return Socket->isAlive() && IsConnected;
|
return Socket->isAlive() && IsConnected;
|
||||||
@@ -182,6 +183,7 @@ private:
|
|||||||
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket);
|
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket);
|
||||||
|
|
||||||
virtual coro<> asyncDestructor() override;
|
virtual coro<> asyncDestructor() override;
|
||||||
|
void resetResourceSyncState();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -35,11 +35,11 @@ struct VoxelVertexPoint {
|
|||||||
|
|
||||||
struct NodeVertexStatic {
|
struct NodeVertexStatic {
|
||||||
uint32_t
|
uint32_t
|
||||||
FX : 9, FY : 9, FZ : 9, // Позиция -224 ~ 288; 64 позиций в одной ноде, 7.5 метров в ряд
|
FX : 11, FY : 11, N1 : 10, // Позиция, 64 позиции на метр, +3.5м запас
|
||||||
N1 : 4, // Не занято
|
FZ : 11, // Позиция
|
||||||
LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
||||||
Tex : 18, // Текстура
|
Tex : 18, // Текстура
|
||||||
N2 : 14, // Не занято
|
N2 : 2, // Не занято
|
||||||
TU : 16, TV : 16; // UV на текстуре
|
TU : 16, TV : 16; // UV на текстуре
|
||||||
|
|
||||||
bool operator==(const NodeVertexStatic& other) const {
|
bool operator==(const NodeVertexStatic& other) const {
|
||||||
|
|||||||
198
Src/Client/Vulkan/AtlasPipeline/PipelinedTextureAtlas.cpp
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
#include "PipelinedTextureAtlas.hpp"
|
||||||
|
|
||||||
|
PipelinedTextureAtlas::PipelinedTextureAtlas(TextureAtlas&& tk)
|
||||||
|
: Super(std::move(tk)) {}
|
||||||
|
|
||||||
|
PipelinedTextureAtlas::AtlasTextureId PipelinedTextureAtlas::getByPipeline(const HashedPipeline& pipeline) {
|
||||||
|
auto iter = _PipeToTexId.find(pipeline);
|
||||||
|
if (iter == _PipeToTexId.end()) {
|
||||||
|
AtlasTextureId atlasTexId = Super.registerTexture();
|
||||||
|
_PipeToTexId.insert({pipeline, atlasTexId});
|
||||||
|
_ChangedPipelines.push_back(pipeline);
|
||||||
|
|
||||||
|
for (uint32_t texId : pipeline.getDependencedTextures()) {
|
||||||
|
_AddictedTextures[texId].push_back(pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
return atlasTexId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelinedTextureAtlas::freeByPipeline(const HashedPipeline& pipeline) {
|
||||||
|
auto iter = _PipeToTexId.find(pipeline);
|
||||||
|
if (iter == _PipeToTexId.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t texId : pipeline.getDependencedTextures()) {
|
||||||
|
auto iterAT = _AddictedTextures.find(texId);
|
||||||
|
assert(iterAT != _AddictedTextures.end());
|
||||||
|
auto iterATSub = std::find(iterAT->second.begin(), iterAT->second.end(), pipeline);
|
||||||
|
assert(iterATSub != iterAT->second.end());
|
||||||
|
iterAT->second.erase(iterATSub);
|
||||||
|
}
|
||||||
|
|
||||||
|
Super.removeTexture(iter->second);
|
||||||
|
_AtlasCpuTextures.erase(iter->second);
|
||||||
|
_PipeToTexId.erase(iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelinedTextureAtlas::updateTexture(uint32_t texId, const StoredTexture& texture) {
|
||||||
|
_ResToTexture[texId] = texture;
|
||||||
|
_ChangedTextures.push_back(texId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelinedTextureAtlas::updateTexture(uint32_t texId, StoredTexture&& texture) {
|
||||||
|
_ResToTexture[texId] = std::move(texture);
|
||||||
|
_ChangedTextures.push_back(texId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelinedTextureAtlas::freeTexture(uint32_t texId) {
|
||||||
|
auto iter = _ResToTexture.find(texId);
|
||||||
|
if (iter != _ResToTexture.end()) {
|
||||||
|
_ResToTexture.erase(iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PipelinedTextureAtlas::getHostTexture(TextureId texId, HostTextureView& out) const {
|
||||||
|
auto fill = [&](const StoredTexture& tex) -> bool {
|
||||||
|
if (tex._Pixels.empty() || tex._Widht == 0 || tex._Height == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.width = tex._Widht;
|
||||||
|
out.height = tex._Height;
|
||||||
|
out.rowPitchBytes = static_cast<uint32_t>(tex._Widht) * 4u;
|
||||||
|
out.pixelsRGBA8 = reinterpret_cast<const uint8_t*>(tex._Pixels.data());
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto it = _ResToTexture.find(texId);
|
||||||
|
if (it != _ResToTexture.end() && fill(it->second)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto itAtlas = _AtlasCpuTextures.find(texId);
|
||||||
|
if (itAtlas != _AtlasCpuTextures.end() && fill(itAtlas->second)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StoredTexture PipelinedTextureAtlas::_generatePipelineTexture(const HashedPipeline& pipeline) {
|
||||||
|
std::vector<detail::Word> words(pipeline._Pipeline.begin(), pipeline._Pipeline.end());
|
||||||
|
if (words.empty()) {
|
||||||
|
if (auto tex = tryCopyFirstDependencyTexture(pipeline)) {
|
||||||
|
return *tex;
|
||||||
|
}
|
||||||
|
return makeSolidColorTexture(0xFFFF00FFu);
|
||||||
|
}
|
||||||
|
|
||||||
|
TexturePipelineProgram program;
|
||||||
|
program.fromWords(std::move(words));
|
||||||
|
|
||||||
|
TexturePipelineProgram::OwnedTexture baked;
|
||||||
|
auto provider = [this](uint32_t texId) -> std::optional<Texture> {
|
||||||
|
auto iter = _ResToTexture.find(texId);
|
||||||
|
if (iter == _ResToTexture.end()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const StoredTexture& stored = iter->second;
|
||||||
|
if (stored._Pixels.empty() || stored._Widht == 0 || stored._Height == 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
Texture tex{};
|
||||||
|
tex.Width = stored._Widht;
|
||||||
|
tex.Height = stored._Height;
|
||||||
|
tex.Pixels = stored._Pixels.data();
|
||||||
|
return tex;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!program.bake(provider, baked, nullptr)) {
|
||||||
|
if (auto tex = tryCopyFirstDependencyTexture(pipeline)) {
|
||||||
|
return *tex;
|
||||||
|
}
|
||||||
|
return makeSolidColorTexture(0xFFFF00FFu);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t width = baked.Width;
|
||||||
|
const uint32_t height = baked.Height;
|
||||||
|
if (width == 0 || height == 0 ||
|
||||||
|
width > std::numeric_limits<uint16_t>::max() ||
|
||||||
|
height > std::numeric_limits<uint16_t>::max() ||
|
||||||
|
baked.Pixels.size() != static_cast<size_t>(width) * static_cast<size_t>(height)) {
|
||||||
|
if (auto tex = tryCopyFirstDependencyTexture(pipeline)) {
|
||||||
|
return *tex;
|
||||||
|
}
|
||||||
|
return makeSolidColorTexture(0xFFFF00FFu);
|
||||||
|
}
|
||||||
|
|
||||||
|
return StoredTexture(static_cast<uint16_t>(width),
|
||||||
|
static_cast<uint16_t>(height),
|
||||||
|
std::move(baked.Pixels));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelinedTextureAtlas::flushNewPipelines() {
|
||||||
|
std::vector<uint32_t> changedTextures = std::move(_ChangedTextures);
|
||||||
|
|
||||||
|
std::sort(changedTextures.begin(), changedTextures.end());
|
||||||
|
changedTextures.erase(std::unique(changedTextures.begin(), changedTextures.end()), changedTextures.end());
|
||||||
|
|
||||||
|
std::vector<HashedPipeline> changedPipelineTextures;
|
||||||
|
for (uint32_t texId : changedTextures) {
|
||||||
|
auto iter = _AddictedTextures.find(texId);
|
||||||
|
if (iter == _AddictedTextures.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
changedPipelineTextures.append_range(iter->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
changedPipelineTextures.append_range(std::move(_ChangedPipelines));
|
||||||
|
changedTextures.clear();
|
||||||
|
|
||||||
|
std::sort(changedPipelineTextures.begin(), changedPipelineTextures.end());
|
||||||
|
changedPipelineTextures.erase(std::unique(changedPipelineTextures.begin(), changedPipelineTextures.end()),
|
||||||
|
changedPipelineTextures.end());
|
||||||
|
|
||||||
|
for (const HashedPipeline& pipeline : changedPipelineTextures) {
|
||||||
|
auto iterPTTI = _PipeToTexId.find(pipeline);
|
||||||
|
assert(iterPTTI != _PipeToTexId.end());
|
||||||
|
|
||||||
|
StoredTexture texture = _generatePipelineTexture(pipeline);
|
||||||
|
AtlasTextureId atlasTexId = iterPTTI->second;
|
||||||
|
auto& stored = _AtlasCpuTextures[atlasTexId];
|
||||||
|
stored = std::move(texture);
|
||||||
|
if (!stored._Pixels.empty()) {
|
||||||
|
Super.setTextureData(atlasTexId,
|
||||||
|
stored._Widht,
|
||||||
|
stored._Height,
|
||||||
|
stored._Pixels.data(),
|
||||||
|
stored._Widht * 4u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas::DescriptorOut PipelinedTextureAtlas::flushUploadsAndBarriers(VkCommandBuffer cmdBuffer) {
|
||||||
|
return Super.flushUploadsAndBarriers(cmdBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelinedTextureAtlas::notifyGpuFinished() {
|
||||||
|
Super.notifyGpuFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<StoredTexture> PipelinedTextureAtlas::tryCopyFirstDependencyTexture(const HashedPipeline& pipeline) const {
|
||||||
|
auto deps = pipeline.getDependencedTextures();
|
||||||
|
if (!deps.empty()) {
|
||||||
|
auto iter = _ResToTexture.find(deps.front());
|
||||||
|
if (iter != _ResToTexture.end()) {
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
StoredTexture PipelinedTextureAtlas::makeSolidColorTexture(uint32_t rgba) {
|
||||||
|
return StoredTexture(1, 1, std::vector<uint32_t>{rgba});
|
||||||
|
}
|
||||||
380
Src/Client/Vulkan/AtlasPipeline/PipelinedTextureAtlas.hpp
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "TextureAtlas.hpp"
|
||||||
|
#include "TexturePipelineProgram.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include "boost/container/small_vector.hpp"
|
||||||
|
|
||||||
|
using TextureId = uint32_t;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
using Word = TexturePipelineProgram::Word;
|
||||||
|
|
||||||
|
enum class Op16 : Word {
|
||||||
|
End = 0,
|
||||||
|
Base_Tex = 1,
|
||||||
|
Base_Fill = 2,
|
||||||
|
Resize = 10,
|
||||||
|
Transform = 11,
|
||||||
|
Opacity = 12,
|
||||||
|
NoAlpha = 13,
|
||||||
|
MakeAlpha = 14,
|
||||||
|
Invert = 15,
|
||||||
|
Brighten = 16,
|
||||||
|
Contrast = 17,
|
||||||
|
Multiply = 18,
|
||||||
|
Screen = 19,
|
||||||
|
Colorize = 20,
|
||||||
|
Overlay = 30,
|
||||||
|
Mask = 31,
|
||||||
|
LowPart = 32,
|
||||||
|
Combine = 40
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SrcKind16 : Word { TexId = 0, Sub = 1 };
|
||||||
|
|
||||||
|
struct SrcRef16 {
|
||||||
|
SrcKind16 kind{SrcKind16::TexId};
|
||||||
|
Word a = 0;
|
||||||
|
Word b = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline uint32_t makeU32(Word lo, Word hi) {
|
||||||
|
return uint32_t(lo) | (uint32_t(hi) << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void addUniqueDep(boost::container::small_vector<uint32_t, 8>& deps, uint32_t id) {
|
||||||
|
if (id == TextureAtlas::kOverflowId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (std::find(deps.begin(), deps.end(), id) == deps.end()) {
|
||||||
|
deps.push_back(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool readSrc(const std::vector<Word>& words, size_t end, size_t& ip, SrcRef16& out) {
|
||||||
|
if (ip + 2 >= end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.kind = static_cast<SrcKind16>(words[ip++]);
|
||||||
|
out.a = words[ip++];
|
||||||
|
out.b = words[ip++];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void extractPipelineDependencies(const std::vector<Word>& words,
|
||||||
|
size_t start,
|
||||||
|
size_t end,
|
||||||
|
boost::container::small_vector<uint32_t, 8>& deps,
|
||||||
|
std::vector<std::pair<size_t, size_t>>& visited) {
|
||||||
|
if (start >= end || end > words.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::pair<size_t, size_t> key{start, end};
|
||||||
|
if (std::find(visited.begin(), visited.end(), key) != visited.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visited.push_back(key);
|
||||||
|
|
||||||
|
size_t ip = start;
|
||||||
|
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));
|
||||||
|
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);
|
||||||
|
if (subStart < subEnd && subEnd <= words.size()) {
|
||||||
|
extractPipelineDependencies(words, subStart, subEnd, deps, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
while (ip < end) {
|
||||||
|
if (!need(1)) break;
|
||||||
|
Op16 op = static_cast<Op16>(words[ip++]);
|
||||||
|
switch (op) {
|
||||||
|
case Op16::End:
|
||||||
|
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::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;
|
||||||
|
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::Transform:
|
||||||
|
case Op16::Opacity:
|
||||||
|
if (!need(1)) return;
|
||||||
|
ip += 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op16::NoAlpha:
|
||||||
|
case Op16::Brighten:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op16::MakeAlpha:
|
||||||
|
if (!need(2)) return;
|
||||||
|
ip += 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op16::Invert:
|
||||||
|
if (!need(1)) return;
|
||||||
|
ip += 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op16::Contrast:
|
||||||
|
if (!need(2)) return;
|
||||||
|
ip += 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op16::Multiply:
|
||||||
|
case Op16::Screen:
|
||||||
|
if (!need(2)) return;
|
||||||
|
ip += 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op16::Colorize:
|
||||||
|
if (!need(3)) return;
|
||||||
|
ip += 3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op16::Combine: {
|
||||||
|
if (!need(3)) return;
|
||||||
|
ip += 2; // skip w,h
|
||||||
|
uint32_t n = words[ip++];
|
||||||
|
for (uint32_t i = 0; i < n; ++i) {
|
||||||
|
if (!need(2 + 3)) return;
|
||||||
|
ip += 2; // x, y
|
||||||
|
SrcRef16 src{};
|
||||||
|
if (!readSrc(words, end, ip, src)) return;
|
||||||
|
handleSrc(src);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline boost::container::small_vector<uint32_t, 8> extractPipelineDependencies(const std::vector<Word>& words) {
|
||||||
|
boost::container::small_vector<uint32_t, 8> deps;
|
||||||
|
std::vector<std::pair<size_t, size_t>> visited;
|
||||||
|
extractPipelineDependencies(words, 0, words.size(), deps, visited);
|
||||||
|
return deps;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline boost::container::small_vector<uint32_t, 8> extractPipelineDependencies(const boost::container::small_vector<Word, 32>& words) {
|
||||||
|
boost::container::small_vector<uint32_t, 8> deps;
|
||||||
|
std::vector<std::pair<size_t, size_t>> visited;
|
||||||
|
std::vector<Word> copy(words.begin(), words.end());
|
||||||
|
extractPipelineDependencies(copy, 0, copy.size(), deps, visited);
|
||||||
|
return deps;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
// Структура нехешированного пайплайна
|
||||||
|
struct Pipeline {
|
||||||
|
std::vector<detail::Word> _Pipeline;
|
||||||
|
|
||||||
|
Pipeline() = default;
|
||||||
|
|
||||||
|
explicit Pipeline(const TexturePipelineProgram& program)
|
||||||
|
: _Pipeline(program.words().begin(), program.words().end())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Pipeline(TextureId texId) {
|
||||||
|
_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>(detail::Op16::End)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Структура хешированного текстурного пайплайна
|
||||||
|
struct HashedPipeline {
|
||||||
|
// Предвычисленный хеш
|
||||||
|
std::size_t _Hash;
|
||||||
|
boost::container::small_vector<detail::Word, 32> _Pipeline;
|
||||||
|
|
||||||
|
HashedPipeline() = default;
|
||||||
|
HashedPipeline(const Pipeline& pipeline) noexcept
|
||||||
|
: _Pipeline(pipeline._Pipeline.begin(), pipeline._Pipeline.end())
|
||||||
|
{
|
||||||
|
reComputeHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перевычисляет хеш
|
||||||
|
void reComputeHash() noexcept {
|
||||||
|
std::size_t hash = 14695981039346656037ull;
|
||||||
|
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 *= prime;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выдаёт список зависимых текстур, на основе которых строится эта
|
||||||
|
boost::container::small_vector<uint32_t, 8> getDependencedTextures() const {
|
||||||
|
return detail::extractPipelineDependencies(_Pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const HashedPipeline& obj) const noexcept {
|
||||||
|
return _Hash == obj._Hash && _Pipeline == obj._Pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const HashedPipeline& obj) const noexcept {
|
||||||
|
return _Hash < obj._Hash || (_Hash == obj._Hash && _Pipeline < obj._Pipeline);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoredTexture {
|
||||||
|
uint16_t _Widht = 0;
|
||||||
|
uint16_t _Height = 0;
|
||||||
|
std::vector<uint32_t> _Pixels;
|
||||||
|
|
||||||
|
StoredTexture() = default;
|
||||||
|
StoredTexture(uint16_t w, uint16_t h, std::vector<uint32_t> pixels)
|
||||||
|
: _Widht(w), _Height(h), _Pixels(std::move(pixels))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Пайплайновый текстурный атлас
|
||||||
|
class PipelinedTextureAtlas {
|
||||||
|
public:
|
||||||
|
using AtlasTextureId = uint32_t;
|
||||||
|
struct HostTextureView {
|
||||||
|
uint32_t width = 0;
|
||||||
|
uint32_t height = 0;
|
||||||
|
uint32_t rowPitchBytes = 0;
|
||||||
|
const uint8_t* pixelsRGBA8 = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Функтор хеша
|
||||||
|
struct HashedPipelineKeyHash {
|
||||||
|
std::size_t operator()(const HashedPipeline& k) const noexcept {
|
||||||
|
return k._Hash;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функтор равенства
|
||||||
|
struct HashedPipelineKeyEqual {
|
||||||
|
bool operator()(const HashedPipeline& a, const HashedPipeline& b) const noexcept {
|
||||||
|
return a._Pipeline == b._Pipeline;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Текстурный атлас
|
||||||
|
TextureAtlas Super;
|
||||||
|
// Пустой пайплайн (указывающий на одну текстуру) ссылается на простой идентификатор (ResToAtlas)
|
||||||
|
std::unordered_map<HashedPipeline, AtlasTextureId, HashedPipelineKeyHash, HashedPipelineKeyEqual> _PipeToTexId;
|
||||||
|
// Загруженные текстуры
|
||||||
|
std::unordered_map<TextureId, StoredTexture> _ResToTexture;
|
||||||
|
std::unordered_map<AtlasTextureId, StoredTexture> _AtlasCpuTextures;
|
||||||
|
// Список зависимых пайплайнов от текстур (при изменении текстуры, нужно перерисовать пайплайны)
|
||||||
|
std::unordered_map<TextureId, boost::container::small_vector<HashedPipeline, 8>> _AddictedTextures;
|
||||||
|
// Изменённые простые текстуры (для последующего массового обновление пайплайнов)
|
||||||
|
std::vector<uint32_t> _ChangedTextures;
|
||||||
|
// Необходимые к созданию/обновлению пайплайны
|
||||||
|
std::vector<HashedPipeline> _ChangedPipelines;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PipelinedTextureAtlas(TextureAtlas&& tk);
|
||||||
|
|
||||||
|
uint32_t atlasSide() const {
|
||||||
|
return Super.atlasSide();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t atlasLayers() const {
|
||||||
|
return Super.atlasLayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AtlasSide() const {
|
||||||
|
return atlasSide();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AtlasLayers() const {
|
||||||
|
return atlasLayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Должны всегда бронировать идентификатор, либо отдавать kOverflowId. При этом запись tex+pipeline остаётся
|
||||||
|
// Выдаёт стабильный идентификатор, привязанный к пайплайну
|
||||||
|
AtlasTextureId getByPipeline(const HashedPipeline& pipeline);
|
||||||
|
|
||||||
|
// Уведомить что текстура+pipeline более не используются (идентификатор будет освобождён)
|
||||||
|
// Освобождать можно при потере ресурсов
|
||||||
|
void freeByPipeline(const HashedPipeline& pipeline);
|
||||||
|
|
||||||
|
void updateTexture(uint32_t texId, const StoredTexture& texture);
|
||||||
|
void updateTexture(uint32_t texId, StoredTexture&& texture);
|
||||||
|
|
||||||
|
void freeTexture(uint32_t texId);
|
||||||
|
|
||||||
|
bool getHostTexture(TextureId texId, HostTextureView& out) const;
|
||||||
|
|
||||||
|
// Генерация текстуры пайплайна
|
||||||
|
StoredTexture _generatePipelineTexture(const HashedPipeline& pipeline);
|
||||||
|
|
||||||
|
// Обновляет пайплайны по необходимости
|
||||||
|
void flushNewPipelines();
|
||||||
|
|
||||||
|
TextureAtlas::DescriptorOut flushUploadsAndBarriers(VkCommandBuffer cmdBuffer);
|
||||||
|
|
||||||
|
void notifyGpuFinished();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<StoredTexture> tryCopyFirstDependencyTexture(const HashedPipeline& pipeline) const;
|
||||||
|
|
||||||
|
static StoredTexture makeSolidColorTexture(uint32_t rgba);
|
||||||
|
};
|
||||||
169
Src/Client/Vulkan/AtlasPipeline/SharedStagingBuffer.hpp
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
/*
|
||||||
|
Межкадровый промежуточный буфер.
|
||||||
|
Для модели рендера Один за одним.
|
||||||
|
После окончания рендера кадра считается синхронизированным
|
||||||
|
и может заполняться по новой.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class SharedStagingBuffer {
|
||||||
|
public:
|
||||||
|
static constexpr VkDeviceSize kDefaultSize = 64ull * 1024ull * 1024ull;
|
||||||
|
|
||||||
|
SharedStagingBuffer(VkDevice device,
|
||||||
|
VkPhysicalDevice physicalDevice,
|
||||||
|
VkDeviceSize sizeBytes = kDefaultSize)
|
||||||
|
: device_(device),
|
||||||
|
physicalDevice_(physicalDevice),
|
||||||
|
size_(sizeBytes) {
|
||||||
|
if (!device_ || !physicalDevice_) {
|
||||||
|
throw std::runtime_error("SharedStagingBuffer: null device/physicalDevice");
|
||||||
|
}
|
||||||
|
if (size_ == 0) {
|
||||||
|
throw std::runtime_error("SharedStagingBuffer: size must be > 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
VkBufferCreateInfo bi{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0,
|
||||||
|
.size = size_,
|
||||||
|
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||||
|
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||||
|
.queueFamilyIndexCount = 0,
|
||||||
|
.pQueueFamilyIndices = nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
if (vkCreateBuffer(device_, &bi, nullptr, &buffer_) != VK_SUCCESS) {
|
||||||
|
throw std::runtime_error("SharedStagingBuffer: vkCreateBuffer failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
VkMemoryRequirements mr{};
|
||||||
|
vkGetBufferMemoryRequirements(device_, buffer_, &mr);
|
||||||
|
|
||||||
|
VkMemoryAllocateInfo ai{};
|
||||||
|
ai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||||
|
ai.allocationSize = mr.size;
|
||||||
|
ai.memoryTypeIndex = FindMemoryType_(mr.memoryTypeBits,
|
||||||
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
||||||
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||||
|
|
||||||
|
if (vkAllocateMemory(device_, &ai, nullptr, &memory_) != VK_SUCCESS) {
|
||||||
|
vkDestroyBuffer(device_, buffer_, nullptr);
|
||||||
|
buffer_ = VK_NULL_HANDLE;
|
||||||
|
throw std::runtime_error("SharedStagingBuffer: vkAllocateMemory failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
vkBindBufferMemory(device_, buffer_, memory_, 0);
|
||||||
|
|
||||||
|
if (vkMapMemory(device_, memory_, 0, VK_WHOLE_SIZE, 0, &mapped_) != VK_SUCCESS) {
|
||||||
|
vkFreeMemory(device_, memory_, nullptr);
|
||||||
|
vkDestroyBuffer(device_, buffer_, nullptr);
|
||||||
|
buffer_ = VK_NULL_HANDLE;
|
||||||
|
memory_ = VK_NULL_HANDLE;
|
||||||
|
throw std::runtime_error("SharedStagingBuffer: vkMapMemory failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~SharedStagingBuffer() { Destroy_(); }
|
||||||
|
|
||||||
|
SharedStagingBuffer(const SharedStagingBuffer&) = delete;
|
||||||
|
SharedStagingBuffer& operator=(const SharedStagingBuffer&) = delete;
|
||||||
|
|
||||||
|
SharedStagingBuffer(SharedStagingBuffer&& other) noexcept {
|
||||||
|
*this = std::move(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedStagingBuffer& operator=(SharedStagingBuffer&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
Destroy_();
|
||||||
|
device_ = other.device_;
|
||||||
|
physicalDevice_ = other.physicalDevice_;
|
||||||
|
buffer_ = other.buffer_;
|
||||||
|
memory_ = other.memory_;
|
||||||
|
mapped_ = other.mapped_;
|
||||||
|
size_ = other.size_;
|
||||||
|
offset_ = other.offset_;
|
||||||
|
|
||||||
|
other.device_ = VK_NULL_HANDLE;
|
||||||
|
other.physicalDevice_ = VK_NULL_HANDLE;
|
||||||
|
other.buffer_ = VK_NULL_HANDLE;
|
||||||
|
other.memory_ = VK_NULL_HANDLE;
|
||||||
|
other.mapped_ = nullptr;
|
||||||
|
other.size_ = 0;
|
||||||
|
other.offset_ = 0;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkBuffer Buffer() const { return buffer_; }
|
||||||
|
void* Mapped() const { return mapped_; }
|
||||||
|
VkDeviceSize Size() const { return size_; }
|
||||||
|
|
||||||
|
std::optional<VkDeviceSize> Allocate(VkDeviceSize bytes, VkDeviceSize alignment) {
|
||||||
|
VkDeviceSize off = Align_(offset_, alignment);
|
||||||
|
if (off + bytes > size_) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
offset_ = off + bytes;
|
||||||
|
return off;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset() { offset_ = 0; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t FindMemoryType_(uint32_t typeBits, VkMemoryPropertyFlags properties) const {
|
||||||
|
VkPhysicalDeviceMemoryProperties mp{};
|
||||||
|
vkGetPhysicalDeviceMemoryProperties(physicalDevice_, &mp);
|
||||||
|
for (uint32_t i = 0; i < mp.memoryTypeCount; ++i) {
|
||||||
|
if ((typeBits & (1u << i)) &&
|
||||||
|
(mp.memoryTypes[i].propertyFlags & properties) == properties) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw std::runtime_error("SharedStagingBuffer: no suitable memory type");
|
||||||
|
}
|
||||||
|
|
||||||
|
static VkDeviceSize Align_(VkDeviceSize value, VkDeviceSize alignment) {
|
||||||
|
if (alignment == 0) return value;
|
||||||
|
return (value + alignment - 1) & ~(alignment - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Destroy_() {
|
||||||
|
if (device_ == VK_NULL_HANDLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mapped_) {
|
||||||
|
vkUnmapMemory(device_, memory_);
|
||||||
|
mapped_ = nullptr;
|
||||||
|
}
|
||||||
|
if (buffer_) {
|
||||||
|
vkDestroyBuffer(device_, buffer_, nullptr);
|
||||||
|
buffer_ = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
if (memory_) {
|
||||||
|
vkFreeMemory(device_, memory_, nullptr);
|
||||||
|
memory_ = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
size_ = 0;
|
||||||
|
offset_ = 0;
|
||||||
|
device_ = VK_NULL_HANDLE;
|
||||||
|
physicalDevice_ = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDevice device_ = VK_NULL_HANDLE;
|
||||||
|
VkPhysicalDevice physicalDevice_ = VK_NULL_HANDLE;
|
||||||
|
VkBuffer buffer_ = VK_NULL_HANDLE;
|
||||||
|
VkDeviceMemory memory_ = VK_NULL_HANDLE;
|
||||||
|
void* mapped_ = nullptr;
|
||||||
|
VkDeviceSize size_ = 0;
|
||||||
|
VkDeviceSize offset_ = 0;
|
||||||
|
};
|
||||||
477
Src/Client/Vulkan/AtlasPipeline/TextureAtlas.cpp
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
#include "TextureAtlas.hpp"
|
||||||
|
|
||||||
|
TextureAtlas::TextureAtlas(VkDevice device,
|
||||||
|
VkPhysicalDevice physicalDevice,
|
||||||
|
const Config& cfg,
|
||||||
|
EventCallback cb,
|
||||||
|
std::shared_ptr<SharedStagingBuffer> staging)
|
||||||
|
: Device_(device),
|
||||||
|
Phys_(physicalDevice),
|
||||||
|
Cfg_(cfg),
|
||||||
|
OnEvent_(std::move(cb)),
|
||||||
|
Staging_(std::move(staging)) {
|
||||||
|
if(!Device_ || !Phys_) {
|
||||||
|
throw std::runtime_error("TextureAtlas: device/physicalDevice == null");
|
||||||
|
}
|
||||||
|
_validateConfigOrThrow();
|
||||||
|
|
||||||
|
VkPhysicalDeviceProperties props{};
|
||||||
|
vkGetPhysicalDeviceProperties(Phys_, &props);
|
||||||
|
CopyOffsetAlignment_ = std::max<VkDeviceSize>(4, props.limits.optimalBufferCopyOffsetAlignment);
|
||||||
|
|
||||||
|
if(!Staging_) {
|
||||||
|
Staging_ = std::make_shared<SharedStagingBuffer>(Device_, Phys_, kStagingSizeBytes);
|
||||||
|
}
|
||||||
|
_validateStagingCapacityOrThrow();
|
||||||
|
|
||||||
|
_createEntriesBufferOrThrow();
|
||||||
|
_createAtlasOrThrow(Cfg_.InitialSide, 1);
|
||||||
|
|
||||||
|
EntriesCpu_.resize(Cfg_.MaxTextureId);
|
||||||
|
std::memset(EntriesCpu_.data(), 0, EntriesCpu_.size() * sizeof(Entry));
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
|
||||||
|
Slots_.resize(Cfg_.MaxTextureId);
|
||||||
|
FreeIds_.reserve(Cfg_.MaxTextureId);
|
||||||
|
PendingInQueue_.assign(Cfg_.MaxTextureId, false);
|
||||||
|
|
||||||
|
if(Cfg_.ExternalSampler != VK_NULL_HANDLE) {
|
||||||
|
Sampler_ = Cfg_.ExternalSampler;
|
||||||
|
OwnsSampler_ = false;
|
||||||
|
} else {
|
||||||
|
_createSamplerOrThrow();
|
||||||
|
OwnsSampler_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rebuildPackersFromPlacements();
|
||||||
|
Alive_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas::~TextureAtlas() { _shutdownNoThrow(); }
|
||||||
|
|
||||||
|
TextureAtlas::TextureAtlas(TextureAtlas&& other) noexcept {
|
||||||
|
_moveFrom(std::move(other));
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas& TextureAtlas::operator=(TextureAtlas&& other) noexcept {
|
||||||
|
if(this != &other) {
|
||||||
|
_shutdownNoThrow();
|
||||||
|
_moveFrom(std::move(other));
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::shutdown() {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
_shutdownNoThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas::TextureId TextureAtlas::registerTexture() {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
|
||||||
|
TextureId id = kOverflowId;
|
||||||
|
if(!FreeIds_.empty()) {
|
||||||
|
id = FreeIds_.back();
|
||||||
|
FreeIds_.pop_back();
|
||||||
|
} else if(NextId_ < Cfg_.MaxTextureId) {
|
||||||
|
id = NextId_++;
|
||||||
|
} else {
|
||||||
|
return kOverflowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
s = Slot{};
|
||||||
|
s.InUse = true;
|
||||||
|
s.StateValue = State::REGISTERED;
|
||||||
|
s.Generation = 1;
|
||||||
|
|
||||||
|
_setEntryInvalid(id, /*diagPending*/false, /*diagTooLarge*/false);
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::setTextureData(TextureId id,
|
||||||
|
uint32_t w,
|
||||||
|
uint32_t h,
|
||||||
|
const void* pixelsRGBA8,
|
||||||
|
uint32_t rowPitchBytes) {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
if(id == kOverflowId) return;
|
||||||
|
_ensureRegisteredIdOrThrow(id);
|
||||||
|
|
||||||
|
if(w == 0 || h == 0) {
|
||||||
|
throw _inputError("setTextureData: w/h must be > 0");
|
||||||
|
}
|
||||||
|
if(w > Cfg_.MaxTextureSize || h > Cfg_.MaxTextureSize) {
|
||||||
|
_handleTooLarge(id);
|
||||||
|
throw _inputError("setTextureData: texture is TOO_LARGE (>2048)");
|
||||||
|
}
|
||||||
|
if(!pixelsRGBA8) {
|
||||||
|
throw _inputError("setTextureData: pixelsRGBA8 == null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rowPitchBytes == 0) {
|
||||||
|
rowPitchBytes = w * 4;
|
||||||
|
}
|
||||||
|
if(rowPitchBytes < w * 4) {
|
||||||
|
throw _inputError("setTextureData: rowPitchBytes < w*4");
|
||||||
|
}
|
||||||
|
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
|
||||||
|
const bool sizeChanged = (s.HasCpuData && (s.W != w || s.H != h));
|
||||||
|
if(sizeChanged) {
|
||||||
|
_freePlacement(id);
|
||||||
|
_setEntryInvalid(id, /*diagPending*/true, /*diagTooLarge*/false);
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
s.W = w;
|
||||||
|
s.H = h;
|
||||||
|
|
||||||
|
s.CpuPixels = static_cast<const uint8_t*>(pixelsRGBA8);
|
||||||
|
s.CpuRowPitchBytes = rowPitchBytes;
|
||||||
|
s.HasCpuData = true;
|
||||||
|
s.StateValue = State::PENDING_UPLOAD;
|
||||||
|
s.Generation++;
|
||||||
|
|
||||||
|
if(!sizeChanged && s.HasPlacement && s.StateWasValid) {
|
||||||
|
// keep entry valid
|
||||||
|
} else if(!s.HasPlacement) {
|
||||||
|
_setEntryInvalid(id, /*diagPending*/true, /*diagTooLarge*/false);
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_enqueuePending(id);
|
||||||
|
|
||||||
|
if(Repack_.Active && Repack_.Plan.count(id) != 0) {
|
||||||
|
_enqueueRepackPending(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::clearTextureData(TextureId id) {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
if(id == kOverflowId) return;
|
||||||
|
_ensureRegisteredIdOrThrow(id);
|
||||||
|
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
s.CpuPixels = nullptr;
|
||||||
|
s.CpuRowPitchBytes = 0;
|
||||||
|
s.HasCpuData = false;
|
||||||
|
|
||||||
|
_freePlacement(id);
|
||||||
|
s.StateValue = State::REGISTERED;
|
||||||
|
s.StateWasValid = false;
|
||||||
|
|
||||||
|
_removeFromPending(id);
|
||||||
|
_removeFromRepackPending(id);
|
||||||
|
|
||||||
|
_setEntryInvalid(id, /*diagPending*/false, /*diagTooLarge*/false);
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::removeTexture(TextureId id) {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
if(id == kOverflowId) return;
|
||||||
|
_ensureRegisteredIdOrThrow(id);
|
||||||
|
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
|
||||||
|
clearTextureData(id);
|
||||||
|
|
||||||
|
s.InUse = false;
|
||||||
|
s.StateValue = State::REMOVED;
|
||||||
|
|
||||||
|
FreeIds_.push_back(id);
|
||||||
|
|
||||||
|
_setEntryInvalid(id, /*diagPending*/false, /*diagTooLarge*/false);
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::requestFullRepack(RepackMode mode) {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
Repack_.Requested = true;
|
||||||
|
Repack_.Mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas::DescriptorOut TextureAtlas::flushUploadsAndBarriers(VkCommandBuffer cmdBuffer) {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
if(cmdBuffer == VK_NULL_HANDLE) {
|
||||||
|
throw _inputError("flushUploadsAndBarriers: cmdBuffer == null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Repack_.SwapReady) {
|
||||||
|
_swapToRepackedAtlas();
|
||||||
|
}
|
||||||
|
if(Repack_.Requested && !Repack_.Active) {
|
||||||
|
_startRepackIfPossible();
|
||||||
|
}
|
||||||
|
|
||||||
|
_processPendingLayerGrow(cmdBuffer);
|
||||||
|
|
||||||
|
bool willTouchEntries = EntriesDirty_;
|
||||||
|
|
||||||
|
auto collectQueue = [this](std::deque<TextureId>& queue,
|
||||||
|
std::vector<bool>& inQueue,
|
||||||
|
std::vector<TextureId>& out) {
|
||||||
|
while (!queue.empty()) {
|
||||||
|
TextureId id = queue.front();
|
||||||
|
queue.pop_front();
|
||||||
|
if(id == kOverflowId || id >= inQueue.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(!inQueue[id]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
inQueue[id] = false;
|
||||||
|
out.push_back(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<TextureId> pendingNow;
|
||||||
|
pendingNow.reserve(Pending_.size());
|
||||||
|
collectQueue(Pending_, PendingInQueue_, pendingNow);
|
||||||
|
|
||||||
|
std::vector<TextureId> repackPending;
|
||||||
|
if(Repack_.Active) {
|
||||||
|
if(Repack_.InPending.empty()) {
|
||||||
|
Repack_.InPending.assign(Cfg_.MaxTextureId, false);
|
||||||
|
}
|
||||||
|
collectQueue(Repack_.Pending, Repack_.InPending, repackPending);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto processPlacement = [&](TextureId id, Slot& s) -> bool {
|
||||||
|
if(s.HasPlacement) return true;
|
||||||
|
const uint32_t wP = s.W + 2u * Cfg_.PaddingPx;
|
||||||
|
const uint32_t hP = s.H + 2u * Cfg_.PaddingPx;
|
||||||
|
if(!_tryPlaceWithGrow(id, wP, hP, cmdBuffer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
willTouchEntries = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool outOfSpace = false;
|
||||||
|
for(TextureId id : pendingNow) {
|
||||||
|
if(id == kOverflowId) continue;
|
||||||
|
if(id >= Slots_.size()) continue;
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
if(!s.InUse || !s.HasCpuData) continue;
|
||||||
|
if(!processPlacement(id, s)) {
|
||||||
|
outOfSpace = true;
|
||||||
|
_enqueuePending(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(outOfSpace) {
|
||||||
|
_emitEventOncePerFlush(AtlasEvent::AtlasOutOfSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool anyAtlasWrites = false;
|
||||||
|
bool anyRepackWrites = false;
|
||||||
|
|
||||||
|
auto uploadTextureIntoAtlas = [&](Slot& s,
|
||||||
|
const Placement& pp,
|
||||||
|
ImageRes& targetAtlas,
|
||||||
|
bool isRepackTarget) {
|
||||||
|
const uint32_t wP = pp.WP;
|
||||||
|
const uint32_t hP = pp.HP;
|
||||||
|
const VkDeviceSize bytes = static_cast<VkDeviceSize>(wP) * hP * 4u;
|
||||||
|
auto stagingOff = Staging_->Allocate(bytes, CopyOffsetAlignment_);
|
||||||
|
if(!stagingOff) {
|
||||||
|
_emitEventOncePerFlush(AtlasEvent::StagingOverflow);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* dst = static_cast<uint8_t*>(Staging_->Mapped()) + *stagingOff;
|
||||||
|
if(!s.CpuPixels) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_writePaddedRGBA8(dst, wP * 4u, s.W, s.H, Cfg_.PaddingPx,
|
||||||
|
s.CpuPixels, s.CpuRowPitchBytes);
|
||||||
|
|
||||||
|
_ensureImageLayoutForTransferDst(cmdBuffer, targetAtlas,
|
||||||
|
isRepackTarget ? anyRepackWrites : anyAtlasWrites);
|
||||||
|
|
||||||
|
VkBufferImageCopy region{};
|
||||||
|
region.bufferOffset = *stagingOff;
|
||||||
|
region.bufferRowLength = wP;
|
||||||
|
region.bufferImageHeight = hP;
|
||||||
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
region.imageSubresource.mipLevel = 0;
|
||||||
|
region.imageSubresource.baseArrayLayer = pp.Layer;
|
||||||
|
region.imageSubresource.layerCount = 1;
|
||||||
|
region.imageOffset = { static_cast<int32_t>(pp.X),
|
||||||
|
static_cast<int32_t>(pp.Y), 0 };
|
||||||
|
region.imageExtent = { wP, hP, 1 };
|
||||||
|
|
||||||
|
vkCmdCopyBufferToImage(cmdBuffer, Staging_->Buffer(), targetAtlas.Image,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
for(TextureId id : pendingNow) {
|
||||||
|
if(id == kOverflowId) continue;
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
if(!s.InUse || !s.HasCpuData || !s.HasPlacement) continue;
|
||||||
|
if(!uploadTextureIntoAtlas(s, s.Place, Atlas_, false)) {
|
||||||
|
_enqueuePending(id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
s.StateValue = State::VALID;
|
||||||
|
s.StateWasValid = true;
|
||||||
|
_setEntryValid(id);
|
||||||
|
EntriesDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Repack_.Active) {
|
||||||
|
for(TextureId id : repackPending) {
|
||||||
|
if(Repack_.Plan.count(id) == 0) continue;
|
||||||
|
Slot& s = Slots_[id];
|
||||||
|
if(!s.InUse || !s.HasCpuData) continue;
|
||||||
|
const PlannedPlacement& pp = Repack_.Plan[id];
|
||||||
|
Placement place{pp.X, pp.Y, pp.WP, pp.HP, pp.Layer};
|
||||||
|
if(!uploadTextureIntoAtlas(s, place, Repack_.Atlas, true)) {
|
||||||
|
_enqueueRepackPending(id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Repack_.WroteSomethingThisFlush = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(willTouchEntries || EntriesDirty_) {
|
||||||
|
const VkDeviceSize entriesBytes = static_cast<VkDeviceSize>(EntriesCpu_.size()) * sizeof(Entry);
|
||||||
|
auto off = Staging_->Allocate(entriesBytes, CopyOffsetAlignment_);
|
||||||
|
if(!off) {
|
||||||
|
_emitEventOncePerFlush(AtlasEvent::StagingOverflow);
|
||||||
|
} else {
|
||||||
|
std::memcpy(static_cast<uint8_t*>(Staging_->Mapped()) + *off,
|
||||||
|
EntriesCpu_.data(),
|
||||||
|
static_cast<size_t>(entriesBytes));
|
||||||
|
|
||||||
|
VkBufferCopy c{};
|
||||||
|
c.srcOffset = *off;
|
||||||
|
c.dstOffset = 0;
|
||||||
|
c.size = entriesBytes;
|
||||||
|
vkCmdCopyBuffer(cmdBuffer, Staging_->Buffer(), Entries_.Buffer, 1, &c);
|
||||||
|
|
||||||
|
VkBufferMemoryBarrier b{};
|
||||||
|
b.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
|
||||||
|
b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||||
|
b.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||||
|
b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
b.buffer = Entries_.Buffer;
|
||||||
|
b.offset = 0;
|
||||||
|
b.size = VK_WHOLE_SIZE;
|
||||||
|
|
||||||
|
vkCmdPipelineBarrier(cmdBuffer,
|
||||||
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
|
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||||
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||||
|
0, 0, nullptr, 1, &b, 0, nullptr);
|
||||||
|
EntriesDirty_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(anyAtlasWrites) {
|
||||||
|
_transitionImage(cmdBuffer, Atlas_,
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
|
VK_ACCESS_SHADER_READ_BIT,
|
||||||
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
||||||
|
} else if(Atlas_.Layout == VK_IMAGE_LAYOUT_UNDEFINED) {
|
||||||
|
_transitionImage(cmdBuffer, Atlas_,
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
0, VK_ACCESS_SHADER_READ_BIT,
|
||||||
|
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(anyRepackWrites) {
|
||||||
|
_transitionImage(cmdBuffer, Repack_.Atlas,
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
|
VK_ACCESS_SHADER_READ_BIT,
|
||||||
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Repack_.Active) {
|
||||||
|
if(Repack_.Pending.empty()) {
|
||||||
|
Repack_.WaitingGpuForReady = true;
|
||||||
|
}
|
||||||
|
Repack_.WroteSomethingThisFlush = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _buildDescriptorOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::notifyGpuFinished() {
|
||||||
|
_ensureAliveOrThrow();
|
||||||
|
|
||||||
|
for(auto& img : DeferredImages_) {
|
||||||
|
_destroyImage(img);
|
||||||
|
}
|
||||||
|
DeferredImages_.clear();
|
||||||
|
|
||||||
|
if(Staging_) {
|
||||||
|
Staging_->Reset();
|
||||||
|
}
|
||||||
|
FlushEventMask_ = 0;
|
||||||
|
|
||||||
|
if(Repack_.Active && Repack_.WaitingGpuForReady && Repack_.Pending.empty()) {
|
||||||
|
Repack_.SwapReady = true;
|
||||||
|
Repack_.WaitingGpuForReady = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::_moveFrom(TextureAtlas&& other) noexcept {
|
||||||
|
Device_ = other.Device_;
|
||||||
|
Phys_ = other.Phys_;
|
||||||
|
Cfg_ = other.Cfg_;
|
||||||
|
OnEvent_ = std::move(other.OnEvent_);
|
||||||
|
Alive_ = other.Alive_;
|
||||||
|
CopyOffsetAlignment_ = other.CopyOffsetAlignment_;
|
||||||
|
Staging_ = std::move(other.Staging_);
|
||||||
|
Entries_ = other.Entries_;
|
||||||
|
Atlas_ = other.Atlas_;
|
||||||
|
Sampler_ = other.Sampler_;
|
||||||
|
OwnsSampler_ = other.OwnsSampler_;
|
||||||
|
EntriesCpu_ = std::move(other.EntriesCpu_);
|
||||||
|
EntriesDirty_ = other.EntriesDirty_;
|
||||||
|
Slots_ = std::move(other.Slots_);
|
||||||
|
FreeIds_ = std::move(other.FreeIds_);
|
||||||
|
NextId_ = other.NextId_;
|
||||||
|
Pending_ = std::move(other.Pending_);
|
||||||
|
PendingInQueue_ = std::move(other.PendingInQueue_);
|
||||||
|
Packers_ = std::move(other.Packers_);
|
||||||
|
DeferredImages_ = std::move(other.DeferredImages_);
|
||||||
|
FlushEventMask_ = other.FlushEventMask_;
|
||||||
|
GrewThisFlush_ = other.GrewThisFlush_;
|
||||||
|
Repack_ = std::move(other.Repack_);
|
||||||
|
|
||||||
|
other.Device_ = VK_NULL_HANDLE;
|
||||||
|
other.Phys_ = VK_NULL_HANDLE;
|
||||||
|
other.OnEvent_ = {};
|
||||||
|
other.Alive_ = false;
|
||||||
|
other.CopyOffsetAlignment_ = 0;
|
||||||
|
other.Staging_.reset();
|
||||||
|
other.Entries_ = {};
|
||||||
|
other.Atlas_ = {};
|
||||||
|
other.Sampler_ = VK_NULL_HANDLE;
|
||||||
|
other.OwnsSampler_ = false;
|
||||||
|
other.EntriesCpu_.clear();
|
||||||
|
other.EntriesDirty_ = false;
|
||||||
|
other.Slots_.clear();
|
||||||
|
other.FreeIds_.clear();
|
||||||
|
other.NextId_ = 0;
|
||||||
|
other.Pending_.clear();
|
||||||
|
other.PendingInQueue_.clear();
|
||||||
|
other.Packers_.clear();
|
||||||
|
other.DeferredImages_.clear();
|
||||||
|
other.FlushEventMask_ = 0;
|
||||||
|
other.GrewThisFlush_ = false;
|
||||||
|
other.Repack_ = RepackState{};
|
||||||
|
}
|
||||||
1394
Src/Client/Vulkan/AtlasPipeline/TextureAtlas.hpp
Normal file
1271
Src/Client/Vulkan/AtlasPipeline/TexturePipelineProgram.hpp
Normal file
@@ -1,12 +1,43 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Vulkan.hpp"
|
#include "Vulkan.hpp"
|
||||||
|
#include "Client/Vulkan/AtlasPipeline/SharedStagingBuffer.hpp"
|
||||||
|
#include <algorithm>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
#include <vulkan/vulkan_core.h>
|
#include <vulkan/vulkan_core.h>
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Client::VK {
|
namespace LV::Client::VK {
|
||||||
|
|
||||||
|
inline std::weak_ptr<SharedStagingBuffer>& globalVertexStaging() {
|
||||||
|
static std::weak_ptr<SharedStagingBuffer> staging;
|
||||||
|
return staging;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::shared_ptr<SharedStagingBuffer> getOrCreateVertexStaging(Vulkan* inst) {
|
||||||
|
auto& staging = globalVertexStaging();
|
||||||
|
std::shared_ptr<SharedStagingBuffer> shared = staging.lock();
|
||||||
|
if(!shared) {
|
||||||
|
shared = std::make_shared<SharedStagingBuffer>(
|
||||||
|
inst->Graphics.Device,
|
||||||
|
inst->Graphics.PhysicalDevice
|
||||||
|
);
|
||||||
|
staging = shared;
|
||||||
|
}
|
||||||
|
return shared;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void resetVertexStaging() {
|
||||||
|
auto& staging = globalVertexStaging();
|
||||||
|
if(auto shared = staging.lock())
|
||||||
|
shared->Reset();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Память на устройстве выделяется пулами
|
Память на устройстве выделяется пулами
|
||||||
Для массивов вершин память выделяется блоками по PerBlock вершин в каждом
|
Для массивов вершин память выделяется блоками по PerBlock вершин в каждом
|
||||||
@@ -22,10 +53,8 @@ class VertexPool {
|
|||||||
Vulkan *Inst;
|
Vulkan *Inst;
|
||||||
|
|
||||||
// Память, доступная для обмена с устройством
|
// Память, доступная для обмена с устройством
|
||||||
Buffer HostCoherent;
|
std::shared_ptr<SharedStagingBuffer> Staging;
|
||||||
Vertex *HCPtr = nullptr;
|
VkDeviceSize CopyOffsetAlignment = 4;
|
||||||
VkFence Fence = nullptr;
|
|
||||||
size_t WritePos = 0;
|
|
||||||
|
|
||||||
struct Pool {
|
struct Pool {
|
||||||
// Память на устройстве
|
// Память на устройстве
|
||||||
@@ -47,7 +76,6 @@ class VertexPool {
|
|||||||
|
|
||||||
struct Task {
|
struct Task {
|
||||||
std::vector<Vertex> Data;
|
std::vector<Vertex> Data;
|
||||||
size_t Pos = -1; // Если данные уже записаны, то будет указана позиция в буфере общения
|
|
||||||
uint8_t PoolId; // Куда потом направить
|
uint8_t PoolId; // Куда потом направить
|
||||||
uint16_t BlockId; // И в какой блок
|
uint16_t BlockId; // И в какой блок
|
||||||
};
|
};
|
||||||
@@ -61,46 +89,21 @@ class VertexPool {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void pushData(std::vector<Vertex>&& data, uint8_t poolId, uint16_t blockId) {
|
void pushData(std::vector<Vertex>&& data, uint8_t poolId, uint16_t blockId) {
|
||||||
if(HC_Buffer_Size-WritePos >= data.size()) {
|
TasksWait.push({std::move(data), poolId, blockId});
|
||||||
// Пишем в общий буфер, TasksWait
|
|
||||||
Vertex *ptr = HCPtr+WritePos;
|
|
||||||
std::copy(data.begin(), data.end(), ptr);
|
|
||||||
size_t count = data.size();
|
|
||||||
TasksWait.push({std::move(data), WritePos, poolId, blockId});
|
|
||||||
WritePos += count;
|
|
||||||
} else {
|
|
||||||
// Отложим запись на следующий такт
|
|
||||||
TasksPostponed.push(Task(std::move(data), -1, poolId, blockId));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VertexPool(Vulkan* inst)
|
VertexPool(Vulkan* inst)
|
||||||
: Inst(inst),
|
: Inst(inst)
|
||||||
HostCoherent(inst,
|
|
||||||
sizeof(Vertex)*HC_Buffer_Size+4 /* Для vkCmdFillBuffer */,
|
|
||||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
|
||||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
|
|
||||||
{
|
{
|
||||||
Pools.reserve(16);
|
Pools.reserve(16);
|
||||||
HCPtr = (Vertex*) HostCoherent.mapMemory();
|
Staging = getOrCreateVertexStaging(inst);
|
||||||
|
VkPhysicalDeviceProperties props{};
|
||||||
const VkFenceCreateInfo info = {
|
vkGetPhysicalDeviceProperties(inst->Graphics.PhysicalDevice, &props);
|
||||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
CopyOffsetAlignment = std::max<VkDeviceSize>(4, props.limits.optimalBufferCopyOffsetAlignment);
|
||||||
.pNext = nullptr,
|
|
||||||
.flags = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
vkAssert(!vkCreateFence(inst->Graphics.Device, &info, nullptr, &Fence));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~VertexPool() {
|
~VertexPool() {
|
||||||
if(HCPtr)
|
|
||||||
HostCoherent.unMapMemory();
|
|
||||||
|
|
||||||
if(Fence) {
|
|
||||||
vkDestroyFence(Inst->Graphics.Device, Fence, nullptr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -229,44 +232,65 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Должно вызываться после приёма всех данных и перед рендером
|
Должно вызываться после приёма всех данных, до начала рендера в командном буфере
|
||||||
*/
|
*/
|
||||||
void update(VkCommandPool commandPool) {
|
void flushUploadsAndBarriers(VkCommandBuffer commandBuffer) {
|
||||||
if(TasksWait.empty())
|
if(TasksWait.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
assert(WritePos);
|
struct CopyTask {
|
||||||
|
VkBuffer DstBuffer;
|
||||||
VkCommandBufferAllocateInfo allocInfo {
|
VkDeviceSize SrcOffset;
|
||||||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
VkDeviceSize DstOffset;
|
||||||
nullptr,
|
VkDeviceSize Size;
|
||||||
commandPool,
|
uint8_t PoolId;
|
||||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
|
||||||
1
|
|
||||||
};
|
};
|
||||||
|
|
||||||
VkCommandBuffer commandBuffer;
|
std::vector<CopyTask> copies;
|
||||||
vkAllocateCommandBuffers(Inst->Graphics.Device, &allocInfo, &commandBuffer);
|
copies.reserve(TasksWait.size());
|
||||||
|
std::vector<uint8_t> touchedPools(Pools.size(), 0);
|
||||||
|
|
||||||
VkCommandBufferBeginInfo beginInfo {
|
while(!TasksWait.empty()) {
|
||||||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
Task task = std::move(TasksWait.front());
|
||||||
nullptr,
|
TasksWait.pop();
|
||||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
|
||||||
nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
vkBeginCommandBuffer(commandBuffer, &beginInfo);
|
VkDeviceSize bytes = task.Data.size()*sizeof(Vertex);
|
||||||
|
std::optional<VkDeviceSize> stagingOffset = Staging->Allocate(bytes, CopyOffsetAlignment);
|
||||||
|
if(!stagingOffset) {
|
||||||
|
TasksPostponed.push(std::move(task));
|
||||||
|
while(!TasksWait.empty()) {
|
||||||
|
TasksPostponed.push(std::move(TasksWait.front()));
|
||||||
|
TasksWait.pop();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
VkBufferMemoryBarrier barrier = {
|
std::memcpy(static_cast<uint8_t*>(Staging->Mapped()) + *stagingOffset,
|
||||||
|
task.Data.data(), bytes);
|
||||||
|
|
||||||
|
copies.push_back({
|
||||||
|
Pools[task.PoolId].DeviceBuff.getBuffer(),
|
||||||
|
*stagingOffset,
|
||||||
|
task.BlockId*sizeof(Vertex)*size_t(PerBlock),
|
||||||
|
bytes,
|
||||||
|
task.PoolId
|
||||||
|
});
|
||||||
|
touchedPools[task.PoolId] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(copies.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
VkBufferMemoryBarrier stagingBarrier = {
|
||||||
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||||
nullptr,
|
nullptr,
|
||||||
VK_ACCESS_HOST_WRITE_BIT,
|
VK_ACCESS_HOST_WRITE_BIT,
|
||||||
VK_ACCESS_TRANSFER_READ_BIT,
|
VK_ACCESS_TRANSFER_READ_BIT,
|
||||||
VK_QUEUE_FAMILY_IGNORED,
|
VK_QUEUE_FAMILY_IGNORED,
|
||||||
VK_QUEUE_FAMILY_IGNORED,
|
VK_QUEUE_FAMILY_IGNORED,
|
||||||
HostCoherent.getBuffer(),
|
Staging->Buffer(),
|
||||||
0,
|
0,
|
||||||
WritePos*sizeof(Vertex)
|
Staging->Size()
|
||||||
};
|
};
|
||||||
|
|
||||||
vkCmdPipelineBarrier(
|
vkCmdPipelineBarrier(
|
||||||
@@ -275,53 +299,60 @@ public:
|
|||||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
0,
|
0,
|
||||||
0, nullptr,
|
0, nullptr,
|
||||||
1, &barrier,
|
1, &stagingBarrier,
|
||||||
0, nullptr
|
0, nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
while(!TasksWait.empty()) {
|
for(const CopyTask& copy : copies) {
|
||||||
Task& task = TasksWait.front();
|
|
||||||
|
|
||||||
VkBufferCopy copyRegion {
|
VkBufferCopy copyRegion {
|
||||||
task.Pos*sizeof(Vertex),
|
copy.SrcOffset,
|
||||||
task.BlockId*sizeof(Vertex)*size_t(PerBlock),
|
copy.DstOffset,
|
||||||
task.Data.size()*sizeof(Vertex)
|
copy.Size
|
||||||
};
|
};
|
||||||
|
|
||||||
assert(copyRegion.dstOffset+copyRegion.size < sizeof(Vertex)*PerBlock*PerPool);
|
assert(copyRegion.dstOffset+copyRegion.size <= Pools[copy.PoolId].DeviceBuff.getSize());
|
||||||
|
|
||||||
vkCmdCopyBuffer(commandBuffer, HostCoherent.getBuffer(), Pools[task.PoolId].DeviceBuff.getBuffer(),
|
vkCmdCopyBuffer(commandBuffer, Staging->Buffer(), copy.DstBuffer, 1, ©Region);
|
||||||
1, ©Region);
|
|
||||||
|
|
||||||
TasksWait.pop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vkEndCommandBuffer(commandBuffer);
|
std::vector<VkBufferMemoryBarrier> dstBarriers;
|
||||||
|
dstBarriers.reserve(Pools.size());
|
||||||
|
for(size_t poolId = 0; poolId < Pools.size(); poolId++) {
|
||||||
|
if(!touchedPools[poolId])
|
||||||
|
continue;
|
||||||
|
|
||||||
VkSubmitInfo submitInfo {
|
VkBufferMemoryBarrier barrier = {
|
||||||
VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||||
nullptr,
|
nullptr,
|
||||||
0, nullptr,
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
nullptr,
|
IsIndex ? VK_ACCESS_INDEX_READ_BIT : VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
|
||||||
1,
|
VK_QUEUE_FAMILY_IGNORED,
|
||||||
&commandBuffer,
|
VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
Pools[poolId].DeviceBuff.getBuffer(),
|
||||||
0,
|
0,
|
||||||
nullptr
|
Pools[poolId].DeviceBuff.getSize()
|
||||||
};
|
};
|
||||||
{
|
dstBarriers.push_back(barrier);
|
||||||
auto lockQueue = Inst->Graphics.DeviceQueueGraphic.lock();
|
|
||||||
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submitInfo, Fence));
|
|
||||||
}
|
}
|
||||||
vkAssert(!vkWaitForFences(Inst->Graphics.Device, 1, &Fence, VK_TRUE, UINT64_MAX));
|
|
||||||
vkAssert(!vkResetFences(Inst->Graphics.Device, 1, &Fence));
|
|
||||||
vkFreeCommandBuffers(Inst->Graphics.Device, commandPool, 1, &commandBuffer);
|
|
||||||
|
|
||||||
|
if(!dstBarriers.empty()) {
|
||||||
|
vkCmdPipelineBarrier(
|
||||||
|
commandBuffer,
|
||||||
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
|
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
|
||||||
|
0,
|
||||||
|
0, nullptr,
|
||||||
|
static_cast<uint32_t>(dstBarriers.size()),
|
||||||
|
dstBarriers.data(),
|
||||||
|
0, nullptr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void notifyGpuFinished() {
|
||||||
std::queue<Task> postponed = std::move(TasksPostponed);
|
std::queue<Task> postponed = std::move(TasksPostponed);
|
||||||
WritePos = 0;
|
|
||||||
|
|
||||||
while(!postponed.empty()) {
|
while(!postponed.empty()) {
|
||||||
Task& task = postponed.front();
|
TasksWait.push(std::move(postponed.front()));
|
||||||
pushData(std::move(task.Data), task.PoolId, task.BlockId);
|
|
||||||
postponed.pop();
|
postponed.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -275,10 +275,6 @@ void Vulkan::run()
|
|||||||
// if(CallBeforeDraw)
|
// if(CallBeforeDraw)
|
||||||
// CallBeforeDraw(this);
|
// CallBeforeDraw(this);
|
||||||
|
|
||||||
if(Game.RSession) {
|
|
||||||
Game.RSession->beforeDraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
|
|
||||||
VkResult err;
|
VkResult err;
|
||||||
@@ -314,6 +310,10 @@ void Vulkan::run()
|
|||||||
vkAssert(!vkBeginCommandBuffer(Graphics.CommandBufferRender, &cmd_buf_info));
|
vkAssert(!vkBeginCommandBuffer(Graphics.CommandBufferRender, &cmd_buf_info));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(Game.RSession) {
|
||||||
|
Game.RSession->beforeDraw();
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
VkImageMemoryBarrier image_memory_barrier =
|
VkImageMemoryBarrier image_memory_barrier =
|
||||||
{
|
{
|
||||||
@@ -602,6 +602,8 @@ void Vulkan::run()
|
|||||||
// Насильно ожидаем завершения рендера кадра
|
// Насильно ожидаем завершения рендера кадра
|
||||||
vkWaitForFences(Graphics.Device, 1, &drawEndFence, true, -1);
|
vkWaitForFences(Graphics.Device, 1, &drawEndFence, true, -1);
|
||||||
vkResetFences(Graphics.Device, 1, &drawEndFence);
|
vkResetFences(Graphics.Device, 1, &drawEndFence);
|
||||||
|
if(Game.RSession)
|
||||||
|
Game.RSession->onGpuFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -2303,6 +2305,10 @@ void Vulkan::gui_ConnectedToServer() {
|
|||||||
if(ImGui::Button("Delimeter"))
|
if(ImGui::Button("Delimeter"))
|
||||||
LOG.debug();
|
LOG.debug();
|
||||||
|
|
||||||
|
if(ImGui::Button("Перезагрузить моды")) {
|
||||||
|
Game.Session->requestModsReload();
|
||||||
|
}
|
||||||
|
|
||||||
if(ImGui::Button("Выйти")) {
|
if(ImGui::Button("Выйти")) {
|
||||||
Game.Выйти = true;
|
Game.Выйти = true;
|
||||||
Game.ImGuiInterfaces.pop_back();
|
Game.ImGuiInterfaces.pop_back();
|
||||||
|
|||||||
@@ -948,8 +948,11 @@ PreparedNodeState::PreparedNodeState(const std::u8string_view data) {
|
|||||||
for(int counter4 = 0; counter4 < transformsSize; counter4++) {
|
for(int counter4 = 0; counter4 < transformsSize; counter4++) {
|
||||||
Transformation tr;
|
Transformation tr;
|
||||||
tr.Op = Transformation::EnumTransform(lr.read<uint8_t>());
|
tr.Op = Transformation::EnumTransform(lr.read<uint8_t>());
|
||||||
|
tr.Value = lr.read<float>();
|
||||||
mod2.Transforms.push_back(tr);
|
mod2.Transforms.push_back(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod.Models.push_back(std::move(mod2));
|
||||||
}
|
}
|
||||||
|
|
||||||
mod.UVLock = lr.read<uint8_t>();
|
mod.UVLock = lr.read<uint8_t>();
|
||||||
@@ -961,6 +964,7 @@ PreparedNodeState::PreparedNodeState(const std::u8string_view data) {
|
|||||||
for(int counter3 = 0; counter3 < transformsSize; counter3++) {
|
for(int counter3 = 0; counter3 < transformsSize; counter3++) {
|
||||||
Transformation tr;
|
Transformation tr;
|
||||||
tr.Op = Transformation::EnumTransform(lr.read<uint8_t>());
|
tr.Op = Transformation::EnumTransform(lr.read<uint8_t>());
|
||||||
|
tr.Value = lr.read<float>();
|
||||||
mod.Transforms.push_back(tr);
|
mod.Transforms.push_back(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -976,12 +980,15 @@ PreparedNodeState::PreparedNodeState(const std::u8string_view data) {
|
|||||||
for(int counter3 = 0; counter3 < transformsSize; counter3++) {
|
for(int counter3 = 0; counter3 < transformsSize; counter3++) {
|
||||||
Transformation tr;
|
Transformation tr;
|
||||||
tr.Op = Transformation::EnumTransform(lr.read<uint8_t>());
|
tr.Op = Transformation::EnumTransform(lr.read<uint8_t>());
|
||||||
|
tr.Value = lr.read<float>();
|
||||||
mod.Transforms.push_back(tr);
|
mod.Transforms.push_back(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
variants.emplace_back(weight, std::move(mod));
|
variants.emplace_back(weight, std::move(mod));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Routes.emplace_back(nodeId, std::move(variants));
|
||||||
}
|
}
|
||||||
|
|
||||||
lr.checkUnreaded();
|
lr.checkUnreaded();
|
||||||
@@ -990,6 +997,9 @@ PreparedNodeState::PreparedNodeState(const std::u8string_view data) {
|
|||||||
std::u8string PreparedNodeState::dump() const {
|
std::u8string PreparedNodeState::dump() const {
|
||||||
Net::Packet result;
|
Net::Packet result;
|
||||||
|
|
||||||
|
const char magic[] = "bn";
|
||||||
|
result.write(reinterpret_cast<const std::byte*>(magic), 2);
|
||||||
|
|
||||||
// ResourceToLocalId
|
// ResourceToLocalId
|
||||||
assert(LocalToModelKD.size() < (1 << 16));
|
assert(LocalToModelKD.size() < (1 << 16));
|
||||||
assert(LocalToModelKD.size() == LocalToModel.size());
|
assert(LocalToModelKD.size() == LocalToModel.size());
|
||||||
@@ -1088,9 +1098,9 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
char c = expression[pos];
|
char c = expression[pos];
|
||||||
|
|
||||||
// Числа
|
// Числа
|
||||||
if(std::isdigit(c)) {
|
if(std::isdigit(static_cast<unsigned char>(c))) {
|
||||||
ssize_t npos = pos;
|
ssize_t npos = pos;
|
||||||
for(; npos < expression.size() && std::isdigit(expression[npos]); npos++);
|
for(; npos < expression.size() && std::isdigit(static_cast<unsigned char>(expression[npos])); npos++);
|
||||||
int value;
|
int value;
|
||||||
std::string_view value_view = expression.substr(pos, npos-pos);
|
std::string_view value_view = expression.substr(pos, npos-pos);
|
||||||
auto [partial_ptr, partial_ec] = std::from_chars(value_view.data(), value_view.data() + value_view.size(), value);
|
auto [partial_ptr, partial_ec] = std::from_chars(value_view.data(), value_view.data() + value_view.size(), value);
|
||||||
@@ -1102,15 +1112,20 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tokens.push_back(value);
|
tokens.push_back(value);
|
||||||
|
pos = npos - 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Переменные
|
// Переменные
|
||||||
if(std::isalpha(c) || c == ':') {
|
if(std::isalpha(static_cast<unsigned char>(c)) || c == '_' || c == ':') {
|
||||||
ssize_t npos = pos;
|
ssize_t npos = pos;
|
||||||
for(; npos < expression.size() && std::isalpha(expression[npos]); npos++);
|
for(; npos < expression.size(); npos++) {
|
||||||
|
char ch = expression[npos];
|
||||||
|
if(!std::isalnum(static_cast<unsigned char>(ch)) && ch != '_' && ch != ':')
|
||||||
|
break;
|
||||||
|
}
|
||||||
std::string_view value = expression.substr(pos, npos-pos);
|
std::string_view value = expression.substr(pos, npos-pos);
|
||||||
pos += value.size();
|
pos = npos - 1;
|
||||||
if(value == "true")
|
if(value == "true")
|
||||||
tokens.push_back(1);
|
tokens.push_back(1);
|
||||||
else if(value == "false")
|
else if(value == "false")
|
||||||
@@ -1121,7 +1136,7 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Двойные операторы
|
// Двойные операторы
|
||||||
if(pos-1 < expression.size()) {
|
if(pos + 1 < expression.size()) {
|
||||||
char n = expression[pos+1];
|
char n = expression[pos+1];
|
||||||
|
|
||||||
if(c == '<' && n == '=') {
|
if(c == '<' && n == '=') {
|
||||||
@@ -1145,22 +1160,23 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
|
|
||||||
// Операторы
|
// Операторы
|
||||||
switch(c) {
|
switch(c) {
|
||||||
case '(': tokens.push_back(EnumTokenKind::LParen);
|
case '(': tokens.push_back(EnumTokenKind::LParen); break;
|
||||||
case ')': tokens.push_back(EnumTokenKind::RParen);
|
case ')': tokens.push_back(EnumTokenKind::RParen); break;
|
||||||
case '+': tokens.push_back(EnumTokenKind::Plus);
|
case '+': tokens.push_back(EnumTokenKind::Plus); break;
|
||||||
case '-': tokens.push_back(EnumTokenKind::Minus);
|
case '-': tokens.push_back(EnumTokenKind::Minus); break;
|
||||||
case '*': tokens.push_back(EnumTokenKind::Star);
|
case '*': tokens.push_back(EnumTokenKind::Star); break;
|
||||||
case '/': tokens.push_back(EnumTokenKind::Slash);
|
case '/': tokens.push_back(EnumTokenKind::Slash); break;
|
||||||
case '%': tokens.push_back(EnumTokenKind::Percent);
|
case '%': tokens.push_back(EnumTokenKind::Percent); break;
|
||||||
case '!': tokens.push_back(EnumTokenKind::Not);
|
case '!': tokens.push_back(EnumTokenKind::Not); break;
|
||||||
case '&': tokens.push_back(EnumTokenKind::And);
|
case '&': tokens.push_back(EnumTokenKind::And); break;
|
||||||
case '|': tokens.push_back(EnumTokenKind::Or);
|
case '|': tokens.push_back(EnumTokenKind::Or); break;
|
||||||
case '<': tokens.push_back(EnumTokenKind::LT);
|
case '<': tokens.push_back(EnumTokenKind::LT); break;
|
||||||
case '>': tokens.push_back(EnumTokenKind::GT);
|
case '>': tokens.push_back(EnumTokenKind::GT); break;
|
||||||
}
|
default:
|
||||||
|
|
||||||
MAKE_ERROR("Недопустимый символ: " << c);
|
MAKE_ERROR("Недопустимый символ: " << c);
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
for(size_t index = 0; index < tokens.size(); index++) {
|
for(size_t index = 0; index < tokens.size(); index++) {
|
||||||
@@ -1344,6 +1360,7 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
bin.rhs = *nodeId;
|
bin.rhs = *nodeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node.v = bin;
|
||||||
Nodes.emplace_back(std::move(node));
|
Nodes.emplace_back(std::move(node));
|
||||||
assert(Nodes.size() < std::pow(2, 16)-64);
|
assert(Nodes.size() < std::pow(2, 16)-64);
|
||||||
leftToken = uint16_t(Nodes.size()-1);
|
leftToken = uint16_t(Nodes.size()-1);
|
||||||
@@ -1743,6 +1760,29 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(boost::system::result<const js::value&> submodels_val = profile.try_at("sub_models")) {
|
||||||
|
const js::array& submodels = submodels_val->as_array();
|
||||||
|
SubModels.reserve(submodels.size());
|
||||||
|
|
||||||
|
for(const js::value& value : submodels) {
|
||||||
|
if(const auto model_key = value.try_as_string()) {
|
||||||
|
auto [domain, key] = parseDomainKey((std::string) *model_key, modid);
|
||||||
|
SubModels.push_back({std::move(domain), std::move(key), std::nullopt});
|
||||||
|
} else {
|
||||||
|
const js::object& obj = value.as_object();
|
||||||
|
const std::string model_key_str = (std::string) obj.at("model").as_string();
|
||||||
|
auto [domain, key] = parseDomainKey(model_key_str, modid);
|
||||||
|
|
||||||
|
std::optional<uint16_t> scene;
|
||||||
|
if(const auto scene_val = obj.try_at("scene")) {
|
||||||
|
scene = static_cast<uint16_t>(scene_val->to_number<int>());
|
||||||
|
}
|
||||||
|
|
||||||
|
SubModels.push_back({std::move(domain), std::move(key), scene});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(boost::system::result<const js::value&> subModels_val = profile.try_at("sub_models")) {
|
if(boost::system::result<const js::value&> subModels_val = profile.try_at("sub_models")) {
|
||||||
const js::array& subModels = subModels_val->as_array();
|
const js::array& subModels = subModels_val->as_array();
|
||||||
|
|
||||||
|
|||||||
@@ -713,13 +713,15 @@ struct PreparedNodeState {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for(const auto& route : Routes)
|
||||||
|
lambda(route.first);
|
||||||
|
|
||||||
std::sort(variables.begin(), variables.end());
|
std::sort(variables.begin(), variables.end());
|
||||||
auto eraseIter = std::unique(variables.begin(), variables.end());
|
auto eraseIter = std::unique(variables.begin(), variables.end());
|
||||||
variables.erase(eraseIter, variables.end());
|
variables.erase(eraseIter, variables.end());
|
||||||
|
|
||||||
bool ok = false;
|
|
||||||
|
|
||||||
for(const std::string_view key : variables) {
|
for(const std::string_view key : variables) {
|
||||||
|
bool ok = false;
|
||||||
if(size_t pos = key.find(':'); pos != std::string::npos) {
|
if(size_t pos = key.find(':'); pos != std::string::npos) {
|
||||||
std::string_view state, value;
|
std::string_view state, value;
|
||||||
state = key.substr(0, pos);
|
state = key.substr(0, pos);
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ enum struct L2System : uint8_t {
|
|||||||
Disconnect,
|
Disconnect,
|
||||||
Test_CAM_PYR_POS,
|
Test_CAM_PYR_POS,
|
||||||
BlockChange,
|
BlockChange,
|
||||||
ResourceRequest
|
ResourceRequest,
|
||||||
|
ReloadMods
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,11 +221,18 @@ std::tuple<ResourceId, std::optional<AssetsManager::DataEntry>&> AssetsManager::
|
|||||||
|
|
||||||
for(size_t index = 0; index < table.size(); index++) {
|
for(size_t index = 0; index < table.size(); index++) {
|
||||||
auto& entry = *table[index];
|
auto& entry = *table[index];
|
||||||
|
if(index == 0 && entry.Empty.test(0)) {
|
||||||
|
entry.Empty.reset(0);
|
||||||
|
}
|
||||||
|
|
||||||
if(entry.IsFull)
|
if(entry.IsFull)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
uint32_t pos = entry.Empty._Find_first();
|
uint32_t pos = entry.Empty._Find_first();
|
||||||
|
if(pos == entry.Empty.size()) {
|
||||||
|
entry.IsFull = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
entry.Empty.reset(pos);
|
entry.Empty.reset(pos);
|
||||||
|
|
||||||
if(entry.Empty._Find_next(pos) == entry.Empty.size())
|
if(entry.Empty._Find_next(pos) == entry.Empty.size())
|
||||||
@@ -233,13 +240,23 @@ std::tuple<ResourceId, std::optional<AssetsManager::DataEntry>&> AssetsManager::
|
|||||||
|
|
||||||
id = index*TableEntry<DataEntry>::ChunkSize + pos;
|
id = index*TableEntry<DataEntry>::ChunkSize + pos;
|
||||||
data = &entry.Entries[pos];
|
data = &entry.Entries[pos];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!data) {
|
if(!data) {
|
||||||
table.emplace_back(std::make_unique<TableEntry<DataEntry>>());
|
table.emplace_back(std::make_unique<TableEntry<DataEntry>>());
|
||||||
id = (table.size()-1)*TableEntry<DataEntry>::ChunkSize;
|
auto& entry = *table.back();
|
||||||
data = &table.back()->Entries[0];
|
if(table.size() == 1 && entry.Empty.test(0)) {
|
||||||
table.back()->Empty.reset(0);
|
entry.Empty.reset(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t pos = entry.Empty._Find_first();
|
||||||
|
entry.Empty.reset(pos);
|
||||||
|
if(entry.Empty._Find_next(pos) == entry.Empty.size())
|
||||||
|
entry.IsFull = true;
|
||||||
|
|
||||||
|
id = (table.size()-1)*TableEntry<DataEntry>::ChunkSize + pos;
|
||||||
|
data = &entry.Entries[pos];
|
||||||
|
|
||||||
// Расширяем таблицу с ресурсами, если необходимо
|
// Расширяем таблицу с ресурсами, если необходимо
|
||||||
if(type == EnumAssets::Nodestate)
|
if(type == EnumAssets::Nodestate)
|
||||||
@@ -499,15 +516,15 @@ AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const
|
|||||||
PreparedNodeState nodestate = _nodestate;
|
PreparedNodeState nodestate = _nodestate;
|
||||||
|
|
||||||
// Ресолвим модели
|
// Ресолвим модели
|
||||||
for(const auto& [lKey, lDomain] : nodestate.LocalToModelKD) {
|
for(const auto& [lDomain, lKey] : nodestate.LocalToModelKD) {
|
||||||
nodestate.LocalToModel.push_back(lock->getId(EnumAssets::Nodestate, lDomain, lKey));
|
nodestate.LocalToModel.push_back(lock->getId(EnumAssets::Model, lDomain, lKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сдампим для отправки клиенту (Кеш в пролёте?)
|
// Сдампим для отправки клиенту (Кеш в пролёте?)
|
||||||
Resource res(nodestate.dump());
|
Resource res(nodestate.dump());
|
||||||
|
|
||||||
// На оповещение
|
// На оповещение
|
||||||
result.NewOrChange[(int) EnumAssets::Model].push_back({resId, res});
|
result.NewOrChange[(int) EnumAssets::Nodestate].push_back({resId, res});
|
||||||
|
|
||||||
// Запись в таблице ресурсов
|
// Запись в таблице ресурсов
|
||||||
data.emplace(ftt, res, domain, key);
|
data.emplace(ftt, res, domain, key);
|
||||||
|
|||||||
@@ -258,6 +258,10 @@ public:
|
|||||||
std::tuple<AssetsNodestate, std::vector<AssetsModel>, std::vector<AssetsTexture>>
|
std::tuple<AssetsNodestate, std::vector<AssetsModel>, std::vector<AssetsTexture>>
|
||||||
getNodeDependency(const std::string& domain, const std::string& key)
|
getNodeDependency(const std::string& domain, const std::string& key)
|
||||||
{
|
{
|
||||||
|
if(domain == "core" && key == "none") {
|
||||||
|
return {0, {}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
auto lock = LocalObj.lock();
|
auto lock = LocalObj.lock();
|
||||||
AssetsNodestate nodestateId = lock->getId(EnumAssets::Nodestate, domain, key+".json");
|
AssetsNodestate nodestateId = lock->getId(EnumAssets::Nodestate, domain, key+".json");
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace LV::Server {
|
|||||||
ContentManager::ContentManager(AssetsManager &am)
|
ContentManager::ContentManager(AssetsManager &am)
|
||||||
: AM(am)
|
: AM(am)
|
||||||
{
|
{
|
||||||
|
std::fill(std::begin(NextId), std::end(NextId), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentManager::~ContentManager() = default;
|
ContentManager::~ContentManager() = default;
|
||||||
@@ -111,6 +111,31 @@ void ContentManager::unRegisterModifier(EnumDefContent type, const std::string&
|
|||||||
ProfileChanges[(int) type].push_back(id);
|
ProfileChanges[(int) type].push_back(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContentManager::markAllProfilesDirty(EnumDefContent type) {
|
||||||
|
const auto &table = ContentKeyToId[(int) type];
|
||||||
|
for(const auto& domainPair : table) {
|
||||||
|
for(const auto& keyPair : domainPair.second) {
|
||||||
|
ProfileChanges[(int) type].push_back(keyPair.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ResourceId> ContentManager::collectProfileIds(EnumDefContent type) const {
|
||||||
|
std::vector<ResourceId> ids;
|
||||||
|
const auto &table = ContentKeyToId[(int) type];
|
||||||
|
|
||||||
|
for(const auto& domainPair : table) {
|
||||||
|
for(const auto& keyPair : domainPair.second) {
|
||||||
|
ids.push_back(keyPair.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(ids.begin(), ids.end());
|
||||||
|
auto last = std::unique(ids.begin(), ids.end());
|
||||||
|
ids.erase(last, ids.end());
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
|
ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
|
||||||
Out_buildEndProfiles result;
|
Out_buildEndProfiles result;
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class ContentManager {
|
|||||||
|
|
||||||
|
|
||||||
// Следующие идентификаторы регистрации контента
|
// Следующие идентификаторы регистрации контента
|
||||||
ResourceId NextId[(int) EnumDefContent::MAX_ENUM] = {0};
|
ResourceId NextId[(int) EnumDefContent::MAX_ENUM] = {};
|
||||||
// Домен -> {ключ -> идентификатор}
|
// Домен -> {ключ -> идентификатор}
|
||||||
std::unordered_map<std::string, std::unordered_map<std::string, ResourceId>> ContentKeyToId[(int) EnumDefContent::MAX_ENUM];
|
std::unordered_map<std::string, std::unordered_map<std::string, ResourceId>> ContentKeyToId[(int) EnumDefContent::MAX_ENUM];
|
||||||
|
|
||||||
@@ -143,6 +143,10 @@ public:
|
|||||||
// Регистрация модификатора предмета модом
|
// Регистрация модификатора предмета модом
|
||||||
void registerModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key, const sol::table& profile);
|
void registerModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key, const sol::table& profile);
|
||||||
void unRegisterModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key);
|
void unRegisterModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key);
|
||||||
|
// Пометить все профили типа как изменённые (например, после перезагрузки ассетов)
|
||||||
|
void markAllProfilesDirty(EnumDefContent type);
|
||||||
|
// Список всех зарегистрированных профилей выбранного типа
|
||||||
|
std::vector<ResourceId> collectProfileIds(EnumDefContent type) const;
|
||||||
// Компилирует изменённые профили
|
// Компилирует изменённые профили
|
||||||
struct Out_buildEndProfiles {
|
struct Out_buildEndProfiles {
|
||||||
std::vector<ResourceId> ChangedProfiles[(int) EnumDefContent::MAX_ENUM];
|
std::vector<ResourceId> ChangedProfiles[(int) EnumDefContent::MAX_ENUM];
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
#include <sol/forward.hpp>
|
#include <sol/forward.hpp>
|
||||||
#include <sol/protected_function_result.hpp>
|
#include <sol/protected_function_result.hpp>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@@ -862,7 +863,6 @@ void GameServer::BackingAsyncLua_t::run(int id) {
|
|||||||
Pos::bvec64u nodePos(x, y, z);
|
Pos::bvec64u nodePos(x, y, z);
|
||||||
auto &node = out.Nodes[Pos::bvec4u(nodePos >> 4).pack()][Pos::bvec16u(nodePos & 0xf).pack()];
|
auto &node = out.Nodes[Pos::bvec4u(nodePos >> 4).pack()][Pos::bvec16u(nodePos & 0xf).pack()];
|
||||||
node.NodeId = id;
|
node.NodeId = id;
|
||||||
node.Meta = 0;
|
|
||||||
|
|
||||||
if(x == 0 && z == 0)
|
if(x == 0 && z == 0)
|
||||||
node.NodeId = 1;
|
node.NodeId = 1;
|
||||||
@@ -876,7 +876,7 @@ void GameServer::BackingAsyncLua_t::run(int id) {
|
|||||||
else if(x == 0 && y == 1)
|
else if(x == 0 && y == 1)
|
||||||
node.NodeId = 0;
|
node.NodeId = 0;
|
||||||
|
|
||||||
// node.Meta = 0;
|
node.Meta = uint8_t((x + y + z + int(node.NodeId)) & 0x3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// else {
|
// else {
|
||||||
@@ -1108,7 +1108,7 @@ coro<> GameServer::pushSocketGameProtocol(tcp::socket socket, const std::string
|
|||||||
co_await Net::AsyncSocket::write<uint8_t>(socket, 0);
|
co_await Net::AsyncSocket::write<uint8_t>(socket, 0);
|
||||||
|
|
||||||
External.NewConnectedPlayers.lock_write()
|
External.NewConnectedPlayers.lock_write()
|
||||||
->push_back(std::make_shared<RemoteClient>(IOC, std::move(socket), username));
|
->push_back(std::make_shared<RemoteClient>(IOC, std::move(socket), username, this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1445,12 +1445,7 @@ void GameServer::init(fs::path worldPath) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
sol::table t = LuaMainState.create_table();
|
sol::table t = LuaMainState.create_table();
|
||||||
Content.CM.registerBase(EnumDefContent::Node, "test", "test0", t);
|
// Content.CM.registerBase(EnumDefContent::Node, "core", "none", t);
|
||||||
Content.CM.registerBase(EnumDefContent::Node, "test", "test1", t);
|
|
||||||
Content.CM.registerBase(EnumDefContent::Node, "test", "test2", t);
|
|
||||||
Content.CM.registerBase(EnumDefContent::Node, "test", "test3", t);
|
|
||||||
Content.CM.registerBase(EnumDefContent::Node, "test", "test4", t);
|
|
||||||
Content.CM.registerBase(EnumDefContent::Node, "test", "test5", t);
|
|
||||||
Content.CM.registerBase(EnumDefContent::World, "test", "devel_world", t);
|
Content.CM.registerBase(EnumDefContent::World, "test", "devel_world", t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1459,11 +1454,11 @@ void GameServer::init(fs::path worldPath) {
|
|||||||
|
|
||||||
// TODO: регистрация контента из mod/content/*
|
// TODO: регистрация контента из mod/content/*
|
||||||
|
|
||||||
Content.CM.buildEndProfiles();
|
|
||||||
|
|
||||||
pushEvent("preInit");
|
pushEvent("preInit");
|
||||||
pushEvent("highPreInit");
|
pushEvent("highPreInit");
|
||||||
|
|
||||||
|
Content.CM.buildEndProfiles();
|
||||||
|
|
||||||
|
|
||||||
LOG.info() << "Инициализация";
|
LOG.info() << "Инициализация";
|
||||||
initLua();
|
initLua();
|
||||||
@@ -1680,6 +1675,13 @@ void GameServer::initLuaPost() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameServer::requestModsReload() {
|
||||||
|
bool expected = false;
|
||||||
|
if(ModsReloadRequested.compare_exchange_strong(expected, true)) {
|
||||||
|
LOG.info() << "Запрошена перезагрузка модов";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GameServer::stepConnections() {
|
void GameServer::stepConnections() {
|
||||||
// Подключить новых игроков
|
// Подключить новых игроков
|
||||||
if(!External.NewConnectedPlayers.no_lock_readable().empty()) {
|
if(!External.NewConnectedPlayers.no_lock_readable().empty()) {
|
||||||
@@ -1721,9 +1723,42 @@ void GameServer::stepConnections() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameServer::stepModInitializations() {
|
void GameServer::stepModInitializations() {
|
||||||
|
if(ModsReloadRequested.exchange(false)) {
|
||||||
|
reloadMods();
|
||||||
|
}
|
||||||
BackingChunkPressure.endWithResults();
|
BackingChunkPressure.endWithResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameServer::reloadMods() {
|
||||||
|
LOG.info() << "Перезагрузка модов: ассеты и зависимости";
|
||||||
|
|
||||||
|
AssetsManager::ResourceChangeObj changes = Content.AM.recheckResources(AssetsInit);
|
||||||
|
AssetsManager::Out_applyResourceChange applied = Content.AM.applyResourceChange(changes);
|
||||||
|
|
||||||
|
size_t changedCount = 0;
|
||||||
|
size_t lostCount = 0;
|
||||||
|
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
||||||
|
for(const auto& entry : applied.NewOrChange[type]) {
|
||||||
|
Content.OnContentChanges.AssetsInfo[type].push_back(entry.first);
|
||||||
|
changedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
lostCount += applied.Lost[type].size();
|
||||||
|
}
|
||||||
|
|
||||||
|
Content.CM.markAllProfilesDirty(EnumDefContent::Node);
|
||||||
|
Content.CM.buildEndProfiles();
|
||||||
|
|
||||||
|
std::vector<ResourceId> nodeIds = Content.CM.collectProfileIds(EnumDefContent::Node);
|
||||||
|
if(!nodeIds.empty()) {
|
||||||
|
Content.OnContentChanges.Node.append_range(nodeIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info() << "Перезагрузка завершена: обновлено ассетов=" << changedCount
|
||||||
|
<< " удалено=" << lostCount
|
||||||
|
<< " нод=" << nodeIds.size();
|
||||||
|
}
|
||||||
|
|
||||||
IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
|
IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
|
||||||
IWorldSaveBackend::TickSyncInfo_In toDB;
|
IWorldSaveBackend::TickSyncInfo_In toDB;
|
||||||
|
|
||||||
@@ -2364,7 +2399,9 @@ void GameServer::stepSyncContent() {
|
|||||||
|
|
||||||
auto region = Expanse.Worlds[0]->Regions.find(rPos);
|
auto region = Expanse.Worlds[0]->Regions.find(rPos);
|
||||||
if(region != Expanse.Worlds[0]->Regions.end()) {
|
if(region != Expanse.Worlds[0]->Regions.end()) {
|
||||||
region->second->Nodes[cPos.pack()][nPos.pack()].NodeId = 4;
|
Node& n = region->second->Nodes[cPos.pack()][nPos.pack()];
|
||||||
|
n.NodeId = 4;
|
||||||
|
n.Meta = uint8_t((int(nPos.x) + int(nPos.y) + int(nPos.z)) & 0x3);
|
||||||
region->second->IsChunkChanged_Nodes |= 1ull << cPos.pack();
|
region->second->IsChunkChanged_Nodes |= 1ull << cPos.pack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2379,7 +2416,9 @@ void GameServer::stepSyncContent() {
|
|||||||
|
|
||||||
auto region = Expanse.Worlds[0]->Regions.find(rPos);
|
auto region = Expanse.Worlds[0]->Regions.find(rPos);
|
||||||
if(region != Expanse.Worlds[0]->Regions.end()) {
|
if(region != Expanse.Worlds[0]->Regions.end()) {
|
||||||
region->second->Nodes[cPos.pack()][nPos.pack()].NodeId = 0;
|
Node& n = region->second->Nodes[cPos.pack()][nPos.pack()];
|
||||||
|
n.NodeId = 0;
|
||||||
|
n.Meta = 0;
|
||||||
region->second->IsChunkChanged_Nodes |= 1ull << cPos.pack();
|
region->second->IsChunkChanged_Nodes |= 1ull << cPos.pack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <Common/Net.hpp>
|
#include <Common/Net.hpp>
|
||||||
#include <Common/Lockable.hpp>
|
#include <Common/Lockable.hpp>
|
||||||
|
#include <atomic>
|
||||||
#include <boost/asio/any_io_executor.hpp>
|
#include <boost/asio/any_io_executor.hpp>
|
||||||
#include <boost/asio/io_context.hpp>
|
#include <boost/asio/io_context.hpp>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
@@ -58,6 +59,7 @@ class GameServer : public AsyncObject {
|
|||||||
|
|
||||||
bool IsAlive = true, IsGoingShutdown = false;
|
bool IsAlive = true, IsGoingShutdown = false;
|
||||||
std::string ShutdownReason;
|
std::string ShutdownReason;
|
||||||
|
std::atomic<bool> ModsReloadRequested = false;
|
||||||
static constexpr float
|
static constexpr float
|
||||||
PerTickDuration = 1/30.f, // Минимальная и стартовая длина такта
|
PerTickDuration = 1/30.f, // Минимальная и стартовая длина такта
|
||||||
PerTickAdjustment = 1/60.f; // Подгонка длительности такта в случае провисаний
|
PerTickAdjustment = 1/60.f; // Подгонка длительности такта в случае провисаний
|
||||||
@@ -283,6 +285,7 @@ public:
|
|||||||
void waitShutdown() {
|
void waitShutdown() {
|
||||||
UseLock.wait_no_use();
|
UseLock.wait_no_use();
|
||||||
}
|
}
|
||||||
|
void requestModsReload();
|
||||||
|
|
||||||
// Подключение tcp сокета
|
// Подключение tcp сокета
|
||||||
coro<> pushSocketConnect(tcp::socket socket);
|
coro<> pushSocketConnect(tcp::socket socket);
|
||||||
@@ -315,6 +318,7 @@ private:
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
void stepModInitializations();
|
void stepModInitializations();
|
||||||
|
void reloadMods();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Пересчёт зон видимости игроков, если необходимо
|
Пересчёт зон видимости игроков, если необходимо
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "Common/Net.hpp"
|
#include "Common/Net.hpp"
|
||||||
#include "Server/Abstract.hpp"
|
#include "Server/Abstract.hpp"
|
||||||
|
#include "Server/GameServer.hpp"
|
||||||
#include "Server/World.hpp"
|
#include "Server/World.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
#include <boost/asio/error.hpp>
|
#include <boost/asio/error.hpp>
|
||||||
#include <boost/system/system_error.hpp>
|
#include <boost/system/system_error.hpp>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
@@ -13,6 +15,23 @@
|
|||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const char* assetTypeName(EnumAssets type) {
|
||||||
|
switch(type) {
|
||||||
|
case EnumAssets::Nodestate: return "nodestate";
|
||||||
|
case EnumAssets::Model: return "model";
|
||||||
|
case EnumAssets::Texture: return "texture";
|
||||||
|
case EnumAssets::Particle: return "particle";
|
||||||
|
case EnumAssets::Animation: return "animation";
|
||||||
|
case EnumAssets::Sound: return "sound";
|
||||||
|
case EnumAssets::Font: return "font";
|
||||||
|
default: return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
RemoteClient::~RemoteClient() {
|
RemoteClient::~RemoteClient() {
|
||||||
shutdown(EnumDisconnect::ByInterface, "~RemoteClient()");
|
shutdown(EnumDisconnect::ByInterface, "~RemoteClient()");
|
||||||
if(Socket.isAlive()) {
|
if(Socket.isAlive()) {
|
||||||
@@ -487,9 +506,13 @@ ResourceRequest RemoteClient::pushPreparedPackets() {
|
|||||||
nextRequest = std::move(lock->NextRequest);
|
nextRequest = std::move(lock->NextRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(AssetsInWork.AssetsPacket.size()) {
|
if(!AssetsInWork.AssetsPackets.empty()) {
|
||||||
toSend.push_back(std::move(AssetsInWork.AssetsPacket));
|
for(Net::Packet& packet : AssetsInWork.AssetsPackets)
|
||||||
|
toSend.push_back(std::move(packet));
|
||||||
|
AssetsInWork.AssetsPackets.clear();
|
||||||
}
|
}
|
||||||
|
if(AssetsInWork.AssetsPacket.size())
|
||||||
|
toSend.push_back(std::move(AssetsInWork.AssetsPacket));
|
||||||
|
|
||||||
{
|
{
|
||||||
Net::Packet p;
|
Net::Packet p;
|
||||||
@@ -508,6 +531,7 @@ ResourceRequest RemoteClient::pushPreparedPackets() {
|
|||||||
void RemoteClient::informateAssets(const std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Resource>>& resources)
|
void RemoteClient::informateAssets(const std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Resource>>& resources)
|
||||||
{
|
{
|
||||||
std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Hash_t, size_t>> newForClient;
|
std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Hash_t, size_t>> newForClient;
|
||||||
|
static std::atomic<uint32_t> debugSendLogCount = 0;
|
||||||
|
|
||||||
for(auto& [type, resId, domain, key, resource] : resources) {
|
for(auto& [type, resId, domain, key, resource] : resources) {
|
||||||
auto hash = resource.hash();
|
auto hash = resource.hash();
|
||||||
@@ -526,6 +550,22 @@ void RemoteClient::informateAssets(const std::vector<std::tuple<EnumAssets, Reso
|
|||||||
if(it == AssetsInWork.OnClient.end() || *it != hash) {
|
if(it == AssetsInWork.OnClient.end() || *it != hash) {
|
||||||
AssetsInWork.OnClient.insert(it, hash);
|
AssetsInWork.OnClient.insert(it, hash);
|
||||||
AssetsInWork.ToSend.emplace_back(type, domain, key, resId, resource, 0);
|
AssetsInWork.ToSend.emplace_back(type, domain, key, resId, resource, 0);
|
||||||
|
if(domain == "test"
|
||||||
|
&& (type == EnumAssets::Nodestate
|
||||||
|
|| type == EnumAssets::Model
|
||||||
|
|| type == EnumAssets::Texture))
|
||||||
|
{
|
||||||
|
if(debugSendLogCount.fetch_add(1) < 64) {
|
||||||
|
LOG.debug() << "Queue resource send type=" << assetTypeName(type)
|
||||||
|
<< " id=" << resId
|
||||||
|
<< " key=" << domain << ':' << key
|
||||||
|
<< " size=" << resource.size()
|
||||||
|
<< " hash=" << int(hash[0]) << '.'
|
||||||
|
<< int(hash[1]) << '.'
|
||||||
|
<< int(hash[2]) << '.'
|
||||||
|
<< int(hash[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG.warn() << "Клиент повторно запросил имеющийся у него ресурс";
|
LOG.warn() << "Клиент повторно запросил имеющийся у него ресурс";
|
||||||
}
|
}
|
||||||
@@ -720,6 +760,7 @@ coro<> RemoteClient::rP_System(Net::AsyncSocket &sock) {
|
|||||||
}
|
}
|
||||||
case ToServer::L2System::ResourceRequest:
|
case ToServer::L2System::ResourceRequest:
|
||||||
{
|
{
|
||||||
|
static std::atomic<uint32_t> debugRequestLogCount = 0;
|
||||||
uint16_t count = co_await sock.read<uint16_t>();
|
uint16_t count = co_await sock.read<uint16_t>();
|
||||||
std::vector<Hash_t> hashes;
|
std::vector<Hash_t> hashes;
|
||||||
hashes.reserve(count);
|
hashes.reserve(count);
|
||||||
@@ -733,6 +774,29 @@ coro<> RemoteClient::rP_System(Net::AsyncSocket &sock) {
|
|||||||
auto lock = NetworkAndResource.lock();
|
auto lock = NetworkAndResource.lock();
|
||||||
lock->NextRequest.Hashes.append_range(hashes);
|
lock->NextRequest.Hashes.append_range(hashes);
|
||||||
lock->ClientRequested.append_range(hashes);
|
lock->ClientRequested.append_range(hashes);
|
||||||
|
|
||||||
|
if(debugRequestLogCount.fetch_add(1) < 64) {
|
||||||
|
if(!hashes.empty()) {
|
||||||
|
const auto& h = hashes.front();
|
||||||
|
LOG.debug() << "ResourceRequest count=" << count
|
||||||
|
<< " first=" << int(h[0]) << '.'
|
||||||
|
<< int(h[1]) << '.'
|
||||||
|
<< int(h[2]) << '.'
|
||||||
|
<< int(h[3]);
|
||||||
|
} else {
|
||||||
|
LOG.debug() << "ResourceRequest count=" << count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
case ToServer::L2System::ReloadMods:
|
||||||
|
{
|
||||||
|
if(Server) {
|
||||||
|
Server->requestModsReload();
|
||||||
|
LOG.info() << "Запрос на перезагрузку модов";
|
||||||
|
} else {
|
||||||
|
LOG.warn() << "Запрос на перезагрузку модов отклонён: сервер не назначен";
|
||||||
|
}
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -818,24 +882,59 @@ void RemoteClient::onUpdate() {
|
|||||||
// Отправка ресурсов
|
// Отправка ресурсов
|
||||||
if(!AssetsInWork.ToSend.empty()) {
|
if(!AssetsInWork.ToSend.empty()) {
|
||||||
auto& toSend = AssetsInWork.ToSend;
|
auto& toSend = AssetsInWork.ToSend;
|
||||||
|
constexpr uint16_t kMaxAssetPacketSize = 64000;
|
||||||
|
const size_t maxChunkPayload = std::max<size_t>(1, kMaxAssetPacketSize - 1 - 1 - 32 - 4);
|
||||||
size_t chunkSize = std::max<size_t>(1'024'000 / toSend.size(), 4096);
|
size_t chunkSize = std::max<size_t>(1'024'000 / toSend.size(), 4096);
|
||||||
|
chunkSize = std::min(chunkSize, maxChunkPayload);
|
||||||
|
static std::atomic<uint32_t> debugInitSendLogCount = 0;
|
||||||
|
|
||||||
Net::Packet& p = AssetsInWork.AssetsPacket;
|
Net::Packet& p = AssetsInWork.AssetsPacket;
|
||||||
|
|
||||||
|
auto flushAssetsPacket = [&]() {
|
||||||
|
if(p.size() == 0)
|
||||||
|
return;
|
||||||
|
AssetsInWork.AssetsPackets.push_back(std::move(p));
|
||||||
|
};
|
||||||
|
|
||||||
bool hasFullSended = false;
|
bool hasFullSended = false;
|
||||||
|
|
||||||
for(auto& [type, domain, key, id, res, sended] : toSend) {
|
for(auto& [type, domain, key, id, res, sended] : toSend) {
|
||||||
if(sended == 0) {
|
if(sended == 0) {
|
||||||
// Оповещаем о начале отправки ресурса
|
// Оповещаем о начале отправки ресурса
|
||||||
|
const size_t initSize = 1 + 1 + 4 + 32 + 4 + 1
|
||||||
|
+ 2 + domain.size()
|
||||||
|
+ 2 + key.size();
|
||||||
|
if(p.size() + initSize > kMaxAssetPacketSize)
|
||||||
|
flushAssetsPacket();
|
||||||
p << (uint8_t) ToClient::L1::Resource
|
p << (uint8_t) ToClient::L1::Resource
|
||||||
<< (uint8_t) ToClient::L2Resource::InitResSend
|
<< (uint8_t) ToClient::L2Resource::InitResSend
|
||||||
<< uint32_t(res.size());
|
<< uint32_t(res.size());
|
||||||
p.write((const std::byte*) res.hash().data(), 32);
|
p.write((const std::byte*) res.hash().data(), 32);
|
||||||
p << uint32_t(id) << uint8_t(type) << domain << key;
|
p << uint32_t(id) << uint8_t(type) << domain << key;
|
||||||
|
if(domain == "test"
|
||||||
|
&& (type == EnumAssets::Nodestate
|
||||||
|
|| type == EnumAssets::Model
|
||||||
|
|| type == EnumAssets::Texture))
|
||||||
|
{
|
||||||
|
if(debugInitSendLogCount.fetch_add(1) < 64) {
|
||||||
|
const auto hash = res.hash();
|
||||||
|
LOG.debug() << "Send InitResSend type=" << assetTypeName(type)
|
||||||
|
<< " id=" << id
|
||||||
|
<< " key=" << domain << ':' << key
|
||||||
|
<< " size=" << res.size()
|
||||||
|
<< " hash=" << int(hash[0]) << '.'
|
||||||
|
<< int(hash[1]) << '.'
|
||||||
|
<< int(hash[2]) << '.'
|
||||||
|
<< int(hash[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отправляем чанк
|
// Отправляем чанк
|
||||||
size_t willSend = std::min(chunkSize, res.size()-sended);
|
size_t willSend = std::min(chunkSize, res.size()-sended);
|
||||||
|
const size_t chunkMsgSize = 1 + 1 + 32 + 4 + willSend;
|
||||||
|
if(p.size() + chunkMsgSize > kMaxAssetPacketSize)
|
||||||
|
flushAssetsPacket();
|
||||||
p << (uint8_t) ToClient::L1::Resource
|
p << (uint8_t) ToClient::L1::Resource
|
||||||
<< (uint8_t) ToClient::L2Resource::ChunkSend;
|
<< (uint8_t) ToClient::L2Resource::ChunkSend;
|
||||||
p.write((const std::byte*) res.hash().data(), 32);
|
p.write((const std::byte*) res.hash().data(), 32);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
class World;
|
class World;
|
||||||
|
class GameServer;
|
||||||
|
|
||||||
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
|
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
|
||||||
class CSChunkedMapper {
|
class CSChunkedMapper {
|
||||||
@@ -316,6 +317,7 @@ class RemoteClient {
|
|||||||
// Тип, домен, ключ, идентификатор, ресурс, количество отправленных байт
|
// Тип, домен, ключ, идентификатор, ресурс, количество отправленных байт
|
||||||
std::vector<std::tuple<EnumAssets, std::string, std::string, ResourceId, Resource, size_t>> ToSend;
|
std::vector<std::tuple<EnumAssets, std::string, std::string, ResourceId, Resource, size_t>> ToSend;
|
||||||
// Пакет с ресурсами
|
// Пакет с ресурсами
|
||||||
|
std::vector<Net::Packet> AssetsPackets;
|
||||||
Net::Packet AssetsPacket;
|
Net::Packet AssetsPacket;
|
||||||
} AssetsInWork;
|
} AssetsInWork;
|
||||||
|
|
||||||
@@ -336,8 +338,8 @@ public:
|
|||||||
std::queue<Pos::GlobalNode> Build, Break;
|
std::queue<Pos::GlobalNode> Build, Break;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username)
|
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, GameServer* server)
|
||||||
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username)
|
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username), Server(server)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
~RemoteClient();
|
~RemoteClient();
|
||||||
@@ -434,6 +436,7 @@ public:
|
|||||||
void onUpdate();
|
void onUpdate();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
GameServer* Server = nullptr;
|
||||||
void protocolError();
|
void protocolError();
|
||||||
coro<> readPacket(Net::AsyncSocket &sock);
|
coro<> readPacket(Net::AsyncSocket &sock);
|
||||||
coro<> rP_System(Net::AsyncSocket &sock);
|
coro<> rP_System(Net::AsyncSocket &sock);
|
||||||
|
|||||||
@@ -16,25 +16,29 @@ layout(push_constant) uniform UniformBufferObject {
|
|||||||
|
|
||||||
// struct NodeVertexStatic {
|
// struct NodeVertexStatic {
|
||||||
// uint32_t
|
// uint32_t
|
||||||
// FX : 9, FY : 9, FZ : 9, // Позиция -224 ~ 288; 64 позиций в одной ноде, 7.5 метров в ряд
|
// FX : 11, FY : 11, N1 : 10, // Позиция, 64 позиции на метр, +3.5м запас
|
||||||
// N1 : 4, // Не занято
|
// FZ : 11, // Позиция
|
||||||
// LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
// LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
||||||
// Tex : 18, // Текстура
|
// Tex : 18, // Текстура
|
||||||
// N2 : 14, // Не занято
|
// N2 : 2, // Не занято
|
||||||
// TU : 16, TV : 16; // UV на текстуре
|
// TU : 16, TV : 16; // UV на текстуре
|
||||||
// };
|
// };
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
|
uint fx = Vertex.x & 0x7ffu;
|
||||||
|
uint fy = (Vertex.x >> 11) & 0x7ffu;
|
||||||
|
uint fz = Vertex.y & 0x7ffu;
|
||||||
|
|
||||||
vec4 baseVec = ubo.model*vec4(
|
vec4 baseVec = ubo.model*vec4(
|
||||||
float(Vertex.x & 0x1ff) / 64.f - 3.5f,
|
float(fx) / 64.f - 3.5f,
|
||||||
float((Vertex.x >> 9) & 0x1ff) / 64.f - 3.5f,
|
float(fy) / 64.f - 3.5f,
|
||||||
float((Vertex.x >> 18) & 0x1ff) / 64.f - 3.5f,
|
float(fz) / 64.f - 3.5f,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
|
||||||
Geometry.GeoPos = baseVec.xyz;
|
Geometry.GeoPos = baseVec.xyz;
|
||||||
Geometry.Texture = Vertex.y & 0x3ffff;
|
Geometry.Texture = (Vertex.y >> 12) & 0x3ffffu;
|
||||||
Geometry.UV = vec2(
|
Geometry.UV = vec2(
|
||||||
float(Vertex.z & 0xffff) / pow(2, 16),
|
float(Vertex.z & 0xffff) / pow(2, 16),
|
||||||
float((Vertex.z >> 16) & 0xffff) / pow(2, 16)
|
float((Vertex.z >> 16) & 0xffff) / pow(2, 16)
|
||||||
|
|||||||
@@ -11,65 +11,36 @@ layout(location = 0) in FragmentObj {
|
|||||||
|
|
||||||
layout(location = 0) out vec4 Frame;
|
layout(location = 0) out vec4 Frame;
|
||||||
|
|
||||||
struct InfoSubTexture {
|
struct AtlasEntry {
|
||||||
uint Flags; // 1 isExist
|
vec4 UVMinMax;
|
||||||
uint PosXY, WidthHeight;
|
uint Layer;
|
||||||
|
uint Flags;
|
||||||
uint AnimationFrames_AnimationTimePerFrame;
|
uint _Pad0;
|
||||||
|
uint _Pad1;
|
||||||
};
|
};
|
||||||
|
|
||||||
uniform layout(set = 0, binding = 0) sampler2D MainAtlas;
|
const uint ATLAS_ENTRY_VALID = 1u;
|
||||||
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
|
||||||
uint SubsCount;
|
|
||||||
uint Counter;
|
|
||||||
uint WidthHeight;
|
|
||||||
|
|
||||||
InfoSubTexture SubTextures[];
|
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
||||||
|
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
||||||
|
AtlasEntry Entries[];
|
||||||
} MainAtlasLayout;
|
} MainAtlasLayout;
|
||||||
|
|
||||||
uniform layout(set = 1, binding = 0) sampler2D LightMap;
|
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
||||||
layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
||||||
vec3 Color;
|
vec3 Color;
|
||||||
} LightMapLayout;
|
} LightMapLayout;
|
||||||
|
|
||||||
vec4 atlasColor(uint texId, vec2 uv)
|
vec4 atlasColor(uint texId, vec2 uv)
|
||||||
{
|
{
|
||||||
uint flags = (texId & 0xffff0000) >> 16;
|
AtlasEntry entry = MainAtlasLayout.Entries[texId];
|
||||||
texId &= 0xffff;
|
if((entry.Flags & ATLAS_ENTRY_VALID) == 0u)
|
||||||
vec4 color = vec4(uv, 0, 1);
|
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2) * vec3(1, 0, 1), 1);
|
||||||
|
|
||||||
if((flags & (2 | 4)) > 0)
|
vec2 baseUV = vec2(uv.x, 1.0f - uv.y);
|
||||||
{
|
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV);
|
||||||
if((flags & 2) > 0)
|
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw);
|
||||||
color = vec4(1, 1, 1, 1);
|
return texture(MainAtlas, vec3(atlasUV, entry.Layer));
|
||||||
else if((flags & 4) > 0)
|
|
||||||
{
|
|
||||||
color = vec4(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(texId >= uint(MainAtlasLayout.SubsCount))
|
|
||||||
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(0, 1, 1), 1);
|
|
||||||
else {
|
|
||||||
InfoSubTexture texInfo = MainAtlasLayout.SubTextures[texId];
|
|
||||||
if(texInfo.Flags == 0)
|
|
||||||
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(1, 0, 1), 1);
|
|
||||||
|
|
||||||
uint posX = texInfo.PosXY & 0xffff;
|
|
||||||
uint posY = (texInfo.PosXY >> 16) & 0xffff;
|
|
||||||
uint width = texInfo.WidthHeight & 0xffff;
|
|
||||||
uint height = (texInfo.WidthHeight >> 16) & 0xffff;
|
|
||||||
uint awidth = MainAtlasLayout.WidthHeight & 0xffff;
|
|
||||||
uint aheight = (MainAtlasLayout.WidthHeight >> 16) & 0xffff;
|
|
||||||
|
|
||||||
if((flags & 1) > 0)
|
|
||||||
color = texture(MainAtlas, vec2((posX+0.5f+uv.x*(width-1))/awidth, (posY+0.5f+(1-uv.y)*(height-1))/aheight));
|
|
||||||
else
|
|
||||||
color = texture(MainAtlas, vec2((posX+uv.x*width)/awidth, (posY+(1-uv.y)*height)/aheight));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 blendOverlay(vec3 base, vec3 blend) {
|
vec3 blendOverlay(vec3 base, vec3 blend) {
|
||||||
@@ -87,6 +58,7 @@ void main() {
|
|||||||
Frame = atlasColor(Fragment.Texture, Fragment.UV);
|
Frame = atlasColor(Fragment.Texture, Fragment.UV);
|
||||||
Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8))));
|
Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8))));
|
||||||
// Frame = vec4(blendOverlay(vec3(Frame), vec3(Fragment.GeoPos/64.f)), Frame.w);
|
// Frame = vec4(blendOverlay(vec3(Frame), vec3(Fragment.GeoPos/64.f)), Frame.w);
|
||||||
|
|
||||||
if(Frame.w == 0)
|
if(Frame.w == 0)
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,19 @@ layout(location = 0) in FragmentObj {
|
|||||||
|
|
||||||
layout(location = 0) out vec4 Frame;
|
layout(location = 0) out vec4 Frame;
|
||||||
|
|
||||||
|
struct AtlasEntry {
|
||||||
|
vec4 UVMinMax;
|
||||||
|
uint Layer;
|
||||||
|
uint Flags;
|
||||||
|
uint _Pad0;
|
||||||
|
uint _Pad1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint ATLAS_ENTRY_VALID = 1u;
|
||||||
|
|
||||||
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
||||||
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
||||||
vec3 Color;
|
AtlasEntry Entries[];
|
||||||
} MainAtlasLayout;
|
} MainAtlasLayout;
|
||||||
|
|
||||||
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
||||||
@@ -19,6 +29,19 @@ layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
|||||||
vec3 Color;
|
vec3 Color;
|
||||||
} LightMapLayout;
|
} LightMapLayout;
|
||||||
|
|
||||||
void main() {
|
vec4 atlasColor(uint texId, vec2 uv)
|
||||||
Frame = vec4(Fragment.GeoPos, 1);
|
{
|
||||||
|
AtlasEntry entry = MainAtlasLayout.Entries[texId];
|
||||||
|
if((entry.Flags & ATLAS_ENTRY_VALID) == 0u)
|
||||||
|
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2) * vec3(1, 0, 1), 1);
|
||||||
|
|
||||||
|
vec2 baseUV = vec2(uv.x, 1.0f - uv.y);
|
||||||
|
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV);
|
||||||
|
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw);
|
||||||
|
return texture(MainAtlas, vec3(atlasUV, entry.Layer));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
Frame = atlasColor(Fragment.Texture, Fragment.UV);
|
||||||
|
Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8))));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,23 +9,22 @@ layout(location = 0) in FragmentObj {
|
|||||||
|
|
||||||
layout(location = 0) out vec4 Frame;
|
layout(location = 0) out vec4 Frame;
|
||||||
|
|
||||||
struct InfoSubTexture {
|
struct AtlasEntry {
|
||||||
uint Flags; // 1 isExist
|
vec4 UVMinMax;
|
||||||
uint PosXY, WidthHeight;
|
uint Layer;
|
||||||
|
uint Flags;
|
||||||
uint AnimationFrames_AnimationTimePerFrame;
|
uint _Pad0;
|
||||||
|
uint _Pad1;
|
||||||
};
|
};
|
||||||
|
|
||||||
uniform layout(set = 0, binding = 0) sampler2D MainAtlas;
|
const uint ATLAS_ENTRY_VALID = 1u;
|
||||||
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
|
||||||
uint SubsCount;
|
|
||||||
uint Counter;
|
|
||||||
uint WidthHeight;
|
|
||||||
|
|
||||||
InfoSubTexture SubTextures[];
|
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
||||||
|
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
||||||
|
AtlasEntry Entries[];
|
||||||
} MainAtlasLayout;
|
} MainAtlasLayout;
|
||||||
|
|
||||||
uniform layout(set = 1, binding = 0) sampler2D LightMap;
|
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
||||||
layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
||||||
vec3 Color;
|
vec3 Color;
|
||||||
} LightMapLayout;
|
} LightMapLayout;
|
||||||
@@ -35,42 +34,14 @@ vec4 atlasColor(uint texId, vec2 uv)
|
|||||||
{
|
{
|
||||||
uv = mod(uv, 1);
|
uv = mod(uv, 1);
|
||||||
|
|
||||||
uint flags = (texId & 0xffff0000) >> 16;
|
AtlasEntry entry = MainAtlasLayout.Entries[texId];
|
||||||
texId &= 0xffff;
|
if((entry.Flags & ATLAS_ENTRY_VALID) == 0u)
|
||||||
vec4 color = vec4(uv, 0, 1);
|
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2) * vec3(1, 0, 1), 1);
|
||||||
|
|
||||||
if((flags & (2 | 4)) > 0)
|
vec2 baseUV = vec2(uv.x, 1.0f - uv.y);
|
||||||
{
|
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV);
|
||||||
if((flags & 2) > 0)
|
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw);
|
||||||
color = vec4(1, 1, 1, 1);
|
return texture(MainAtlas, vec3(atlasUV, entry.Layer));
|
||||||
else if((flags & 4) > 0)
|
|
||||||
{
|
|
||||||
color = vec4(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(texId >= uint(MainAtlasLayout.SubsCount))
|
|
||||||
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(0, 1, 1), 1);
|
|
||||||
else {
|
|
||||||
InfoSubTexture texInfo = MainAtlasLayout.SubTextures[texId];
|
|
||||||
if(texInfo.Flags == 0)
|
|
||||||
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2 ) * vec3(1, 0, 1), 1);
|
|
||||||
|
|
||||||
uint posX = texInfo.PosXY & 0xffff;
|
|
||||||
uint posY = (texInfo.PosXY >> 16) & 0xffff;
|
|
||||||
uint width = texInfo.WidthHeight & 0xffff;
|
|
||||||
uint height = (texInfo.WidthHeight >> 16) & 0xffff;
|
|
||||||
uint awidth = MainAtlasLayout.WidthHeight & 0xffff;
|
|
||||||
uint aheight = (MainAtlasLayout.WidthHeight >> 16) & 0xffff;
|
|
||||||
|
|
||||||
if((flags & 1) > 0)
|
|
||||||
color = texture(MainAtlas, vec2((posX+0.5f+uv.x*(width-1))/awidth, (posY+0.5f+(1-uv.y)*(height-1))/aheight));
|
|
||||||
else
|
|
||||||
color = texture(MainAtlas, vec2((posX+uv.x*width)/awidth, (posY+(1-uv.y)*height)/aheight));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|||||||
@@ -9,9 +9,19 @@ layout(location = 0) in Fragment {
|
|||||||
|
|
||||||
layout(location = 0) out vec4 Frame;
|
layout(location = 0) out vec4 Frame;
|
||||||
|
|
||||||
|
struct AtlasEntry {
|
||||||
|
vec4 UVMinMax;
|
||||||
|
uint Layer;
|
||||||
|
uint Flags;
|
||||||
|
uint _Pad0;
|
||||||
|
uint _Pad1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint ATLAS_ENTRY_VALID = 1u;
|
||||||
|
|
||||||
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
uniform layout(set = 0, binding = 0) sampler2DArray MainAtlas;
|
||||||
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
layout(set = 0, binding = 1) readonly buffer MainAtlasLayoutObj {
|
||||||
vec3 Color;
|
AtlasEntry Entries[];
|
||||||
} MainAtlasLayout;
|
} MainAtlasLayout;
|
||||||
|
|
||||||
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
uniform layout(set = 1, binding = 0) sampler2DArray LightMap;
|
||||||
@@ -19,6 +29,39 @@ layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
|||||||
vec3 Color;
|
vec3 Color;
|
||||||
} LightMapLayout;
|
} LightMapLayout;
|
||||||
|
|
||||||
void main() {
|
vec4 atlasColor(uint texId, vec2 uv)
|
||||||
Frame = vec4(1);
|
{
|
||||||
|
uv = mod(uv, 1);
|
||||||
|
|
||||||
|
AtlasEntry entry = MainAtlasLayout.Entries[texId];
|
||||||
|
if((entry.Flags & ATLAS_ENTRY_VALID) == 0u)
|
||||||
|
return vec4(((int(gl_FragCoord.x / 128) + int(gl_FragCoord.y / 128)) % 2) * vec3(1, 0, 1), 1);
|
||||||
|
|
||||||
|
vec2 baseUV = vec2(uv.x, 1.0f - uv.y);
|
||||||
|
vec2 atlasUV = mix(entry.UVMinMax.xy, entry.UVMinMax.zw, baseUV);
|
||||||
|
atlasUV = clamp(atlasUV, entry.UVMinMax.xy, entry.UVMinMax.zw);
|
||||||
|
return texture(MainAtlas, vec3(atlasUV, entry.Layer));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv;
|
||||||
|
|
||||||
|
switch(fragment.Place) {
|
||||||
|
case 0:
|
||||||
|
uv = fragment.GeoPos.xz; break;
|
||||||
|
case 1:
|
||||||
|
uv = fragment.GeoPos.xy; break;
|
||||||
|
case 2:
|
||||||
|
uv = fragment.GeoPos.zy; break;
|
||||||
|
case 3:
|
||||||
|
uv = fragment.GeoPos.xz*vec2(-1, -1); break;
|
||||||
|
case 4:
|
||||||
|
uv = fragment.GeoPos.xy*vec2(-1, 1); break;
|
||||||
|
case 5:
|
||||||
|
uv = fragment.GeoPos.zy*vec2(-1, 1); break;
|
||||||
|
default:
|
||||||
|
uv = vec2(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Frame = atlasColor(fragment.VoxMTL, uv);
|
||||||
}
|
}
|
||||||
|
|||||||
81
mods/test/assets/test/model/node/acacia_planks.json
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"textures": {
|
||||||
|
"default": "acacia_planks.png"
|
||||||
|
},
|
||||||
|
"cuboids": [
|
||||||
|
{
|
||||||
|
"from": [
|
||||||
|
-0.5,
|
||||||
|
-0.5,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"faces": {
|
||||||
|
"down": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "down"
|
||||||
|
},
|
||||||
|
"up": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "up"
|
||||||
|
},
|
||||||
|
"north": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "north"
|
||||||
|
},
|
||||||
|
"south": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "south"
|
||||||
|
},
|
||||||
|
"west": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "west"
|
||||||
|
},
|
||||||
|
"east": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "east"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
81
mods/test/assets/test/model/node/frame.json
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"textures": {
|
||||||
|
"default": "frame.png"
|
||||||
|
},
|
||||||
|
"cuboids": [
|
||||||
|
{
|
||||||
|
"from": [
|
||||||
|
-0.5,
|
||||||
|
-0.5,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"faces": {
|
||||||
|
"down": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "down"
|
||||||
|
},
|
||||||
|
"up": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "up"
|
||||||
|
},
|
||||||
|
"north": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "north"
|
||||||
|
},
|
||||||
|
"south": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "south"
|
||||||
|
},
|
||||||
|
"west": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "west"
|
||||||
|
},
|
||||||
|
"east": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "east"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
81
mods/test/assets/test/model/node/grass.json
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"textures": {
|
||||||
|
"default": "grass.png"
|
||||||
|
},
|
||||||
|
"cuboids": [
|
||||||
|
{
|
||||||
|
"from": [
|
||||||
|
-0.5,
|
||||||
|
-0.5,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"faces": {
|
||||||
|
"down": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "down"
|
||||||
|
},
|
||||||
|
"up": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "up"
|
||||||
|
},
|
||||||
|
"north": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "north"
|
||||||
|
},
|
||||||
|
"south": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "south"
|
||||||
|
},
|
||||||
|
"west": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "west"
|
||||||
|
},
|
||||||
|
"east": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "east"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
81
mods/test/assets/test/model/node/jungle_planks.json
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"textures": {
|
||||||
|
"default": "jungle_planks.png"
|
||||||
|
},
|
||||||
|
"cuboids": [
|
||||||
|
{
|
||||||
|
"from": [
|
||||||
|
-0.5,
|
||||||
|
-0.5,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"faces": {
|
||||||
|
"down": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "down"
|
||||||
|
},
|
||||||
|
"up": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "up"
|
||||||
|
},
|
||||||
|
"north": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "north"
|
||||||
|
},
|
||||||
|
"south": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "south"
|
||||||
|
},
|
||||||
|
"west": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "west"
|
||||||
|
},
|
||||||
|
"east": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "east"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
81
mods/test/assets/test/model/node/oak_planks.json
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"textures": {
|
||||||
|
"default": "oak_planks.png"
|
||||||
|
},
|
||||||
|
"cuboids": [
|
||||||
|
{
|
||||||
|
"from": [
|
||||||
|
-0.5,
|
||||||
|
-0.5,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"faces": {
|
||||||
|
"down": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "down"
|
||||||
|
},
|
||||||
|
"up": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "up"
|
||||||
|
},
|
||||||
|
"north": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "north"
|
||||||
|
},
|
||||||
|
"south": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "south"
|
||||||
|
},
|
||||||
|
"west": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "west"
|
||||||
|
},
|
||||||
|
"east": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "east"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"textures": {
|
||||||
|
"default": "tropical_rainforest_wood.png"
|
||||||
|
},
|
||||||
|
"cuboids": [
|
||||||
|
{
|
||||||
|
"from": [
|
||||||
|
-0.5,
|
||||||
|
-0.5,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"faces": {
|
||||||
|
"down": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "down"
|
||||||
|
},
|
||||||
|
"up": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "up"
|
||||||
|
},
|
||||||
|
"north": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "north"
|
||||||
|
},
|
||||||
|
"south": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "south"
|
||||||
|
},
|
||||||
|
"west": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "west"
|
||||||
|
},
|
||||||
|
"east": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "east"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
81
mods/test/assets/test/model/node/willow_wood.json
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"textures": {
|
||||||
|
"default": "willow_wood.png"
|
||||||
|
},
|
||||||
|
"cuboids": [
|
||||||
|
{
|
||||||
|
"from": [
|
||||||
|
-0.5,
|
||||||
|
-0.5,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"faces": {
|
||||||
|
"down": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "down"
|
||||||
|
},
|
||||||
|
"up": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "up"
|
||||||
|
},
|
||||||
|
"north": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "north"
|
||||||
|
},
|
||||||
|
"south": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "south"
|
||||||
|
},
|
||||||
|
"west": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "west"
|
||||||
|
},
|
||||||
|
"east": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "east"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
81
mods/test/assets/test/model/node/xnether_blue_wood.json
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"textures": {
|
||||||
|
"default": "xnether_blue_wood.png"
|
||||||
|
},
|
||||||
|
"cuboids": [
|
||||||
|
{
|
||||||
|
"from": [
|
||||||
|
-0.5,
|
||||||
|
-0.5,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"faces": {
|
||||||
|
"down": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "down"
|
||||||
|
},
|
||||||
|
"up": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "up"
|
||||||
|
},
|
||||||
|
"north": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "north"
|
||||||
|
},
|
||||||
|
"south": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "south"
|
||||||
|
},
|
||||||
|
"west": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "west"
|
||||||
|
},
|
||||||
|
"east": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "east"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
81
mods/test/assets/test/model/node/xnether_purple_wood.json
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"textures": {
|
||||||
|
"default": "xnether_purple_wood.png"
|
||||||
|
},
|
||||||
|
"cuboids": [
|
||||||
|
{
|
||||||
|
"from": [
|
||||||
|
-0.5,
|
||||||
|
-0.5,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"faces": {
|
||||||
|
"down": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "down"
|
||||||
|
},
|
||||||
|
"up": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "up"
|
||||||
|
},
|
||||||
|
"north": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "north"
|
||||||
|
},
|
||||||
|
"south": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "south"
|
||||||
|
},
|
||||||
|
"west": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "west"
|
||||||
|
},
|
||||||
|
"east": {
|
||||||
|
"uv": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"texture": "default",
|
||||||
|
"cullface": "east"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
14
mods/test/assets/test/nodestate/test0.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"meta==0": {
|
||||||
|
"model": "node/grass.json"
|
||||||
|
},
|
||||||
|
"meta==1": {
|
||||||
|
"model": "node/oak_planks.json"
|
||||||
|
},
|
||||||
|
"meta==2": {
|
||||||
|
"model": "node/jungle_planks.json"
|
||||||
|
},
|
||||||
|
"meta==3": {
|
||||||
|
"model": "node/acacia_planks.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
mods/test/assets/test/nodestate/test1.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"meta==0": {
|
||||||
|
"model": "node/tropical_rainforest_wood.json"
|
||||||
|
},
|
||||||
|
"meta==1": {
|
||||||
|
"model": "node/willow_wood.json"
|
||||||
|
},
|
||||||
|
"meta==2": {
|
||||||
|
"model": "node/xnether_blue_wood.json"
|
||||||
|
},
|
||||||
|
"meta==3": {
|
||||||
|
"model": "node/xnether_purple_wood.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
mods/test/assets/test/nodestate/test2.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"meta==0": {
|
||||||
|
"model": "node/frame.json"
|
||||||
|
},
|
||||||
|
"meta==1": {
|
||||||
|
"model": "node/grass.json"
|
||||||
|
},
|
||||||
|
"meta==2": {
|
||||||
|
"model": "node/oak_planks.json"
|
||||||
|
},
|
||||||
|
"meta==3": {
|
||||||
|
"model": "node/acacia_planks.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
mods/test/assets/test/nodestate/test3.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"meta==0": {
|
||||||
|
"model": "node/jungle_planks.json"
|
||||||
|
},
|
||||||
|
"meta==1": {
|
||||||
|
"model": "node/tropical_rainforest_wood.json"
|
||||||
|
},
|
||||||
|
"meta==2": {
|
||||||
|
"model": "node/willow_wood.json"
|
||||||
|
},
|
||||||
|
"meta==3": {
|
||||||
|
"model": "node/xnether_blue_wood.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
mods/test/assets/test/nodestate/test4.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"meta==0": {
|
||||||
|
"model": "node/oak_planks.json"
|
||||||
|
},
|
||||||
|
"meta==1": {
|
||||||
|
"model": "node/jungle_planks.json"
|
||||||
|
},
|
||||||
|
"meta==2": {
|
||||||
|
"model": "node/acacia_planks.json"
|
||||||
|
},
|
||||||
|
"meta==3": {
|
||||||
|
"model": "node/willow_wood.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
mods/test/assets/test/nodestate/test5.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"meta==0": {
|
||||||
|
"model": "node/grass.json"
|
||||||
|
},
|
||||||
|
"meta==1": {
|
||||||
|
"model": "node/frame.json"
|
||||||
|
},
|
||||||
|
"meta==2": {
|
||||||
|
"model": "node/xnether_purple_wood.json"
|
||||||
|
},
|
||||||
|
"meta==3": {
|
||||||
|
"model": "node/tropical_rainforest_wood.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
mods/test/assets/test/texture/0.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
mods/test/assets/test/texture/acacia_planks.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
mods/test/assets/test/texture/frame.png
Normal file
|
After Width: | Height: | Size: 138 B |
BIN
mods/test/assets/test/texture/grass.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
mods/test/assets/test/texture/jungle_planks.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
mods/test/assets/test/texture/oak_planks.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
mods/test/assets/test/texture/tropical_rainforest_wood.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
mods/test/assets/test/texture/willow_wood.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
mods/test/assets/test/texture/xnether_blue_wood.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
mods/test/assets/test/texture/xnether_purple_wood.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
98
mods/test/init.lua
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
-- parent = default:air
|
||||||
|
--
|
||||||
|
-- hasHalfTransparency
|
||||||
|
-- collideBox = {}
|
||||||
|
-- plantLike = {}
|
||||||
|
-- nodebox = {}
|
||||||
|
|
||||||
|
local node_template = {
|
||||||
|
parent = "default:normal" or node_template,
|
||||||
|
render = {
|
||||||
|
has_half_transparency = false
|
||||||
|
},
|
||||||
|
collision = {
|
||||||
|
|
||||||
|
},
|
||||||
|
events = {
|
||||||
|
|
||||||
|
},
|
||||||
|
node_advancement_factory = function(world_id, node_pos)
|
||||||
|
local node_advancement = {
|
||||||
|
onLoad = function(data)
|
||||||
|
|
||||||
|
end,
|
||||||
|
onSave = function()
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
return node_advancement
|
||||||
|
end
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
local instance = {}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Движок автоматически подгружает ассеты из папки assets
|
||||||
|
В этом методе можно зарегистрировать ассеты из иных источников
|
||||||
|
Состояния нод, частицы, анимации, модели, текстуры, звуки, шрифты
|
||||||
|
]]--
|
||||||
|
function instance.assetsInit()
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
*preInit. События для регистрации определений игрового контента
|
||||||
|
Ноды, воксели, миры, порталы, сущности, предметы
|
||||||
|
]]--
|
||||||
|
function instance.lowPreInit()
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
До вызова preInit будет выполнена регистрация
|
||||||
|
контента из файлов в папке content
|
||||||
|
]]--
|
||||||
|
function instance.preInit()
|
||||||
|
local node_air = {}
|
||||||
|
|
||||||
|
node_air.hasHalfTransparency = false
|
||||||
|
node_air.collideBox = nil
|
||||||
|
node_air.render = nil
|
||||||
|
|
||||||
|
core.register_node('test0', {})
|
||||||
|
core.register_node('test1', {})
|
||||||
|
core.register_node('test2', {})
|
||||||
|
core.register_node('test3', {})
|
||||||
|
core.register_node('test4', {})
|
||||||
|
core.register_node('test5', {})
|
||||||
|
end
|
||||||
|
|
||||||
|
function instance.highPreInit()
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
На этом этапе можно наложить изменения
|
||||||
|
на зарегистрированный другими модами контент
|
||||||
|
]]--
|
||||||
|
function instance.init()
|
||||||
|
end
|
||||||
|
|
||||||
|
function instance.postInit()
|
||||||
|
end
|
||||||
|
|
||||||
|
function instance.preDeInit()
|
||||||
|
end
|
||||||
|
|
||||||
|
function instance.deInit()
|
||||||
|
end
|
||||||
|
|
||||||
|
function instance.postDeInit()
|
||||||
|
core.unregister_node('test0')
|
||||||
|
core.unregister_node('test1')
|
||||||
|
core.unregister_node('test2')
|
||||||
|
core.unregister_node('test3')
|
||||||
|
core.unregister_node('test4')
|
||||||
|
core.unregister_node('test5')
|
||||||
|
end
|
||||||
|
|
||||||
|
return instance
|
||||||
9
mods/test/mod.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"id": "test",
|
||||||
|
"name": "Test Mod",
|
||||||
|
"description": "Это тестовый мод",
|
||||||
|
"depends": [],
|
||||||
|
"optional_depends": [],
|
||||||
|
"author": "DrSocalkwe3n",
|
||||||
|
"version": [0, 0, 0, 1]
|
||||||
|
}
|
||||||