Skip to content

Commit de9f239

Browse files
committed
asset/cache: implement asset cache
this will save vram especially in hyprpaper's case by not loading multiple copies of the same image
1 parent a07c89a commit de9f239

File tree

9 files changed

+207
-22
lines changed

9 files changed

+207
-22
lines changed

src/element/image/Image.cpp

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#include "../../core/InternalBackend.hpp"
66
#include "../../window/ToolkitWindow.hpp"
77
#include "../../system/Icons.hpp"
8+
#include "../../resource/assetCache/AssetCache.hpp"
9+
#include "../../renderer/RendererTexture.hpp"
810

911
#include "../Element.hpp"
1012

@@ -25,12 +27,12 @@ void CImageElement::paint() {
2527
if (m_impl->failed)
2628
return;
2729

28-
SP<IRendererTexture> textureToUse = m_impl->tex;
30+
auto assetToUse = m_impl->cacheEntry;
2931

30-
if (!m_impl->tex)
31-
textureToUse = m_impl->oldTex;
32+
if (!assetToUse || !assetToUse->tex())
33+
assetToUse = m_impl->oldCacheEntry;
3234

33-
if (!textureToUse) {
35+
if (!assetToUse || !assetToUse->tex()) {
3436
if (!m_impl->waitingForTex)
3537
renderTex();
3638
return;
@@ -41,15 +43,15 @@ void CImageElement::paint() {
4143

4244
if (m_impl->data.icon && m_impl->preferredSvgSize() != m_impl->size && !m_impl->waitingForTex) {
4345
renderTex();
44-
textureToUse = m_impl->oldTex;
46+
assetToUse = m_impl->oldCacheEntry;
4547
}
4648

47-
if (!textureToUse)
49+
if (!assetToUse || !assetToUse->tex())
4850
return; // ???
4951

5052
g_renderer->renderTexture({
5153
.box = impl->position,
52-
.texture = textureToUse,
54+
.texture = assetToUse->tex(),
5355
.a = 1.F,
5456
.rounding = 0,
5557
});
@@ -59,13 +61,27 @@ void CImageElement::renderTex() {
5961
if (m_impl->waitingForTex)
6062
return;
6163

62-
// TODO: this happens in hyprpaper's case, but if we have two or more wallpapers of the same
63-
// image we duplicate VRAM. Maybe keep an asset ref table?
64-
// ofc this won't work for svg but rasters of course.
65-
6664
m_impl->resource.reset();
67-
m_impl->oldTex = m_impl->tex;
68-
m_impl->tex.reset();
65+
m_impl->oldCacheEntry = m_impl->cacheEntry;
66+
m_impl->cacheEntry.reset();
67+
68+
const auto CACHE_STR = m_impl->getCacheString();
69+
70+
const auto ASSET = Asset::assetCache()->get(CACHE_STR);
71+
if (ASSET) {
72+
g_logger->log(HT_LOG_DEBUG, "CImageElement: path {} was already cached, reusing entry", m_impl->data.path);
73+
m_impl->failed = false;
74+
m_impl->cacheEntry = ASSET;
75+
if (ASSET->status() == Asset::CACHE_ENTRY_DONE)
76+
m_impl->postImageScheduleRecalc();
77+
else {
78+
m_impl->listeners.cacheEntryDone = ASSET->m_events.done.listen([this] {
79+
m_impl->postImageScheduleRecalc();
80+
m_impl->listeners.cacheEntryDone.reset();
81+
});
82+
}
83+
return;
84+
}
6985

7086
if (!m_impl->data.icon) {
7187
m_impl->resource = makeAtomicShared<CImageResource>(m_impl->data.path);
@@ -79,6 +95,9 @@ void CImageElement::renderTex() {
7995
return;
8096
}
8197

98+
m_impl->cacheEntry = makeShared<Asset::CAssetCacheEntry>(CACHE_STR);
99+
Asset::assetCache()->cache(m_impl->cacheEntry);
100+
82101
m_impl->waitingForTex = true;
83102

84103
ASP<IAsyncResource> resourceGeneric(m_impl->resource);
@@ -104,28 +123,41 @@ void CImageElement::renderTex() {
104123
}
105124

106125
void SImageImpl::postImageLoad() {
107-
if (!resource)
126+
if (!resource || !cacheEntry)
108127
return;
109128

110129
if (resource->m_asset.cairoSurface) {
111130
ASP<IAsyncResource> resourceGeneric(resource);
112131
size = resource->m_asset.pixelSize;
113-
tex = g_renderer->uploadTexture({.resource = resourceGeneric, .fitMode = data.fitMode});
132+
cacheEntry->texDone(g_renderer->uploadTexture({.resource = resourceGeneric, .fitMode = data.fitMode}));
114133
} else {
115134
failed = true;
116135
g_logger->log(HT_LOG_ERROR, "Image: failed loading, hyprgraphics couldn't load asset {}", lastPath);
117136
}
118137

119-
oldTex.reset();
138+
oldCacheEntry.reset();
120139
resource.reset();
121140

141+
postImageScheduleRecalc();
142+
}
143+
144+
void SImageImpl::postImageScheduleRecalc() {
122145
waitingForTex = false;
123146
if (!failed) {
147+
if (cacheEntry)
148+
size = cacheEntry->tex()->size();
124149
self->impl->damageEntire();
125150
self->impl->window->scheduleReposition(self);
126151
}
127152
}
128153

154+
std::string SImageImpl::getCacheString() {
155+
if (!data.icon)
156+
return data.path.ends_with(".svg") ? std::format("{}-{}x{}", data.path, preferredSvgSize().x, preferredSvgSize().y) : data.path;
157+
else
158+
return std::format("icon-{}-{}x{}", reinterpretPointerCast<CSystemIconDescription>(data.icon)->m_bestPath, preferredSvgSize().x, preferredSvgSize().y);
159+
}
160+
129161
SP<CImageBuilder> CImageElement::rebuild() {
130162
auto p = SP<CImageBuilder>(new CImageBuilder());
131163
p->m_self = p;

src/element/image/Image.hpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <hyprtoolkit/element/Image.hpp>
22

33
#include "../../helpers/Memory.hpp"
4+
#include "../../resource/assetCache/AssetCacheEntry.hpp"
45

56
namespace Hyprtoolkit {
67
struct SImageData {
@@ -20,8 +21,8 @@ namespace Hyprtoolkit {
2021

2122
float lastScale = 1.F;
2223

23-
Hyprutils::Memory::CSharedPointer<IRendererTexture> tex;
24-
Hyprutils::Memory::CSharedPointer<IRendererTexture> oldTex; // while loading a new one
24+
Hyprutils::Memory::CSharedPointer<Asset::CAssetCacheEntry> cacheEntry;
25+
Hyprutils::Memory::CSharedPointer<Asset::CAssetCacheEntry> oldCacheEntry; // while loading a new one
2526
Hyprutils::Memory::CAtomicSharedPointer<Hyprgraphics::CImageResource> resource;
2627
Hyprutils::Math::Vector2D size;
2728

@@ -31,5 +32,11 @@ namespace Hyprtoolkit {
3132

3233
Hyprutils::Math::Vector2D preferredSvgSize();
3334
void postImageLoad();
35+
void postImageScheduleRecalc();
36+
std::string getCacheString();
37+
38+
struct {
39+
Hyprutils::Signal::CHyprSignalListener cacheEntryDone;
40+
} listeners;
3441
};
3542
}

src/renderer/RendererTexture.hpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <cstddef>
44

55
#include <hyprtoolkit/types/ImageTypes.hpp>
6+
#include <hyprutils/math/Vector2D.hpp>
67

78
namespace Hyprtoolkit {
89
class IRendererTexture {
@@ -14,9 +15,10 @@ namespace Hyprtoolkit {
1415
TEXTURE_GL,
1516
};
1617

17-
virtual size_t id() = 0;
18-
virtual eTextureType type() = 0;
19-
virtual void destroy() = 0;
20-
virtual eImageFitMode fitMode() = 0;
18+
virtual size_t id() = 0;
19+
virtual eTextureType type() = 0;
20+
virtual void destroy() = 0;
21+
virtual eImageFitMode fitMode() = 0;
22+
virtual Hyprutils::Math::Vector2D size() = 0;
2123
};
2224
}

src/renderer/gl/GLTexture.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,7 @@ void CGLTexture::bind() {
9292
eImageFitMode CGLTexture::fitMode() {
9393
return m_fitMode;
9494
}
95+
96+
Vector2D CGLTexture::size() {
97+
return m_size;
98+
}

src/renderer/gl/GLTexture.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ namespace Hyprtoolkit {
3030
virtual eTextureType type();
3131
virtual void destroy();
3232
virtual eImageFitMode fitMode();
33+
virtual Hyprutils::Math::Vector2D size();
3334

3435
eGLTextureType m_type = TEXTURE_RGBA;
3536
GLenum m_target = GL_TEXTURE_2D;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include "AssetCache.hpp"
2+
3+
using namespace Hyprtoolkit;
4+
using namespace Hyprtoolkit::Asset;
5+
6+
SP<CAssetCache> Asset::assetCache() {
7+
static auto cache = makeShared<CAssetCache>();
8+
return cache;
9+
}
10+
11+
SP<CAssetCacheEntry> CAssetCache::get(const std::string_view& source) {
12+
for (const auto& e : m_entries) {
13+
if (e && e->source() == source)
14+
return e.lock();
15+
}
16+
17+
return nullptr;
18+
}
19+
20+
void CAssetCache::cache(SP<CAssetCacheEntry> entry) {
21+
gc();
22+
m_entries.emplace_back(entry);
23+
}
24+
25+
void CAssetCache::gc() {
26+
std::erase_if(m_entries, [](const auto& e) { return !e; });
27+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#pragma once
2+
3+
#include "AssetCacheEntry.hpp"
4+
5+
#include <vector>
6+
7+
namespace Hyprtoolkit::Asset {
8+
9+
// AssetCache is a cache for loading assets. Since we keep assets (images) as
10+
// textures in VRAM, we don't want to load the same raster asset multiple times.
11+
// IMPORTANT: AssetCache holds only WEAK references to the entries. You need
12+
// to hold your own ref somewhere to avoid the asset going dead
13+
class CAssetCache {
14+
public:
15+
CAssetCache() = default;
16+
~CAssetCache() = default;
17+
18+
CAssetCache(const CAssetCache&) = delete;
19+
CAssetCache(CAssetCache&) = delete;
20+
CAssetCache(CAssetCache&&) = delete;
21+
22+
// TODO: implement this for svg images. These can be loaded at different res's
23+
SP<CAssetCacheEntry> get(const std::string_view& source);
24+
void cache(SP<CAssetCacheEntry> entry);
25+
26+
private:
27+
void gc();
28+
29+
std::vector<WP<CAssetCacheEntry>> m_entries;
30+
};
31+
32+
SP<CAssetCache> assetCache();
33+
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#include "AssetCacheEntry.hpp"
2+
3+
using namespace Hyprtoolkit;
4+
using namespace Hyprtoolkit::Asset;
5+
6+
CAssetCacheEntry::CAssetCacheEntry(const std::string_view& source, const SP<IRendererTexture> tex) : m_source(source), m_tex(tex), m_status(CACHE_ENTRY_DONE) {
7+
;
8+
}
9+
10+
CAssetCacheEntry::CAssetCacheEntry(const std::string_view& source) : m_source(source) {
11+
;
12+
}
13+
14+
std::string_view CAssetCacheEntry::source() const {
15+
return m_source;
16+
}
17+
18+
SP<IRendererTexture> CAssetCacheEntry::tex() const {
19+
return m_tex;
20+
}
21+
22+
eAssetCacheEntryStatus CAssetCacheEntry::status() const {
23+
return m_status;
24+
}
25+
26+
void CAssetCacheEntry::texDone(SP<IRendererTexture> tex) {
27+
m_tex = tex;
28+
m_status = CACHE_ENTRY_DONE;
29+
m_events.done.emit();
30+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#pragma once
2+
3+
#include <string_view>
4+
5+
#include <hyprutils/signal/Signal.hpp>
6+
7+
#include "../../helpers/Memory.hpp"
8+
9+
namespace Hyprtoolkit {
10+
class IRendererTexture;
11+
}
12+
13+
namespace Hyprtoolkit::Asset {
14+
enum eAssetCacheEntryStatus : uint8_t {
15+
CACHE_ENTRY_PENDING = 0,
16+
CACHE_ENTRY_DONE = 1,
17+
};
18+
19+
class CAssetCacheEntry {
20+
public:
21+
CAssetCacheEntry(const std::string_view& source, const SP<IRendererTexture> tex);
22+
CAssetCacheEntry(const std::string_view& source);
23+
~CAssetCacheEntry() = default;
24+
25+
CAssetCacheEntry(const CAssetCacheEntry&) = delete;
26+
CAssetCacheEntry(CAssetCacheEntry&) = delete;
27+
CAssetCacheEntry(CAssetCacheEntry&&) = delete;
28+
29+
std::string_view source() const;
30+
SP<IRendererTexture> tex() const;
31+
eAssetCacheEntryStatus status() const;
32+
33+
// if created without a tex, this will mark asset as done
34+
void texDone(SP<IRendererTexture> tex);
35+
36+
bool operator==(const CAssetCacheEntry& e) const {
37+
return m_tex == e.m_tex;
38+
}
39+
40+
struct {
41+
Hyprutils::Signal::CSignalT<> done;
42+
} m_events;
43+
44+
private:
45+
const std::string m_source;
46+
SP<IRendererTexture> m_tex;
47+
eAssetCacheEntryStatus m_status = CACHE_ENTRY_PENDING;
48+
};
49+
};

0 commit comments

Comments
 (0)