+void
+WaveView::set_start_shift (double pixels)
+{
+ if (pixels < 0) {
+ return;
+ }
+
+ begin_visual_change ();
+ _start_shift = pixels;
+ end_visual_change ();
+}
+
+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.
+ */
+
+ Glib::Threads::Mutex::Lock lm (request_queue_lock);
+
+ if (current_request) {
+ current_request->cancel ();
+ }
+
+ /* 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 ();
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("%1 now has no request %2\n", this));
+
+}
+
+void
+WaveView::set_image_cache_size (uint64_t sz)
+{
+ if (!images) {
+ images = new WaveViewCache;
+ }
+
+ images->set_image_cache_threshold (sz);
+}
+
+/*-------------------------------------------------*/
+
+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 ()
+{
+ while (_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);
+ }
+
+ if (request_queue.empty()) {
+ continue;
+ }
+
+ /* 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());
+
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("start request for %1 at %2\n", requestor, g_get_monotonic_time()));
+
+ boost::shared_ptr<WaveViewThreadRequest> req = requestor->current_request;
+
+ if (!req) {
+ continue;
+ }
+
+ /* Generate an image. Unlock the request queue lock
+ * while we do this, so that other things can happen
+ * as we do rendering.
+ */
+
+ lm.release (); /* 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 */
+ }
+
+ lm.acquire ();
+
+ 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,
+ bool& full_coverage)
+{
+ 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;
+ boost::shared_ptr<Entry> best_partial;
+ framecnt_t max_coverage = 0;
+
+ /* 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;
+ }
+
+ switch (Evoral::coverage (start, end, e->start, e->end)) {
+ case Evoral::OverlapExternal: /* required range is inside image range */
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("found image spanning %1..%2 covers %3..%4\n",
+ e->start, e->end, start, end));
+ use (src, e);
+ full_coverage = true;
+ return e;
+
+ case Evoral::OverlapStart: /* required range start is covered by image range */
+ if ((e->end - start) > max_coverage) {
+ best_partial = e;
+ max_coverage = e->end - start;
+ }
+ break;
+
+ case Evoral::OverlapNone:
+ case Evoral::OverlapEnd:
+ case Evoral::OverlapInternal:
+ break;
+ }
+ }
+
+ if (best_partial) {
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("found PARTIAL image spanning %1..%2 partially covers %3..%4\n",
+ best_partial->start, best_partial->end, start, end));
+ use (src, best_partial);
+ full_coverage = false;
+ return best_partial;
+ }
+
+ 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 */
+ caches.erase (c2);
+
+ /* and re-start the whole iteration */
+ nxt = caches.begin ();
+ break;
+ }
+
+ 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);
+}
+
+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 ()
+{
+ /* Build a sortable list of all cache entries */
+
+ CacheList cache_list;
+
+ for (ImageCache::const_iterator cm = cache_map.begin(); cm != cache_map.end(); ++cm) {
+ for (CacheLine::const_iterator cl = cm->second.begin(); cl != cm->second.end(); ++cl) {
+ cache_list.push_back (make_pair (cm->first, *cl));
+ }
+ }
+
+ /* sort list in LRU order */
+ SortByTimestamp sorter;
+ sort (cache_list.begin(), cache_list.end(), sorter);
+
+ while (image_cache_size > _image_cache_threshold && !cache_map.empty() && !cache_list.empty()) {
+
+ ListEntry& le (cache_list.front());
+
+ ImageCache::iterator x;
+
+ if ((x = cache_map.find (le.first)) != cache_map.end ()) {
+
+ CacheLine& cl = x->second;
+
+ for (CacheLine::iterator c = cl.begin(); c != cl.end(); ++c) {
+
+ if (*c == le.second) {
+
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("Removing cache line entry for %1\n", x->first->name()));
+
+ /* 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;
+ }
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("cache shrunk to %1\n", image_cache_size));
+ }
+
+ /* Remove from the linear list, even if we didn't find it in
+ * the actual cache_mao
+ */
+ cache_list.erase (cache_list.begin());
+ }
+}
+
+void
+WaveViewCache::clear_cache ()
+{
+ DEBUG_TRACE (DEBUG::WaveView, "clear cache\n");
+ const uint64_t image_cache_threshold = _image_cache_threshold;
+ _image_cache_threshold = 0;
+ cache_flush ();
+ _image_cache_threshold = image_cache_threshold;
+}
+
+void
+WaveViewCache::set_image_cache_threshold (uint64_t sz)
+{
+ DEBUG_TRACE (DEBUG::WaveView, string_compose ("new image cache size %1\n", sz));
+ _image_cache_threshold = sz;
+ cache_flush ();
+}