diff options
Diffstat (limited to 'src/ui/widget/canvas/texturecache.cpp')
-rw-r--r-- | src/ui/widget/canvas/texturecache.cpp | 115 |
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 : |