make texture cache scale down images on load to the requested size

This commit is contained in:
Green Sky
2025-12-04 15:56:39 +01:00
parent 997e2517a3
commit 2dbce14e5e
13 changed files with 95 additions and 40 deletions

View File

@@ -65,7 +65,7 @@ std::optional<TextureEntry> BitsetImageLoader::haveToTexture(TextureUploaderI& t
BitsetImageLoader::BitsetImageLoader(void) { BitsetImageLoader::BitsetImageLoader(void) {
} }
TextureLoaderResult BitsetImageLoader::load(TextureUploaderI& tu, ObjectHandle o) { TextureLoaderResult BitsetImageLoader::load(TextureUploaderI& tu, ObjectHandle o, uint32_t w, uint32_t h) {
if (!static_cast<bool>(o)) { if (!static_cast<bool>(o)) {
std::cerr << "BIL error: trying to load invalid object\n"; std::cerr << "BIL error: trying to load invalid object\n";
return {}; return {};

View File

@@ -34,7 +34,7 @@ class BitsetImageLoader {
public: public:
BitsetImageLoader(void); BitsetImageLoader(void);
TextureLoaderResult load(TextureUploaderI& tu, ObjectHandle o); TextureLoaderResult load(TextureUploaderI& tu, ObjectHandle o, uint32_t w, uint32_t h);
std::optional<TextureEntry> load(TextureUploaderI& tu, ObjectContactSub ocs); std::optional<TextureEntry> load(TextureUploaderI& tu, ObjectContactSub ocs);
}; };

View File

@@ -41,8 +41,12 @@ void renderAvatar(
} }
} }
// TODO: per display?
// TODO: do we really need this? test dpi scaling
const auto [g_scale_x, g_scyle_y] = ImGui::GetIO().DisplayFramebufferScale;
// avatar // avatar
const auto [id, width, height] = contact_tc.get(c); const auto [id, width, height] = contact_tc.get(c, box.x*g_scale_x, box.y*g_scyle_y);
ImGui::Image( ImGui::Image(
id, id,
box, box,

View File

@@ -14,6 +14,8 @@ void ImageViewerPopup::view(Message3Handle m) {
} }
_m = m; _m = m;
_width = 0;
_height = 0;
_open_popup = true; _open_popup = true;
} }
@@ -35,10 +37,13 @@ void ImageViewerPopup::render(float) {
ImGui::SliderFloat("scale", &_scale, 0.05f, 2.f); ImGui::SliderFloat("scale", &_scale, 0.05f, 2.f);
auto [id, img_width, img_height] = _mtc.get(_m); auto [id, img_width, img_height] = _mtc.get(_m, _width, _height);
img_width = std::max<int32_t>(5, _scale * img_width); img_width = std::max<uint32_t>(5, _scale * img_width);
img_height = std::max<int32_t>(5, _scale * img_height); img_height = std::max<uint32_t>(5, _scale * img_height);
_width = img_width;
_height = img_height;
ImGui::Image( ImGui::Image(
id, id,

View File

@@ -12,6 +12,9 @@ struct ImageViewerPopup {
Message3Handle _m{}; Message3Handle _m{};
float _scale {1.f}; float _scale {1.f};
// keep track of full size from last frame
uint32_t _width {0};
uint32_t _height {0};
bool _open_popup {false}; bool _open_popup {false};

View File

@@ -15,6 +15,7 @@
#include <cstdint> #include <cstdint>
#include <cmath> #include <cmath>
#include <iostream>
SendImagePopup::SendImagePopup(TextureUploaderI& tu) : _tu(tu) { SendImagePopup::SendImagePopup(TextureUploaderI& tu) : _tu(tu) {
_image_loaders.push_back(std::make_unique<ImageLoaderSDLBMP>()); _image_loaders.push_back(std::make_unique<ImageLoaderSDLBMP>());

View File

@@ -27,9 +27,7 @@
#include "./chat_gui/contact_list.hpp" #include "./chat_gui/contact_list.hpp"
#include "./media_meta_info_loader.hpp"
#include "./sdl_clipboard_utils.hpp" #include "./sdl_clipboard_utils.hpp"
#include "os_comps.hpp"
#include "./string_formatter_utils.hpp" #include "./string_formatter_utils.hpp"
@@ -42,7 +40,7 @@
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <variant> #include <iostream>
// TODO: split into msg and c // TODO: split into msg and c
namespace Components { namespace Components {
@@ -1420,7 +1418,7 @@ void ChatGui4::renderMessageBodyFile(Message3Registry& reg, const Message3 e) {
if (ImGui::IsItemVisible() && o.all_of<ObjComp::F::TagLocalHaveAll, ObjComp::F::SingleInfo, ObjComp::Ephemeral::BackendFile2>()) { if (ImGui::IsItemVisible() && o.all_of<ObjComp::F::TagLocalHaveAll, ObjComp::F::SingleInfo, ObjComp::Ephemeral::BackendFile2>()) {
ImGui::SetCursorPos(orig_curser_pos); // reset for actual img ImGui::SetCursorPos(orig_curser_pos); // reset for actual img
auto [id, img_width, img_height] = _msg_tc.get(Message3Handle{reg, e}); auto [id, img_width, img_height] = _msg_tc.get(Message3Handle{reg, e}, width, height);
// if cache gives 0s, fall back to frame dims (eg if pic not loaded yet) // if cache gives 0s, fall back to frame dims (eg if pic not loaded yet)
//if (img_width == 0 || img_height == 0) { //if (img_width == 0 || img_height == 0) {

View File

@@ -8,9 +8,8 @@
#include <solanaceae/message3/components.hpp> #include <solanaceae/message3/components.hpp>
#include "./os_comps.hpp"
#include <solanaceae/object_store/object_store.hpp> #include <solanaceae/object_store/object_store.hpp>
#include <solanaceae/object_store/meta_components_file.hpp>
#include <solanaceae/file/file2.hpp> #include <solanaceae/file/file2.hpp>
@@ -25,7 +24,7 @@ MessageImageLoader::MessageImageLoader(void) {
_image_loaders.push_back(std::make_unique<ImageLoaderSDLImage>()); _image_loaders.push_back(std::make_unique<ImageLoaderSDLImage>());
} }
TextureLoaderResult MessageImageLoader::load(TextureUploaderI& tu, Message3Handle m) { TextureLoaderResult MessageImageLoader::load(TextureUploaderI& tu, Message3Handle m, uint32_t w, uint32_t h) {
if (!static_cast<bool>(m)) { if (!static_cast<bool>(m)) {
return {std::nullopt}; return {std::nullopt};
} }
@@ -101,6 +100,17 @@ TextureLoaderResult MessageImageLoader::load(TextureUploaderI& tu, Message3Handl
TextureEntry new_entry; TextureEntry new_entry;
new_entry.timestamp_last_rendered = getTimeMS(); new_entry.timestamp_last_rendered = getTimeMS();
new_entry.current_texture = 0; new_entry.current_texture = 0;
new_entry.src_width = res.width;
new_entry.src_height = res.height;
if (w != 0 && h != 0 && w < res.width && h < res.height) {
res = res.scale(w, h);
}
new_entry.width = res.width;
new_entry.height = res.height;
for (const auto& [ms, data] : res.frames) { for (const auto& [ms, data] : res.frames) {
const auto n_t = tu.upload(data.data(), res.width, res.height); const auto n_t = tu.upload(data.data(), res.width, res.height);
if (n_t == 0) { if (n_t == 0) {
@@ -114,9 +124,6 @@ TextureLoaderResult MessageImageLoader::load(TextureUploaderI& tu, Message3Handl
continue; continue;
} }
new_entry.width = res.width;
new_entry.height = res.height;
std::cout << "MIL: loaded image file o:" << /*file_path*/ entt::to_integral(o.entity()) << "\n"; std::cout << "MIL: loaded image file o:" << /*file_path*/ entt::to_integral(o.entity()) << "\n";
return {new_entry}; return {new_entry};

View File

@@ -5,13 +5,11 @@
#include "./image_loader.hpp" #include "./image_loader.hpp"
#include "./texture_cache.hpp" #include "./texture_cache.hpp"
#include <optional>
class MessageImageLoader { class MessageImageLoader {
std::vector<std::unique_ptr<ImageLoaderI>> _image_loaders; std::vector<std::unique_ptr<ImageLoaderI>> _image_loaders;
public: public:
MessageImageLoader(void); MessageImageLoader(void);
TextureLoaderResult load(TextureUploaderI& tu, Message3Handle m); TextureLoaderResult load(TextureUploaderI& tu, Message3Handle m, uint32_t w, uint32_t h);
}; };

View File

@@ -51,8 +51,11 @@ TextureEntry generateTestAnim(TextureUploaderI& tu) {
new_entry.textures.emplace_back(n_t); new_entry.textures.emplace_back(n_t);
new_entry.frame_duration.emplace_back(250); new_entry.frame_duration.emplace_back(250);
} }
// TODO: 2x2?
new_entry.width = 0; new_entry.width = 0;
new_entry.height = 0; new_entry.height = 0;
new_entry.src_width = 0;
new_entry.src_height = 0;
return new_entry; return new_entry;
} }

View File

@@ -16,6 +16,10 @@
struct TextureEntry { struct TextureEntry {
uint32_t width {0}; uint32_t width {0};
uint32_t height {0}; uint32_t height {0};
uint32_t src_width {0};
uint32_t src_height {0};
std::vector<uint64_t> textures; std::vector<uint64_t> textures;
std::vector<uint32_t> frame_duration; // ms std::vector<uint32_t> frame_duration; // ms
size_t current_texture {0}; size_t current_texture {0};
@@ -28,6 +32,8 @@ struct TextureEntry {
TextureEntry(const TextureEntry& other) : TextureEntry(const TextureEntry& other) :
width(other.width), width(other.width),
height(other.height), height(other.height),
src_width(other.src_width),
src_height(other.src_height),
textures(other.textures), textures(other.textures),
frame_duration(other.frame_duration), frame_duration(other.frame_duration),
current_texture(other.current_texture), current_texture(other.current_texture),
@@ -39,6 +45,8 @@ struct TextureEntry {
TextureEntry& operator=(const TextureEntry& other) { TextureEntry& operator=(const TextureEntry& other) {
width = other.width; width = other.width;
height = other.height; height = other.height;
src_width = other.src_width;
src_height = other.src_height;
textures = other.textures; textures = other.textures;
frame_duration = other.frame_duration; frame_duration = other.frame_duration;
current_texture = other.current_texture; current_texture = other.current_texture;
@@ -97,7 +105,11 @@ struct TextureCache {
TextureEntry _default_texture; TextureEntry _default_texture;
entt::dense_map<KeyType, TextureEntry> _cache; entt::dense_map<KeyType, TextureEntry> _cache;
entt::dense_set<KeyType> _to_load; struct LoadDims {
uint32_t w{0};
uint32_t h{0};
};
entt::dense_map<KeyType, LoadDims> _to_load;
// to_reload // to_update? _marked_stale? // to_reload // to_update? _marked_stale?
const uint64_t ms_before_purge {60 * 1000ull}; const uint64_t ms_before_purge {60 * 1000ull};
@@ -126,21 +138,37 @@ struct TextureCache {
uint32_t width; uint32_t width;
uint32_t height; uint32_t height;
}; };
GetInfo get(const KeyType& key) { GetInfo get(const KeyType& key, uint32_t width = 0, uint32_t height = 0) {
auto it = _cache.find(key); auto it = _cache.find(key);
if (it != _cache.end()) { if (it != _cache.end()) {
// if scaled down AND smaller than requested, reload larger
if (
(width != 0 && height != 0) &&
(
(it->second.width < width && it->second.width < it->second.src_width) ||
(it->second.height < height && it->second.height < it->second.src_height)
)
) {
// TODO: only overwrite smaller dims (or combine max)
_to_load.insert({key, LoadDims{width, height}});
}
// return current texture either way
return { return {
it->second.template getID<TextureType>(), it->second.template getID<TextureType>(),
it->second.width, it->second.src_width,
it->second.height it->second.src_height
}; };
} else { } else {
_to_load.insert(key); // TODO: only overwrite smaller dims (or combine max)
_to_load.insert({key, LoadDims{width, height}});
// return fallback
return { return {
_default_texture.getID<TextureType>(), _default_texture.getID<TextureType>(),
_default_texture.width, _default_texture.src_width,
_default_texture.height _default_texture.src_height
}; };
} }
} }
@@ -153,7 +181,7 @@ struct TextureCache {
if (it == _cache.end()) { if (it == _cache.end()) {
return false; return false;
} }
_to_load.insert(key); _to_load.insert({key, {it->second.width, it->second.height}});
return true; return true;
} }
@@ -202,17 +230,18 @@ struct TextureCache {
bool workLoadQueue(void) { bool workLoadQueue(void) {
auto it = _to_load.cbegin(); auto it = _to_load.cbegin();
for (; it != _to_load.cend(); it++) { for (; it != _to_load.cend(); it++) {
auto new_entry_opt = _l.load(_tu, *it); const auto& load_key = it->first;
if (_cache.count(*it)) { auto new_entry_opt = _l.load(_tu, load_key, it->second.w, it->second.h);
if (_cache.count(load_key)) {
if (new_entry_opt.texture.has_value()) { if (new_entry_opt.texture.has_value()) {
auto old_entry = _cache.at(*it); // copy auto old_entry = _cache.at(load_key); // copy
assert(!old_entry.textures.empty()); assert(!old_entry.textures.empty());
for (const auto& tex_id : old_entry.textures) { for (const auto& tex_id : old_entry.textures) {
_tu.destroy(tex_id); _tu.destroy(tex_id);
} }
_cache.erase(*it); _cache.erase(load_key);
auto& new_entry = _cache[*it] = new_entry_opt.texture.value(); auto& new_entry = _cache[load_key] = new_entry_opt.texture.value();
// TODO: make update interface and let loader handle this // TODO: make update interface and let loader handle this
//new_entry.current_texture = old_entry.current_texture; // ?? //new_entry.current_texture = old_entry.current_texture; // ??
new_entry.rendered_this_frame = old_entry.rendered_this_frame; new_entry.rendered_this_frame = old_entry.rendered_this_frame;
@@ -228,8 +257,8 @@ struct TextureCache {
} }
} else { } else {
if (new_entry_opt.texture.has_value()) { if (new_entry_opt.texture.has_value()) {
_cache.emplace(*it, new_entry_opt.texture.value()); _cache.emplace(load_key, new_entry_opt.texture.value());
_cache.at(*it).rendered_this_frame = true; // ? _cache.at(load_key).rendered_this_frame = true; // ?
it = _to_load.erase(it); it = _to_load.erase(it);
// TODO: not a good idea? // TODO: not a good idea?

View File

@@ -195,7 +195,7 @@ static std::vector<uint8_t> generateToxIdenticon(const ToxKey& key) {
return pixels; return pixels;
} }
TextureLoaderResult ToxAvatarLoader::load(TextureUploaderI& tu, Contact4 c) { TextureLoaderResult ToxAvatarLoader::load(TextureUploaderI& tu, Contact4 c, uint32_t w, uint32_t h) {
const auto& cr = _cs.registry(); const auto& cr = _cs.registry();
if (!cr.valid(c)) { if (!cr.valid(c)) {
return {std::nullopt}; return {std::nullopt};
@@ -234,14 +234,23 @@ TextureLoaderResult ToxAvatarLoader::load(TextureUploaderI& tu, Contact4 c) {
TextureEntry new_entry; TextureEntry new_entry;
new_entry.timestamp_last_rendered = getTimeMS(); new_entry.timestamp_last_rendered = getTimeMS();
new_entry.current_texture = 0; new_entry.current_texture = 0;
new_entry.src_width = res.width;
new_entry.src_height = res.height;
if (w != 0 && h != 0 && w < res.width && h < res.height) {
res = res.scale(w, h);
}
new_entry.width = res.width;
new_entry.height = res.height;
for (const auto& [ms, data] : res.frames) { for (const auto& [ms, data] : res.frames) {
const auto n_t = tu.upload(data.data(), res.width, res.height); const auto n_t = tu.upload(data.data(), res.width, res.height);
new_entry.textures.push_back(n_t); new_entry.textures.push_back(n_t);
new_entry.frame_duration.push_back(ms); new_entry.frame_duration.push_back(ms);
} }
new_entry.width = res.width;
new_entry.height = res.height;
if (cr.all_of<Contact::Components::AvatarFile>(c)) { if (cr.all_of<Contact::Components::AvatarFile>(c)) {
std::cout << "TAL: loaded image file " << cr.get<Contact::Components::AvatarFile>(c).file_path << "\n"; std::cout << "TAL: loaded image file " << cr.get<Contact::Components::AvatarFile>(c).file_path << "\n";

View File

@@ -8,8 +8,6 @@
#include "./image_loader.hpp" #include "./image_loader.hpp"
#include "./texture_cache.hpp" #include "./texture_cache.hpp"
#include <optional>
class ToxAvatarLoader { class ToxAvatarLoader {
ContactStore4I& _cs; ContactStore4I& _cs;
ObjectStore2& _os; ObjectStore2& _os;
@@ -21,6 +19,6 @@ class ToxAvatarLoader {
public: public:
ToxAvatarLoader(ContactStore4I& cs, ObjectStore2& os); ToxAvatarLoader(ContactStore4I& cs, ObjectStore2& os);
TextureLoaderResult load(TextureUploaderI& tu, Contact4 c); TextureLoaderResult load(TextureUploaderI& tu, Contact4 c, uint32_t w, uint32_t h);
}; };