forked from mia/Aegisub
Change AudioRenderer to use DataBlockCache.
Originally committed to SVN as r3414.
This commit is contained in:
parent
d29d267cce
commit
e9e3b2d107
3 changed files with 196 additions and 171 deletions
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue