summaryrefslogtreecommitdiffstats
path: root/src/ui/widget/canvas/texturecache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/widget/canvas/texturecache.cpp')
-rw-r--r--src/ui/widget/canvas/texturecache.cpp115
1 files changed, 115 insertions, 0 deletions
diff --git a/src/ui/widget/canvas/texturecache.cpp b/src/ui/widget/canvas/texturecache.cpp
new file mode 100644
index 0000000..6215849
--- /dev/null
+++ b/src/ui/widget/canvas/texturecache.cpp
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <unordered_map>
+#include <vector>
+#include <cassert>
+#include <boost/unordered_map.hpp> // For hash of pair
+#include "helper/mathfns.h"
+#include "texturecache.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+namespace {
+
+class BasicTextureCache : public TextureCache
+{
+ static int constexpr min_dimension = 16;
+ static int constexpr expiration_timeout = 10000;
+
+ static int constexpr dim_to_ind(int dim) { return Util::floorlog2((dim - 1) / min_dimension) + 1; }
+ static int constexpr ind_to_maxdim(int index) { return min_dimension * (1 << index); }
+
+ static std::pair<int, int> dims_to_inds(Geom::IntPoint const &dims) { return { dim_to_ind(dims.x()), dim_to_ind(dims.y()) }; }
+ static Geom::IntPoint inds_to_maxdims(std::pair<int, int> const &inds) { return { ind_to_maxdim(inds.first), ind_to_maxdim(inds.second) }; }
+
+ // A cache of POT textures.
+ struct Bucket
+ {
+ std::vector<Texture> unused;
+ int used = 0;
+ int high_use_count = 0;
+ };
+ boost::unordered_map<std::pair<int, int>, Bucket> buckets;
+
+ // Used to periodicially discard excess cached textures.
+ int expiration_timer = 0;
+
+public:
+ Texture request(Geom::IntPoint const &dimensions) override
+ {
+ // Find the bucket that the dimensions fall into.
+ auto indexes = dims_to_inds(dimensions);
+ auto &b = buckets[indexes];
+
+ // Reuse or create a texture of the appropriate dimensions.
+ Texture tex;
+ if (!b.unused.empty()) {
+ tex = std::move(b.unused.back());
+ b.unused.pop_back();
+ glBindTexture(GL_TEXTURE_2D, tex.id());
+ } else {
+ tex = Texture(inds_to_maxdims(indexes)); // binds
+ }
+
+ // Record the new use count of the bucket.
+ b.used++;
+ if (b.used > b.high_use_count) {
+ // If the use count has gone above the high-water mark, record this, and reset the timer for when to clean up excess unused textures.
+ b.high_use_count = b.used;
+ expiration_timer = 0;
+ }
+
+ return tex;
+ }
+
+ void finish(Texture tex) override
+ {
+ auto indexes = dims_to_inds(tex.size());
+ auto &b = buckets[indexes];
+
+ // Orphan the texture, if possible.
+ tex.invalidate();
+
+ // Put the texture back in its corresponding bucket's cache of unused textures.
+ b.unused.emplace_back(std::move(tex));
+ b.used--;
+
+ // If the expiration timeout has been reached, prune the cache of textures down to what was actually used in the last cycle.
+ expiration_timer++;
+ if (expiration_timer >= expiration_timeout) {
+ expiration_timer = 0;
+
+ for (auto &[k, b] : buckets) {
+ int max_unused = b.high_use_count - b.used;
+ assert(max_unused >= 0);
+ if (b.unused.size() > max_unused) {
+ b.unused.resize(max_unused);
+ }
+ b.high_use_count = b.used;
+ }
+ }
+ }
+};
+
+} // namespace
+
+std::unique_ptr<TextureCache> TextureCache::create()
+{
+ return std::make_unique<BasicTextureCache>();
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :