+
+void
+WaveView::cancel_my_render_request () const
+{
+ if (!images) {
+ return;
+ }
+
+ /* try to stop any current rendering of the request, or prevent it from
+ * ever starting up.
+ */
+
+ if (current_request) {
+ current_request->cancel ();
+ }
+
+ Glib::Threads::Mutex::Lock lm (request_queue_lock);
+
+ /* now remove it from the queue and reset our request pointer so that
+ have no outstanding request (that we know about)
+ */
+
+ request_queue.erase (this);
+ current_request.reset ();
+}
+
+void
+WaveView::send_request (boost::shared_ptr<WaveViewThreadRequest> req) const
+{
+ if (req->type == WaveViewThreadRequest::Draw && current_request) {
+ /* this will stop rendering in progress (which might otherwise
+ be long lived) for any current request.
+ */
+ current_request->cancel ();
+ }
+
+ start_drawing_thread ();
+
+ Glib::signal_idle().connect (sigc::bind (sigc::mem_fun (this, &WaveView::idle_send_request), req));
+}
+
+bool
+WaveView::idle_send_request (boost::shared_ptr<WaveViewThreadRequest> req) const
+{
+ {
+ Glib::Threads::Mutex::Lock lm (request_queue_lock);
+ /* swap requests (protected by lock) */
+ current_request = req;
+ request_queue.insert (this);
+ }
+
+ request_cond.signal (); /* wake thread */
+
+ return false; /* do not call from idle again */
+}
+
+/*-------------------------------------------------*/
+
+void
+WaveView::start_drawing_thread ()
+{
+ if (!_drawing_thread) {
+ _drawing_thread = Glib::Threads::Thread::create (sigc::ptr_fun (WaveView::drawing_thread));
+ }
+}
+
+void
+WaveView::stop_drawing_thread ()
+{
+ if (_drawing_thread) {
+ Glib::Threads::Mutex::Lock lm (request_queue_lock);
+ g_atomic_int_set (&drawing_thread_should_quit, 1);
+ request_cond.signal ();
+ }
+}
+
+void
+WaveView::drawing_thread ()
+{
+ using namespace Glib::Threads;
+
+ WaveView const * requestor;
+ Mutex::Lock lm (request_queue_lock);
+ bool run = true;
+
+ while (run) {
+
+ /* remember that we hold the lock at this point, no matter what */
+
+ if (g_atomic_int_get (&drawing_thread_should_quit)) {
+ break;
+ }
+
+ if (request_queue.empty()) {
+ request_cond.wait (request_queue_lock);
+ }
+
+ /* remove the request from the queue (remember: the "request"
+ * is just a pointer to a WaveView object)
+ */
+
+ requestor = *(request_queue.begin());
+ request_queue.erase (request_queue.begin());
+
+ boost::shared_ptr<WaveViewThreadRequest> req = requestor->current_request;
+
+ if (!req) {
+ cerr << requestor->debug_name() << " no current request\n";
+ continue;
+ }
+
+ /* Generate an image. Unlock the request queue lock
+ * while we do this, so that other things can happen
+ * as we do rendering.
+ */
+
+ request_queue_lock.unlock (); /* some RAII would be good here */
+
+ try {
+ requestor->generate_image (req, true);
+ } catch (...) {
+ req->image.clear(); /* just in case it was set before the exception, whatever it was */
+ }
+
+ request_queue_lock.lock ();
+
+ req.reset (); /* drop/delete request as appropriate */
+ }
+
+ /* thread is vanishing */
+ _drawing_thread = 0;
+}
+
+/*-------------------------------------------------*/
+
+WaveViewCache::WaveViewCache ()
+ : image_cache_size (0)
+ , _image_cache_threshold (100 * 1048576) /* bytes */
+{
+}
+
+WaveViewCache::~WaveViewCache ()
+{
+}
+
+
+boost::shared_ptr<WaveViewCache::Entry>
+WaveViewCache::lookup_image (boost::shared_ptr<ARDOUR::AudioSource> src,
+ framepos_t start, framepos_t end,
+ int channel,
+ Coord height,
+ float amplitude,
+ Color fill_color,
+ double samples_per_pixel)
+{
+ ImageCache::iterator x;
+
+ if ((x = cache_map.find (src)) == cache_map.end ()) {
+ /* nothing in the cache for this audio source at all */
+ return boost::shared_ptr<WaveViewCache::Entry> ();
+ }
+
+ CacheLine& caches = x->second;
+
+ /* Find a suitable ImageSurface, if it exists.
+ */
+
+ for (CacheLine::iterator c = caches.begin(); c != caches.end(); ++c) {
+
+ boost::shared_ptr<Entry> e (*c);
+
+ if (channel != e->channel
+ || height != e->height
+ || amplitude != e->amplitude
+ || samples_per_pixel != e->samples_per_pixel
+ || fill_color != e->fill_color) {
+ continue;
+ }
+
+ if (end <= e->end && start >= e->start) {
+ /* found an image that covers the range we need */
+ use (src, e);
+ return e;
+ }
+ }
+
+ return boost::shared_ptr<Entry> ();
+}
+
+void
+WaveViewCache::consolidate_image_cache (boost::shared_ptr<ARDOUR::AudioSource> src,
+ int channel,
+ Coord height,
+ float amplitude,
+ Color fill_color,
+ double samples_per_pixel)
+{
+ list <uint32_t> deletion_list;
+ uint32_t other_entries = 0;
+ ImageCache::iterator x;
+
+ /* MUST BE CALLED FROM (SINGLE) GUI THREAD */
+
+ if ((x = cache_map.find (src)) == cache_map.end ()) {
+ return;
+ }
+
+ CacheLine& caches = x->second;
+
+ for (CacheLine::iterator c1 = caches.begin(); c1 != caches.end(); ) {
+
+ CacheLine::iterator nxt = c1;
+ ++nxt;
+
+ boost::shared_ptr<Entry> e1 (*c1);
+
+ if (channel != e1->channel
+ || height != e1->height
+ || amplitude != e1->amplitude
+ || samples_per_pixel != e1->samples_per_pixel
+ || fill_color != e1->fill_color) {
+
+ /* doesn't match current properties, ignore and move on
+ * to the next one.
+ */
+
+ other_entries++;
+ c1 = nxt;
+ continue;
+ }
+
+ /* c1 now points to a cached image entry that matches current
+ * properties. Check all subsequent cached imaged entries to
+ * see if there are others that also match but represent
+ * subsets of the range covered by this one.
+ */
+
+ for (CacheLine::iterator c2 = c1; c2 != caches.end(); ) {
+
+ CacheLine::iterator nxt2 = c2;
+ ++nxt2;
+
+ boost::shared_ptr<Entry> e2 (*c2);
+
+ if (e1 == e2 || channel != e2->channel
+ || height != e2->height
+ || amplitude != e2->amplitude
+ || samples_per_pixel != e2->samples_per_pixel
+ || fill_color != e2->fill_color) {
+
+ /* properties do not match, ignore for the
+ * purposes of consolidation.
+ */
+ c2 = nxt2;
+ continue;
+ }
+
+ if (e2->start >= e1->start && e2->end <= e1->end) {
+ /* c2 is fully contained by c1, so delete it */
+ c2 = caches.erase (c2);
+ continue;
+ }
+
+ c2 = nxt2;
+ }
+
+ c1 = nxt;
+ }
+}
+
+void
+WaveViewCache::use (boost::shared_ptr<ARDOUR::AudioSource> src, boost::shared_ptr<Entry> ce)
+{
+ ce->timestamp = g_get_monotonic_time ();
+}
+
+void
+WaveViewCache::add (boost::shared_ptr<ARDOUR::AudioSource> src, boost::shared_ptr<Entry> ce)
+{
+ /* MUST BE CALLED FROM (SINGLE) GUI THREAD */
+
+ Cairo::RefPtr<Cairo::ImageSurface> img (ce->image);
+
+ image_cache_size += img->get_height() * img->get_width () * 4; /* 4 = bytes per FORMAT_ARGB32 pixel */
+
+ if (cache_full()) {
+ cache_flush ();
+ }
+
+ ce->timestamp = g_get_monotonic_time ();
+
+ cache_map[src].push_back (ce);
+ cache_list.push_back (make_pair (src, ce));
+}
+
+uint64_t
+WaveViewCache::compute_image_cache_size()
+{
+ uint64_t total = 0;
+ for (ImageCache::iterator s = cache_map.begin(); s != cache_map.end(); ++s) {
+ CacheLine& per_source_cache (s->second);
+ for (CacheLine::iterator c = per_source_cache.begin(); c != per_source_cache.end(); ++c) {
+ Cairo::RefPtr<Cairo::ImageSurface> img ((*c)->image);
+ total += img->get_height() * img->get_width() * 4; /* 4 = bytes per FORMAT_ARGB32 pixel */
+ }
+ }
+ return total;
+}
+
+bool
+WaveViewCache::cache_full()
+{
+ return image_cache_size > _image_cache_threshold;
+}
+
+void
+WaveViewCache::cache_flush ()
+{
+ SortByTimestamp sorter;
+
+ /* sort list in LRU order */
+
+ sort (cache_list.begin(), cache_list.end(), sorter);
+
+ while (image_cache_size > _image_cache_threshold) {
+
+ ListEntry& le (cache_list.front());
+
+ ImageCache::iterator x;
+
+ if ((x = cache_map.find (le.first)) == cache_map.end ()) {
+ /* wierd ... no entry for this AudioSource */
+ continue;
+ }
+
+ CacheLine& cl = x->second;
+
+ for (CacheLine::iterator c = cl.begin(); c != cl.end(); ++c) {
+
+ if (*c == le.second) {
+
+ /* Remove this entry from this cache line */
+ cl.erase (c);
+
+ if (cl.empty()) {
+ /* remove cache line from main cache: no more entries */
+ cache_map.erase (x);
+ }
+
+ break;
+ }
+ }
+
+ Cairo::RefPtr<Cairo::ImageSurface> img (le.second->image);
+ uint64_t size = img->get_height() * img->get_width() * 4; /* 4 = bytes per FORMAT_ARGB32 pixel */
+
+ if (image_cache_size > size) {
+ image_cache_size -= size;
+ } else {
+ image_cache_size = 0;
+ }
+
+ /* Remove from the linear list */
+ cache_list.erase (cache_list.begin());
+ }
+}
+
+void
+WaveViewCache::set_image_cache_threshold (uint64_t sz)
+{
+ _image_cache_threshold = sz;
+ cache_flush ();
+}