13
0

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)
This commit is contained in:
Paul Davis 2013-06-20 14:37:31 -04:00
parent 3604bf12a2
commit 27c943f1dd
3 changed files with 84 additions and 165 deletions

View File

@ -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);
}

View File

@ -71,6 +71,7 @@ public:
WaveView (Group *, boost::shared_ptr<ARDOUR::AudioRegion>);
~WaveView ();
void render (Rect const & area, Cairo::RefPtr<Cairo::Context>) 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<CacheEntry*> _cache;
mutable CacheEntry* _cache;
PBD::ScopedConnection invalidation_connection;
@ -220,6 +221,8 @@ private:
static PBD::Signal0<void> 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;
};
}

View File

@ -67,10 +67,17 @@ WaveView::WaveView (Group* parent, boost::shared_ptr<ARDOUR::AudioRegion> 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<Cairo::Context> context) const
{
@ -170,139 +212,23 @@ WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> 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);
/* 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);
list<CacheEntry*>::iterator cache;
ensure_cache (draw_start, draw_end, sample_start, sample_end);
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
*/
image = *cache;
++cache;
}
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<CacheEntry*>::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<CacheEntry*>::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);
}