codex-5.2: ресурсы
This commit is contained in:
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
Reference in New Issue
Block a user