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 "include/aegisub/audio_provider.h"
#include <wx/bitmap.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);
};
AudioRendererBitmapCache::AudioRendererBitmapCache()
AudioRendererBitmapCacheBitmapFactory::AudioRendererBitmapCacheBitmapFactory(AudioRenderer *_renderer)
{
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)
{
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);
}
delete bmp;
}
void AudioRendererBitmapCache::Resize(int new_width, int new_height)
size_t AudioRendererBitmapCacheBitmapFactory::GetBlockSize() const
{
Age(0);
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;
return sizeof(wxBitmap) + renderer->cache_bitmap_width * renderer->pixel_height * 4;
}
@ -197,9 +71,8 @@ bool AudioRendererBitmapCache::Get(wxBitmap &bmp, int key, int timestamp)
AudioRenderer::AudioRenderer()
: cache_bitmap_width(32) // arbitrary value for now
, bitmaps_normal(new AudioRendererBitmapCache)
, bitmaps_selected(new AudioRendererBitmapCache)
, cache_clock(0)
, bitmaps_normal(256, AudioRendererBitmapCacheBitmapFactory(this))
, bitmaps_selected(256, AudioRendererBitmapCacheBitmapFactory(this))
, cache_maxsize(0)
, renderer(0)
, provider(0)
@ -218,7 +91,14 @@ AudioRenderer::~AudioRenderer()
void AudioRenderer::SetSamplesPerPixel(int _pixel_samples)
{
if (pixel_samples == _pixel_samples) return;
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;
pixel_height = _pixel_height;
bitmaps_normal->Resize(cache_bitmap_width, pixel_height);
bitmaps_selected->Resize(cache_bitmap_width, pixel_height);
Invalidate();
}
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 = _renderer;
bitmaps_normal->Age(0);
bitmaps_selected->Age(0);
Invalidate();
if (renderer)
{
renderer->SetProvider(provider);
renderer->SetAmplitudeScale(amplitude_scale);
renderer->SetSamplesPerPixel(pixel_samples);
}
}
@ -253,11 +150,23 @@ void AudioRenderer::SetAudioProvider(AudioProvider *_provider)
if (provider == _provider) return;
provider = _provider;
bitmaps_normal->Age(0);
bitmaps_selected->Age(0);
Invalidate();
if (renderer)
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);
// Pick the cache to use
AudioRendererBitmapCache *cache = (selected ? bitmaps_selected : bitmaps_normal).get();
AudioRendererBitmapCache *cache = selected ? &bitmaps_selected : &bitmaps_normal;
wxBitmap bmp;
if (!cache->Get(bmp, i, cache_clock))
bool created = false;
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());
return bmp;
assert(bmp->IsOk());
return *bmp;
}
@ -344,9 +255,16 @@ void AudioRenderer::Render(wxDC &dc, wxPoint origin, int start, int length, bool
}
if (selected)
bitmaps_selected->Age(cache_maxsize);
bitmaps_selected.Age(cache_maxsize);
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();
}
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/image.h>
#include <wx/gdicmn.h>
#include <memory>
#include "block_cache.h"
// Some forward declarations for outside stuff
class AudioProvider;
// Forwards declarations for internal stuff
class AudioRendererBitmapCache;
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
@ -57,20 +91,22 @@ class AudioRendererBitmapProvider;
///
/// To implement a new audio renderer, see AudioRendererBitmapProvider.
class AudioRenderer {
friend AudioRendererBitmapCacheBitmapFactory;
/// Horizontal zoom level, samples per pixel
int pixel_samples;
/// Vertical zoom level, height in pixels
/// Rendering height in pixels
int pixel_height;
/// Vertical zoom level/amplitude scale
float amplitude_scale;
/// Width of bitmaps to store in cache
const int cache_bitmap_width;
/// Cached bitmaps for normal audio ranges
std::auto_ptr<AudioRendererBitmapCache> bitmaps_normal;
AudioRendererBitmapCache bitmaps_normal;
/// Cached bitmaps for marked (selected) audio ranges
std::auto_ptr<AudioRendererBitmapCache> bitmaps_selected;
/// The "clock" used for cache aging, increased on each render operation
int cache_clock;
AudioRendererBitmapCache bitmaps_selected;
/// The maximum allowed size of the cache, in bytes
size_t cache_maxsize;
@ -89,6 +125,13 @@ class AudioRenderer {
/// if the cache doesn't have it.
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:
/// @brief Constructor
///
@ -108,20 +151,35 @@ public:
/// Changing the zoom level invalidates all cached bitmaps.
void SetSamplesPerPixel(int pixel_samples);
/// @brief Set vertical zoom
/// @brief Set rendering height
/// @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);
/// @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
/// @return Audio samples per pixel rendering at
int GetSamplesPerPixel() const { return pixel_samples; }
/// @brief Get vertical zoom
/// @brief Get rendering height
/// @return Height in pixels rendering at
int GetHeight() const { return pixel_height; }
/// @brief Get vertical zoom
/// @return The amplitude scaling factor
float GetAmplitudeScale() const { return amplitude_scale; }
/// @brief Change renderer
/// @param renderer New renderer to use
///
@ -166,6 +224,16 @@ public:
/// The first audio sample rendered is start*pixel_samples, and the number
/// of audio samples rendered is length*pixel_samples.
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;
/// Horizontal zoom in samples per pixel
int pixel_samples;
/// Vertical zoom/amplitude scale factor
float amplitude_scale;
/// @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
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:
/// @brief Constructor
AudioRendererBitmapProvider() : provider(0), pixel_samples(0) { };
@ -209,7 +284,12 @@ public:
/// @brief Change audio provider
/// @param provider Audio provider to change to
void SetProvider(AudioProvider *provider);
/// @brief Change horizontal zoom
/// @param pixel_samples Samples per pixel to zoom to
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 <algorithm>
@ -104,8 +105,11 @@ class DataBlockCache {
/// Type of an array of blocks
typedef std::vector<BlockT*> BlockArray;
/// DOCME
struct MacroBlock {
/// How many times data in the macroblock has been accessed
int access_count;
/// The blocks contained in the macroblock
BlockArray blocks;
};
@ -131,7 +135,7 @@ class DataBlockCache {
MacroBlock &mb = data[mb_index];
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];
if (!b)
@ -153,11 +157,7 @@ public:
DataBlockCache(size_t block_count, BlockFactoryT factory = BlockFactoryT())
: factory(factory)
{
macroblock_size = 1 << MacroblockExponent;
macroblock_index_mask = ~(((~0) >> MacroblockExponent) << MacroblockExponent);
data.resize( (block_count + macroblock_size - 1) >> MacroblockExponent );
SetBlockCount(block_count);
}
@ -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
/// @param max_size Target maximum size of the cache in bytes
///
@ -202,7 +219,7 @@ public:
access_data.reserve(data.size());
for (MacroBlockArray::iterator mb = data.begin(); mb != data.end(); ++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
size_t cur_size = 0;