tomato/src/texture_cache.hpp

245 lines
6.3 KiB
C++
Raw Normal View History

#pragma once
#include "./texture_uploader.hpp"
#include <entt/container/dense_map.hpp>
#include <entt/container/dense_set.hpp>
2025-01-07 21:30:40 +01:00
#include <solanaceae/util/time.hpp>
#include <optional>
#include <vector>
#include <cassert>
#include <iostream>
struct TextureEntry {
uint32_t width {0};
uint32_t height {0};
std::vector<uint64_t> textures;
std::vector<uint32_t> frame_duration; // ms
size_t current_texture {0};
bool rendered_this_frame {false};
// or flipped for animations
int64_t timestamp_last_rendered {0}; // ms
TextureEntry(void) = default;
TextureEntry(const TextureEntry& other) :
width(other.width),
height(other.height),
textures(other.textures),
frame_duration(other.frame_duration),
current_texture(other.current_texture),
rendered_this_frame(other.rendered_this_frame),
timestamp_last_rendered(other.timestamp_last_rendered)
{}
TextureEntry& operator=(const TextureEntry& other) {
width = other.width;
height = other.height;
textures = other.textures;
frame_duration = other.frame_duration;
current_texture = other.current_texture;
rendered_this_frame = other.rendered_this_frame;
timestamp_last_rendered = other.timestamp_last_rendered;
return *this;
}
uint32_t getDuration(void) const {
return frame_duration.at(current_texture);
}
void next(void) {
current_texture = (current_texture + 1) % frame_duration.size();
}
// returns ts for next frame
int64_t doAnimation(const int64_t ts_now);
template<typename TextureType>
TextureType getID(void) {
static_assert(
sizeof(TextureType) == sizeof(uint64_t) ||
sizeof(TextureType) == sizeof(uint32_t)
);
rendered_this_frame = true;
assert(current_texture < textures.size());
if constexpr (sizeof(TextureType) == sizeof(uint64_t)) {
return reinterpret_cast<TextureType>(textures.at(current_texture));
} else if constexpr (sizeof(TextureType) == sizeof(uint32_t)) {
return reinterpret_cast<TextureType>(static_cast<uint32_t>(textures.at(current_texture)));
}
}
};
struct TextureLoaderResult {
std::optional<TextureEntry> texture;
bool keep_trying{false}; // if set, cant be cleared, as some async might be going on
};
TextureEntry generateTestAnim(TextureUploaderI& tu);
2023-08-01 13:21:16 +02:00
template<typename TextureType, typename KeyType, class Loader>
struct TextureCache {
static_assert(
sizeof(TextureType) == sizeof(uint64_t) ||
sizeof(TextureType) == sizeof(uint32_t)
);
2023-08-01 13:21:16 +02:00
Loader& _l;
TextureUploaderI& _tu;
TextureEntry _default_texture;
entt::dense_map<KeyType, TextureEntry> _cache;
entt::dense_set<KeyType> _to_load;
2024-12-13 00:53:48 +01:00
// to_reload // to_update? _marked_stale?
const uint64_t ms_before_purge {60 * 1000ull};
const size_t min_count_before_purge {0}; // starts purging after that
2023-08-01 13:21:16 +02:00
TextureCache(Loader& l, TextureUploaderI& tu) : _l(l), _tu(tu) {
//_image_loaders.push_back(std::make_unique<ImageLoaderWebP>());
//_image_loaders.push_back(std::make_unique<ImageLoaderSTB>());
_default_texture = generateTestAnim(_tu);
//_default_texture = loadTestWebPAnim();
}
2023-08-02 15:37:37 +02:00
~TextureCache(void) {
for (const auto& it : _cache) {
for (const auto& tex_id : it.second.textures) {
_tu.destroy(tex_id);
}
}
}
struct GetInfo {
TextureType id;
uint32_t width;
uint32_t height;
};
GetInfo get(const KeyType& key) {
auto it = _cache.find(key);
if (it != _cache.end()) {
return {
it->second.template getID<TextureType>(),
it->second.width,
it->second.height
};
} else {
_to_load.insert(key);
return {
_default_texture.getID<TextureType>(),
_default_texture.width,
_default_texture.height
};
}
}
2024-12-13 00:53:48 +01:00
// markes a texture as stale and will reload it
// only if it already is loaded, does not update ts
bool stale(const KeyType& key) {
auto it = _cache.find(key);
if (it == _cache.end()) {
return false;
}
_to_load.insert(key);
return true;
}
float update(void) {
2025-01-07 21:30:40 +01:00
const uint64_t ts_now = getTimeMS();
uint64_t ts_min_next = ts_now + ms_before_purge;
std::vector<KeyType> to_purge;
for (auto&& [key, te] : _cache) {
if (te.rendered_this_frame) {
const uint64_t ts_next = te.doAnimation(ts_now);
te.rendered_this_frame = false;
ts_min_next = std::min(ts_min_next, ts_next);
} else if (
_cache.size() > min_count_before_purge &&
ts_now - te.timestamp_last_rendered >= ms_before_purge
) {
to_purge.push_back(key);
}
}
2023-08-01 18:25:56 +02:00
invalidate(to_purge);
// we ignore the default texture ts :)
2023-08-01 18:25:56 +02:00
_default_texture.doAnimation(ts_now);
return (ts_min_next - ts_now) / 1000.f;
2023-08-01 18:25:56 +02:00
}
void invalidate(const std::vector<KeyType>& to_purge) {
for (const auto& key : to_purge) {
if (_to_load.count(key)) {
// TODO: only remove if not keep trying
_to_load.erase(key);
}
if (_cache.count(key)) {
for (const auto& tex_id : _cache.at(key).textures) {
_tu.destroy(tex_id);
}
_cache.erase(key);
}
}
}
2023-08-01 13:21:16 +02:00
// returns true if there is still work queued up
bool workLoadQueue(void) {
auto it = _to_load.cbegin();
for (; it != _to_load.cend(); it++) {
auto new_entry_opt = _l.load(_tu, *it);
2024-12-13 00:53:48 +01:00
if (_cache.count(*it)) {
if (new_entry_opt.texture.has_value()) {
auto old_entry = _cache.at(*it); // copy
assert(!old_entry.textures.empty());
2024-12-13 00:53:48 +01:00
for (const auto& tex_id : old_entry.textures) {
_tu.destroy(tex_id);
}
_cache.erase(*it);
auto& new_entry = _cache[*it] = new_entry_opt.texture.value();
// TODO: make update interface and let loader handle this
//new_entry.current_texture = old_entry.current_texture; // ??
new_entry.rendered_this_frame = old_entry.rendered_this_frame;
new_entry.timestamp_last_rendered = old_entry.timestamp_last_rendered;
2024-12-13 00:53:48 +01:00
it = _to_load.erase(it);
// TODO: not a good idea?
break; // end load from queue/onlyload 1 per update
} else if (!new_entry_opt.keep_trying) {
// failed to load and the loader is done
it = _to_load.erase(it);
2024-12-13 00:53:48 +01:00
}
} else {
if (new_entry_opt.texture.has_value()) {
_cache.emplace(*it, new_entry_opt.texture.value());
_cache.at(*it).rendered_this_frame = true; // ?
2024-12-13 00:53:48 +01:00
it = _to_load.erase(it);
// TODO: not a good idea?
break; // end load from queue/onlyload 1 per update
} else if (!new_entry_opt.keep_trying) {
// failed to load and the loader is done
it = _to_load.erase(it);
2024-12-13 00:53:48 +01:00
}
2023-08-01 13:21:16 +02:00
}
}
// peek
return it != _to_load.cend();
2023-08-01 13:21:16 +02:00
}
};