13
0
livetrax/libs/canvas/wave_view_private.cc
Tim Mayberry 6e91ee071c 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
2017-06-26 08:40:47 +10:00

456 lines
9.8 KiB
C++

/*
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