a788eb9c6ff985c2d9ef02cfa26d2fa7d113ca72
[ardour.git] / libs / canvas / wave_view_private.cc
1 /*
2     Copyright (C) 2017 Tim Mayberry
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <cmath>
21 #include "ardour/lmath.h"
22
23 #include "canvas/wave_view_private.h"
24
25 #include "pbd/cpus.h"
26
27 #include "ardour/audioregion.h"
28 #include "ardour/audiosource.h"
29
30 namespace ArdourCanvas {
31
32 WaveViewProperties::WaveViewProperties (boost::shared_ptr<ARDOUR::AudioRegion> region)
33     : region_start (region->start ())
34     , region_end (region->start () + region->length ())
35     , channel (0)
36     , height (64)
37     , samples_per_pixel (0)
38     , amplitude (region->scale_amplitude ())
39     , amplitude_above_axis (1.0)
40     , fill_color (0x000000ff)
41     , outline_color (0xff0000ff)
42     , zero_color (0xff0000ff)
43     , clip_color (0xff0000ff)
44     , show_zero (false)
45     , logscaled (WaveView::global_logscaled())
46     , shape (WaveView::global_shape())
47     , gradient_depth (WaveView::global_gradient_depth ())
48     , start_shift (0.0) // currently unused
49     , sample_start (0)
50     , sample_end (0)
51 {
52
53 }
54
55 /*-------------------------------------------------*/
56
57 WaveViewImage::WaveViewImage (boost::shared_ptr<const ARDOUR::AudioRegion> const& region_ptr,
58                               WaveViewProperties const& properties)
59         : region (region_ptr)
60         , props (properties)
61         , timestamp (0)
62 {
63
64 }
65
66 WaveViewImage::~WaveViewImage ()
67 {
68
69 }
70
71 /*-------------------------------------------------*/
72
73 WaveViewCacheGroup::WaveViewCacheGroup (WaveViewCache& parent_cache)
74         : _parent_cache (parent_cache)
75 {
76
77 }
78
79 WaveViewCacheGroup::~WaveViewCacheGroup ()
80 {
81         clear_cache ();
82 }
83
84 void
85 WaveViewCacheGroup::add_image (boost::shared_ptr<WaveViewImage> image)
86 {
87         if (!image) {
88                 // Not adding invalid image to cache
89                 return;
90         }
91
92         ImageCache::iterator oldest_image_it = _cached_images.begin();
93         ImageCache::iterator second_oldest_image_it = _cached_images.end();
94
95         for (ImageCache::iterator it = _cached_images.begin (); it != _cached_images.end (); ++it) {
96                 if ((*it) == image) {
97                         // Must never be more than one instance of the image in the cache
98                         (*it)->timestamp = g_get_monotonic_time ();
99                         return;
100                 } else if ((*it)->props.is_equivalent (image->props)) {
101                         // Equivalent Image already in cache, updating timestamp
102                         (*it)->timestamp = g_get_monotonic_time ();
103                         return;
104                 }
105
106                 if ((*it)->timestamp < (*oldest_image_it)->timestamp) {
107                         second_oldest_image_it = oldest_image_it;
108                         oldest_image_it = it;
109                 }
110         }
111
112         // no duplicate or equivalent image so we are definitely adding it to cache
113         image->timestamp = g_get_monotonic_time ();
114
115         if (_parent_cache.full () || full ()) {
116                 if (oldest_image_it != _cached_images.end()) {
117                         // Replacing oldest Image in cache
118                         _parent_cache.decrease_size ((*oldest_image_it)->size_in_bytes ());
119                         *oldest_image_it = image;
120                         _parent_cache.increase_size (image->size_in_bytes ());
121
122                         if (second_oldest_image_it != _cached_images.end ()) {
123                                 // Removing second oldest Image in cache
124                                 _parent_cache.decrease_size ((*second_oldest_image_it)->size_in_bytes ());
125                                 _cached_images.erase (second_oldest_image_it);
126                         }
127                         return;
128                 } else {
129                         /**
130                          * Add the image to the cache even if the threshold is exceeded so that
131                          * new WaveViews can still cache images with a full cache, the size of
132                          * the cache will quickly equalize back to the threshold as new images
133                          * are added and the size of the cache is reduced.
134                          */
135                 }
136         }
137
138         _cached_images.push_back (image);
139         _parent_cache.increase_size (image->size_in_bytes ());
140 }
141
142 boost::shared_ptr<WaveViewImage>
143 WaveViewCacheGroup::lookup_image (WaveViewProperties const& props)
144 {
145         for (ImageCache::iterator i = _cached_images.begin (); i != _cached_images.end (); ++i) {
146                 if ((*i)->props.is_equivalent (props)) {
147                         return (*i);
148                 }
149         }
150         return boost::shared_ptr<WaveViewImage>();
151 }
152
153 void
154 WaveViewCacheGroup::clear_cache ()
155 {
156         // Tell the parent cache about the images we are about to drop references to
157         for (ImageCache::iterator it = _cached_images.begin (); it != _cached_images.end (); ++it) {
158                 _parent_cache.decrease_size ((*it)->size_in_bytes ());
159         }
160         _cached_images.clear ();
161 }
162
163 /*-------------------------------------------------*/
164
165 WaveViewCache::WaveViewCache ()
166         : image_cache_size (0)
167         , _image_cache_threshold (100 * 1048576) /* bytes */
168 {
169
170 }
171
172 WaveViewCache::~WaveViewCache ()
173 {
174 }
175
176 WaveViewCache*
177 WaveViewCache::get_instance ()
178 {
179         static WaveViewCache* instance = new WaveViewCache;
180         return instance;
181 }
182
183 void
184 WaveViewCache::increase_size (uint64_t bytes)
185 {
186         image_cache_size += bytes;
187 }
188
189 void
190 WaveViewCache::decrease_size (uint64_t bytes)
191 {
192         assert (image_cache_size - bytes < image_cache_size);
193         image_cache_size -= bytes;
194 }
195
196 boost::shared_ptr<WaveViewCacheGroup>
197 WaveViewCache::get_cache_group (boost::shared_ptr<ARDOUR::AudioSource> source)
198 {
199         CacheGroups::iterator it = cache_group_map.find (source);
200
201         if (it != cache_group_map.end()) {
202                 // Found existing CacheGroup for AudioSource
203                 return it->second;
204         }
205
206         boost::shared_ptr<WaveViewCacheGroup> new_group (new WaveViewCacheGroup (*this));
207
208         bool inserted = cache_group_map.insert (std::make_pair (source, new_group)).second;
209
210         assert (inserted);
211
212         return new_group;
213 }
214
215 void
216 WaveViewCache::reset_cache_group (boost::shared_ptr<WaveViewCacheGroup>& group)
217 {
218         if (!group) {
219                 return;
220         }
221
222         CacheGroups::iterator it = cache_group_map.begin();
223
224         while (it != cache_group_map.end()) {
225                 if (it->second == group) {
226                         break;
227                 }
228                 ++it;
229         }
230
231         assert (it != cache_group_map.end ());
232
233         group.reset();
234
235         if (it->second.unique()) {
236                 cache_group_map.erase (it);
237         }
238 }
239
240 void
241 WaveViewCache::clear_cache ()
242 {
243         for (CacheGroups::iterator it = cache_group_map.begin (); it != cache_group_map.end (); ++it) {
244                 (*it).second->clear_cache ();
245         }
246 }
247
248 void
249 WaveViewCache::set_image_cache_threshold (uint64_t sz)
250 {
251         _image_cache_threshold = sz;
252 }
253
254 /*-------------------------------------------------*/
255
256 WaveViewDrawRequest::WaveViewDrawRequest () : stop (0)
257 {
258
259 }
260
261 WaveViewDrawRequest::~WaveViewDrawRequest ()
262 {
263
264 }
265
266 void
267 WaveViewDrawRequestQueue::enqueue (boost::shared_ptr<WaveViewDrawRequest>& request)
268 {
269         Glib::Threads::Mutex::Lock lm (_queue_mutex);
270
271         _queue.push_back (request);
272         _cond.broadcast ();
273 }
274
275 void
276 WaveViewDrawRequestQueue::wake_up ()
277 {
278         boost::shared_ptr<WaveViewDrawRequest> null_ptr;
279         // hack!?...wake up the drawing thread
280         enqueue (null_ptr);
281 }
282
283 boost::shared_ptr<WaveViewDrawRequest>
284 WaveViewDrawRequestQueue::dequeue (bool block)
285 {
286         if (block) {
287                 _queue_mutex.lock();
288         } else {
289                 if (!_queue_mutex.trylock()) {
290                         return boost::shared_ptr<WaveViewDrawRequest>();
291                 }
292         }
293
294         // _queue_mutex is always held at this point
295
296         if (_queue.empty()) {
297                 if (block) {
298                         _cond.wait (_queue_mutex);
299                 } else {
300                         _queue_mutex.unlock();
301                         return boost::shared_ptr<WaveViewDrawRequest>();
302                 }
303         }
304
305         boost::shared_ptr<WaveViewDrawRequest> req;
306
307         if (!_queue.empty()) {
308                 req = _queue.front ();
309                 _queue.pop_front ();
310         } else {
311                 // Queue empty, returning empty DrawRequest
312         }
313
314         _queue_mutex.unlock();
315
316         return req;
317 }
318
319 /*-------------------------------------------------*/
320
321 WaveViewThreads::WaveViewThreads ()
322 {
323
324 }
325
326 WaveViewThreads::~WaveViewThreads ()
327 {
328
329 }
330
331 uint32_t WaveViewThreads::init_count = 0;
332
333 WaveViewThreads* WaveViewThreads::instance = 0;
334
335 void
336 WaveViewThreads::initialize ()
337 {
338         // no need for atomics as only called from GUI thread
339         if (++init_count == 1) {
340                 assert(!instance);
341                 instance = new WaveViewThreads;
342                 instance->start_threads();
343         }
344 }
345
346 void
347 WaveViewThreads::deinitialize ()
348 {
349         if (--init_count == 0) {
350                 instance->stop_threads();
351                 delete instance;
352                 instance = 0;
353         }
354 }
355
356 void
357 WaveViewThreads::enqueue_draw_request (boost::shared_ptr<WaveViewDrawRequest>& request)
358 {
359         assert (instance);
360         instance->_request_queue.enqueue (request);
361 }
362
363 boost::shared_ptr<WaveViewDrawRequest>
364 WaveViewThreads::dequeue_draw_request ()
365 {
366         assert (instance);
367         return instance->_request_queue.dequeue (true);
368 }
369
370 void
371 WaveViewThreads::wake_up ()
372 {
373         assert (instance);
374         return instance->_request_queue.wake_up ();
375 }
376
377 void
378 WaveViewThreads::start_threads ()
379 {
380         assert (!_threads.size());
381
382         int num_cpus = hardware_concurrency ();
383
384         uint32_t num_threads = std::max (1, num_cpus - 1);
385
386         for (uint32_t i = 0; i != num_threads; ++i) {
387                 boost::shared_ptr<WaveViewDrawingThread> new_thread (new WaveViewDrawingThread ());
388                 _threads.push_back(new_thread);
389         }
390 }
391
392 void
393 WaveViewThreads::stop_threads ()
394 {
395         assert (_threads.size());
396
397         _threads.clear ();
398 }
399
400 /*-------------------------------------------------*/
401
402 WaveViewDrawingThread::WaveViewDrawingThread ()
403                 : _thread(0)
404                 , _quit(0)
405 {
406         start ();
407 }
408
409 WaveViewDrawingThread::~WaveViewDrawingThread ()
410 {
411         quit ();
412 }
413
414 void
415 WaveViewDrawingThread::start ()
416 {
417         assert (!_thread);
418
419         _thread = Glib::Threads::Thread::create (sigc::mem_fun (*this, &WaveViewDrawingThread::run));
420 }
421
422 void
423 WaveViewDrawingThread::quit ()
424 {
425         assert (_thread);
426
427         g_atomic_int_set (&_quit, 1);
428         WaveViewThreads::wake_up ();
429         _thread->join();
430         _thread = 0;
431 }
432
433 void
434 WaveViewDrawingThread::run ()
435 {
436         while (true) {
437
438                 if (g_atomic_int_get (&_quit)) {
439                         break;
440                 }
441
442                 // block until a request is available.
443                 boost::shared_ptr<WaveViewDrawRequest> req = WaveViewThreads::dequeue_draw_request ();
444
445                 if (req && !req->stopped()) {
446                         try {
447                                 WaveView::process_draw_request (req);
448                         } catch (...) {
449                                 /* just in case it was set before the exception, whatever it was */
450                                 req->image->cairo_image.clear ();
451                         }
452                 } else {
453                         // null or stopped Request, processing skipped
454                 }
455         }
456 }
457
458 } // namespace ArdourCanvas