QImagePool.hpp
1 #pragma once
2 #include <score/tools/Debug.hpp>
3 
4 #include <ossia/detail/hash.hpp>
5 #include <ossia/detail/hash_map.hpp>
6 #include <ossia/detail/mutex.hpp>
7 
8 #include <QDebug>
9 #include <QImage>
10 #include <QVector>
11 
12 #include <chrono>
13 #include <mutex>
14 
15 namespace std
16 {
17 template <>
18 class hash<std::pair<int, int>>
19 {
20 public:
21  std::size_t operator()(const std::pair<int, int>& p) const
22  {
23  std::size_t seed = 0;
24  ossia::hash_combine(seed, p.first);
25  ossia::hash_combine(seed, p.second);
26  return seed;
27  }
28 };
29 }
30 
31 namespace Media::Sound
32 {
33 struct QImagePool
34 {
35  struct Images
36  {
37  QVector<QImage*> images;
38  std::chrono::steady_clock::time_point last_touched;
39  };
40 
41  static const constexpr int max_count = 100;
42  using pool_t = ossia::hash_map<std::pair<int, int>, Images>;
43  pool_t pool TS_GUARDED_BY(m_mtx);
44  std::mutex m_mtx;
45 
46  static QImagePool& instance() noexcept
47  {
48  static QImagePool pool;
49  return pool;
50  }
51 
52  ~QImagePool()
53  {
54  for(auto& pair : pool)
55  {
56  for(QImage* img : pair.second.images)
57  {
58  delete img;
59  }
60  }
61  }
62 
63  static inline int hit = 0;
64  static inline int miss = 0;
65  QImage* request(int w, int h)
66  {
67  std::lock_guard _{m_mtx};
68  // cacheStats();
69  auto it = pool.find({w, h});
70  if(it != pool.end())
71  {
72  auto& vec = it->second.images;
73  if(!vec.empty())
74  {
75  auto img = vec.front();
76  vec.pop_front();
77  img->fill(Qt::transparent);
78  it->second.last_touched = std::chrono::steady_clock::now();
79  hit++;
80  return img;
81  }
82  }
83 
84  auto img = new QImage(w, h, QImage::Format_ARGB32_Premultiplied);
85  img->fill(Qt::transparent);
86  miss++;
87  return img;
88  }
89 
90  void giveBack(const QVector<QImage*>& imgs)
91  {
92  for(auto img : imgs)
93  {
94  QVector<QImage*> to_delete;
95 
96  {
97  std::lock_guard _{m_mtx};
98  to_delete = gc();
99  Images& images = pool[std::make_pair(img->width(), img->height())];
100  SCORE_ASSERT(!images.images.contains(img));
101  images.images.push_back(img);
102  images.last_touched = std::chrono::steady_clock::now();
103  }
104 
105  for(auto img : to_delete)
106  delete img;
107  }
108  }
109 
110  QVector<QImage*> gc()
111  {
112  if(pool.empty())
113  return {};
114 
115  int count = 0;
116  auto oldest = pool.begin();
117  auto oldest_t = oldest->second.last_touched;
118  for(auto it = oldest; it != pool.end(); ++it)
119  {
120  if(it->second.last_touched < oldest->second.last_touched)
121  {
122  oldest = it;
123  oldest_t = it->second.last_touched;
124  }
125  count += it->second.images.size();
126  }
127 
128  if(count < max_count)
129  return {};
130  auto res = std::move(oldest->second.images);
131  SCORE_ASSERT(oldest->second.images.isEmpty());
132  oldest->second.last_touched = std::chrono::steady_clock::now();
133  return res;
134  }
135 
136  void cacheStats()
137  {
138  std::size_t bytes = 0;
139  int images = 0;
140  for(auto& pair : pool)
141  {
142  for(QImage* img : pair.second.images)
143  {
144  bytes += img->sizeInBytes();
145  images++;
146  }
147  }
148 
149  qDebug() << QString("%1 images: %2 megabytes ; hit/miss ratio : %3 / %4 = %5")
150  .arg(images)
151  .arg(bytes / (1024 * 1024))
152  .arg(hit)
153  .arg(miss)
154  .arg(double(hit) / miss);
155  }
156 };
157 }
Definition: QImagePool.hpp:36
Definition: QImagePool.hpp:34