2 Copyright (C) 2017 Tim Mayberry
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.
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.
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.
21 #include "ardour/lmath.h"
23 #include "canvas/wave_view_private.h"
27 #include "ardour/audioregion.h"
28 #include "ardour/audiosource.h"
30 namespace ArdourCanvas {
32 WaveViewProperties::WaveViewProperties (boost::shared_ptr<ARDOUR::AudioRegion> region)
33 : region_start (region->start ())
34 , region_end (region->start () + region->length ())
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)
45 , logscaled (WaveView::global_logscaled())
46 , shape (WaveView::global_shape())
47 , gradient_depth (WaveView::global_gradient_depth ())
48 , start_shift (0.0) // currently unused
55 /*-------------------------------------------------*/
57 WaveViewImage::WaveViewImage (boost::shared_ptr<const ARDOUR::AudioRegion> const& region_ptr,
58 WaveViewProperties const& properties)
66 WaveViewImage::~WaveViewImage ()
71 /*-------------------------------------------------*/
73 WaveViewCacheGroup::WaveViewCacheGroup (WaveViewCache& parent_cache)
74 : _parent_cache (parent_cache)
79 WaveViewCacheGroup::~WaveViewCacheGroup ()
85 WaveViewCacheGroup::add_image (boost::shared_ptr<WaveViewImage> image)
88 // Not adding invalid image to cache
92 ImageCache::iterator oldest_image_it = _cached_images.begin();
93 ImageCache::iterator second_oldest_image_it = _cached_images.end();
95 for (ImageCache::iterator it = _cached_images.begin (); it != _cached_images.end (); ++it) {
97 // Must never be more than one instance of the image in the cache
98 (*it)->timestamp = g_get_monotonic_time ();
100 } else if ((*it)->props.is_equivalent (image->props)) {
101 // Equivalent Image already in cache, updating timestamp
102 (*it)->timestamp = g_get_monotonic_time ();
106 if ((*it)->timestamp < (*oldest_image_it)->timestamp) {
107 second_oldest_image_it = oldest_image_it;
108 oldest_image_it = it;
112 // no duplicate or equivalent image so we are definitely adding it to cache
113 image->timestamp = g_get_monotonic_time ();
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 ());
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);
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.
138 _cached_images.push_back (image);
139 _parent_cache.increase_size (image->size_in_bytes ());
142 boost::shared_ptr<WaveViewImage>
143 WaveViewCacheGroup::lookup_image (WaveViewProperties const& props)
145 for (ImageCache::iterator i = _cached_images.begin (); i != _cached_images.end (); ++i) {
146 if ((*i)->props.is_equivalent (props)) {
150 return boost::shared_ptr<WaveViewImage>();
154 WaveViewCacheGroup::clear_cache ()
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 ());
160 _cached_images.clear ();
163 /*-------------------------------------------------*/
165 WaveViewCache::WaveViewCache ()
166 : image_cache_size (0)
167 , _image_cache_threshold (100 * 1048576) /* bytes */
172 WaveViewCache::~WaveViewCache ()
177 WaveViewCache::get_instance ()
179 static WaveViewCache* instance = new WaveViewCache;
184 WaveViewCache::increase_size (uint64_t bytes)
186 image_cache_size += bytes;
190 WaveViewCache::decrease_size (uint64_t bytes)
192 assert (image_cache_size - bytes < image_cache_size);
193 image_cache_size -= bytes;
196 boost::shared_ptr<WaveViewCacheGroup>
197 WaveViewCache::get_cache_group (boost::shared_ptr<ARDOUR::AudioSource> source)
199 CacheGroups::iterator it = cache_group_map.find (source);
201 if (it != cache_group_map.end()) {
202 // Found existing CacheGroup for AudioSource
206 boost::shared_ptr<WaveViewCacheGroup> new_group (new WaveViewCacheGroup (*this));
208 bool inserted = cache_group_map.insert (std::make_pair (source, new_group)).second;
216 WaveViewCache::reset_cache_group (boost::shared_ptr<WaveViewCacheGroup>& group)
222 CacheGroups::iterator it = cache_group_map.begin();
224 while (it != cache_group_map.end()) {
225 if (it->second == group) {
231 assert (it != cache_group_map.end ());
235 if (it->second.unique()) {
236 cache_group_map.erase (it);
241 WaveViewCache::clear_cache ()
243 for (CacheGroups::iterator it = cache_group_map.begin (); it != cache_group_map.end (); ++it) {
244 (*it).second->clear_cache ();
249 WaveViewCache::set_image_cache_threshold (uint64_t sz)
251 _image_cache_threshold = sz;
254 /*-------------------------------------------------*/
256 WaveViewDrawRequest::WaveViewDrawRequest () : stop (0)
261 WaveViewDrawRequest::~WaveViewDrawRequest ()
267 WaveViewDrawRequestQueue::enqueue (boost::shared_ptr<WaveViewDrawRequest>& request)
269 Glib::Threads::Mutex::Lock lm (_queue_mutex);
271 _queue.push_back (request);
276 WaveViewDrawRequestQueue::wake_up ()
278 boost::shared_ptr<WaveViewDrawRequest> null_ptr;
279 // hack!?...wake up the drawing thread
283 boost::shared_ptr<WaveViewDrawRequest>
284 WaveViewDrawRequestQueue::dequeue (bool block)
289 if (!_queue_mutex.trylock()) {
290 return boost::shared_ptr<WaveViewDrawRequest>();
294 // _queue_mutex is always held at this point
296 if (_queue.empty()) {
298 _cond.wait (_queue_mutex);
300 _queue_mutex.unlock();
301 return boost::shared_ptr<WaveViewDrawRequest>();
305 boost::shared_ptr<WaveViewDrawRequest> req;
307 if (!_queue.empty()) {
308 req = _queue.front ();
311 // Queue empty, returning empty DrawRequest
314 _queue_mutex.unlock();
319 /*-------------------------------------------------*/
321 WaveViewThreads::WaveViewThreads ()
326 WaveViewThreads::~WaveViewThreads ()
331 uint32_t WaveViewThreads::init_count = 0;
333 WaveViewThreads* WaveViewThreads::instance = 0;
336 WaveViewThreads::initialize ()
338 // no need for atomics as only called from GUI thread
339 if (++init_count == 1) {
341 instance = new WaveViewThreads;
342 instance->start_threads();
347 WaveViewThreads::deinitialize ()
349 if (--init_count == 0) {
350 instance->stop_threads();
357 WaveViewThreads::enqueue_draw_request (boost::shared_ptr<WaveViewDrawRequest>& request)
360 instance->_request_queue.enqueue (request);
363 boost::shared_ptr<WaveViewDrawRequest>
364 WaveViewThreads::dequeue_draw_request ()
367 return instance->_request_queue.dequeue (true);
371 WaveViewThreads::wake_up ()
374 return instance->_request_queue.wake_up ();
378 WaveViewThreads::start_threads ()
380 assert (!_threads.size());
382 int num_cpus = hardware_concurrency ();
384 uint32_t num_threads = std::max (1, num_cpus - 1);
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);
393 WaveViewThreads::stop_threads ()
395 assert (_threads.size());
400 /*-------------------------------------------------*/
402 WaveViewDrawingThread::WaveViewDrawingThread ()
409 WaveViewDrawingThread::~WaveViewDrawingThread ()
415 WaveViewDrawingThread::start ()
419 _thread = Glib::Threads::Thread::create (sigc::mem_fun (*this, &WaveViewDrawingThread::run));
423 WaveViewDrawingThread::quit ()
427 g_atomic_int_set (&_quit, 1);
428 WaveViewThreads::wake_up ();
434 WaveViewDrawingThread::run ()
438 if (g_atomic_int_get (&_quit)) {
442 // block until a request is available.
443 boost::shared_ptr<WaveViewDrawRequest> req = WaveViewThreads::dequeue_draw_request ();
445 if (req && !req->stopped()) {
447 WaveView::process_draw_request (req);
449 /* just in case it was set before the exception, whatever it was */
450 req->image->cairo_image.clear ();
453 // null or stopped Request, processing skipped
458 } // namespace ArdourCanvas