From 27c943f1ddca0890ac2e5547b249334e0266c0a5 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Thu, 20 Jun 2013 14:37:31 -0400 Subject: new image cache design for waveviews, with various fixes. Rather than maintain a set of images in a cache, when we no longer have the required waveform data, create a new image that is appropriately centered and extends to roughly twice the screen width (or the limits of the region's source file(s), as necessary) --- libs/ardour/audiosource.cc | 5 +- libs/canvas/canvas/wave_view.h | 11 +- libs/canvas/wave_view.cc | 233 +++++++++++++---------------------------- 3 files changed, 84 insertions(+), 165 deletions(-) (limited to 'libs') diff --git a/libs/ardour/audiosource.cc b/libs/ardour/audiosource.cc index 74dd52d504..767ea1f9a8 100644 --- a/libs/ardour/audiosource.cc +++ b/libs/ardour/audiosource.cc @@ -342,14 +342,14 @@ AudioSource::read_peaks_with_fpp (PeakData *peaks, framecnt_t npeaks, framepos_t /* fix for near-end-of-file conditions */ if (cnt > _length - start) { - // cerr << "too close to end @ " << _length << " given " << start << " + " << cnt << endl; + cerr << "too close to end @ " << _length << " given " << start << " + " << cnt << " (" << _length - start << ")" << endl; cnt = _length - start; framecnt_t old = npeaks; npeaks = min ((framecnt_t) floor (cnt / samples_per_visual_peak), npeaks); zero_fill = old - npeaks; } - // cerr << "actual npeaks = " << npeaks << " zf = " << zero_fill << endl; + cerr << "actual npeaks = " << npeaks << " zf = " << zero_fill << endl; if (npeaks == cnt) { @@ -527,6 +527,7 @@ AudioSource::read_peaks_with_fpp (PeakData *peaks, framecnt_t npeaks, framepos_t } if (zero_fill) { + cerr << "Zero fill end of peaks (@ " << npeaks << " with " << zero_fill << endl; memset (&peaks[npeaks], 0, sizeof (PeakData) * zero_fill); } diff --git a/libs/canvas/canvas/wave_view.h b/libs/canvas/canvas/wave_view.h index e4e9d9b7ed..78c6486fce 100644 --- a/libs/canvas/canvas/wave_view.h +++ b/libs/canvas/canvas/wave_view.h @@ -71,6 +71,7 @@ public: WaveView (Group *, boost::shared_ptr); + ~WaveView (); void render (Rect const & area, Cairo::RefPtr) const; void compute_bounding_box () const; @@ -141,7 +142,7 @@ private: class CacheEntry { public: - CacheEntry (WaveView const *, double, double); + CacheEntry (WaveView const *, double, double, ARDOUR::framepos_t, ARDOUR::framepos_t); ~CacheEntry (); double pixel_start () const { @@ -171,7 +172,7 @@ private: Coord position (Coord) const; WaveView const * _wave_view; - + double _pixel_start; double _pixel_end; ARDOUR::framecnt_t _sample_start; @@ -208,8 +209,8 @@ private: * value as the crossfade editor needs to alter it. */ ARDOUR::frameoffset_t _region_start; - - mutable std::list _cache; + + mutable CacheEntry* _cache; PBD::ScopedConnection invalidation_connection; @@ -220,6 +221,8 @@ private: static PBD::Signal0 VisualPropertiesChanged; void handle_visual_property_change (); + void ensure_cache (ARDOUR::framecnt_t pixel_start, ARDOUR::framecnt_t pixel_end, + ARDOUR::framepos_t sample_start, ARDOUR::framepos_t sample_end) const; }; } diff --git a/libs/canvas/wave_view.cc b/libs/canvas/wave_view.cc index 1839b88cc4..6e190ee71a 100644 --- a/libs/canvas/wave_view.cc +++ b/libs/canvas/wave_view.cc @@ -67,10 +67,17 @@ WaveView::WaveView (Group* parent, boost::shared_ptr region , _gradient_depth_independent (false) , _amplitude_above_axis (1.0) , _region_start (region->start()) + , _cache (0) { VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this)); } +WaveView::~WaveView () +{ + delete _cache; + _cache = 0; +} + void WaveView::handle_visual_property_change () { @@ -130,18 +137,6 @@ WaveView::set_samples_per_pixel (double samples_per_pixel) } } -static inline double -to_src_sample_offset (frameoffset_t src_sample_start, double pixel_offset, double spp) -{ - return llrintf (src_sample_start + (pixel_offset * spp)); -} - -static inline double -to_pixel_offset (frameoffset_t src_sample_start, double sample_offset, double spp) -{ - return llrintf ((sample_offset - src_sample_start) / spp); -} - static inline double image_to_window (double wave_origin, double image_start) { @@ -154,6 +149,53 @@ window_to_image (double wave_origin, double image_start) return image_start - wave_origin; } +void +WaveView::ensure_cache (framecnt_t start, framecnt_t end, + framepos_t sample_start, framepos_t sample_end) const +{ + if (_cache && _cache->sample_start() <= sample_start && _cache->sample_end() >= sample_end) { + /* cache already covers required range, do nothing */ + return; + } + + if (_cache) { + delete _cache; + _cache = 0; + } + + /* sample position is canonical here, and we want to generate + * an image that spans about twice the canvas width + */ + + const framepos_t center = sample_start + ((sample_end - sample_start) / 2); + const framecnt_t canvas_samples = 2 * (_canvas->visible_area().width() * _samples_per_pixel); + + /* we can request data from anywhere in the Source, between 0 and its length + */ + + sample_start = max ((framepos_t) 0, (center - canvas_samples)); + sample_end = min (center + canvas_samples, _region->source_length (0)); + + if (sample_end <= sample_start) { + cerr << "sample start = " << sample_start << endl; + cerr << "center+ = " << center<< endl; + cerr << "CS = " << canvas_samples << endl; + cerr << "pui = " << center + canvas_samples << endl; + cerr << "sl = " << _region->source_length (0) << endl; + cerr << "st = " << _region->start () << endl; + cerr << "END: " << sample_end << endl; + assert (false); + } + + start = floor (sample_start / (double) _samples_per_pixel); + end = ceil (sample_end / (double) _samples_per_pixel); + + assert (end > start); + + cerr << name << " cache miss - new CE, span " << start << " .. " << end << " (" << sample_start << " .. " << sample_end << ")\n"; + _cache = new CacheEntry (this, start, end, sample_start, sample_end); +} + void WaveView::render (Rect const & area, Cairo::RefPtr context) const { @@ -170,139 +212,23 @@ WaveView::render (Rect const & area, Cairo::RefPtr context) cons return; } - /* we have a set of cached images that have precise pixel positions - * whose origin is 0,0 within our own rect. To convert these pixel - * positions so that they are useful when rendering, they need to - * be offset by the window position of our own origin. This is given - * by self.x0 - */ - Rect draw = d.get(); + /* pixel coordinates - we round up and down in case we were asked to + * draw "between" pixels at the start and/or end + */ + double draw_start = floor (draw.x0); + double draw_end = ceil (draw.x1); - /* pixel coordinates */ - double start = floor (draw.x0); - double const end = ceil (draw.x1); - - list::iterator cache; - - cache = _cache.begin (); - - while (end > start) { - - /* Step through cache entries that end at or before our current position */ - - for (; cache != _cache.end(); ++cache) { - if (image_to_window (self.x0, (*cache)->pixel_start()) <= start) { - break; - } - } - - /* Now either: - - 1. we have run out of cache entries - - 2. we have found a cache entry that starts after start - create a new cache entry to "fill in" before the one we have found. - - 3. we have found a cache entry that starts at or before - start, but finishes before end: create a new cache entry - to extend the cache further along the timeline. - - Set up a pointer to the cache entry that we will use on this iteration. - */ - - CacheEntry* image = 0; - - /* Cairo limit, caused by its use of 16.16 fixed point */ - const double BIG_IMAGE_SIZE = 32767.0; - - if (cache == _cache.end ()) { - - /* Case 1: we have run out of cache entries, so make a new one for - the whole required area and put it in the list. - - We would like to avoid lots of little images in the - cache, so when we create a new one, make it as wide - as possible, within the limits inherent in Cairo. - - However, we don't want to try to make it larger than - the source could allow, so clamp with that too. - */ - - double const region_end_pixel = image_to_window (self.x0, floor (_region->latest_possible_frame() / _samples_per_pixel)); - double const end_pixel = min (region_end_pixel, start + BIG_IMAGE_SIZE); - - if (end_pixel <= start) { - /* nothing more to draw */ - image = 0; - } else { - - CacheEntry* c = new CacheEntry (this, window_to_image (self.x0, start), window_to_image (self.x0, end_pixel)); - - _cache.push_back (c); - image = c; - } - - } else if (image_to_window (self.x0, (*cache)->pixel_start()) > start) { - - /* Case 2: we have a cache entry, but it begins after - * start, so we need another one for the missing section. - * - * Create a new cached image that extends as far as the - * next cached image's start, or the end of the region, - * or the end of a BIG_IMAGE, whichever comes first. - */ - - double end_pixel; - - if (end > image_to_window (self.x0, (*cache)->pixel_start())) { - double const region_end_pixel = image_to_window (self.x0, floor (_region->length() / _samples_per_pixel)); - end_pixel = min (region_end_pixel, max (image_to_window (self.x0, (*cache)->pixel_start()), start + BIG_IMAGE_SIZE)); - } else { - end_pixel = image_to_window (self.x0, (*cache)->pixel_start()); - } - - CacheEntry* c = new CacheEntry (this, window_to_image (self.x0, start), window_to_image (self.x0, end_pixel)); - - cache = _cache.insert (cache, c); - ++cache; - image = c; - - } else { - - /* Case 3: we have a cache entry that covers some of what - we have left to render - */ - + /* sample coordinates - note, these are not subject to rounding error */ + framepos_t sample_start = _region_start + (draw_start * _samples_per_pixel); + framepos_t sample_end = _region_start + (draw_end * _samples_per_pixel); - image = *cache; - ++cache; - } + ensure_cache (draw_start, draw_end, sample_start, sample_end); - if (!image) { - break; - } - - double this_end = min (end, image_to_window (self.x0, image->pixel_end ())); -#if 0 - cerr << "\t\tDraw image between " - << start << " .. " << this_end - << " using image spanning " - << image->pixel_start() << " (" << image_to_window (self.x0, image->pixel_start()) << ")" - << " .. " - << image->pixel_end () << " (" << image_to_window (self.x0, image->pixel_end()) << ")" - << " offset into image = " << image_to_window (self.x0, image->pixel_start()) - start - << endl; -#endif - - context->rectangle (start, draw.y0, this_end - start, draw.height()); - context->set_source (image->image(), self.x0 + (image_to_window (self.x0, image->pixel_start()) - start), self.y0); - context->fill (); - - start = this_end; - - } + context->rectangle (draw_start, draw.y0, draw_end - draw_start, draw.height()); + context->set_source (_cache->image(), self.x0 + _cache->pixel_start(), self.y0); + context->fill (); } void @@ -351,26 +277,15 @@ void WaveView::invalidate_whole_cache () { begin_visual_change (); - - for (list::iterator i = _cache.begin(); i != _cache.end(); ++i) { - delete *i; - } - - _cache.clear (); - + delete _cache; + _cache = 0; end_visual_change (); } void WaveView::invalidate_image_cache () { - begin_visual_change (); - - for (list::iterator i = _cache.begin(); i != _cache.end(); ++i) { - (*i)->clear_image (); - } - - end_visual_change (); + invalidate_whole_cache (); } void @@ -485,19 +400,19 @@ WaveView::region_resized () end_change (); } -WaveView::CacheEntry::CacheEntry (WaveView const * wave_view, double pixel_start, double pixel_end) +WaveView::CacheEntry::CacheEntry (WaveView const * wave_view, double pixel_start, double pixel_end, + framepos_t sample_start,framepos_t sample_end) : _wave_view (wave_view) , _pixel_start (pixel_start) , _pixel_end (pixel_end) + , _sample_start (sample_start) + , _sample_end (sample_end) , _n_peaks (_pixel_end - _pixel_start) { _peaks.reset (new PeakData[_n_peaks]); - _sample_start = _wave_view->_region_start + pixel_start * _wave_view->_samples_per_pixel; - _sample_end = _wave_view->_region_start + pixel_end * _wave_view->_samples_per_pixel; - _wave_view->_region->read_peaks (_peaks.get(), _n_peaks, - _sample_start, _sample_end, + _sample_start, _sample_end - _sample_start, _wave_view->_channel, _wave_view->_samples_per_pixel); } -- cgit v1.2.3