Change AudioRenderer to use DataBlockCache.

Originally committed to SVN as r3414.
This commit is contained in:
Niels Martin Hansen 2009-08-16 00:28:26 +00:00
parent d29d267cce
commit e9e3b2d107
3 changed files with 196 additions and 171 deletions

View file

@ -35,161 +35,35 @@
#include "audio_renderer.h" #include "audio_renderer.h"
#include "include/aegisub/audio_provider.h"
#include <wx/bitmap.h> #include <wx/bitmap.h>
#include <wx/dcmemory.h> #include <wx/dcmemory.h>
#include <map>
/// @class AudioRendererBitmapCache
/// @brief Caches bitmaps of rendered audio at a specific resolution
///
/// Used internally by the AudioRenderer class.
class AudioRendererBitmapCache {
/// Width of cached bitmaps
int width;
/// Height of cached bitmaps
int height;
/// Memory size of each cached image, for cache management
size_t bytes_per_image;
// Cached bitmaps
// Intentionally not doxygened, too internal
struct CachedBitmap {
// The bitmap itself
wxBitmap bmp;
// "Timestamp" of last access (not necessarily related to any clocks)
int last_access;
CachedBitmap(wxBitmap bmp = wxBitmap()) : bmp(bmp), last_access(0) { }
~CachedBitmap() { }
bool operator < (const CachedBitmap &other) const { return last_access < other.last_access; }
};
typedef std::map<int, CachedBitmap> CacheType;
CacheType cache;
public:
/// @brief Constructor
///
/// Does nothing.
AudioRendererBitmapCache();
/// @brief Destructor
///
/// Does nothing.
~AudioRendererBitmapCache();
/// @brief Age the cache, purging old items
/// @param max_size Maximum allowed cache size, in bytes
///
/// Purges the least recently used bitmaps to bring the cache size below max_size.
/// Aging is a somewhat expensive operation and should only be done once in a while.
///
/// If max_size is specified to 0, the cache will be entirely cleared in a fast manner.
void Age(size_t max_size);
/// @brief Change the size of cached images
/// @param new_width New width of images to cache
/// @param new_height New height of images to cache
///
/// Clears and re-initialises the cache.
void Resize(int new_width, int new_height);
/// @brief Retrieve an image from cache
/// @param bmp [out] Bitmap to return the image in
/// @param key Key to request the image for
/// @param timestamp Timestamp to use for cache age management
/// @return Returns false if the image had to be created in cache; if the return is
/// false, the consumer must draw into the returned bitmap.
///
/// Example:
/// @code
/// wxBitmap bmp;
/// if (!cache->Get(bmp, key, timestamp))
/// RenderBitmap(bmp);
/// // Use bmp
/// @endcode
///
/// The timestamp passed should never decrease between calls to the same cache.
///
/// The key has no inherent meaning to the cache.
bool Get(wxBitmap &bmp, int key, int timestamp);
};
AudioRendererBitmapCacheBitmapFactory::AudioRendererBitmapCacheBitmapFactory(AudioRenderer *_renderer)
AudioRendererBitmapCache::AudioRendererBitmapCache()
{ {
Resize(0, 0); assert(_renderer != 0);
renderer = _renderer;
} }
AudioRendererBitmapCache::~AudioRendererBitmapCache() wxBitmap *AudioRendererBitmapCacheBitmapFactory::ProduceBlock(int i)
{ {
// Nothing to do (void)i;
return new wxBitmap(renderer->cache_bitmap_width, renderer->pixel_height, 32);
} }
void AudioRendererBitmapCache::Age(size_t max_size) void AudioRendererBitmapCacheBitmapFactory::DisposeBlock(wxBitmap *bmp)
{ {
if (max_size == 0) delete bmp;
{
cache.clear();
return;
}
/// @todo Make this faster than O(n^2)
size_t max_items = max_size / bytes_per_image;
while (cache.size() > max_items)
{
// Find the oldest item and remove it
CacheType::iterator next = cache.begin();
CacheType::iterator oldest = next;
while (++next != cache.end())
{
if (next->second.last_access < oldest->second.last_access)
oldest = next;
}
cache.erase(oldest);
}
} }
void AudioRendererBitmapCache::Resize(int new_width, int new_height) size_t AudioRendererBitmapCacheBitmapFactory::GetBlockSize() const
{ {
Age(0); return sizeof(wxBitmap) + renderer->cache_bitmap_width * renderer->pixel_height * 4;
width = new_width;
height = new_height;
// Assuming 32 bpp
// This probably isn't completely accurate, but just a reasonable approximation
bytes_per_image = sizeof(CachedBitmap) + width*height*4;
}
bool AudioRendererBitmapCache::Get(wxBitmap &bmp, int key, int timestamp)
{
CacheType::iterator item = cache.find(key);
bool found = true;
if (item == cache.end())
{
cache[key] = CachedBitmap(wxBitmap(width, height, 32));
item = cache.find(key);
assert(item != cache.end());
found = false;
}
item->second.last_access = timestamp;
bmp = item->second.bmp;
return found;
} }
@ -197,9 +71,8 @@ bool AudioRendererBitmapCache::Get(wxBitmap &bmp, int key, int timestamp)
AudioRenderer::AudioRenderer() AudioRenderer::AudioRenderer()
: cache_bitmap_width(32) // arbitrary value for now : cache_bitmap_width(32) // arbitrary value for now
, bitmaps_normal(new AudioRendererBitmapCache) , bitmaps_normal(256, AudioRendererBitmapCacheBitmapFactory(this))
, bitmaps_selected(new AudioRendererBitmapCache) , bitmaps_selected(256, AudioRendererBitmapCacheBitmapFactory(this))
, cache_clock(0)
, cache_maxsize(0) , cache_maxsize(0)
, renderer(0) , renderer(0)
, provider(0) , provider(0)
@ -218,7 +91,14 @@ AudioRenderer::~AudioRenderer()
void AudioRenderer::SetSamplesPerPixel(int _pixel_samples) void AudioRenderer::SetSamplesPerPixel(int _pixel_samples)
{ {
if (pixel_samples == _pixel_samples) return;
pixel_samples = _pixel_samples; pixel_samples = _pixel_samples;
if (renderer)
renderer->SetSamplesPerPixel(pixel_samples);
ResetBlockCount();
} }
@ -227,8 +107,22 @@ void AudioRenderer::SetHeight(int _pixel_height)
if (pixel_height == _pixel_height) return; if (pixel_height == _pixel_height) return;
pixel_height = _pixel_height; pixel_height = _pixel_height;
bitmaps_normal->Resize(cache_bitmap_width, pixel_height); Invalidate();
bitmaps_selected->Resize(cache_bitmap_width, pixel_height); }
void AudioRenderer::SetAmplitudeScale(float _amplitude_scale)
{
if (amplitude_scale == _amplitude_scale) return;
// A scaling of 0 or a negative scaling makes no sense
assert(_amplitude_scale > 0);
amplitude_scale = _amplitude_scale;
if (renderer)
renderer->SetAmplitudeScale(amplitude_scale);
Invalidate();
} }
@ -240,11 +134,14 @@ void AudioRenderer::SetRenderer(AudioRendererBitmapProvider *_renderer)
renderer->SetProvider(0); renderer->SetProvider(0);
renderer = _renderer; renderer = _renderer;
bitmaps_normal->Age(0); Invalidate();
bitmaps_selected->Age(0);
if (renderer) if (renderer)
{
renderer->SetProvider(provider); renderer->SetProvider(provider);
renderer->SetAmplitudeScale(amplitude_scale);
renderer->SetSamplesPerPixel(pixel_samples);
}
} }
@ -253,11 +150,23 @@ void AudioRenderer::SetAudioProvider(AudioProvider *_provider)
if (provider == _provider) return; if (provider == _provider) return;
provider = _provider; provider = _provider;
bitmaps_normal->Age(0); Invalidate();
bitmaps_selected->Age(0);
if (renderer) if (renderer)
renderer->SetProvider(provider); renderer->SetProvider(provider);
ResetBlockCount();
}
void AudioRenderer::ResetBlockCount()
{
if (provider)
{
size_t num_bitmaps = (size_t)((provider->GetNumSamples() + pixel_samples - 1) / pixel_samples);
bitmaps_normal.SetBlockCount(num_bitmaps);
bitmaps_selected.SetBlockCount(num_bitmaps);
}
} }
@ -267,16 +176,18 @@ wxBitmap AudioRenderer::GetCachedBitmap(int i, bool selected)
assert(renderer); assert(renderer);
// Pick the cache to use // Pick the cache to use
AudioRendererBitmapCache *cache = (selected ? bitmaps_selected : bitmaps_normal).get(); AudioRendererBitmapCache *cache = selected ? &bitmaps_selected : &bitmaps_normal;
wxBitmap bmp; bool created = false;
if (!cache->Get(bmp, i, cache_clock)) wxBitmap *bmp = cache->Get(i, &created);
assert(bmp);
if (created)
{ {
renderer->Render(bmp, i*cache_bitmap_width, selected); renderer->Render(*bmp, i*cache_bitmap_width, selected);
} }
assert(bmp.IsOk()); assert(bmp->IsOk());
return bmp; return *bmp;
} }
@ -344,9 +255,16 @@ void AudioRenderer::Render(wxDC &dc, wxPoint origin, int start, int length, bool
} }
if (selected) if (selected)
bitmaps_selected->Age(cache_maxsize); bitmaps_selected.Age(cache_maxsize);
else else
bitmaps_normal->Age(cache_maxsize); bitmaps_normal.Age(cache_maxsize);
}
void AudioRenderer::Invalidate()
{
bitmaps_normal.Age(0);
bitmaps_selected.Age(0);
} }
@ -371,3 +289,13 @@ void AudioRendererBitmapProvider::SetSamplesPerPixel(int _pixel_samples)
OnSetSamplesPerPixel(); OnSetSamplesPerPixel();
} }
void AudioRendererBitmapProvider::SetAmplitudeScale(float _amplitude_scale)
{
if (amplitude_scale == _amplitude_scale) return;
amplitude_scale = _amplitude_scale;
OnSetAmplitudeScale();
}

View file

@ -37,17 +37,51 @@
#include <wx/dc.h> #include <wx/dc.h>
#include <wx/image.h>
#include <wx/gdicmn.h> #include <wx/gdicmn.h>
#include <memory> #include <memory>
#include "block_cache.h"
// Some forward declarations for outside stuff // Some forward declarations for outside stuff
class AudioProvider; class AudioProvider;
// Forwards declarations for internal stuff // Forwards declarations for internal stuff
class AudioRendererBitmapCache;
class AudioRendererBitmapProvider; class AudioRendererBitmapProvider;
class AudioRenderer;
/// @class AudioRendererBitmapCacheBitmapFactory
/// @brief Produces wxBitmap objects for DataBlockCache storage for the audio renderer
struct AudioRendererBitmapCacheBitmapFactory {
/// The audio renderer we're producing bitmaps for
AudioRenderer *renderer;
/// @brief Constructor
/// @param renderer The audio renderer to produce bitmaps for
AudioRendererBitmapCacheBitmapFactory(AudioRenderer *renderer);
/// @brief Create a new bitmap
/// @param i Unused
/// @return A fresh wxBitmap
///
/// Produces a wxBitmap with dimensions pulled from our master AudioRenderer.
wxBitmap *ProduceBlock(int i);
/// @brief Delete a bitmap
/// @param bmp The bitmap to delete
///
/// Deletes said bitmap.
void DisposeBlock(wxBitmap *bmp);
/// @brief Calculate the size of bitmaps
/// @return The size of bitmaps created
size_t GetBlockSize() const;
};
/// The type of a bitmap cache
typedef DataBlockCache<wxBitmap, 8, AudioRendererBitmapCacheBitmapFactory> AudioRendererBitmapCache;
/// @class AudioRenderer /// @class AudioRenderer
@ -57,20 +91,22 @@ class AudioRendererBitmapProvider;
/// ///
/// To implement a new audio renderer, see AudioRendererBitmapProvider. /// To implement a new audio renderer, see AudioRendererBitmapProvider.
class AudioRenderer { class AudioRenderer {
friend AudioRendererBitmapCacheBitmapFactory;
/// Horizontal zoom level, samples per pixel /// Horizontal zoom level, samples per pixel
int pixel_samples; int pixel_samples;
/// Vertical zoom level, height in pixels /// Rendering height in pixels
int pixel_height; int pixel_height;
/// Vertical zoom level/amplitude scale
float amplitude_scale;
/// Width of bitmaps to store in cache /// Width of bitmaps to store in cache
const int cache_bitmap_width; const int cache_bitmap_width;
/// Cached bitmaps for normal audio ranges /// Cached bitmaps for normal audio ranges
std::auto_ptr<AudioRendererBitmapCache> bitmaps_normal; AudioRendererBitmapCache bitmaps_normal;
/// Cached bitmaps for marked (selected) audio ranges /// Cached bitmaps for marked (selected) audio ranges
std::auto_ptr<AudioRendererBitmapCache> bitmaps_selected; AudioRendererBitmapCache bitmaps_selected;
/// The "clock" used for cache aging, increased on each render operation
int cache_clock;
/// The maximum allowed size of the cache, in bytes /// The maximum allowed size of the cache, in bytes
size_t cache_maxsize; size_t cache_maxsize;
@ -89,6 +125,13 @@ class AudioRenderer {
/// if the cache doesn't have it. /// if the cache doesn't have it.
wxBitmap GetCachedBitmap(int i, bool selected); wxBitmap GetCachedBitmap(int i, bool selected);
/// @brief Update the block count in the bitmap caches
///
/// Should be called when the width of the virtual bitmap has changed, i.e.
/// when the samples-per-pixel resolution or the number of audio samples
/// has changed.
void ResetBlockCount();
public: public:
/// @brief Constructor /// @brief Constructor
/// ///
@ -108,20 +151,35 @@ public:
/// Changing the zoom level invalidates all cached bitmaps. /// Changing the zoom level invalidates all cached bitmaps.
void SetSamplesPerPixel(int pixel_samples); void SetSamplesPerPixel(int pixel_samples);
/// @brief Set vertical zoom /// @brief Set rendering height
/// @param pixel_height Height in pixels to render at /// @param pixel_height Height in pixels to render at
/// ///
/// Changing the zoom level invalidates all cached bitmaps. /// Changing the rendering height invalidates all cached bitmaps.
void SetHeight(int pixel_height); void SetHeight(int pixel_height);
/// @brief Set vertical zoom
/// @param amplitude_scale Scaling factor
///
/// Changing the scaling factor invalidates all cached bitmaps.
///
/// A scaling factor of 1.0 is no scaling, a factor of 0.5 causes the audio to be
/// rendered as if it had half its actual amplitude, a factor of 2 causes the audio
/// to be rendered as if it had double amplitude. (The exact meaning of the scaling
/// depends on the bitmap provider used.)
void SetAmplitudeScale(float amplitude_scale);
/// @brief Get horizontal zoom /// @brief Get horizontal zoom
/// @return Audio samples per pixel rendering at /// @return Audio samples per pixel rendering at
int GetSamplesPerPixel() const { return pixel_samples; } int GetSamplesPerPixel() const { return pixel_samples; }
/// @brief Get vertical zoom /// @brief Get rendering height
/// @return Height in pixels rendering at /// @return Height in pixels rendering at
int GetHeight() const { return pixel_height; } int GetHeight() const { return pixel_height; }
/// @brief Get vertical zoom
/// @return The amplitude scaling factor
float GetAmplitudeScale() const { return amplitude_scale; }
/// @brief Change renderer /// @brief Change renderer
/// @param renderer New renderer to use /// @param renderer New renderer to use
/// ///
@ -166,6 +224,16 @@ public:
/// The first audio sample rendered is start*pixel_samples, and the number /// The first audio sample rendered is start*pixel_samples, and the number
/// of audio samples rendered is length*pixel_samples. /// of audio samples rendered is length*pixel_samples.
void Render(wxDC &dc, wxPoint origin, int start, int length, bool selected); void Render(wxDC &dc, wxPoint origin, int start, int length, bool selected);
/// @brief Invalidate all cached data
///
/// Invalidates all cached bitmaps for another reason, usually as a signal that
/// implementation-defined data in the bitmap provider have been changed.
///
/// If the consumer of audio rendering changes properties of the bitmap renderer
/// that will affect the rendered images, it should call this function to ensure
/// the cache is kept consistent.
void Invalidate();
}; };
@ -179,6 +247,8 @@ protected:
AudioProvider *provider; AudioProvider *provider;
/// Horizontal zoom in samples per pixel /// Horizontal zoom in samples per pixel
int pixel_samples; int pixel_samples;
/// Vertical zoom/amplitude scale factor
float amplitude_scale;
/// @brief Called when the audio provider changes /// @brief Called when the audio provider changes
/// ///
@ -190,6 +260,11 @@ protected:
/// Implementations can override this method to do something when the horizontal zoom is changed /// Implementations can override this method to do something when the horizontal zoom is changed
virtual void OnSetSamplesPerPixel() { } virtual void OnSetSamplesPerPixel() { }
/// @brief Called when vertical zoom changes
///
/// Implementations can override this method to do something when the vertical zoom is changed
virtual void OnSetAmplitudeScale() { }
public: public:
/// @brief Constructor /// @brief Constructor
AudioRendererBitmapProvider() : provider(0), pixel_samples(0) { }; AudioRendererBitmapProvider() : provider(0), pixel_samples(0) { };
@ -209,7 +284,12 @@ public:
/// @brief Change audio provider /// @brief Change audio provider
/// @param provider Audio provider to change to /// @param provider Audio provider to change to
void SetProvider(AudioProvider *provider); void SetProvider(AudioProvider *provider);
/// @brief Change horizontal zoom /// @brief Change horizontal zoom
/// @param pixel_samples Samples per pixel to zoom to /// @param pixel_samples Samples per pixel to zoom to
void SetSamplesPerPixel(int pixel_samples); void SetSamplesPerPixel(int pixel_samples);
/// @brief Change vertical zoom
/// @param amplitude_scale Scaling factor to zoom to
void SetAmplitudeScale(float amplitude_scale);
}; };

View file

@ -40,6 +40,7 @@
#include <vector> #include <vector>
#include <algorithm>
@ -104,8 +105,11 @@ class DataBlockCache {
/// Type of an array of blocks /// Type of an array of blocks
typedef std::vector<BlockT*> BlockArray; typedef std::vector<BlockT*> BlockArray;
/// DOCME
struct MacroBlock { struct MacroBlock {
/// How many times data in the macroblock has been accessed
int access_count; int access_count;
/// The blocks contained in the macroblock
BlockArray blocks; BlockArray blocks;
}; };
@ -131,7 +135,7 @@ class DataBlockCache {
MacroBlock &mb = data[mb_index]; MacroBlock &mb = data[mb_index];
mb.access_count = 0; mb.access_count = 0;
for (size_t bi = 0; bi < blocks.size(); ++bi) for (size_t bi = 0; bi < mb.blocks.size(); ++bi)
{ {
BlockT *b = mb.blocks[bi]; BlockT *b = mb.blocks[bi];
if (!b) if (!b)
@ -153,11 +157,7 @@ public:
DataBlockCache(size_t block_count, BlockFactoryT factory = BlockFactoryT()) DataBlockCache(size_t block_count, BlockFactoryT factory = BlockFactoryT())
: factory(factory) : factory(factory)
{ {
macroblock_size = 1 << MacroblockExponent; SetBlockCount(block_count);
macroblock_index_mask = ~(((~0) >> MacroblockExponent) << MacroblockExponent);
data.resize( (block_count + macroblock_size - 1) >> MacroblockExponent );
} }
@ -171,6 +171,23 @@ public:
} }
/// @brief Change the number of blocks in cache
/// @param block_count New number of blocks to hold
///
/// This will completely de-allocate the cache and re-allocate it with the new block count.
void SetBlockCount(size_t block_count)
{
if (data.size() > 0)
Age(0);
macroblock_size = 1 << MacroblockExponent;
macroblock_index_mask = ~(((~0) >> MacroblockExponent) << MacroblockExponent);
data.resize( (block_count + macroblock_size - 1) >> MacroblockExponent );
}
/// @brief Clean up the cache /// @brief Clean up the cache
/// @param max_size Target maximum size of the cache in bytes /// @param max_size Target maximum size of the cache in bytes
/// ///
@ -202,7 +219,7 @@ public:
access_data.reserve(data.size()); access_data.reserve(data.size());
for (MacroBlockArray::iterator mb = data.begin(); mb != data.end(); ++mb) for (MacroBlockArray::iterator mb = data.begin(); mb != data.end(); ++mb)
access_data.push_back(AccessData(mb)); access_data.push_back(AccessData(mb));
access_data.sort(); std::sort(access_data.begin(), access_data.end());
// Sum up data size until we hit the max // Sum up data size until we hit the max
size_t cur_size = 0; size_t cur_size = 0;