codex-5.2: ресурсы
This commit is contained in:
@@ -35,12 +35,12 @@ struct VoxelVertexPoint {
|
||||
|
||||
struct NodeVertexStatic {
|
||||
uint32_t
|
||||
FX : 9, FY : 9, FZ : 9, // Позиция -224 ~ 288; 64 позиций в одной ноде, 7.5 метров в ряд
|
||||
N1 : 4, // Не занято
|
||||
LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
||||
Tex : 18, // Текстура
|
||||
N2 : 14, // Не занято
|
||||
TU : 16, TV : 16; // UV на текстуре
|
||||
FX : 11, FY : 11, N1 : 10, // Позиция, 64 позиции на метр, +3.5м запас
|
||||
FZ : 11, // Позиция
|
||||
LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
||||
Tex : 18, // Текстура
|
||||
N2 : 2, // Не занято
|
||||
TU : 16, TV : 16; // UV на текстуре
|
||||
|
||||
bool operator==(const NodeVertexStatic& other) const {
|
||||
return std::memcmp(this, &other, sizeof(*this)) == 0;
|
||||
@@ -49,4 +49,4 @@ struct NodeVertexStatic {
|
||||
bool operator<=>(const NodeVertexStatic&) const = default;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
198
Src/Client/Vulkan/AtlasPipeline/PipelinedTextureAtlas.cpp
Normal file
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
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
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
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
1394
Src/Client/Vulkan/AtlasPipeline/TextureAtlas.hpp
Normal file
File diff suppressed because it is too large
Load Diff
1271
Src/Client/Vulkan/AtlasPipeline/TexturePipelineProgram.hpp
Normal file
1271
Src/Client/Vulkan/AtlasPipeline/TexturePipelineProgram.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "Vulkan.hpp"
|
||||
#include "Client/Vulkan/AtlasPipeline/SharedStagingBuffer.hpp"
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
|
||||
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 вершин в каждом
|
||||
@@ -22,10 +53,8 @@ class VertexPool {
|
||||
Vulkan *Inst;
|
||||
|
||||
// Память, доступная для обмена с устройством
|
||||
Buffer HostCoherent;
|
||||
Vertex *HCPtr = nullptr;
|
||||
VkFence Fence = nullptr;
|
||||
size_t WritePos = 0;
|
||||
std::shared_ptr<SharedStagingBuffer> Staging;
|
||||
VkDeviceSize CopyOffsetAlignment = 4;
|
||||
|
||||
struct Pool {
|
||||
// Память на устройстве
|
||||
@@ -47,7 +76,6 @@ class VertexPool {
|
||||
|
||||
struct Task {
|
||||
std::vector<Vertex> Data;
|
||||
size_t Pos = -1; // Если данные уже записаны, то будет указана позиция в буфере общения
|
||||
uint8_t PoolId; // Куда потом направить
|
||||
uint16_t BlockId; // И в какой блок
|
||||
};
|
||||
@@ -61,46 +89,21 @@ class VertexPool {
|
||||
|
||||
private:
|
||||
void pushData(std::vector<Vertex>&& data, uint8_t poolId, uint16_t blockId) {
|
||||
if(HC_Buffer_Size-WritePos >= data.size()) {
|
||||
// Пишем в общий буфер, 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));
|
||||
}
|
||||
TasksWait.push({std::move(data), poolId, blockId});
|
||||
}
|
||||
|
||||
public:
|
||||
VertexPool(Vulkan* 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)
|
||||
: Inst(inst)
|
||||
{
|
||||
Pools.reserve(16);
|
||||
HCPtr = (Vertex*) HostCoherent.mapMemory();
|
||||
|
||||
const VkFenceCreateInfo info = {
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0
|
||||
};
|
||||
|
||||
vkAssert(!vkCreateFence(inst->Graphics.Device, &info, nullptr, &Fence));
|
||||
Staging = getOrCreateVertexStaging(inst);
|
||||
VkPhysicalDeviceProperties props{};
|
||||
vkGetPhysicalDeviceProperties(inst->Graphics.PhysicalDevice, &props);
|
||||
CopyOffsetAlignment = std::max<VkDeviceSize>(4, props.limits.optimalBufferCopyOffsetAlignment);
|
||||
}
|
||||
|
||||
~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())
|
||||
return;
|
||||
|
||||
assert(WritePos);
|
||||
|
||||
VkCommandBufferAllocateInfo allocInfo {
|
||||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
nullptr,
|
||||
commandPool,
|
||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
1
|
||||
struct CopyTask {
|
||||
VkBuffer DstBuffer;
|
||||
VkDeviceSize SrcOffset;
|
||||
VkDeviceSize DstOffset;
|
||||
VkDeviceSize Size;
|
||||
uint8_t PoolId;
|
||||
};
|
||||
|
||||
VkCommandBuffer commandBuffer;
|
||||
vkAllocateCommandBuffers(Inst->Graphics.Device, &allocInfo, &commandBuffer);
|
||||
std::vector<CopyTask> copies;
|
||||
copies.reserve(TasksWait.size());
|
||||
std::vector<uint8_t> touchedPools(Pools.size(), 0);
|
||||
|
||||
VkCommandBufferBeginInfo beginInfo {
|
||||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
nullptr,
|
||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
nullptr
|
||||
};
|
||||
while(!TasksWait.empty()) {
|
||||
Task task = std::move(TasksWait.front());
|
||||
TasksWait.pop();
|
||||
|
||||
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,
|
||||
nullptr,
|
||||
VK_ACCESS_HOST_WRITE_BIT,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_QUEUE_FAMILY_IGNORED,
|
||||
VK_QUEUE_FAMILY_IGNORED,
|
||||
HostCoherent.getBuffer(),
|
||||
Staging->Buffer(),
|
||||
0,
|
||||
WritePos*sizeof(Vertex)
|
||||
Staging->Size()
|
||||
};
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
@@ -275,53 +299,60 @@ public:
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
1, &barrier,
|
||||
1, &stagingBarrier,
|
||||
0, nullptr
|
||||
);
|
||||
|
||||
while(!TasksWait.empty()) {
|
||||
Task& task = TasksWait.front();
|
||||
|
||||
for(const CopyTask& copy : copies) {
|
||||
VkBufferCopy copyRegion {
|
||||
task.Pos*sizeof(Vertex),
|
||||
task.BlockId*sizeof(Vertex)*size_t(PerBlock),
|
||||
task.Data.size()*sizeof(Vertex)
|
||||
copy.SrcOffset,
|
||||
copy.DstOffset,
|
||||
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(),
|
||||
1, ©Region);
|
||||
|
||||
TasksWait.pop();
|
||||
vkCmdCopyBuffer(commandBuffer, Staging->Buffer(), copy.DstBuffer, 1, ©Region);
|
||||
}
|
||||
|
||||
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 {
|
||||
VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
nullptr,
|
||||
0, nullptr,
|
||||
nullptr,
|
||||
1,
|
||||
&commandBuffer,
|
||||
0,
|
||||
nullptr
|
||||
};
|
||||
{
|
||||
auto lockQueue = Inst->Graphics.DeviceQueueGraphic.lock();
|
||||
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submitInfo, Fence));
|
||||
VkBufferMemoryBarrier barrier = {
|
||||
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
||||
nullptr,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
IsIndex ? VK_ACCESS_INDEX_READ_BIT : VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
|
||||
VK_QUEUE_FAMILY_IGNORED,
|
||||
VK_QUEUE_FAMILY_IGNORED,
|
||||
Pools[poolId].DeviceBuff.getBuffer(),
|
||||
0,
|
||||
Pools[poolId].DeviceBuff.getSize()
|
||||
};
|
||||
dstBarriers.push_back(barrier);
|
||||
}
|
||||
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);
|
||||
WritePos = 0;
|
||||
|
||||
while(!postponed.empty()) {
|
||||
Task& task = postponed.front();
|
||||
pushData(std::move(task.Data), task.PoolId, task.BlockId);
|
||||
TasksWait.push(std::move(postponed.front()));
|
||||
postponed.pop();
|
||||
}
|
||||
}
|
||||
@@ -330,4 +361,4 @@ public:
|
||||
template<typename Type, uint16_t PerBlock = 1 << 10, uint16_t PerPool = 1 << 12>
|
||||
using IndexPool = VertexPool<Type, PerBlock, PerPool, true>;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,10 +275,6 @@ void Vulkan::run()
|
||||
// if(CallBeforeDraw)
|
||||
// CallBeforeDraw(this);
|
||||
|
||||
if(Game.RSession) {
|
||||
Game.RSession->beforeDraw();
|
||||
}
|
||||
|
||||
glfwPollEvents();
|
||||
|
||||
VkResult err;
|
||||
@@ -314,6 +310,10 @@ void Vulkan::run()
|
||||
vkAssert(!vkBeginCommandBuffer(Graphics.CommandBufferRender, &cmd_buf_info));
|
||||
}
|
||||
|
||||
if(Game.RSession) {
|
||||
Game.RSession->beforeDraw();
|
||||
}
|
||||
|
||||
{
|
||||
VkImageMemoryBarrier image_memory_barrier =
|
||||
{
|
||||
@@ -602,6 +602,8 @@ void Vulkan::run()
|
||||
// Насильно ожидаем завершения рендера кадра
|
||||
vkWaitForFences(Graphics.Device, 1, &drawEndFence, true, -1);
|
||||
vkResetFences(Graphics.Device, 1, &drawEndFence);
|
||||
if(Game.RSession)
|
||||
Game.RSession->onGpuFinished();
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -211,7 +211,7 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
||||
} else {
|
||||
for(int z = 0; z < 16; z++)
|
||||
for(int y = 0; y < 16; y++)
|
||||
fullNodes[17][y+1][z+1] = 1;
|
||||
fullNodes[17][y+1][z+1] = 0;
|
||||
}
|
||||
|
||||
if(chunks[1]) {
|
||||
@@ -223,7 +223,7 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
||||
} else {
|
||||
for(int z = 0; z < 16; z++)
|
||||
for(int y = 0; y < 16; y++)
|
||||
fullNodes[0][y+1][z+1] = 1;
|
||||
fullNodes[0][y+1][z+1] = 0;
|
||||
}
|
||||
|
||||
if(chunks[2]) {
|
||||
@@ -235,7 +235,7 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
||||
} else {
|
||||
for(int z = 0; z < 16; z++)
|
||||
for(int x = 0; x < 16; x++)
|
||||
fullNodes[x+1][17][z+1] = 1;
|
||||
fullNodes[x+1][17][z+1] = 0;
|
||||
}
|
||||
|
||||
if(chunks[3]) {
|
||||
@@ -247,7 +247,7 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
||||
} else {
|
||||
for(int z = 0; z < 16; z++)
|
||||
for(int x = 0; x < 16; x++)
|
||||
fullNodes[x+1][0][z+1] = 1;
|
||||
fullNodes[x+1][0][z+1] = 0;
|
||||
}
|
||||
|
||||
if(chunks[4]) {
|
||||
@@ -259,7 +259,7 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
||||
} else {
|
||||
for(int y = 0; y < 16; y++)
|
||||
for(int x = 0; x < 16; x++)
|
||||
fullNodes[x+1][y+1][17] = 1;
|
||||
fullNodes[x+1][y+1][17] = 0;
|
||||
}
|
||||
|
||||
if(chunks[5]) {
|
||||
@@ -271,7 +271,7 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
||||
} else {
|
||||
for(int y = 0; y < 16; y++)
|
||||
for(int x = 0; x < 16; x++)
|
||||
fullNodes[x+0][y+1][0] = 1;
|
||||
fullNodes[x+0][y+1][0] = 0;
|
||||
}
|
||||
} else
|
||||
goto end;
|
||||
@@ -307,6 +307,72 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
||||
NodeVertexStatic v;
|
||||
std::memset(&v, 0, sizeof(v));
|
||||
|
||||
struct ModelCacheEntry {
|
||||
std::vector<std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>> Routes;
|
||||
};
|
||||
|
||||
std::unordered_map<uint32_t, ModelCacheEntry> modelCache;
|
||||
std::unordered_map<AssetsTexture, uint16_t> baseTextureCache;
|
||||
|
||||
std::vector<NodeStateInfo> metaStatesInfo;
|
||||
{
|
||||
NodeStateInfo info;
|
||||
info.Name = "meta";
|
||||
info.Variations = 256;
|
||||
metaStatesInfo.push_back(std::move(info));
|
||||
}
|
||||
|
||||
auto isFaceCovered = [&](EnumFace face, int covered) -> bool {
|
||||
switch(face) {
|
||||
case EnumFace::Up: return covered & (1 << 2);
|
||||
case EnumFace::Down: return covered & (1 << 3);
|
||||
case EnumFace::East: return covered & (1 << 0);
|
||||
case EnumFace::West: return covered & (1 << 1);
|
||||
case EnumFace::South: return covered & (1 << 4);
|
||||
case EnumFace::North: return covered & (1 << 5);
|
||||
default: return false;
|
||||
}
|
||||
};
|
||||
|
||||
auto pickVariant = [&](const std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>& variants, uint32_t seed)
|
||||
-> const std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>*
|
||||
{
|
||||
if(variants.empty())
|
||||
return nullptr;
|
||||
|
||||
float total = 0.0f;
|
||||
for(const auto& entry : variants)
|
||||
total += std::max(0.0f, entry.first);
|
||||
|
||||
if(total <= 0.0f)
|
||||
return &variants.front().second;
|
||||
|
||||
float r = (seed % 10000u) / 10000.0f * total;
|
||||
float accum = 0.0f;
|
||||
for(const auto& entry : variants) {
|
||||
accum += std::max(0.0f, entry.first);
|
||||
if(r <= accum)
|
||||
return &entry.second;
|
||||
}
|
||||
|
||||
return &variants.back().second;
|
||||
};
|
||||
|
||||
auto appendModel = [&](const std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>& faces, int covered, int x, int y, int z) {
|
||||
for(const auto& [face, verts] : faces) {
|
||||
if(face != EnumFace::None && isFaceCovered(face, covered))
|
||||
continue;
|
||||
|
||||
for(const NodeVertexStatic& baseVert : verts) {
|
||||
NodeVertexStatic vert = baseVert;
|
||||
vert.FX = uint32_t(vert.FX + x * 64);
|
||||
vert.FY = uint32_t(vert.FY + y * 64);
|
||||
vert.FZ = uint32_t(vert.FZ + z * 64);
|
||||
result.NodeVertexs.push_back(vert);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Сбор вершин
|
||||
for(int z = 0; z < 16; z++)
|
||||
for(int y = 0; y < 16; y++)
|
||||
@@ -323,208 +389,259 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
||||
if(fullCovered == 0b111111)
|
||||
continue;
|
||||
|
||||
const DefNode_t* node = getNodeProfile((*chunk)[x+y*16+z*16*16].NodeId);
|
||||
const Node& nodeData = (*chunk)[x+y*16+z*16*16];
|
||||
const DefNode_t* node = getNodeProfile(nodeData.NodeId);
|
||||
|
||||
v.Tex = node->TexId;
|
||||
bool usedModel = false;
|
||||
|
||||
if(NSP && node->NodestateId != 0) {
|
||||
auto iterCache = modelCache.find(nodeData.Data);
|
||||
if(iterCache == modelCache.end()) {
|
||||
std::unordered_map<std::string, int32_t> states;
|
||||
states.emplace("meta", nodeData.Meta);
|
||||
|
||||
ModelCacheEntry entry;
|
||||
entry.Routes = NSP->getModelsForNode(node->NodestateId, metaStatesInfo, states);
|
||||
iterCache = modelCache.emplace(nodeData.Data, std::move(entry)).first;
|
||||
}
|
||||
|
||||
if(!iterCache->second.Routes.empty()) {
|
||||
uint32_t seed = uint32_t(nodeData.Data) * 2654435761u;
|
||||
seed ^= uint32_t(x) * 73856093u;
|
||||
seed ^= uint32_t(y) * 19349663u;
|
||||
seed ^= uint32_t(z) * 83492791u;
|
||||
|
||||
for(size_t routeIndex = 0; routeIndex < iterCache->second.Routes.size(); routeIndex++) {
|
||||
const auto& variants = iterCache->second.Routes[routeIndex];
|
||||
const auto* faces = pickVariant(variants, seed + uint32_t(routeIndex) * 374761393u);
|
||||
if(faces)
|
||||
appendModel(*faces, fullCovered, x, y, z);
|
||||
}
|
||||
|
||||
usedModel = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(usedModel)
|
||||
continue;
|
||||
|
||||
if(NSP && node->TexId != 0) {
|
||||
auto iterTex = baseTextureCache.find(node->TexId);
|
||||
if(iterTex != baseTextureCache.end()) {
|
||||
v.Tex = iterTex->second;
|
||||
} else {
|
||||
uint16_t resolvedTex = NSP->getTextureId(node->TexId);
|
||||
v.Tex = resolvedTex;
|
||||
baseTextureCache.emplace(node->TexId, resolvedTex);
|
||||
}
|
||||
} else {
|
||||
v.Tex = node->TexId;
|
||||
}
|
||||
|
||||
if(v.Tex == 0)
|
||||
continue;
|
||||
|
||||
// Рендерим обычный кубоид
|
||||
// XZ+Y
|
||||
if(!(fullCovered & 0b000100)) {
|
||||
v.FX = 224+x*16;
|
||||
v.FY = 224+y*16+16;
|
||||
v.FZ = 224+z*16+16;
|
||||
v.FX = 224+x*64;
|
||||
v.FY = 224+y*64+64;
|
||||
v.FZ = 224+z*64+64;
|
||||
v.TU = 0;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX += 16;
|
||||
v.FX += 64;
|
||||
v.TU = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FZ -= 16;
|
||||
v.FZ -= 64;
|
||||
v.TV = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX = 224+x*16;
|
||||
v.FZ = 224+z*16+16;
|
||||
v.FX = 224+x*64;
|
||||
v.FZ = 224+z*64+64;
|
||||
v.TU = 0;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX += 16;
|
||||
v.FZ -= 16;
|
||||
v.FX += 64;
|
||||
v.FZ -= 64;
|
||||
v.TV = 65535;
|
||||
v.TU = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX -= 16;
|
||||
v.FX -= 64;
|
||||
v.TU = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
}
|
||||
|
||||
// XZ-Y
|
||||
if(!(fullCovered & 0b001000)) {
|
||||
v.FX = 224+x*16;
|
||||
v.FY = 224+y*16;
|
||||
v.FZ = 224+z*16+16;
|
||||
v.FX = 224+x*64;
|
||||
v.FY = 224+y*64;
|
||||
v.FZ = 224+z*64+64;
|
||||
v.TU = 0;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FZ -= 16;
|
||||
v.FZ -= 64;
|
||||
v.TV = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX += 16;
|
||||
v.FX += 64;
|
||||
v.TU = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX = 224+x*16;
|
||||
v.FZ = 224+z*16+16;
|
||||
v.FX = 224+x*64;
|
||||
v.FZ = 224+z*64+64;
|
||||
v.TU = 0;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX += 16;
|
||||
v.FZ -= 16;
|
||||
v.FX += 64;
|
||||
v.FZ -= 64;
|
||||
v.TV = 65535;
|
||||
v.TU = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FZ += 16;
|
||||
v.FZ += 64;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
}
|
||||
|
||||
//YZ+X
|
||||
if(!(fullCovered & 0b000001)) {
|
||||
v.FX = 224+x*16+16;
|
||||
v.FY = 224+y*16;
|
||||
v.FZ = 224+z*16+16;
|
||||
v.FX = 224+x*64+64;
|
||||
v.FY = 224+y*64;
|
||||
v.FZ = 224+z*64+64;
|
||||
v.TU = 0;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FZ -= 16;
|
||||
v.FZ -= 64;
|
||||
v.TV = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FY += 16;
|
||||
v.FY += 64;
|
||||
v.TU = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FY = 224+y*16;
|
||||
v.FZ = 224+z*16+16;
|
||||
v.FY = 224+y*64;
|
||||
v.FZ = 224+z*64+64;
|
||||
v.TU = 0;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FY += 16;
|
||||
v.FZ -= 16;
|
||||
v.FY += 64;
|
||||
v.FZ -= 64;
|
||||
v.TV = 65535;
|
||||
v.TU = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FZ += 16;
|
||||
v.FZ += 64;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
}
|
||||
|
||||
//YZ-X
|
||||
if(!(fullCovered & 0b000010)) {
|
||||
v.FX = 224+x*16;
|
||||
v.FY = 224+y*16;
|
||||
v.FZ = 224+z*16+16;
|
||||
v.FX = 224+x*64;
|
||||
v.FY = 224+y*64;
|
||||
v.FZ = 224+z*64+64;
|
||||
v.TU = 0;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FY += 16;
|
||||
v.FY += 64;
|
||||
v.TU = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FZ -= 16;
|
||||
v.FZ -= 64;
|
||||
v.TV = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FY = 224+y*16;
|
||||
v.FZ = 224+z*16+16;
|
||||
v.FY = 224+y*64;
|
||||
v.FZ = 224+z*64+64;
|
||||
v.TU = 0;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FY += 16;
|
||||
v.FZ -= 16;
|
||||
v.FY += 64;
|
||||
v.FZ -= 64;
|
||||
v.TV = 65535;
|
||||
v.TU = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FY -= 16;
|
||||
v.FY -= 64;
|
||||
v.TU = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
}
|
||||
|
||||
//XY+Z
|
||||
if(!(fullCovered & 0b010000)) {
|
||||
v.FX = 224+x*16;
|
||||
v.FY = 224+y*16;
|
||||
v.FZ = 224+z*16+16;
|
||||
v.FX = 224+x*64;
|
||||
v.FY = 224+y*64;
|
||||
v.FZ = 224+z*64+64;
|
||||
v.TU = 0;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX += 16;
|
||||
v.FX += 64;
|
||||
v.TU = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FY += 16;
|
||||
v.FY += 64;
|
||||
v.TV = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX = 224+x*16;
|
||||
v.FY = 224+y*16;
|
||||
v.FX = 224+x*64;
|
||||
v.FY = 224+y*64;
|
||||
v.TU = 0;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX += 16;
|
||||
v.FY += 16;
|
||||
v.FX += 64;
|
||||
v.FY += 64;
|
||||
v.TV = 65535;
|
||||
v.TU = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX -= 16;
|
||||
v.FX -= 64;
|
||||
v.TU = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
}
|
||||
|
||||
// XY-Z
|
||||
if(!(fullCovered & 0b100000)) {
|
||||
v.FX = 224+x*16;
|
||||
v.FY = 224+y*16;
|
||||
v.FZ = 224+z*16;
|
||||
v.FX = 224+x*64;
|
||||
v.FY = 224+y*64;
|
||||
v.FZ = 224+z*64;
|
||||
v.TU = 0;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FY += 16;
|
||||
v.FY += 64;
|
||||
v.TV = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX += 16;
|
||||
v.FX += 64;
|
||||
v.TU = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX = 224+x*16;
|
||||
v.FY = 224+y*16;
|
||||
v.FX = 224+x*64;
|
||||
v.FY = 224+y*64;
|
||||
v.TU = 0;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FX += 16;
|
||||
v.FY += 16;
|
||||
v.FX += 64;
|
||||
v.FY += 64;
|
||||
v.TV = 65535;
|
||||
v.TU = 65535;
|
||||
result.NodeVertexs.push_back(v);
|
||||
|
||||
v.FY -= 16;
|
||||
v.FY -= 64;
|
||||
v.TV = 0;
|
||||
result.NodeVertexs.push_back(v);
|
||||
}
|
||||
@@ -563,7 +680,6 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
Output.lock()->emplace_back(std::move(result));
|
||||
|
||||
@@ -588,10 +704,74 @@ void ChunkPreparator::tickSync(const TickSyncData& data) {
|
||||
// Пересчёт соседних чанков
|
||||
// Проверить необходимость пересчёта чанков при изменении профилей
|
||||
|
||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalChunk>> changedChunks = data.ChangedChunks;
|
||||
|
||||
if(!data.ChangedNodes.empty()) {
|
||||
std::unordered_set<DefNodeId> changedNodes(data.ChangedNodes.begin(), data.ChangedNodes.end());
|
||||
|
||||
for(const auto& [wId, regions] : ChunksMesh) {
|
||||
for(const auto& [rPos, chunks] : regions) {
|
||||
Pos::GlobalChunk base = Pos::GlobalChunk(rPos) << 2;
|
||||
|
||||
for(size_t index = 0; index < chunks.size(); index++) {
|
||||
const ChunkObj_t& chunk = chunks[index];
|
||||
if(chunk.Nodes.empty())
|
||||
continue;
|
||||
|
||||
bool hit = false;
|
||||
for(DefNodeId nodeId : chunk.Nodes) {
|
||||
if(changedNodes.contains(nodeId)) {
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!hit)
|
||||
continue;
|
||||
|
||||
Pos::bvec4u localPos;
|
||||
localPos.unpack(index);
|
||||
changedChunks[wId].push_back(base + Pos::GlobalChunk(localPos));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!data.ChangedVoxels.empty()) {
|
||||
std::unordered_set<DefVoxelId> changedVoxels(data.ChangedVoxels.begin(), data.ChangedVoxels.end());
|
||||
|
||||
for(const auto& [wId, regions] : ChunksMesh) {
|
||||
for(const auto& [rPos, chunks] : regions) {
|
||||
Pos::GlobalChunk base = Pos::GlobalChunk(rPos) << 2;
|
||||
|
||||
for(size_t index = 0; index < chunks.size(); index++) {
|
||||
const ChunkObj_t& chunk = chunks[index];
|
||||
if(chunk.Voxels.empty())
|
||||
continue;
|
||||
|
||||
bool hit = false;
|
||||
for(DefVoxelId voxelId : chunk.Voxels) {
|
||||
if(changedVoxels.contains(voxelId)) {
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!hit)
|
||||
continue;
|
||||
|
||||
Pos::bvec4u localPos;
|
||||
localPos.unpack(index);
|
||||
changedChunks[wId].push_back(base + Pos::GlobalChunk(localPos));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем к изменёным чанкам пересчёт соседей
|
||||
{
|
||||
std::vector<std::tuple<WorldId_t, Pos::GlobalChunk, uint32_t>> toBuild;
|
||||
for(auto& [wId, chunks] : data.ChangedChunks) {
|
||||
for(auto& [wId, chunks] : changedChunks) {
|
||||
std::vector<Pos::GlobalChunk> list;
|
||||
for(const Pos::GlobalChunk& pos : chunks) {
|
||||
list.push_back(pos);
|
||||
@@ -683,11 +863,6 @@ void ChunkPreparator::tickSync(const TickSyncData& data) {
|
||||
}
|
||||
}
|
||||
|
||||
VertexPool_Voxels.update(CMDPool);
|
||||
VertexPool_Nodes.update(CMDPool);
|
||||
IndexPool_Nodes_16.update(CMDPool);
|
||||
IndexPool_Nodes_32.update(CMDPool);
|
||||
|
||||
CMG.endTickSync();
|
||||
}
|
||||
|
||||
@@ -824,7 +999,7 @@ VulkanRenderSession::VulkanRenderSession(Vulkan *vkInst, IServerSession *serverS
|
||||
: VkInst(vkInst),
|
||||
ServerSession(serverSession),
|
||||
CP(vkInst, serverSession),
|
||||
MainTest(vkInst), LightDummy(vkInst),
|
||||
LightDummy(vkInst),
|
||||
TestQuad(vkInst, sizeof(NodeVertexStatic)*6*3*2)
|
||||
{
|
||||
assert(vkInst);
|
||||
@@ -849,49 +1024,9 @@ VulkanRenderSession::VulkanRenderSession(Vulkan *vkInst, IServerSession *serverS
|
||||
&DescriptorPool));
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> shaderLayoutBindings =
|
||||
{
|
||||
{
|
||||
.binding = 0,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.pImmutableSamplers = nullptr
|
||||
}, {
|
||||
.binding = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.pImmutableSamplers = nullptr
|
||||
}
|
||||
};
|
||||
|
||||
const VkDescriptorSetLayoutCreateInfo descriptorLayout =
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.bindingCount = (uint32_t) shaderLayoutBindings.size(),
|
||||
.pBindings = shaderLayoutBindings.data()
|
||||
};
|
||||
|
||||
vkAssert(!vkCreateDescriptorSetLayout(
|
||||
VkInst->Graphics.Device, &descriptorLayout, nullptr, &MainAtlasDescLayout));
|
||||
}
|
||||
|
||||
{
|
||||
VkDescriptorSetAllocateInfo ciAllocInfo =
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.descriptorPool = DescriptorPool,
|
||||
.descriptorSetCount = 1,
|
||||
.pSetLayouts = &MainAtlasDescLayout
|
||||
};
|
||||
|
||||
vkAssert(!vkAllocateDescriptorSets(VkInst->Graphics.Device, &ciAllocInfo, &MainAtlasDescriptor));
|
||||
}
|
||||
TP = std::make_unique<TextureProvider>(VkInst, DescriptorPool);
|
||||
NSP = std::make_unique<NodestateProvider>(MP, *TP);
|
||||
CP.setNodestateProvider(NSP.get());
|
||||
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> shaderLayoutBindings =
|
||||
@@ -937,39 +1072,11 @@ VulkanRenderSession::VulkanRenderSession(Vulkan *vkInst, IServerSession *serverS
|
||||
}
|
||||
|
||||
|
||||
MainTest.atlasAddCallbackOnUniformChange([this]() -> bool {
|
||||
updateDescriptor_MainAtlas();
|
||||
return true;
|
||||
});
|
||||
|
||||
LightDummy.atlasAddCallbackOnUniformChange([this]() -> bool {
|
||||
updateDescriptor_VoxelsLight();
|
||||
return true;
|
||||
});
|
||||
|
||||
{
|
||||
uint16_t texId = MainTest.atlasAddTexture(2, 2);
|
||||
uint32_t colors[4] = {0xfffffffful, 0x00fffffful, 0xffffff00ul, 0xff00fffful};
|
||||
MainTest.atlasChangeTextureData(texId, (const uint32_t*) colors);
|
||||
}
|
||||
|
||||
{
|
||||
int width, height;
|
||||
bool hasAlpha;
|
||||
for(const char *path : {
|
||||
"grass.png",
|
||||
"willow_wood.png",
|
||||
"tropical_rainforest_wood.png",
|
||||
"xnether_blue_wood.png",
|
||||
"xnether_purple_wood.png",
|
||||
"frame.png"
|
||||
}) {
|
||||
ByteBuffer image = VK::loadPNG(getResource(std::string("textures/") + path)->makeStream().Stream, width, height, hasAlpha);
|
||||
uint16_t texId = MainTest.atlasAddTexture(width, height);
|
||||
MainTest.atlasChangeTextureData(texId, (const uint32_t*) image.data());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
x left -1 ~ right 1
|
||||
y up 1 ~ down -1
|
||||
@@ -981,47 +1088,47 @@ VulkanRenderSession::VulkanRenderSession(Vulkan *vkInst, IServerSession *serverS
|
||||
|
||||
{
|
||||
NodeVertexStatic *array = (NodeVertexStatic*) TestQuad.mapMemory();
|
||||
array[0] = {135, 135, 135, 0, 0, 0, 0, 65535, 0};
|
||||
array[1] = {135, 135+16, 135, 0, 0, 0, 0, 0, 65535};
|
||||
array[2] = {135+16, 135+16, 135, 0, 0, 0, 0, 0, 65535};
|
||||
array[3] = {135, 135, 135, 0, 0, 0, 0, 65535, 0};
|
||||
array[4] = {135+16, 135+16, 135, 0, 0, 0, 0, 0, 65535};
|
||||
array[5] = {135+16, 135, 135, 0, 0, 0, 0, 0, 0};
|
||||
array[0] = {224, 224, 0, 224, 0, 0, 0, 65535, 0};
|
||||
array[1] = {224, 224+64, 0, 224, 0, 0, 0, 0, 65535};
|
||||
array[2] = {224+64, 224+64, 0, 224, 0, 0, 0, 0, 65535};
|
||||
array[3] = {224, 224, 0, 224, 0, 0, 0, 65535, 0};
|
||||
array[4] = {224+64, 224+64, 0, 224, 0, 0, 0, 0, 65535};
|
||||
array[5] = {224+64, 224, 0, 224, 0, 0, 0, 0, 0};
|
||||
|
||||
array[6] = {135, 135, 135+16, 0, 0, 0, 0, 0, 0};
|
||||
array[7] = {135+16, 135, 135+16, 0, 0, 0, 0, 65535, 0};
|
||||
array[8] = {135+16, 135+16, 135+16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[9] = {135, 135, 135+16, 0, 0, 0, 0, 0, 0};
|
||||
array[10] = {135+16, 135+16, 135+16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[11] = {135, 135+16, 135+16, 0, 0, 0, 0, 0, 65535};
|
||||
array[6] = {224, 224, 0, 224+64, 0, 0, 0, 0, 0};
|
||||
array[7] = {224+64, 224, 0, 224+64, 0, 0, 0, 65535, 0};
|
||||
array[8] = {224+64, 224+64, 0, 224+64, 0, 0, 0, 65535, 65535};
|
||||
array[9] = {224, 224, 0, 224+64, 0, 0, 0, 0, 0};
|
||||
array[10] = {224+64, 224+64, 0, 224+64, 0, 0, 0, 65535, 65535};
|
||||
array[11] = {224, 224+64, 0, 224+64, 0, 0, 0, 0, 65535};
|
||||
|
||||
array[12] = {135, 135, 135, 0, 0, 0, 0, 0, 0};
|
||||
array[13] = {135, 135, 135+16, 0, 0, 0, 0, 65535, 0};
|
||||
array[14] = {135, 135+16, 135+16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[15] = {135, 135, 135, 0, 0, 0, 0, 0, 0};
|
||||
array[16] = {135, 135+16, 135+16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[17] = {135, 135+16, 135, 0, 0, 0, 0, 0, 65535};
|
||||
array[12] = {224, 224, 0, 224, 0, 0, 0, 0, 0};
|
||||
array[13] = {224, 224, 0, 224+64, 0, 0, 0, 65535, 0};
|
||||
array[14] = {224, 224+64, 0, 224+64, 0, 0, 0, 65535, 65535};
|
||||
array[15] = {224, 224, 0, 224, 0, 0, 0, 0, 0};
|
||||
array[16] = {224, 224+64, 0, 224+64, 0, 0, 0, 65535, 65535};
|
||||
array[17] = {224, 224+64, 0, 224, 0, 0, 0, 0, 65535};
|
||||
|
||||
array[18] = {135+16, 135, 135+16, 0, 0, 0, 0, 0, 0};
|
||||
array[19] = {135+16, 135, 135, 0, 0, 0, 0, 65535, 0};
|
||||
array[20] = {135+16, 135+16, 135, 0, 0, 0, 0, 65535, 65535};
|
||||
array[21] = {135+16, 135, 135+16, 0, 0, 0, 0, 0, 0};
|
||||
array[22] = {135+16, 135+16, 135, 0, 0, 0, 0, 65535, 65535};
|
||||
array[23] = {135+16, 135+16, 135+16, 0, 0, 0, 0, 0, 65535};
|
||||
array[18] = {224+64, 224, 0, 224+64, 0, 0, 0, 0, 0};
|
||||
array[19] = {224+64, 224, 0, 224, 0, 0, 0, 65535, 0};
|
||||
array[20] = {224+64, 224+64, 0, 224, 0, 0, 0, 65535, 65535};
|
||||
array[21] = {224+64, 224, 0, 224+64, 0, 0, 0, 0, 0};
|
||||
array[22] = {224+64, 224+64, 0, 224, 0, 0, 0, 65535, 65535};
|
||||
array[23] = {224+64, 224+64, 0, 224+64, 0, 0, 0, 0, 65535};
|
||||
|
||||
array[24] = {135, 135, 135, 0, 0, 0, 0, 0, 0};
|
||||
array[25] = {135+16, 135, 135, 0, 0, 0, 0, 65535, 0};
|
||||
array[26] = {135+16, 135, 135+16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[27] = {135, 135, 135, 0, 0, 0, 0, 0, 0};
|
||||
array[28] = {135+16, 135, 135+16, 0, 0, 0, 0, 65535, 65535};
|
||||
array[29] = {135, 135, 135+16, 0, 0, 0, 0, 0, 65535};
|
||||
array[24] = {224, 224, 0, 224, 0, 0, 0, 0, 0};
|
||||
array[25] = {224+64, 224, 0, 224, 0, 0, 0, 65535, 0};
|
||||
array[26] = {224+64, 224, 0, 224+64, 0, 0, 0, 65535, 65535};
|
||||
array[27] = {224, 224, 0, 224, 0, 0, 0, 0, 0};
|
||||
array[28] = {224+64, 224, 0, 224+64, 0, 0, 0, 65535, 65535};
|
||||
array[29] = {224, 224, 0, 224+64, 0, 0, 0, 0, 65535};
|
||||
|
||||
array[30] = {135, 135+16, 135+16, 0, 0, 0, 0, 0, 0};
|
||||
array[31] = {135+16, 135+16, 135+16, 0, 0, 0, 0, 65535, 0};
|
||||
array[32] = {135+16, 135+16, 135, 0, 0, 0, 0, 65535, 65535};
|
||||
array[33] = {135, 135+16, 135+16, 0, 0, 0, 0, 0, 0};
|
||||
array[34] = {135+16, 135+16, 135, 0, 0, 0, 0, 65535, 65535};
|
||||
array[35] = {135, 135+16, 135, 0, 0, 0, 0, 0, 65535};
|
||||
array[30] = {224, 224+64, 0, 224+64, 0, 0, 0, 0, 0};
|
||||
array[31] = {224+64, 224+64, 0, 224+64, 0, 0, 0, 65535, 0};
|
||||
array[32] = {224+64, 224+64, 0, 224, 0, 0, 0, 65535, 65535};
|
||||
array[33] = {224, 224+64, 0, 224+64, 0, 0, 0, 0, 0};
|
||||
array[34] = {224+64, 224+64, 0, 224, 0, 0, 0, 65535, 65535};
|
||||
array[35] = {224, 224+64, 0, 224, 0, 0, 0, 0, 65535};
|
||||
|
||||
for(int iter = 0; iter < 36; iter++) {
|
||||
array[iter].Tex = 6;
|
||||
@@ -1067,7 +1174,6 @@ VulkanRenderSession::VulkanRenderSession(Vulkan *vkInst, IServerSession *serverS
|
||||
}
|
||||
}
|
||||
|
||||
updateDescriptor_MainAtlas();
|
||||
updateDescriptor_VoxelsLight();
|
||||
updateDescriptor_ChunksLight();
|
||||
|
||||
@@ -1084,7 +1190,7 @@ VulkanRenderSession::VulkanRenderSession(Vulkan *vkInst, IServerSession *serverS
|
||||
|
||||
std::vector<VkDescriptorSetLayout> layouts =
|
||||
{
|
||||
MainAtlasDescLayout,
|
||||
TP ? TP->getDescriptorLayout() : VK_NULL_HANDLE,
|
||||
VoxelLightMapDescLayout
|
||||
};
|
||||
|
||||
@@ -1433,9 +1539,7 @@ VulkanRenderSession::~VulkanRenderSession() {
|
||||
|
||||
if(MainAtlas_LightMap_PipelineLayout)
|
||||
vkDestroyPipelineLayout(VkInst->Graphics.Device, MainAtlas_LightMap_PipelineLayout, nullptr);
|
||||
|
||||
if(MainAtlasDescLayout)
|
||||
vkDestroyDescriptorSetLayout(VkInst->Graphics.Device, MainAtlasDescLayout, nullptr);
|
||||
|
||||
if(VoxelLightMapDescLayout)
|
||||
vkDestroyDescriptorSetLayout(VkInst->Graphics.Device, VoxelLightMapDescLayout, nullptr);
|
||||
|
||||
@@ -1459,36 +1563,93 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) {
|
||||
ChunkPreparator::TickSyncData mcpData;
|
||||
mcpData.ChangedChunks = data.Chunks_ChangeOrAdd;
|
||||
mcpData.LostRegions = data.Chunks_Lost;
|
||||
CP.tickSync(mcpData);
|
||||
|
||||
{
|
||||
std::vector<std::tuple<ResourceId, Resource>> resources;
|
||||
std::vector<ResourceId> lost;
|
||||
if(auto iter = data.Profiles_ChangeOrAdd.find(EnumDefContent::Node); iter != data.Profiles_ChangeOrAdd.end())
|
||||
mcpData.ChangedNodes.insert(mcpData.ChangedNodes.end(), iter->second.begin(), iter->second.end());
|
||||
if(auto iter = data.Profiles_Lost.find(EnumDefContent::Node); iter != data.Profiles_Lost.end())
|
||||
mcpData.ChangedNodes.insert(mcpData.ChangedNodes.end(), iter->second.begin(), iter->second.end());
|
||||
if(auto iter = data.Profiles_ChangeOrAdd.find(EnumDefContent::Voxel); iter != data.Profiles_ChangeOrAdd.end())
|
||||
mcpData.ChangedVoxels.insert(mcpData.ChangedVoxels.end(), iter->second.begin(), iter->second.end());
|
||||
if(auto iter = data.Profiles_Lost.find(EnumDefContent::Voxel); iter != data.Profiles_Lost.end())
|
||||
mcpData.ChangedVoxels.insert(mcpData.ChangedVoxels.end(), iter->second.begin(), iter->second.end());
|
||||
|
||||
for(const auto& [type, ids] : data.Assets_ChangeOrAdd) {
|
||||
if(type != EnumAssets::Model)
|
||||
std::vector<std::tuple<AssetsModel, Resource>> modelResources;
|
||||
std::vector<AssetsModel> modelLost;
|
||||
if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Model); iter != data.Assets_ChangeOrAdd.end()) {
|
||||
const auto& list = ServerSession->Assets[EnumAssets::Model];
|
||||
for(ResourceId id : iter->second) {
|
||||
auto entryIter = list.find(id);
|
||||
if(entryIter == list.end())
|
||||
continue;
|
||||
|
||||
const auto& list = ServerSession->Assets[type];
|
||||
for(ResourceId id : ids) {
|
||||
auto iter = list.find(id);
|
||||
if(iter == list.end())
|
||||
modelResources.emplace_back(id, entryIter->second.Res);
|
||||
}
|
||||
}
|
||||
if(auto iter = data.Assets_Lost.find(EnumAssets::Model); iter != data.Assets_Lost.end())
|
||||
modelLost.insert(modelLost.end(), iter->second.begin(), iter->second.end());
|
||||
|
||||
std::vector<AssetsModel> changedModels;
|
||||
if(!modelResources.empty() || !modelLost.empty())
|
||||
changedModels = MP.onModelChanges(std::move(modelResources), std::move(modelLost));
|
||||
|
||||
if(TP) {
|
||||
std::vector<std::tuple<AssetsTexture, Resource>> textureResources;
|
||||
std::vector<AssetsTexture> textureLost;
|
||||
|
||||
if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Texture); iter != data.Assets_ChangeOrAdd.end()) {
|
||||
const auto& list = ServerSession->Assets[EnumAssets::Texture];
|
||||
for(ResourceId id : iter->second) {
|
||||
auto entryIter = list.find(id);
|
||||
if(entryIter == list.end())
|
||||
continue;
|
||||
|
||||
resources.emplace_back(id, iter->second.Res);
|
||||
textureResources.emplace_back(id, entryIter->second.Res);
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& [type, ids] : data.Assets_Lost) {
|
||||
if(type != EnumAssets::Model)
|
||||
continue;
|
||||
if(auto iter = data.Assets_Lost.find(EnumAssets::Texture); iter != data.Assets_Lost.end())
|
||||
textureLost.insert(textureLost.end(), iter->second.begin(), iter->second.end());
|
||||
|
||||
lost.append_range(ids);
|
||||
if(!textureResources.empty() || !textureLost.empty())
|
||||
TP->onTexturesChanges(std::move(textureResources), std::move(textureLost));
|
||||
}
|
||||
|
||||
std::vector<AssetsNodestate> changedNodestates;
|
||||
if(NSP) {
|
||||
std::vector<std::tuple<AssetsNodestate, Resource>> nodestateResources;
|
||||
std::vector<AssetsNodestate> nodestateLost;
|
||||
|
||||
if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Nodestate); iter != data.Assets_ChangeOrAdd.end()) {
|
||||
const auto& list = ServerSession->Assets[EnumAssets::Nodestate];
|
||||
for(ResourceId id : iter->second) {
|
||||
auto entryIter = list.find(id);
|
||||
if(entryIter == list.end())
|
||||
continue;
|
||||
|
||||
nodestateResources.emplace_back(id, entryIter->second.Res);
|
||||
}
|
||||
}
|
||||
|
||||
if(!resources.empty() || !lost.empty())
|
||||
MP.onModelChanges(std::move(resources), std::move(lost));
|
||||
if(auto iter = data.Assets_Lost.find(EnumAssets::Nodestate); iter != data.Assets_Lost.end())
|
||||
nodestateLost.insert(nodestateLost.end(), iter->second.begin(), iter->second.end());
|
||||
|
||||
if(!nodestateResources.empty() || !nodestateLost.empty() || !changedModels.empty())
|
||||
changedNodestates = NSP->onNodestateChanges(std::move(nodestateResources), std::move(nodestateLost), changedModels);
|
||||
}
|
||||
|
||||
if(!changedNodestates.empty()) {
|
||||
std::unordered_set<AssetsNodestate> changed;
|
||||
changed.reserve(changedNodestates.size());
|
||||
for(AssetsNodestate id : changedNodestates)
|
||||
changed.insert(id);
|
||||
|
||||
for(const auto& [nodeId, def] : ServerSession->Profiles.DefNode) {
|
||||
if(changed.contains(def.NodestateId))
|
||||
mcpData.ChangedNodes.push_back(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
CP.tickSync(mcpData);
|
||||
}
|
||||
|
||||
void VulkanRenderSession::setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) {
|
||||
@@ -1502,8 +1663,14 @@ void VulkanRenderSession::setCameraPos(WorldId_t worldId, Pos::Object pos, glm::
|
||||
}
|
||||
|
||||
void VulkanRenderSession::beforeDraw() {
|
||||
MainTest.atlasUpdateDynamicData();
|
||||
if(TP)
|
||||
TP->update();
|
||||
LightDummy.atlasUpdateDynamicData();
|
||||
CP.flushUploadsAndBarriers(VkInst->Graphics.CommandBufferRender);
|
||||
}
|
||||
|
||||
void VulkanRenderSession::onGpuFinished() {
|
||||
CP.notifyGpuFinished();
|
||||
}
|
||||
|
||||
void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuffer drawCmd) {
|
||||
@@ -1643,7 +1810,7 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff
|
||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(WorldPCO), &PCO);
|
||||
vkCmdBindDescriptorSets(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
MainAtlas_LightMap_PipelineLayout, 0, 2,
|
||||
(const VkDescriptorSet[]) {MainAtlasDescriptor, VoxelLightMapDescriptor}, 0, nullptr);
|
||||
(const VkDescriptorSet[]) {TP ? TP->getDescriptorSet() : VK_NULL_HANDLE, VoxelLightMapDescriptor}, 0, nullptr);
|
||||
|
||||
{
|
||||
// glm::vec4 offset = glm::inverse(Quat)*glm::vec4(0, 0, -64, 1);
|
||||
@@ -1662,13 +1829,13 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff
|
||||
|
||||
auto [voxelVertexs, nodeVertexs] = CP.getChunksForRender(WorldId, Pos, 1, PCO.ProjView, x64offset_region);
|
||||
|
||||
{
|
||||
static uint32_t l = TOS::Time::getSeconds();
|
||||
if(l != TOS::Time::getSeconds()) {
|
||||
l = TOS::Time::getSeconds();
|
||||
TOS::Logger("Test").debug() << nodeVertexs.size();
|
||||
}
|
||||
}
|
||||
// {
|
||||
// static uint32_t l = TOS::Time::getSeconds();
|
||||
// if(l != TOS::Time::getSeconds()) {
|
||||
// l = TOS::Time::getSeconds();
|
||||
// TOS::Logger("Test").debug() << nodeVertexs.size();
|
||||
// }
|
||||
// }
|
||||
|
||||
size_t count = 0;
|
||||
|
||||
@@ -1789,36 +1956,6 @@ std::vector<VoxelVertexPoint> VulkanRenderSession::generateMeshForVoxelChunks(co
|
||||
return out;
|
||||
}
|
||||
|
||||
void VulkanRenderSession::updateDescriptor_MainAtlas() {
|
||||
VkDescriptorBufferInfo bufferInfo = MainTest;
|
||||
VkDescriptorImageInfo imageInfo = MainTest;
|
||||
|
||||
std::vector<VkWriteDescriptorSet> ciDescriptorSet =
|
||||
{
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = MainAtlasDescriptor,
|
||||
.dstBinding = 0,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
.pImageInfo = &imageInfo
|
||||
}, {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = MainAtlasDescriptor,
|
||||
.dstBinding = 1,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.pBufferInfo = &bufferInfo
|
||||
}
|
||||
};
|
||||
|
||||
vkUpdateDescriptorSets(VkInst->Graphics.Device, ciDescriptorSet.size(), ciDescriptorSet.data(), 0, nullptr);
|
||||
}
|
||||
|
||||
void VulkanRenderSession::updateDescriptor_VoxelsLight() {
|
||||
VkDescriptorBufferInfo bufferInfo = LightDummy;
|
||||
VkDescriptorImageInfo imageInfo = LightDummy;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <bitset>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
@@ -18,6 +19,7 @@
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
#include "Client/Vulkan/AtlasPipeline/PipelinedTextureAtlas.hpp"
|
||||
#include "Abstract.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include "VertexPool.hpp"
|
||||
@@ -243,7 +245,7 @@ public:
|
||||
continue;
|
||||
}
|
||||
|
||||
Models.insert({key, std::move(model)});
|
||||
Models.insert_or_assign(key, std::move(model));
|
||||
}
|
||||
|
||||
std::sort(result.begin(), result.end());
|
||||
@@ -388,63 +390,11 @@ private:
|
||||
Хранить все текстуры в оперативке
|
||||
*/
|
||||
class TextureProvider {
|
||||
// Хедер для атласа перед описанием текстур
|
||||
struct alignas(16) UniformInfo {
|
||||
uint32_t
|
||||
// Количество текстур
|
||||
SubsCount,
|
||||
// Счётчик времени с разрешением 8 бит в секунду
|
||||
Counter,
|
||||
// Размер атласа
|
||||
Size;
|
||||
|
||||
// Дальше в шейдере массив на описания текстур
|
||||
// std::vector<InfoSubTexture> SubsInfo;
|
||||
};
|
||||
|
||||
// Описание текстуры на стороне шейдера
|
||||
struct alignas(16) InfoSubTexture {
|
||||
uint32_t isExist = 0;
|
||||
uint32_t
|
||||
// Точная позиция в атласе
|
||||
PosX = 0, PosY = 0, PosZ = 0,
|
||||
// Размер текстуры в атласе
|
||||
Width = 0, Height = 0;
|
||||
|
||||
struct {
|
||||
uint16_t Enabled : 1 = 0, Frames : 15 = 0;
|
||||
uint16_t TimePerFrame = 0;
|
||||
} Animation;
|
||||
};
|
||||
|
||||
public:
|
||||
TextureProvider(Vulkan* inst, VkDescriptorPool descPool)
|
||||
: Inst(inst), DescPool(descPool)
|
||||
{
|
||||
{
|
||||
const VkSamplerCreateInfo ciSampler =
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.magFilter = VK_FILTER_NEAREST,
|
||||
.minFilter = VK_FILTER_NEAREST,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
|
||||
.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
||||
.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
||||
.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
||||
.mipLodBias = 0.0f,
|
||||
.anisotropyEnable = VK_FALSE,
|
||||
.maxAnisotropy = 1,
|
||||
.compareEnable = 0,
|
||||
.compareOp = VK_COMPARE_OP_NEVER,
|
||||
.minLod = 0.0f,
|
||||
.maxLod = 0.0f,
|
||||
.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE,
|
||||
.unnormalizedCoordinates = VK_FALSE
|
||||
};
|
||||
vkAssert(!vkCreateSampler(inst->Graphics.Device, &ciSampler, nullptr, &Sampler));
|
||||
}
|
||||
assert(inst);
|
||||
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> shaderLayoutBindings =
|
||||
@@ -476,391 +426,251 @@ public:
|
||||
vkAssert(!vkCreateDescriptorSetLayout(
|
||||
Inst->Graphics.Device, &descriptorLayout, nullptr, &DescLayout));
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
Atlases.resize(BackupAtlasCount);
|
||||
|
||||
VkDescriptorSetAllocateInfo ciAllocInfo =
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.descriptorPool = descPool,
|
||||
.descriptorSetCount = (uint32_t) Atlases.size(),
|
||||
.descriptorPool = DescPool,
|
||||
.descriptorSetCount = 1,
|
||||
.pSetLayouts = &DescLayout
|
||||
};
|
||||
|
||||
std::vector<VkDescriptorSet> descriptors;
|
||||
descriptors.resize(Atlases.size());
|
||||
vkAssert(!vkAllocateDescriptorSets(inst->Graphics.Device, &ciAllocInfo, descriptors.data()));
|
||||
|
||||
for(auto& atlas : Atlases) {
|
||||
atlas.recreate(Inst, true);
|
||||
atlas.Descriptor = descriptors.back();
|
||||
descriptors.pop_back();
|
||||
}
|
||||
vkAssert(!vkAllocateDescriptorSets(Inst->Graphics.Device, &ciAllocInfo, &Descriptor));
|
||||
}
|
||||
|
||||
{
|
||||
VkSemaphoreCreateInfo semaphoreCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
TextureAtlas::Config cfg;
|
||||
cfg.MaxTextureId = 1 << 18;
|
||||
AtlasStaging = std::make_shared<SharedStagingBuffer>(
|
||||
Inst->Graphics.Device,
|
||||
Inst->Graphics.PhysicalDevice
|
||||
);
|
||||
Atlas = std::make_unique<PipelinedTextureAtlas>(
|
||||
TextureAtlas(Inst->Graphics.Device, Inst->Graphics.PhysicalDevice, cfg, {}, AtlasStaging)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const VkFenceCreateInfo info = {
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0
|
||||
};
|
||||
|
||||
vkAssert(!vkCreateSemaphore(Inst->Graphics.Device, &semaphoreCreateInfo, nullptr, &SendChanges));
|
||||
vkAssert(!vkCreateFence(Inst->Graphics.Device, &info, nullptr, &UpdateFence));
|
||||
}
|
||||
|
||||
{
|
||||
const VkCommandBufferAllocateInfo infoCmd =
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.commandPool = Inst->Graphics.Pool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1
|
||||
};
|
||||
|
||||
vkAssert(!vkAllocateCommandBuffers(Inst->Graphics.Device, &infoCmd, &CMD));
|
||||
}
|
||||
|
||||
Cache.recreate(Inst, false);
|
||||
|
||||
AtlasTextureUnusedId.all();
|
||||
NeedsUpload = true;
|
||||
}
|
||||
|
||||
~TextureProvider() {
|
||||
if(UpdateFence)
|
||||
vkDestroyFence(Inst->Graphics.Device, UpdateFence, nullptr);
|
||||
|
||||
if(DescLayout)
|
||||
vkDestroyDescriptorSetLayout(Inst->Graphics.Device, DescLayout, nullptr);
|
||||
|
||||
if(Sampler) {
|
||||
vkDestroySampler(Inst->Graphics.Device, Sampler, nullptr);
|
||||
Sampler = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& atlas : Atlases) {
|
||||
atlas.destroy(Inst);
|
||||
}
|
||||
VkDescriptorSetLayout getDescriptorLayout() const {
|
||||
return DescLayout;
|
||||
}
|
||||
|
||||
Atlases.clear();
|
||||
|
||||
Cache.destroy(Inst);
|
||||
Cache.unMap(Inst);
|
||||
|
||||
if(SendChanges) {
|
||||
vkDestroySemaphore(Inst->Graphics.Device, SendChanges, nullptr);
|
||||
SendChanges = nullptr;
|
||||
}
|
||||
|
||||
if(CMD) {
|
||||
vkFreeCommandBuffers(Inst->Graphics.Device, Inst->Graphics.Pool, 1, &CMD);
|
||||
CMD = nullptr;
|
||||
}
|
||||
VkDescriptorSet getDescriptorSet() const {
|
||||
return Descriptor;
|
||||
}
|
||||
|
||||
uint16_t getTextureId(const TexturePipeline& pipe) {
|
||||
return 0;
|
||||
}
|
||||
std::lock_guard lock(Mutex);
|
||||
auto iter = PipelineToAtlas.find(pipe);
|
||||
if(iter != PipelineToAtlas.end())
|
||||
return iter->second;
|
||||
|
||||
// Устанавливает новый размер единицы в массиве текстур атласа
|
||||
enum class EnumAtlasSize {
|
||||
_2048 = 2048, _4096 = 4096, _8192 = 8192, _16_384 = 16'384
|
||||
};
|
||||
void setAtlasSize(EnumAtlasSize size) {
|
||||
ReferenceSize = size;
|
||||
}
|
||||
::HashedPipeline hashed = makeHashedPipeline(pipe);
|
||||
uint32_t atlasId = Atlas->getByPipeline(hashed);
|
||||
|
||||
// Максимальный размер выделенный под атласы в памяти устройства
|
||||
void setDeviceMemorySize(size_t size) {
|
||||
std::unreachable();
|
||||
uint16_t result = 0;
|
||||
if(atlasId <= std::numeric_limits<uint16_t>::max())
|
||||
result = static_cast<uint16_t>(atlasId);
|
||||
else
|
||||
LOG.warn() << "Atlas texture id overflow: " << atlasId;
|
||||
|
||||
PipelineToAtlas.emplace(pipe, result);
|
||||
NeedsUpload = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Применяет изменения, возвращая все затронутые модели
|
||||
std::vector<AssetsTexture> onTexturesChanges(std::vector<std::tuple<AssetsTexture, Resource>> newOrChanged, std::vector<AssetsTexture> lost) {
|
||||
std::lock_guard lock(Mutex);
|
||||
std::vector<AssetsTexture> result;
|
||||
|
||||
for(const auto& [key, res] : newOrChanged) {
|
||||
result.push_back(key);
|
||||
ChangedOrAdded.push_back(key);
|
||||
|
||||
TextureEntry entry;
|
||||
iResource sres((const uint8_t*) res.data(), res.size());
|
||||
iBinaryStream stream = sres.makeStream();
|
||||
png::image<png::rgba_pixel> img(stream.Stream);
|
||||
entry.Width = img.get_width();
|
||||
entry.Height = img.get_height();
|
||||
entry.RGBA.resize(4*entry.Width*entry.Height);
|
||||
uint32_t width = img.get_width();
|
||||
uint32_t height = img.get_height();
|
||||
|
||||
for(int i = 0; i < entry.Height; i++) {
|
||||
std::copy(
|
||||
((const uint32_t*) &img.get_pixbuf().operator [](i)[0]),
|
||||
((const uint32_t*) &img.get_pixbuf().operator [](i)[0])+entry.Width,
|
||||
((uint32_t*) entry.RGBA.data())+entry.Width*(false ? entry.Height-i-1 : i)
|
||||
);
|
||||
std::vector<uint32_t> pixels;
|
||||
pixels.resize(width*height);
|
||||
|
||||
for(uint32_t y = 0; y < height; y++) {
|
||||
const auto& row = img.get_pixbuf().operator [](y);
|
||||
for(uint32_t x = 0; x < width; x++) {
|
||||
const auto& px = row[x];
|
||||
uint32_t rgba = (uint32_t(px.alpha) << 24)
|
||||
| (uint32_t(px.red) << 16)
|
||||
| (uint32_t(px.green) << 8)
|
||||
| uint32_t(px.blue);
|
||||
pixels[x + y * width] = rgba;
|
||||
}
|
||||
}
|
||||
|
||||
Textures[key] = std::move(entry);
|
||||
Atlas->updateTexture(key, StoredTexture(
|
||||
static_cast<uint16_t>(width),
|
||||
static_cast<uint16_t>(height),
|
||||
std::move(pixels)
|
||||
));
|
||||
|
||||
NeedsUpload = true;
|
||||
}
|
||||
|
||||
for(AssetsTexture key : lost) {
|
||||
result.push_back(key);
|
||||
Lost.push_back(key);
|
||||
Atlas->freeTexture(key);
|
||||
NeedsUpload = true;
|
||||
}
|
||||
|
||||
{
|
||||
std::sort(result.begin(), result.end());
|
||||
auto eraseIter = std::unique(result.begin(), result.end());
|
||||
result.erase(eraseIter, result.end());
|
||||
}
|
||||
|
||||
{
|
||||
std::sort(ChangedOrAdded.begin(), ChangedOrAdded.end());
|
||||
auto eraseIter = std::unique(ChangedOrAdded.begin(), ChangedOrAdded.end());
|
||||
ChangedOrAdded.erase(eraseIter, ChangedOrAdded.end());
|
||||
}
|
||||
|
||||
{
|
||||
std::sort(Lost.begin(), Lost.end());
|
||||
auto eraseIter = std::unique(Lost.begin(), Lost.end());
|
||||
Lost.erase(eraseIter, Lost.end());
|
||||
}
|
||||
std::sort(result.begin(), result.end());
|
||||
auto eraseIter = std::unique(result.begin(), result.end());
|
||||
result.erase(eraseIter, result.end());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void update() {
|
||||
// Подготовить обновления атласа
|
||||
// Если предыдущий освободился, то записать изменения в него
|
||||
std::lock_guard lock(Mutex);
|
||||
if(!NeedsUpload || !Atlas)
|
||||
return;
|
||||
|
||||
// Держать на стороне хоста полную версию атласа и все изменения писать туда
|
||||
// Когерентная память сама разберётся что отсылать на устройство
|
||||
// Синхронизировать всё из внутреннего буфера в атлас
|
||||
// При пересоздании хостового буфера, скопировать всё из старого.
|
||||
Atlas->flushNewPipelines();
|
||||
|
||||
// Оптимизации копирования при указании конкретных изменённых слоёв?
|
||||
VkCommandBufferAllocateInfo allocInfo {
|
||||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
nullptr,
|
||||
Inst->Graphics.Pool,
|
||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
1
|
||||
};
|
||||
|
||||
VkCommandBuffer commandBuffer;
|
||||
vkAssert(!vkAllocateCommandBuffers(Inst->Graphics.Device, &allocInfo, &commandBuffer));
|
||||
|
||||
VkCommandBufferBeginInfo beginInfo {
|
||||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
nullptr,
|
||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
nullptr
|
||||
};
|
||||
|
||||
vkAssert(!vkBeginCommandBuffer(commandBuffer, &beginInfo));
|
||||
|
||||
TextureAtlas::DescriptorOut desc = Atlas->flushUploadsAndBarriers(commandBuffer);
|
||||
|
||||
vkAssert(!vkEndCommandBuffer(commandBuffer));
|
||||
|
||||
VkSubmitInfo submitInfo {
|
||||
VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
nullptr,
|
||||
0, nullptr,
|
||||
nullptr,
|
||||
1,
|
||||
&commandBuffer,
|
||||
0,
|
||||
nullptr
|
||||
};
|
||||
|
||||
{
|
||||
auto lockQueue = Inst->Graphics.DeviceQueueGraphic.lock();
|
||||
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submitInfo, UpdateFence));
|
||||
}
|
||||
|
||||
vkAssert(!vkWaitForFences(Inst->Graphics.Device, 1, &UpdateFence, VK_TRUE, UINT64_MAX));
|
||||
vkAssert(!vkResetFences(Inst->Graphics.Device, 1, &UpdateFence));
|
||||
|
||||
vkFreeCommandBuffers(Inst->Graphics.Device, Inst->Graphics.Pool, 1, &commandBuffer);
|
||||
|
||||
Atlas->notifyGpuFinished();
|
||||
updateDescriptor(desc);
|
||||
|
||||
NeedsUpload = false;
|
||||
}
|
||||
|
||||
VkDescriptorSet getDescriptor() {
|
||||
return Atlases[ActiveAtlas].Descriptor;
|
||||
private:
|
||||
::HashedPipeline makeHashedPipeline(const TexturePipeline& pipe) const {
|
||||
::Pipeline pipeline;
|
||||
|
||||
if(!pipe.Pipeline.empty() && (pipe.Pipeline.size() % 2u) == 0u) {
|
||||
std::vector<TexturePipelineProgram::Word> words;
|
||||
words.reserve(pipe.Pipeline.size() / 2u);
|
||||
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(pipe.Pipeline.data());
|
||||
for(size_t i = 0; i < pipe.Pipeline.size(); i += 2) {
|
||||
uint16_t lo = bytes[i];
|
||||
uint16_t hi = bytes[i + 1];
|
||||
words.push_back(static_cast<TexturePipelineProgram::Word>(lo | (hi << 8)));
|
||||
}
|
||||
pipeline._Pipeline.assign(words.begin(), words.end());
|
||||
}
|
||||
|
||||
if(pipeline._Pipeline.empty()) {
|
||||
if(!pipe.BinTextures.empty())
|
||||
pipeline = ::Pipeline(pipe.BinTextures.front());
|
||||
}
|
||||
|
||||
return ::HashedPipeline(pipeline);
|
||||
}
|
||||
|
||||
void pushFrame() {
|
||||
for(auto& atlas : Atlases)
|
||||
if(atlas.NotUsedFrames < 100)
|
||||
atlas.NotUsedFrames++;
|
||||
void updateDescriptor(const TextureAtlas::DescriptorOut& desc) {
|
||||
VkWriteDescriptorSet writes[2] = {};
|
||||
|
||||
Atlases[ActiveAtlas].NotUsedFrames = 0;
|
||||
writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writes[0].dstSet = Descriptor;
|
||||
writes[0].dstBinding = 0;
|
||||
writes[0].descriptorCount = 1;
|
||||
writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
writes[0].pImageInfo = &desc.ImageInfo;
|
||||
|
||||
// Если есть новые текстуры или они поменялись
|
||||
//
|
||||
writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writes[1].dstSet = Descriptor;
|
||||
writes[1].dstBinding = 1;
|
||||
writes[1].descriptorCount = 1;
|
||||
writes[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
writes[1].pBufferInfo = &desc.EntriesInfo;
|
||||
|
||||
vkUpdateDescriptorSets(Inst->Graphics.Device, 2, writes, 0, nullptr);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
Vulkan* Inst = nullptr;
|
||||
VkDescriptorPool DescPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout DescLayout = VK_NULL_HANDLE;
|
||||
// Для всех атласов
|
||||
VkSampler Sampler = VK_NULL_HANDLE;
|
||||
// Ожидание завершения работы с хостовым буфером
|
||||
VkSemaphore SendChanges = VK_NULL_HANDLE;
|
||||
//
|
||||
VkCommandBuffer CMD = VK_NULL_HANDLE;
|
||||
VkDescriptorSet Descriptor = VK_NULL_HANDLE;
|
||||
VkFence UpdateFence = VK_NULL_HANDLE;
|
||||
|
||||
// Размер, которому должны соответствовать все атласы
|
||||
EnumAtlasSize ReferenceSize = EnumAtlasSize::_2048;
|
||||
std::shared_ptr<SharedStagingBuffer> AtlasStaging;
|
||||
std::unique_ptr<PipelinedTextureAtlas> Atlas;
|
||||
std::unordered_map<TexturePipeline, uint16_t> PipelineToAtlas;
|
||||
|
||||
struct TextureEntry {
|
||||
uint16_t Width, Height;
|
||||
std::vector<glm::i8vec4> RGBA;
|
||||
|
||||
// Идентификатор текстуры в атласе
|
||||
uint16_t InAtlasId = uint16_t(-1);
|
||||
};
|
||||
|
||||
// Текстуры, загруженные с файлов
|
||||
std::unordered_map<AssetsTexture, TextureEntry> Textures;
|
||||
|
||||
struct TextureFromPipeline {
|
||||
|
||||
};
|
||||
|
||||
std::unordered_map<TexturePipeline, TextureFromPipeline> Pipelines;
|
||||
|
||||
struct AtlasTextureEntry {
|
||||
uint16_t PosX, PosY, PosZ, Width, Height;
|
||||
|
||||
};
|
||||
|
||||
std::bitset<1 << 16> AtlasTextureUnusedId;
|
||||
std::unordered_map<uint16_t, AtlasTextureEntry> AtlasTextureInfo;
|
||||
|
||||
std::vector<AssetsTexture> ChangedOrAdded, Lost;
|
||||
|
||||
struct VkAtlasInfo {
|
||||
VkImage Image = VK_NULL_HANDLE;
|
||||
VkImageLayout ImageLayout = VK_IMAGE_LAYOUT_MAX_ENUM;
|
||||
|
||||
VkDeviceMemory Memory = VK_NULL_HANDLE;
|
||||
VkImageView View = VK_NULL_HANDLE;
|
||||
|
||||
VkDescriptorSet Descriptor;
|
||||
EnumAtlasSize Size = EnumAtlasSize::_2048;
|
||||
uint16_t Depth = 1;
|
||||
|
||||
// Сколько кадров уже не используется атлас
|
||||
int NotUsedFrames = 0;
|
||||
|
||||
void destroy(Vulkan* inst) {
|
||||
if(View) {
|
||||
vkDestroyImageView(inst->Graphics.Device, View, nullptr);
|
||||
View = nullptr;
|
||||
}
|
||||
|
||||
if(Image) {
|
||||
vkDestroyImage(inst->Graphics.Device, Image, nullptr);
|
||||
Image = nullptr;
|
||||
}
|
||||
|
||||
if(Memory) {
|
||||
vkFreeMemory(inst->Graphics.Device, Memory, nullptr);
|
||||
Memory = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void recreate(Vulkan* inst, bool deviceLocal) {
|
||||
// Уничтожаем то, что не понадобится
|
||||
if(View) {
|
||||
vkDestroyImageView(inst->Graphics.Device, View, nullptr);
|
||||
View = nullptr;
|
||||
}
|
||||
|
||||
if(Image) {
|
||||
vkDestroyImage(inst->Graphics.Device, Image, nullptr);
|
||||
Image = nullptr;
|
||||
}
|
||||
|
||||
if(Memory) {
|
||||
vkFreeMemory(inst->Graphics.Device, Memory, nullptr);
|
||||
Memory = nullptr;
|
||||
}
|
||||
|
||||
// Создаём атлас
|
||||
uint32_t size = uint32_t(Size);
|
||||
|
||||
VkImageCreateInfo infoImageCreate =
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = VK_FORMAT_B8G8R8A8_UNORM,
|
||||
.extent = { size, size, 1 },
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = Depth,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_MAX_ENUM,
|
||||
.usage =
|
||||
static_cast<VkImageUsageFlags>(deviceLocal
|
||||
? VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT
|
||||
: VK_IMAGE_USAGE_TRANSFER_SRC_BIT),
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = 0,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED
|
||||
};
|
||||
|
||||
VkFormatProperties props;
|
||||
vkGetPhysicalDeviceFormatProperties(inst->Graphics.PhysicalDevice, infoImageCreate.format, &props);
|
||||
|
||||
if (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)
|
||||
infoImageCreate.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
else if (props.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)
|
||||
infoImageCreate.tiling = VK_IMAGE_TILING_LINEAR;
|
||||
else
|
||||
vkAssert(!"No support for B8G8R8A8_UNORM as texture image format");
|
||||
|
||||
vkAssert(!vkCreateImage(inst->Graphics.Device, &infoImageCreate, nullptr, &Image));
|
||||
|
||||
// Выделяем память
|
||||
VkMemoryRequirements memoryReqs;
|
||||
vkGetImageMemoryRequirements(inst->Graphics.Device, Image, &memoryReqs);
|
||||
|
||||
VkMemoryAllocateInfo memoryAlloc
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.allocationSize = memoryReqs.size,
|
||||
.memoryTypeIndex = inst->memoryTypeFromProperties(memoryReqs.memoryTypeBits,
|
||||
deviceLocal
|
||||
? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
|
||||
: VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
|
||||
)
|
||||
};
|
||||
|
||||
vkAssert(!vkAllocateMemory(inst->Graphics.Device, &memoryAlloc, nullptr, &Memory));
|
||||
vkAssert(!vkBindImageMemory(inst->Graphics.Device, Image, Memory, 0));
|
||||
|
||||
// Порядок пикселей и привязка к картинке
|
||||
VkImageViewCreateInfo ciView =
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.image = Image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
|
||||
.format = infoImageCreate.format,
|
||||
.components =
|
||||
{
|
||||
VK_COMPONENT_SWIZZLE_B,
|
||||
VK_COMPONENT_SWIZZLE_G,
|
||||
VK_COMPONENT_SWIZZLE_R,
|
||||
VK_COMPONENT_SWIZZLE_A
|
||||
},
|
||||
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }
|
||||
};
|
||||
|
||||
vkAssert(!vkCreateImageView(inst->Graphics.Device, &ciView, nullptr, &View));
|
||||
}
|
||||
};
|
||||
|
||||
struct HostCache : public VkAtlasInfo {
|
||||
std::vector<uint32_t*> Layers;
|
||||
std::vector<VkSubresourceLayout> Layouts;
|
||||
std::vector<rbp::MaxRectsBinPack> Packs;
|
||||
|
||||
void map(Vulkan* inst) {
|
||||
Layers.resize(Depth);
|
||||
Layouts.resize(Depth);
|
||||
|
||||
for(uint32_t layer = 0; layer < Depth; layer++) {
|
||||
const VkImageSubresource memorySubres = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .arrayLayer = layer, };
|
||||
vkGetImageSubresourceLayout(inst->Graphics.Device, Image, &memorySubres, &Layouts[layer]);
|
||||
|
||||
vkAssert(!vkMapMemory(inst->Graphics.Device, Memory, Layouts[layer].offset, Layouts[layer].size, 0, (void**) &Layers[layer]));
|
||||
}
|
||||
}
|
||||
|
||||
void unMap(Vulkan* inst) {
|
||||
vkUnmapMemory(inst->Graphics.Device, Memory);
|
||||
|
||||
Layers.clear();
|
||||
Layouts.clear();
|
||||
}
|
||||
};
|
||||
|
||||
HostCache Cache;
|
||||
|
||||
static constexpr size_t BackupAtlasCount = 2;
|
||||
|
||||
// Атласы, используемые в кадре.
|
||||
// Изменения пишутся в не используемый в данный момент атлас
|
||||
// и изменённый атлас становится активным. Новые изменения
|
||||
// можно писать по прошествии нескольких кадров.
|
||||
std::vector<VkAtlasInfo> Atlases;
|
||||
int ActiveAtlas = 0;
|
||||
bool NeedsUpload = false;
|
||||
Logger LOG = "Client>TextureProvider";
|
||||
mutable std::mutex Mutex;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Хранит информацию о моделях при различных состояниях нод
|
||||
*/
|
||||
@@ -904,7 +714,23 @@ public:
|
||||
continue;
|
||||
}
|
||||
|
||||
Nodestates.insert({key, std::move(nodestate)});
|
||||
Nodestates.insert_or_assign(key, std::move(nodestate));
|
||||
}
|
||||
|
||||
if(!changedModels.empty()) {
|
||||
std::unordered_set<AssetsModel> changed;
|
||||
changed.reserve(changedModels.size());
|
||||
for(AssetsModel modelId : changedModels)
|
||||
changed.insert(modelId);
|
||||
|
||||
for(const auto& [nodestateId, nodestate] : Nodestates) {
|
||||
for(AssetsModel modelId : nodestate.LocalToModel) {
|
||||
if(changed.contains(modelId)) {
|
||||
result.push_back(nodestateId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(result.begin(), result.end());
|
||||
@@ -922,51 +748,78 @@ public:
|
||||
if(iterNodestate == Nodestates.end())
|
||||
return {};
|
||||
|
||||
std::vector<uint16_t> routes = iterNodestate->second.getModelsForState(statesInfo, states);
|
||||
PreparedNodeState& nodestate = iterNodestate->second;
|
||||
std::vector<uint16_t> routes = nodestate.getModelsForState(statesInfo, states);
|
||||
std::vector<std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>>> result;
|
||||
|
||||
std::unordered_map<TexturePipeline, uint16_t> pipelineResolveCache;
|
||||
|
||||
for(uint16_t routeId : routes) {
|
||||
std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>> routeModels;
|
||||
const auto& route = iterNodestate->second.Routes[routeId];
|
||||
for(const auto& [w, m] : route.second) {
|
||||
if(const PreparedNodeState::Model* ptr = std::get_if<PreparedNodeState::Model>(&m)) {
|
||||
ModelProvider::Model model = MP.getModel(ptr->Id);
|
||||
Transformations trf(ptr->Transforms);
|
||||
std::unordered_map<EnumFace, std::vector<NodeVertexStatic>> out;
|
||||
auto appendModel = [&](AssetsModel modelId, const std::vector<Transformation>& transforms, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>& out) {
|
||||
ModelProvider::Model model = MP.getModel(modelId);
|
||||
Transformations trf{transforms};
|
||||
|
||||
for(auto& [l, r] : model.Vertecies) {
|
||||
trf.apply(r);
|
||||
for(auto& [l, r] : model.Vertecies) {
|
||||
trf.apply(r);
|
||||
|
||||
// Позиция -224 ~ 288; 64 позиций в одной ноде, 7.5 метров в ряд
|
||||
for(const Vertex& v : r) {
|
||||
NodeVertexStatic vert;
|
||||
// Позиция -224 ~ 288; 64 позиций в одной ноде, 7.5 метров в ряд
|
||||
for(const Vertex& v : r) {
|
||||
NodeVertexStatic vert;
|
||||
|
||||
vert.FX = (v.Pos.x+0.5)*64+224;
|
||||
vert.FY = (v.Pos.y+0.5)*64+224;
|
||||
vert.FZ = (v.Pos.z+0.5)*64+224;
|
||||
vert.FX = (v.Pos.x+0.5f)*64+224;
|
||||
vert.FY = (v.Pos.y+0.5f)*64+224;
|
||||
vert.FZ = (v.Pos.z+0.5f)*64+224;
|
||||
|
||||
vert.TU = std::clamp<int32_t>(v.UV.x * (1 << 16), 0, (1 << 16));
|
||||
vert.TV = std::clamp<int32_t>(v.UV.y * (1 << 16), 0, (1 << 16));
|
||||
vert.TU = std::clamp<int32_t>(v.UV.x * (1 << 16), 0, (1 << 16) - 1);
|
||||
vert.TV = std::clamp<int32_t>(v.UV.y * (1 << 16), 0, (1 << 16) - 1);
|
||||
|
||||
const TexturePipeline& pipe = model.TextureMap[model.TextureKeys[v.TexId]];
|
||||
if(auto iterPipe = pipelineResolveCache.find(pipe); iterPipe != pipelineResolveCache.end()) {
|
||||
vert.Tex = iterPipe->second;
|
||||
} else {
|
||||
vert.Tex = TP.getTextureId(pipe);
|
||||
pipelineResolveCache[pipe] = vert.Tex;
|
||||
}
|
||||
|
||||
out[l].push_back(vert);
|
||||
}
|
||||
const TexturePipeline& pipe = model.TextureMap[model.TextureKeys[v.TexId]];
|
||||
if(auto iterPipe = pipelineResolveCache.find(pipe); iterPipe != pipelineResolveCache.end()) {
|
||||
vert.Tex = iterPipe->second;
|
||||
} else {
|
||||
vert.Tex = TP.getTextureId(pipe);
|
||||
pipelineResolveCache[pipe] = vert.Tex;
|
||||
}
|
||||
|
||||
/// TODO: uvlock
|
||||
|
||||
routeModels.emplace_back(w, std::move(out));
|
||||
out[l].push_back(vert);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto resolveModelId = [&](uint16_t localId, AssetsModel& outId) -> bool {
|
||||
if(localId >= nodestate.LocalToModel.size())
|
||||
return false;
|
||||
outId = nodestate.LocalToModel[localId];
|
||||
return true;
|
||||
};
|
||||
|
||||
for(uint16_t routeId : routes) {
|
||||
if(routeId >= nodestate.Routes.size())
|
||||
continue;
|
||||
|
||||
std::vector<std::pair<float, std::unordered_map<EnumFace, std::vector<NodeVertexStatic>>>> routeModels;
|
||||
const auto& route = nodestate.Routes[routeId];
|
||||
for(const auto& [w, m] : route.second) {
|
||||
std::unordered_map<EnumFace, std::vector<NodeVertexStatic>> out;
|
||||
|
||||
if(const PreparedNodeState::Model* ptr = std::get_if<PreparedNodeState::Model>(&m)) {
|
||||
AssetsModel modelId;
|
||||
if(resolveModelId(ptr->Id, modelId))
|
||||
appendModel(modelId, ptr->Transforms, out);
|
||||
} else if(const PreparedNodeState::VectorModel* ptr = std::get_if<PreparedNodeState::VectorModel>(&m)) {
|
||||
for(const auto& sub : ptr->Models) {
|
||||
AssetsModel modelId;
|
||||
if(!resolveModelId(sub.Id, modelId))
|
||||
continue;
|
||||
|
||||
std::vector<Transformation> transforms = sub.Transforms;
|
||||
transforms.insert(transforms.end(), ptr->Transforms.begin(), ptr->Transforms.end());
|
||||
appendModel(modelId, transforms, out);
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: uvlock
|
||||
routeModels.emplace_back(w, std::move(out));
|
||||
}
|
||||
|
||||
result.push_back(std::move(routeModels));
|
||||
}
|
||||
@@ -974,6 +827,15 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
uint16_t getTextureId(AssetsTexture texId) {
|
||||
if(texId == 0)
|
||||
return 0;
|
||||
|
||||
TexturePipeline pipe;
|
||||
pipe.BinTextures.push_back(texId);
|
||||
return TP.getTextureId(pipe);
|
||||
}
|
||||
|
||||
private:
|
||||
Logger LOG = "Client>NodestateProvider";
|
||||
ModelProvider& MP;
|
||||
@@ -1027,6 +889,10 @@ public:
|
||||
// Меняет количество обрабатывающих потоков
|
||||
void changeThreadsCount(uint8_t threads);
|
||||
|
||||
void setNodestateProvider(NodestateProvider* provider) {
|
||||
NSP = provider;
|
||||
}
|
||||
|
||||
void prepareTickSync() {
|
||||
Sync.Stop = true;
|
||||
}
|
||||
@@ -1051,6 +917,7 @@ private:
|
||||
} Sync;
|
||||
|
||||
IServerSession *SS;
|
||||
NodestateProvider* NSP = nullptr;
|
||||
std::vector<std::thread> Threads;
|
||||
|
||||
void run(uint8_t id);
|
||||
@@ -1083,23 +950,10 @@ public:
|
||||
assert(serverSession);
|
||||
|
||||
CMG.changeThreadsCount(1);
|
||||
|
||||
const VkCommandPoolCreateInfo infoCmdPool =
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||
.queueFamilyIndex = VkInst->getSettings().QueueGraphics
|
||||
};
|
||||
|
||||
vkAssert(!vkCreateCommandPool(VkInst->Graphics.Device, &infoCmdPool, nullptr, &CMDPool));
|
||||
}
|
||||
|
||||
~ChunkPreparator() {
|
||||
CMG.changeThreadsCount(0);
|
||||
|
||||
if(CMDPool)
|
||||
vkDestroyCommandPool(VkInst->Graphics.Device, CMDPool, nullptr);
|
||||
}
|
||||
|
||||
|
||||
@@ -1111,7 +965,24 @@ public:
|
||||
CMG.pushStageTickSync();
|
||||
}
|
||||
|
||||
void setNodestateProvider(NodestateProvider* provider) {
|
||||
CMG.setNodestateProvider(provider);
|
||||
}
|
||||
|
||||
void tickSync(const TickSyncData& data);
|
||||
void notifyGpuFinished() {
|
||||
resetVertexStaging();
|
||||
VertexPool_Voxels.notifyGpuFinished();
|
||||
VertexPool_Nodes.notifyGpuFinished();
|
||||
IndexPool_Nodes_16.notifyGpuFinished();
|
||||
IndexPool_Nodes_32.notifyGpuFinished();
|
||||
}
|
||||
void flushUploadsAndBarriers(VkCommandBuffer commandBuffer) {
|
||||
VertexPool_Voxels.flushUploadsAndBarriers(commandBuffer);
|
||||
VertexPool_Nodes.flushUploadsAndBarriers(commandBuffer);
|
||||
IndexPool_Nodes_16.flushUploadsAndBarriers(commandBuffer);
|
||||
IndexPool_Nodes_32.flushUploadsAndBarriers(commandBuffer);
|
||||
}
|
||||
|
||||
// Готовность кадров определяет когда можно удалять ненужные ресурсы, которые ещё используются в рендере
|
||||
void pushFrame();
|
||||
@@ -1126,7 +997,6 @@ private:
|
||||
static constexpr uint8_t FRAME_COUNT_RESOURCE_LATENCY = 6;
|
||||
|
||||
Vulkan* VkInst;
|
||||
VkCommandPool CMDPool = nullptr;
|
||||
|
||||
// Генератор вершин чанков
|
||||
ChunkMeshGenerator CMG;
|
||||
@@ -1193,8 +1063,10 @@ class VulkanRenderSession : public IRenderSession {
|
||||
|
||||
ChunkPreparator CP;
|
||||
ModelProvider MP;
|
||||
std::unique_ptr<TextureProvider> TP;
|
||||
std::unique_ptr<NodestateProvider> NSP;
|
||||
|
||||
AtlasImage MainTest, LightDummy;
|
||||
AtlasImage LightDummy;
|
||||
Buffer TestQuad;
|
||||
std::optional<Buffer> TestVoxel;
|
||||
|
||||
@@ -1206,8 +1078,6 @@ class VulkanRenderSession : public IRenderSession {
|
||||
.binding = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, Данные к атласу
|
||||
*/
|
||||
VkDescriptorSetLayout MainAtlasDescLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSet MainAtlasDescriptor = VK_NULL_HANDLE;
|
||||
/*
|
||||
.binding = 2,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, Воксельная карта освещения
|
||||
@@ -1232,7 +1102,6 @@ class VulkanRenderSession : public IRenderSession {
|
||||
NodeStaticOpaquePipeline = VK_NULL_HANDLE,
|
||||
NodeStaticTransparentPipeline = VK_NULL_HANDLE;
|
||||
|
||||
std::map<AssetsTexture, uint16_t> ServerToAtlas;
|
||||
|
||||
public:
|
||||
WorldPCO PCO;
|
||||
@@ -1254,13 +1123,13 @@ public:
|
||||
}
|
||||
|
||||
void beforeDraw();
|
||||
void onGpuFinished();
|
||||
void drawWorld(GlobalTime gTime, float dTime, VkCommandBuffer drawCmd);
|
||||
void pushStage(EnumRenderStage stage);
|
||||
|
||||
static std::vector<VoxelVertexPoint> generateMeshForVoxelChunks(const std::vector<VoxelCube>& cubes);
|
||||
|
||||
private:
|
||||
void updateDescriptor_MainAtlas();
|
||||
void updateDescriptor_VoxelsLight();
|
||||
void updateDescriptor_ChunksLight();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user