Reimplementation of large parts of the WaveView class

The drawing itself should be unchanged but much of the rest of the
implementation has changed. The WaveViewThreads and WaveViewDrawingThread
classes were added and allow multiple drawing threads.

The Item::prepare_for_render interface is implemented by WaveView to enable
queuing draw requests for the drawing threads to process as soon as the state
change occurs during Editor::visual_changer, which often means the images will
be finished by the time they are needed in WaveView::render. This can
significantly reduce total render time and also flickering caused by images not
being ready for display.

If the drawing thread/s cannot finish the request by the time it is required in
WaveView::render then cancel it and draw the WaveViewImage in the GUI thread if
it is likely it can be completed in the current render pass/frame.  This change
also helps reduce the flickering caused by images not being ready with threaded
rendering, but with several drawing threads, drawing in the GUI thread may not
often occur (unless explicitly requested).

Allow unfinished images to be returned from the cache in
WaveView::prepare_for_render so that new draw requests aren't queued for
duplicate images. This reduces the amount of drawing for instance in
compositions where there are many instances of the same sample/waveform
displayed on the canvas as only a single image should be drawn.

Use a random width within a certain range for
WaveView::optimal_image_width_samples so that image drawing is less likely to
occur at the same time (which will cause a spike in render/draw time and
increase the chance of flickering waveforms).

Move implementations of the private WaveView classes into wave_view_private.h
and wave_view_private.cc source files.

Incorporate a fix for limiting the waveview image size to the cairo image size
limit.

Should hopefully Resolve: #6478
This commit is contained in:
Tim Mayberry 2017-03-16 14:12:05 +10:00
parent beac4b4190
commit 69c29a5346
5 changed files with 1487 additions and 1288 deletions

View File

@ -1,5 +1,6 @@
/*
Copyright (C) 2011-2013 Paul Davis
Copyright (C) 2017 Tim Mayberry
Author: Carl Hetherington <cth@carlh.net>
This program is free software; you can redistribute it and/or modify
@ -22,10 +23,7 @@
#define __CANVAS_WAVE_VIEW_H__
#include <boost/shared_ptr.hpp>
#include <boost/shared_array.hpp>
#include <boost/scoped_array.hpp>
#include "pbd/properties.h"
#include <boost/scoped_ptr.hpp>
#include "ardour/types.h"
@ -33,8 +31,6 @@
#include "canvas/visibility.h"
#include "canvas/item.h"
#include "canvas/fill.h"
#include "canvas/outline.h"
namespace ARDOUR {
class AudioRegion;
@ -48,157 +44,19 @@ class WaveViewTest;
namespace ArdourCanvas {
struct LIBCANVAS_API WaveViewThreadRequest
{
public:
enum RequestType {
Quit,
Cancel,
Draw
};
WaveViewThreadRequest () : stop (0) {}
bool should_stop () const { return (bool) g_atomic_int_get (const_cast<gint*>(&stop)); }
void cancel() { g_atomic_int_set (&stop, 1); }
RequestType type;
framepos_t start;
framepos_t end;
double width;
double height;
double samples_per_pixel;
uint16_t channel;
double amplitude;
Color fill_color;
boost::weak_ptr<const ARDOUR::Region> region;
/* resulting image, after request has been satisfied */
Cairo::RefPtr<Cairo::ImageSurface> image;
private:
gint stop; /* intended for atomic access */
};
class LIBCANVAS_API WaveView;
class LIBCANVAS_API WaveViewCache
{
public:
WaveViewCache();
~WaveViewCache();
struct Entry {
/* these properties define the cache entry as unique.
If an image is in use by a WaveView and any of these
properties are modified on the WaveView, the image can no
longer be used (or may no longer be usable for start/end
parameters). It will remain in the cache until flushed for
some reason (typically the cache is full).
*/
int channel;
Coord height;
float amplitude;
Color fill_color;
double samples_per_pixel;
framepos_t start;
framepos_t end;
/* the actual image referred to by the cache entry */
Cairo::RefPtr<Cairo::ImageSurface> image;
/* last time the cache entry was used */
uint64_t timestamp;
Entry (int chan, Coord hght, float amp, Color fcl, double spp, framepos_t strt, framepos_t ed,
Cairo::RefPtr<Cairo::ImageSurface> img)
: channel (chan)
, height (hght)
, amplitude (amp)
, fill_color (fcl)
, samples_per_pixel (spp)
, start (strt)
, end (ed)
, image (img) {}
};
uint64_t image_cache_threshold () const { return _image_cache_threshold; }
void set_image_cache_threshold (uint64_t);
void clear_cache ();
void add (boost::shared_ptr<ARDOUR::AudioSource>, boost::shared_ptr<Entry>);
void use (boost::shared_ptr<ARDOUR::AudioSource>, boost::shared_ptr<Entry>);
void consolidate_image_cache (boost::shared_ptr<ARDOUR::AudioSource>,
int channel,
Coord height,
float amplitude,
Color fill_color,
double samples_per_pixel);
boost::shared_ptr<Entry> lookup_image (boost::shared_ptr<ARDOUR::AudioSource>,
framepos_t start, framepos_t end,
int _channel,
Coord height,
float amplitude,
Color fill_color,
double samples_per_pixel,
bool& full_image);
private:
/* an unsorted, unindexd collection of cache entries associated with
a particular AudioSource. All cache Entries in the collection
share the AudioSource in common, but represent different parameter
settings (e.g. height, color, samples per pixel etc.)
*/
typedef std::vector<boost::shared_ptr<Entry> > CacheLine;
/* Indexed, non-sortable structure used to lookup images associated
* with a particular AudioSource
*/
typedef std::map <boost::shared_ptr<ARDOUR::AudioSource>,CacheLine> ImageCache;
ImageCache cache_map;
/* Linear, sortable structure used when we need to do a timestamp-based
* flush of entries from the cache.
*/
typedef std::pair<boost::shared_ptr<ARDOUR::AudioSource>,boost::shared_ptr<Entry> > ListEntry;
typedef std::vector<ListEntry> CacheList;
struct SortByTimestamp {
bool operator() (const WaveViewCache::ListEntry& a, const WaveViewCache::ListEntry& b) {
return a.second->timestamp < b.second->timestamp;
}
};
friend struct SortByTimestamp;
uint64_t image_cache_size;
uint64_t _image_cache_threshold;
uint64_t compute_image_cache_size ();
void cache_flush ();
bool cache_full ();
};
class WaveViewCacheGroup;
class WaveViewDrawRequest;
class WaveViewDrawRequestQueue;
class WaveViewImage;
class WaveViewProperties;
class WaveViewDrawingThread;
class LIBCANVAS_API WaveView : public Item, public sigc::trackable
{
public:
enum Shape { Normal, Rectified };
enum Shape {
Normal,
Rectified
};
std::string debug_name() const;
/* final ImageSurface rendered with colours */
Cairo::RefPtr<Cairo::ImageSurface> _image;
PBD::Signal0<void> ImageReady;
std::string debug_name () const;
/* Displays a single channel of waveform data for the given Region.
@ -218,11 +76,14 @@ public:
other view parameters).
*/
WaveView (Canvas *, boost::shared_ptr<ARDOUR::AudioRegion>);
WaveView (Canvas*, boost::shared_ptr<ARDOUR::AudioRegion>);
WaveView (Item*, boost::shared_ptr<ARDOUR::AudioRegion>);
~WaveView ();
~WaveView ();
virtual void prepare_for_render (Rect const& window_area) const;
virtual void render (Rect const & area, Cairo::RefPtr<Cairo::Context>) const;
void render (Rect const & area, Cairo::RefPtr<Cairo::Context>) const;
void compute_bounding_box () const;
void set_samples_per_pixel (double);
@ -239,39 +100,45 @@ public:
*/
void set_start_shift (double pixels);
void set_fill_color (Color);
void set_outline_color (Color);
void set_fill_color (Color);
void set_outline_color (Color);
void region_resized ();
void gain_changed ();
void gain_changed ();
void set_show_zero_line (bool);
bool show_zero_line() const { return _show_zero; }
void set_zero_color (Color);
void set_clip_color (Color);
void set_logscaled (bool);
void set_gradient_depth (double);
double gradient_depth() const { return _gradient_depth; }
void set_shape (Shape);
void set_show_zero_line (bool);
bool show_zero_line () const;
void set_always_get_image_in_thread (bool yn);
void set_zero_color (Color);
void set_clip_color (Color);
void set_logscaled (bool);
/* currently missing because we don't need them (yet):
set_shape_independent();
set_logscaled_independent()
*/
void set_gradient_depth (double);
double gradient_depth () const;
static void set_global_gradient_depth (double);
static void set_global_logscaled (bool);
static void set_global_shape (Shape);
static void set_global_show_waveform_clipping (bool);
void set_shape (Shape);
static double global_gradient_depth() { return _global_gradient_depth; }
static bool global_logscaled() { return _global_logscaled; }
static Shape global_shape() { return _global_shape; }
void set_always_get_image_in_thread (bool yn);
void set_amplitude_above_axis (double v);
double amplitude_above_axis () const { return _amplitude_above_axis; }
/* currently missing because we don't need them (yet):
* set_shape_independent();
* set_logscaled_independent();
*/
static void set_global_gradient_depth (double);
static void set_global_logscaled (bool);
static void set_global_shape (Shape);
static void set_global_show_waveform_clipping (bool);
static double global_gradient_depth () { return _global_gradient_depth; }
static bool global_logscaled () { return _global_logscaled; }
static Shape global_shape () { return _global_shape; }
void set_amplitude_above_axis (double v);
double amplitude_above_axis () const;
static void set_clip_level (double dB);
static PBD::Signal0<void> ClipLevelChanged;
@ -288,127 +155,115 @@ public:
void*& property_gain_function () {
return _foo_void;
}
private:
void* _foo_void;
private:
void* _foo_void;
#endif
private:
friend class ::WaveViewTest;
friend class WaveViewThreadClient;
void invalidate_image_cache ();
private:
friend class ::WaveViewTest;
friend class WaveViewThreadClient;
friend class WaveViewDrawingThread;
boost::shared_ptr<ARDOUR::AudioRegion> _region;
int _channel;
double _samples_per_pixel;
Coord _height;
bool _show_zero;
Color _zero_color;
Color _clip_color;
bool _logscaled;
Shape _shape;
double _gradient_depth;
bool _shape_independent;
bool _logscaled_independent;
bool _gradient_depth_independent;
double _amplitude_above_axis;
float _region_amplitude;
double _start_shift;
/** The `start' value to use for the region; we can't use the region's
* value as the crossfade editor needs to alter it.
*/
ARDOUR::frameoffset_t _region_start;
boost::scoped_ptr<WaveViewProperties> _props;
mutable boost::shared_ptr<WaveViewImage> _image;
mutable boost::shared_ptr<WaveViewCacheGroup> _cache_group;
bool _shape_independent;
bool _logscaled_independent;
bool _gradient_depth_independent;
/** Under almost conditions, this is going to return _region->length(),
* but if _region_start has been reset, then we need
* to use this modified computation.
* but if region_start has been reset, then we need to use this modified
* computation.
*/
ARDOUR::framecnt_t region_length() const;
ARDOUR::framecnt_t region_length () const;
/** Under almost conditions, this is going to return _region->start() +
* _region->length(), but if _region_start has been reset, then we need
* to use this modified computation.
* _region->length(), but if region_start has been reset, then we need to use
* this modified computation.
*/
ARDOUR::framepos_t region_end() const;
ARDOUR::framepos_t region_end () const;
/** If true, calls to get_image() will render a missing wave image
in the calling thread. Generally set to false, but true after a
call to set_height().
*/
mutable bool get_image_in_thread;
/** If true, calls to get_image() will render a missing wave image
in the calling thread. Set true for waveviews we expect to
keep updating (e.g. while recording)
*/
bool always_get_image_in_thread;
/** Set to true by render(). Used so that we know if the wave view
* has actually been displayed on screen. ::set_height() when this
* is true does not use get_image_in_thread, because it implies
* that the height is being set BEFORE the waveview is drawn.
/**
* _image stays non-null after the first time it is set
*/
mutable bool rendered;
bool rendered () const { return _image.get(); }
PBD::ScopedConnectionList invalidation_connection;
PBD::ScopedConnection image_ready_connection;
bool draw_image_in_gui_thread () const;
static double _global_gradient_depth;
static bool _global_logscaled;
static Shape _global_shape;
static bool _global_show_waveform_clipping;
static double _clip_level;
/** If true, calls to render() will render a missing wave image in the GUI
* thread. Generally set to false, but true after a call to set_height().
*/
mutable bool _draw_image_in_gui_thread;
static PBD::Signal0<void> VisualPropertiesChanged;
/** If true, calls to render() will render a missing wave image in the GUI
* thread. Set true for waveviews we expect to keep updating (e.g. while
* recording)
*/
bool _always_draw_image_in_gui_thread;
void handle_visual_property_change ();
void handle_clip_level_change ();
void init();
boost::shared_ptr<WaveViewCache::Entry> get_image (framepos_t start, framepos_t end, bool& full_image) const;
boost::shared_ptr<WaveViewCache::Entry> get_image_from_cache (framepos_t start, framepos_t end, bool& full_image) const;
mutable boost::shared_ptr<WaveViewDrawRequest> current_request;
struct LineTips {
double top;
double bot;
double spread;
bool clip_max;
bool clip_min;
PBD::ScopedConnectionList invalidation_connection;
LineTips() : top (0.0), bot (0.0), clip_max (false), clip_min (false) {}
};
static double _global_gradient_depth;
static bool _global_logscaled;
static Shape _global_shape;
static bool _global_show_waveform_clipping;
static double _global_clip_level;
ArdourCanvas::Coord y_extent (double) const;
void compute_tips (ARDOUR::PeakData const & peak, LineTips& tips) const;
static PBD::Signal0<void> VisualPropertiesChanged;
ARDOUR::framecnt_t desired_image_width () const;
void handle_visual_property_change ();
void handle_clip_level_change ();
void draw_image (Cairo::RefPtr<Cairo::ImageSurface>&, ARDOUR::PeakData*, int n_peaks, boost::shared_ptr<WaveViewThreadRequest>) const;
void draw_absent_image (Cairo::RefPtr<Cairo::ImageSurface>&, ARDOUR::PeakData*, int) const;
struct LineTips {
double top;
double bot;
double spread;
bool clip_max;
bool clip_min;
void cancel_my_render_request () const;
LineTips () : top (0.0), bot (0.0), clip_max (false), clip_min (false) {}
};
void queue_get_image (boost::shared_ptr<const ARDOUR::Region> region, framepos_t start, framepos_t end) const;
void generate_image (boost::shared_ptr<WaveViewThreadRequest>, bool in_render_thread) const;
boost::shared_ptr<WaveViewCache::Entry> cache_request_result (boost::shared_ptr<WaveViewThreadRequest> req) const;
static ArdourCanvas::Coord y_extent (double, Shape const, double const height);
void image_ready ();
static void compute_tips (ARDOUR::PeakData const& peak, LineTips& tips, double const effective_height);
mutable boost::shared_ptr<WaveViewCache::Entry> _current_image;
static void draw_image (Cairo::RefPtr<Cairo::ImageSurface>&, ARDOUR::PeakData*, int n_peaks,
boost::shared_ptr<WaveViewDrawRequest>);
static void draw_absent_image (Cairo::RefPtr<Cairo::ImageSurface>&, ARDOUR::PeakData*, int);
mutable boost::shared_ptr<WaveViewThreadRequest> current_request;
ARDOUR::framecnt_t optimal_image_width_samples () const;
static WaveViewCache* images;
void set_image (boost::shared_ptr<WaveViewImage> img) const;
static void drawing_thread ();
// @return true if item area intersects with draw area
bool get_item_and_draw_rect_in_window_coords (Rect const& canvas_rect, Rect& item_area,
Rect& draw_rect) const;
static gint drawing_thread_should_quit;
static Glib::Threads::Mutex request_queue_lock;
static Glib::Threads::Mutex current_image_lock;
static Glib::Threads::Cond request_cond;
static Glib::Threads::Thread* _drawing_thread;
typedef std::set<WaveView const *> DrawingRequestQueue;
static DrawingRequestQueue request_queue;
boost::shared_ptr<WaveViewDrawRequest> create_draw_request (WaveViewProperties const&) const;
void queue_draw_request (boost::shared_ptr<WaveViewDrawRequest> const&) const;
static void process_draw_request (boost::shared_ptr<WaveViewDrawRequest>);
boost::shared_ptr<WaveViewCacheGroup> get_cache_group () const;
/**
* Notify the Cache that we are dropping our reference to the
* CacheGroup so it can also do so if it is the only reference holder
* of the cache group.
*/
void reset_cache_group ();
};
} // namespace ArdourCanvas

View File

@ -0,0 +1,365 @@
/*
Copyright (C) 2017 Tim Mayberry
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __CANVAS_WAVE_VIEW_PRIVATE_H__
#define __CANVAS_WAVE_VIEW_PRIVATE_H__
#include <deque>
#include "canvas/wave_view.h"
namespace ARDOUR {
class AudioRegion;
}
namespace ArdourCanvas {
struct WaveViewProperties
{
public: // ctors
WaveViewProperties (boost::shared_ptr<ARDOUR::AudioRegion> region);
// WaveViewProperties (WaveViewProperties const& other) = default;
// WaveViewProperties& operator=(WaveViewProperties const& other) = default;
public: // member variables
framepos_t region_start;
framepos_t region_end;
uint16_t channel;
double height;
double samples_per_pixel;
double amplitude;
double amplitude_above_axis;
Color fill_color;
Color outline_color;
Color zero_color;
Color clip_color;
bool show_zero;
bool logscaled;
WaveView::Shape shape;
double gradient_depth;
double start_shift;
private: // member variables
framepos_t sample_start;
framepos_t sample_end;
public: // methods
bool is_valid () const
{
return (sample_end != 0 && samples_per_pixel != 0);
}
void set_width_samples (ARDOUR::framecnt_t const width_samples)
{
assert (is_valid());
assert (width_samples != 0);
ARDOUR::framecnt_t half_width = width_samples / 2;
framepos_t new_sample_start = std::max (region_start, get_center_sample () - half_width);
framepos_t new_sample_end = std::min (get_center_sample () + half_width, region_end);
assert (new_sample_start <= new_sample_end);
sample_start = new_sample_start;
sample_end = new_sample_end;
}
uint64_t get_width_pixels () const
{
return (uint64_t)std::max (1LL, llrint (ceil (get_length_samples () / samples_per_pixel)));
}
void set_sample_offsets (framepos_t const start, framepos_t const end)
{
assert (start <= end);
// sample_start and sample_end are bounded by the region limits.
if (start < region_start) {
sample_start = region_start;
} else if (start > region_end) {
sample_start = region_end;
} else {
sample_start = start;
}
if (end > region_end) {
sample_end = region_end;
} else if (end < region_start) {
sample_end = region_start;
} else {
sample_end = end;
}
assert (sample_start <= sample_end);
}
framepos_t get_sample_start () const
{
return sample_start;
}
framepos_t get_sample_end () const
{
return sample_end;
}
void set_sample_positions_from_pixel_offsets (double start_pixel, double end_pixel)
{
assert (start_pixel <= end_pixel);
/**
* It is possible for the new sample positions to be past the region_end,
* so we have to do bounds checking/adjustment for this in set_sample_offsets.
*/
framepos_t new_sample_start = region_start + (start_pixel * samples_per_pixel);
framepos_t new_sample_end = region_start + (end_pixel * samples_per_pixel);
set_sample_offsets (new_sample_start, new_sample_end);
}
ARDOUR::framecnt_t get_length_samples () const
{
assert (sample_start <= sample_end);
return sample_end - sample_start;
}
framepos_t get_center_sample ()
{
return sample_start + (get_length_samples() / 2);
}
bool is_equivalent (WaveViewProperties const& other)
{
return (samples_per_pixel == other.samples_per_pixel &&
contains (other.sample_start, other.sample_end) && channel == other.channel &&
height == other.height && amplitude == other.amplitude &&
amplitude_above_axis == other.amplitude_above_axis && fill_color == other.fill_color &&
outline_color == other.outline_color && zero_color == other.zero_color &&
clip_color == other.clip_color && show_zero == other.show_zero &&
logscaled == other.logscaled && shape == other.shape &&
gradient_depth == other.gradient_depth);
// region_start && start_shift??
}
bool contains (framepos_t start, framepos_t end)
{
return (sample_start <= start && end <= sample_end);
}
};
struct WaveViewImage {
public: // ctors
WaveViewImage (boost::shared_ptr<const ARDOUR::AudioRegion> const& region_ptr,
WaveViewProperties const& properties);
~WaveViewImage ();
public: // member variables
boost::weak_ptr<const ARDOUR::AudioRegion> region;
WaveViewProperties props;
Cairo::RefPtr<Cairo::ImageSurface> cairo_image;
uint64_t timestamp;
public: // methods
bool finished() { return static_cast<bool>(cairo_image); }
bool
contains_image_with_properties (WaveViewProperties const& other_props)
{
return cairo_image && props.is_equivalent (other_props);
}
bool is_valid () {
return props.is_valid ();
}
size_t size_in_bytes ()
{
// 4 = bytes per FORMAT_ARGB32 pixel
return props.height * props.get_width_pixels() * 4;
}
};
struct WaveViewDrawRequest
{
public:
WaveViewDrawRequest ();
~WaveViewDrawRequest ();
bool stopped() const { return (bool) g_atomic_int_get (const_cast<gint*>(&stop)); }
void cancel() { g_atomic_int_set (&stop, 1); }
bool finished() { return image->finished(); }
boost::shared_ptr<WaveViewImage> image;
bool is_valid () {
return (image && image->is_valid());
}
private:
gint stop; /* intended for atomic access */
};
class WaveViewCache;
class WaveViewCacheGroup
{
public:
WaveViewCacheGroup (WaveViewCache& parent_cache);
~WaveViewCacheGroup ();
public:
// @return image with matching properties or null
boost::shared_ptr<WaveViewImage> lookup_image (WaveViewProperties const&);
void add_image (boost::shared_ptr<WaveViewImage>);
bool full () const { return _cached_images.size() > max_size(); }
static uint32_t max_size () { return 16; }
void clear_cache ();
private:
/**
* At time of writing we don't strictly need a reference to the parent cache
* as there is only a single global cache but if the image cache ever becomes
* a per canvas cache then a using a reference is handy.
*/
WaveViewCache& _parent_cache;
typedef std::list<boost::shared_ptr<WaveViewImage> > ImageCache;
ImageCache _cached_images;
};
class WaveViewCache
{
public:
static WaveViewCache* get_instance ();
uint64_t image_cache_threshold () const { return _image_cache_threshold; }
void set_image_cache_threshold (uint64_t);
void clear_cache ();
boost::shared_ptr<WaveViewCacheGroup> get_cache_group (boost::shared_ptr<ARDOUR::AudioSource>);
void reset_cache_group (boost::shared_ptr<WaveViewCacheGroup>&);
private:
WaveViewCache();
~WaveViewCache();
private:
typedef std::map<boost::shared_ptr<ARDOUR::AudioSource>, boost::shared_ptr<WaveViewCacheGroup> >
CacheGroups;
CacheGroups cache_group_map;
uint64_t image_cache_size;
uint64_t _image_cache_threshold;
private:
friend WaveViewCacheGroup;
void increase_size (uint64_t bytes);
void decrease_size (uint64_t bytes);
bool full () { return image_cache_size > _image_cache_threshold; }
};
class WaveViewDrawRequestQueue
{
public:
void enqueue (boost::shared_ptr<WaveViewDrawRequest>&);
// @return valid request or null if non-blocking or no request is available
boost::shared_ptr<WaveViewDrawRequest> dequeue (bool block);
void wake_up ();
private:
mutable Glib::Threads::Mutex _queue_mutex;
Glib::Threads::Cond _cond;
typedef std::deque<boost::shared_ptr<WaveViewDrawRequest> > DrawRequestQueueType;
DrawRequestQueueType _queue;
};
class WaveViewDrawingThread
{
public:
WaveViewDrawingThread ();
~WaveViewDrawingThread ();
private:
void start ();
void quit ();
void run ();
private:
Glib::Threads::Thread* _thread;
gint _quit;
};
class WaveViewThreads {
private:
WaveViewThreads ();
~WaveViewThreads ();
public:
static void initialize ();
static void deinitialize ();
static bool enabled () { return (instance); }
static void enqueue_draw_request (boost::shared_ptr<WaveViewDrawRequest>&);
private:
friend WaveViewDrawingThread;
static void wake_up ();
// will block until a request is available
static boost::shared_ptr<WaveViewDrawRequest> dequeue_draw_request ();
void start_threads ();
void stop_threads ();
private:
static uint32_t init_count;
static WaveViewThreads* instance;
// TODO use std::unique_ptr when possible
typedef std::vector<boost::shared_ptr<WaveViewDrawingThread> > WaveViewThreadList;
WaveViewThreadList _threads;
WaveViewDrawRequestQueue _request_queue;
};
} // namespace ArdourCanvas
#endif // __CANVAS_WAVE_VIEW_PRIVATE_H__

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,455 @@
/*
Copyright (C) 2017 Tim Mayberry
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "canvas/wave_view_private.h"
#include "pbd/cpus.h"
#include "ardour/audioregion.h"
#include "ardour/audiosource.h"
namespace ArdourCanvas {
WaveViewProperties::WaveViewProperties (boost::shared_ptr<ARDOUR::AudioRegion> region)
: region_start (region->start ())
, region_end (region->start () + region->length ())
, channel (0)
, height (64)
, samples_per_pixel (0)
, amplitude (region->scale_amplitude ())
, amplitude_above_axis (1.0)
, fill_color (0x000000ff)
, outline_color (0xff0000ff)
, zero_color (0xff0000ff)
, clip_color (0xff0000ff)
, show_zero (false)
, logscaled (WaveView::global_logscaled())
, shape (WaveView::global_shape())
, gradient_depth (WaveView::global_gradient_depth ())
, start_shift (0.0) // currently unused
, sample_start (0)
, sample_end (0)
{
}
/*-------------------------------------------------*/
WaveViewImage::WaveViewImage (boost::shared_ptr<const ARDOUR::AudioRegion> const& region_ptr,
WaveViewProperties const& properties)
: region (region_ptr)
, props (properties)
, timestamp (0)
{
}
WaveViewImage::~WaveViewImage ()
{
}
/*-------------------------------------------------*/
WaveViewCacheGroup::WaveViewCacheGroup (WaveViewCache& parent_cache)
: _parent_cache (parent_cache)
{
}
WaveViewCacheGroup::~WaveViewCacheGroup ()
{
clear_cache ();
}
void
WaveViewCacheGroup::add_image (boost::shared_ptr<WaveViewImage> image)
{
if (!image) {
// Not adding invalid image to cache
return;
}
ImageCache::iterator oldest_image_it = _cached_images.begin();
ImageCache::iterator second_oldest_image_it = _cached_images.end();
for (ImageCache::iterator it = _cached_images.begin (); it != _cached_images.end (); ++it) {
if ((*it) == image) {
// Must never be more than one instance of the image in the cache
(*it)->timestamp = g_get_monotonic_time ();
return;
} else if ((*it)->props.is_equivalent (image->props)) {
// Equivalent Image already in cache, updating timestamp
(*it)->timestamp = g_get_monotonic_time ();
return;
}
if ((*it)->timestamp < (*oldest_image_it)->timestamp) {
second_oldest_image_it = oldest_image_it;
oldest_image_it = it;
}
}
// no duplicate or equivalent image so we are definitely adding it to cache
image->timestamp = g_get_monotonic_time ();
if (_parent_cache.full () || full ()) {
if (oldest_image_it != _cached_images.end()) {
// Replacing oldest Image in cache
_parent_cache.decrease_size ((*oldest_image_it)->size_in_bytes ());
*oldest_image_it = image;
_parent_cache.increase_size (image->size_in_bytes ());
if (second_oldest_image_it != _cached_images.end ()) {
// Removing second oldest Image in cache
_parent_cache.decrease_size ((*second_oldest_image_it)->size_in_bytes ());
_cached_images.erase (second_oldest_image_it);
}
return;
} else {
/**
* Add the image to the cache even if the threshold is exceeded so that
* new WaveViews can still cache images with a full cache, the size of
* the cache will quickly equalize back to the threshold as new images
* are added and the size of the cache is reduced.
*/
}
}
_cached_images.push_back (image);
_parent_cache.increase_size (image->size_in_bytes ());
}
boost::shared_ptr<WaveViewImage>
WaveViewCacheGroup::lookup_image (WaveViewProperties const& props)
{
for (ImageCache::iterator i = _cached_images.begin (); i != _cached_images.end (); ++i) {
if ((*i)->props.is_equivalent (props)) {
return (*i);
}
}
return boost::shared_ptr<WaveViewImage>();
}
void
WaveViewCacheGroup::clear_cache ()
{
// Tell the parent cache about the images we are about to drop references to
for (ImageCache::iterator it = _cached_images.begin (); it != _cached_images.end (); ++it) {
_parent_cache.decrease_size ((*it)->size_in_bytes ());
}
_cached_images.clear ();
}
/*-------------------------------------------------*/
WaveViewCache::WaveViewCache ()
: image_cache_size (0)
, _image_cache_threshold (100 * 1048576) /* bytes */
{
}
WaveViewCache::~WaveViewCache ()
{
}
WaveViewCache*
WaveViewCache::get_instance ()
{
static WaveViewCache* instance = new WaveViewCache;
return instance;
}
void
WaveViewCache::increase_size (uint64_t bytes)
{
image_cache_size += bytes;
}
void
WaveViewCache::decrease_size (uint64_t bytes)
{
assert (image_cache_size - bytes < image_cache_size);
image_cache_size -= bytes;
}
boost::shared_ptr<WaveViewCacheGroup>
WaveViewCache::get_cache_group (boost::shared_ptr<ARDOUR::AudioSource> source)
{
CacheGroups::iterator it = cache_group_map.find (source);
if (it != cache_group_map.end()) {
// Found existing CacheGroup for AudioSource
return it->second;
}
boost::shared_ptr<WaveViewCacheGroup> new_group (new WaveViewCacheGroup (*this));
bool inserted = cache_group_map.insert (std::make_pair (source, new_group)).second;
assert (inserted);
return new_group;
}
void
WaveViewCache::reset_cache_group (boost::shared_ptr<WaveViewCacheGroup>& group)
{
if (!group) {
return;
}
CacheGroups::iterator it = cache_group_map.begin();
while (it != cache_group_map.end()) {
if (it->second == group) {
break;
}
++it;
}
assert (it != cache_group_map.end ());
group.reset();
if (it->second.unique()) {
cache_group_map.erase (it);
}
}
void
WaveViewCache::clear_cache ()
{
for (CacheGroups::iterator it = cache_group_map.begin (); it != cache_group_map.end (); ++it) {
(*it).second->clear_cache ();
}
}
void
WaveViewCache::set_image_cache_threshold (uint64_t sz)
{
_image_cache_threshold = sz;
}
/*-------------------------------------------------*/
WaveViewDrawRequest::WaveViewDrawRequest () : stop (0)
{
}
WaveViewDrawRequest::~WaveViewDrawRequest ()
{
}
void
WaveViewDrawRequestQueue::enqueue (boost::shared_ptr<WaveViewDrawRequest>& request)
{
Glib::Threads::Mutex::Lock lm (_queue_mutex);
_queue.push_back (request);
_cond.broadcast ();
}
void
WaveViewDrawRequestQueue::wake_up ()
{
boost::shared_ptr<WaveViewDrawRequest> null_ptr;
// hack!?...wake up the drawing thread
enqueue (null_ptr);
}
boost::shared_ptr<WaveViewDrawRequest>
WaveViewDrawRequestQueue::dequeue (bool block)
{
if (block) {
_queue_mutex.lock();
} else {
if (!_queue_mutex.trylock()) {
return boost::shared_ptr<WaveViewDrawRequest>();
}
}
// _queue_mutex is always held at this point
if (_queue.empty()) {
if (block) {
_cond.wait (_queue_mutex);
} else {
_queue_mutex.unlock();
return boost::shared_ptr<WaveViewDrawRequest>();
}
}
boost::shared_ptr<WaveViewDrawRequest> req;
if (!_queue.empty()) {
req = _queue.front ();
_queue.pop_front ();
} else {
// Queue empty, returning empty DrawRequest
}
_queue_mutex.unlock();
return req;
}
/*-------------------------------------------------*/
WaveViewThreads::WaveViewThreads ()
{
}
WaveViewThreads::~WaveViewThreads ()
{
}
uint32_t WaveViewThreads::init_count = 0;
WaveViewThreads* WaveViewThreads::instance = 0;
void
WaveViewThreads::initialize ()
{
// no need for atomics as only called from GUI thread
if (++init_count == 1) {
assert(!instance);
instance = new WaveViewThreads;
instance->start_threads();
}
}
void
WaveViewThreads::deinitialize ()
{
if (--init_count == 0) {
instance->stop_threads();
delete instance;
instance = 0;
}
}
void
WaveViewThreads::enqueue_draw_request (boost::shared_ptr<WaveViewDrawRequest>& request)
{
assert (instance);
instance->_request_queue.enqueue (request);
}
boost::shared_ptr<WaveViewDrawRequest>
WaveViewThreads::dequeue_draw_request ()
{
assert (instance);
return instance->_request_queue.dequeue (true);
}
void
WaveViewThreads::wake_up ()
{
assert (instance);
return instance->_request_queue.wake_up ();
}
void
WaveViewThreads::start_threads ()
{
assert (!_threads.size());
int num_cpus = hardware_concurrency ();
uint32_t num_threads = std::max (1, num_cpus - 1);
for (uint32_t i = 0; i != num_threads; ++i) {
boost::shared_ptr<WaveViewDrawingThread> new_thread (new WaveViewDrawingThread ());
_threads.push_back(new_thread);
}
}
void
WaveViewThreads::stop_threads ()
{
assert (_threads.size());
_threads.clear ();
}
/*-------------------------------------------------*/
WaveViewDrawingThread::WaveViewDrawingThread ()
: _thread(0)
, _quit(0)
{
start ();
}
WaveViewDrawingThread::~WaveViewDrawingThread ()
{
quit ();
}
void
WaveViewDrawingThread::start ()
{
assert (!_thread);
_thread = Glib::Threads::Thread::create (sigc::mem_fun (*this, &WaveViewDrawingThread::run));
}
void
WaveViewDrawingThread::quit ()
{
assert (_thread);
g_atomic_int_set (&_quit, 1);
WaveViewThreads::wake_up ();
_thread->join();
_thread = 0;
}
void
WaveViewDrawingThread::run ()
{
while (true) {
if (g_atomic_int_get (&_quit)) {
break;
}
// block until a request is available.
boost::shared_ptr<WaveViewDrawRequest> req = WaveViewThreads::dequeue_draw_request ();
if (req && !req->stopped()) {
try {
WaveView::process_draw_request (req);
} catch (...) {
/* just in case it was set before the exception, whatever it was */
req->image->cairo_image.clear ();
}
} else {
// null or stopped Request, processing skipped
}
}
}
} // namespace ArdourCanvas

View File

@ -63,6 +63,7 @@ canvas_sources = [
'types.cc',
'utils.cc',
'wave_view.cc',
'wave_view_private.cc',
'widget.cc',
'xfade_curve.cc',
]