#pragma once #include "TextureAtlas.hpp" #include "TexturePipelineProgram.hpp" #include #include #include #include #include #include #include #include #include #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, Base_Anim = 3, Resize = 10, Transform = 11, Opacity = 12, NoAlpha = 13, MakeAlpha = 14, Invert = 15, Brighten = 16, Contrast = 17, Multiply = 18, Screen = 19, Colorize = 20, Anim = 21, Overlay = 30, Mask = 31, LowPart = 32, Combine = 40 }; enum class SrcKind16 : Word { TexId = 0, Sub = 1 }; struct SrcRef16 { SrcKind16 kind{SrcKind16::TexId}; uint32_t TexId = 0; uint32_t Off = 0; uint32_t Len = 0; }; enum AnimFlags16 : Word { AnimSmooth = 1 << 0, AnimHorizontal = 1 << 1 }; struct AnimSpec16 { uint32_t TexId = 0; uint16_t FrameW = 0; uint16_t FrameH = 0; uint16_t FrameCount = 0; uint16_t FpsQ = 0; uint16_t Flags = 0; }; inline void addUniqueDep(boost::container::small_vector& 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 read16(const std::vector& words, size_t end, size_t& ip, uint16_t& out) { if (ip + 1 >= end) { return false; } out = uint16_t(words[ip]) | (uint16_t(words[ip + 1]) << 8); ip += 2; return true; } inline bool read24(const std::vector& words, size_t end, size_t& ip, uint32_t& out) { if (ip + 2 >= end) { return false; } out = uint32_t(words[ip]) | (uint32_t(words[ip + 1]) << 8) | (uint32_t(words[ip + 2]) << 16); ip += 3; return true; } inline bool read32(const std::vector& words, size_t end, size_t& ip, uint32_t& out) { if (ip + 3 >= end) { return false; } out = uint32_t(words[ip]) | (uint32_t(words[ip + 1]) << 8) | (uint32_t(words[ip + 2]) << 16) | (uint32_t(words[ip + 3]) << 24); ip += 4; return true; } inline bool readSrc(const std::vector& words, size_t end, size_t& ip, SrcRef16& out) { if (ip >= end) { return false; } out.kind = static_cast(words[ip++]); if (out.kind == SrcKind16::TexId) { return read24(words, end, ip, out.TexId); } if (out.kind == SrcKind16::Sub) { return read24(words, end, ip, out.Off) && read24(words, end, ip, out.Len); } return false; } inline void extractPipelineDependencies(const std::vector& words, size_t start, size_t end, boost::container::small_vector& deps, std::vector>& visited) { if (start >= end || end > words.size()) { return; } const std::pair 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, src.TexId); return; } if (src.kind == SrcKind16::Sub) { size_t subStart = static_cast(src.Off); size_t subEnd = subStart + static_cast(src.Len); if (subStart < subEnd && subEnd <= words.size()) { extractPipelineDependencies(words, subStart, subEnd, deps, visited); } } }; while (ip < end) { if (!need(1)) break; Op16 op = static_cast(words[ip++]); switch (op) { case Op16::End: return; case Op16::Base_Tex: { SrcRef16 src{}; if (!readSrc(words, end, ip, src)) return; handleSrc(src); } break; case Op16::Base_Anim: { SrcRef16 src{}; if (!readSrc(words, end, ip, src)) return; handleSrc(src); uint16_t tmp16 = 0; uint8_t tmp8 = 0; if (!read16(words, end, ip, tmp16)) return; if (!read16(words, end, ip, tmp16)) return; if (!read16(words, end, ip, tmp16)) return; if (!read16(words, end, ip, tmp16)) return; if (!need(1)) return; tmp8 = words[ip++]; (void)tmp8; } break; case Op16::Base_Fill: { uint16_t tmp16 = 0; uint32_t tmp32 = 0; if (!read16(words, end, ip, tmp16)) return; if (!read16(words, end, ip, tmp16)) return; if (!read32(words, end, ip, tmp32)) return; } break; case Op16::Overlay: case Op16::Mask: { SrcRef16 src{}; if (!readSrc(words, end, ip, src)) return; handleSrc(src); } break; case Op16::LowPart: { if (!need(1)) return; ip += 1; // percent SrcRef16 src{}; if (!readSrc(words, end, ip, src)) return; handleSrc(src); } break; case Op16::Resize: { uint16_t tmp16 = 0; if (!read16(words, end, ip, tmp16)) return; if (!read16(words, end, ip, tmp16)) return; } break; case Op16::Transform: case Op16::Opacity: if (!need(1)) return; ip += 1; break; case Op16::NoAlpha: case Op16::Brighten: break; case Op16::MakeAlpha: if (!need(3)) return; ip += 3; 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: { uint32_t tmp32 = 0; if (!read32(words, end, ip, tmp32)) return; } break; case Op16::Colorize: { uint32_t tmp32 = 0; if (!read32(words, end, ip, tmp32)) return; if (!need(1)) return; ip += 1; } break; case Op16::Anim: { uint16_t tmp16 = 0; if (!read16(words, end, ip, tmp16)) return; if (!read16(words, end, ip, tmp16)) return; if (!read16(words, end, ip, tmp16)) return; if (!read16(words, end, ip, tmp16)) return; if (!need(1)) return; ip += 1; } break; case Op16::Combine: { uint16_t w = 0, h = 0, n = 0; if (!read16(words, end, ip, w)) return; if (!read16(words, end, ip, h)) return; if (!read16(words, end, ip, n)) return; for (uint32_t i = 0; i < n; ++i) { uint16_t tmp16 = 0; if (!read16(words, end, ip, tmp16)) return; // x if (!read16(words, end, ip, tmp16)) return; // y SrcRef16 src{}; if (!readSrc(words, end, ip, src)) return; handleSrc(src); } (void)w; (void)h; } break; default: return; } } } inline boost::container::small_vector extractPipelineDependencies(const std::vector& words) { boost::container::small_vector deps; std::vector> visited; extractPipelineDependencies(words, 0, words.size(), deps, visited); return deps; } inline boost::container::small_vector extractPipelineDependencies(const boost::container::small_vector& words) { boost::container::small_vector deps; std::vector> visited; std::vector copy(words.begin(), words.end()); extractPipelineDependencies(copy, 0, copy.size(), deps, visited); return deps; } } // namespace detail // Структура нехешированного пайплайна struct Pipeline { std::vector _Pipeline; Pipeline() = default; explicit Pipeline(const TexturePipelineProgram& program) : _Pipeline(program.words().begin(), program.words().end()) { } Pipeline(TextureId texId) { _Pipeline = { static_cast(detail::Op16::Base_Tex), static_cast(detail::SrcKind16::TexId), static_cast(texId & 0xFFu), static_cast((texId >> 8) & 0xFFu), static_cast((texId >> 16) & 0xFFu), static_cast(detail::Op16::End) }; } }; // Структура хешированного текстурного пайплайна struct HashedPipeline { // Предвычисленный хеш std::size_t _Hash; boost::container::small_vector _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(w); hash *= prime; } _Hash = hash; } // Выдаёт список зависимых текстур, на основе которых строится эта boost::container::small_vector 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 _Pixels; StoredTexture() = default; StoredTexture(uint16_t w, uint16_t h, std::vector 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 _PipeToTexId; // Загруженные текстуры std::unordered_map _ResToTexture; std::unordered_map _AtlasCpuTextures; // Список зависимых пайплайнов от текстур (при изменении текстуры, нужно перерисовать пайплайны) std::unordered_map> _AddictedTextures; // Изменённые простые текстуры (для последующего массового обновление пайплайнов) std::vector _ChangedTextures; // Необходимые к созданию/обновлению пайплайны std::vector _ChangedPipelines; struct AnimatedPipelineState { std::vector Specs; std::vector LastFrames; bool Smooth = false; }; std::unordered_map _AnimatedPipelines; double _AnimTimeSeconds = 0.0; 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(); } uint32_t maxLayers() const { return Super.maxLayers(); } uint32_t maxTextureId() const { return Super.maxTextureId(); } TextureAtlas::TextureId reservedOverflowId() const { return Super.reservedOverflowId(); } TextureAtlas::TextureId reservedLayerId(uint32_t layer) const { return Super.reservedLayerId(layer); } void requestLayerCount(uint32_t layers) { Super.requestLayerCount(layers); } // Должны всегда бронировать идентификатор, либо отдавать kOverflowId. При этом запись tex+pipeline остаётся // Выдаёт стабильный идентификатор, привязанный к пайплайну AtlasTextureId getByPipeline(const HashedPipeline& pipeline); // Уведомить что текстура+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(); bool updateAnimatedPipelines(double timeSeconds); private: std::optional tryCopyFirstDependencyTexture(const HashedPipeline& pipeline) const; static StoredTexture makeSolidColorTexture(uint32_t rgba); };