Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor WaveformPreview: Optimize texture handling and improve performance #34

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 129 additions & 141 deletions Editor/Mono/Audio/WaveformPreview.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Unity C# reference source
// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

Expand All @@ -12,40 +12,40 @@ class WaveformPreview : IDisposable
static int s_BaseTextureWidth = 4096;
static Material s_Material;

public double start { get { return m_Start; } }
public double length { get { return m_Length; } }
public double Start { get; private set; }
public double Length { get; private set; }

public Color backgroundColor { get; set; }
public Color waveColor { get; set; }
public event Action updated;
public UnityEngine.Object presentedObject;
public Color BackgroundColor { get; set; }
public Color WaveColor { get; set; }
public event Action Updated;
public UnityEngine.Object PresentedObject { get; private set; }

public bool optimized
public bool Optimized
{
get { return m_Optimized; }
get => _optimized;
set
{
if (m_Optimized != value)
if (_optimized != value)
{
if (value)
m_Dirty = true;
_dirty = true;

m_Optimized = value;
m_Flags |= MessageFlags.Optimization;
_optimized = value;
SetFlag(MessageFlags.Optimization, true);
}
}
}

public bool looping
public bool Looping
{
get { return m_Looping; }
get => _looping;
set
{
if (m_Looping != value)
if (_looping != value)
{
m_Dirty = true;
m_Looping = value;
m_Flags |= MessageFlags.Looping;
_dirty = true;
_looping = value;
SetFlag(MessageFlags.Looping, true);
}
}
}
Expand All @@ -70,72 +70,66 @@ protected enum MessageFlags
Looping = 1 << 6
}

protected static bool HasFlag(MessageFlags flags, MessageFlags test)
private double _start;
private double _length;
private bool _clearTexture = true;
private Vector2 _size;
private Texture2D _texture;
private int _channels;
private int _samples;
private int _specificChannel;
private ChannelMode _channelMode;

private bool _looping;
private bool _optimized;
private bool _dirty;
private bool _disposed;
private MessageFlags _flags;

protected WaveformPreview(UnityEngine.Object presentedObject, int sampleCount, int channels)
{
return (flags & test) != 0;
PresentedObject = presentedObject;
Optimized = true;
_samples = sampleCount;
_channels = channels;
BackgroundColor = new Color(40 / 255.0f, 40 / 255.0f, 40 / 255.0f, 1.0f);
WaveColor = new Color(255.0f / 255.0f, 140.0f / 255.0f, 0 / 255.0f, 1.0f);
UpdateTexture(sampleCount, channels);
}

protected double m_Start;
protected double m_Length;
protected bool m_ClearTexture = true;
protected Vector2 Size { get { return m_Size; } }

Texture2D m_Texture;
Vector2 m_Size;
int m_Channels;
int m_Samples;
int m_SpecificChannel;
ChannelMode m_ChannelMode;

bool m_Looping;
bool m_Optimized;
bool m_Dirty;
bool m_Disposed;
MessageFlags m_Flags;

protected WaveformPreview(UnityEngine.Object presentedObject, int samplesAndWidth, int channels)
protected internal WaveformPreview(UnityEngine.Object presentedObject, int sampleCount, int channels, bool deferTextureCreation)
{
this.presentedObject = presentedObject;
optimized = true;
m_Samples = samplesAndWidth;
m_Channels = channels;
backgroundColor = new Color(40 / 255.0f, 40 / 255.0f, 40 / 255.0f, 1.0f);
waveColor = new Color(255.0f / 255.0f, 140.0f / 255.0f, 0 / 255.0f, 1.0f);
UpdateTexture(samplesAndWidth, channels);
}
PresentedObject = presentedObject;
Optimized = true;
_samples = sampleCount;
_channels = channels;
BackgroundColor = new Color(40 / 255.0f, 40 / 255.0f, 40 / 255.0f, 1.0f);
WaveColor = new Color(255.0f / 255.0f, 140.0f / 255.0f, 0 / 255.0f, 1.0f);

protected internal WaveformPreview(UnityEngine.Object presentedObject, int samplesAndWidth, int channels, bool deferTextureCreation)
{
this.presentedObject = presentedObject;
optimized = true;
m_Samples = samplesAndWidth;
m_Channels = channels;
backgroundColor = new Color(40 / 255.0f, 40 / 255.0f, 40 / 255.0f, 1.0f);
waveColor = new Color(255.0f / 255.0f, 140.0f / 255.0f, 0 / 255.0f, 1.0f);
if (!deferTextureCreation)
UpdateTexture(samplesAndWidth, channels);
UpdateTexture(sampleCount, channels);
}

public void Dispose()
{
if (!m_Disposed)
if (!_disposed)
{
m_Disposed = true;
_disposed = true;
InternalDispose();

if (m_Texture != null)
if (_texture != null)
{
if (Application.isPlaying)
UnityEngine.Object.Destroy(m_Texture);
UnityEngine.Object.Destroy(_texture);
else
UnityEngine.Object.DestroyImmediate(m_Texture);
UnityEngine.Object.DestroyImmediate(_texture);
}

m_Texture = null;
_texture = null;
}
}

protected virtual void InternalDispose() {}
protected virtual void InternalDispose() { }

public void Render(Rect rect)
{
Expand All @@ -144,135 +138,129 @@ public void Render(Rect rect)
s_Material = EditorGUIUtility.LoadRequired("Previews/PreviewAudioClipWaveform.mat") as Material;
}

s_Material.SetTexture("_WavTex", m_Texture);
s_Material.SetFloat("_SampCount", m_Samples);
s_Material.SetFloat("_ChanCount", m_Channels);
s_Material.SetTexture("_WavTex", _texture);
s_Material.SetFloat("_SampCount", _samples);
s_Material.SetFloat("_ChanCount", _channels);
s_Material.SetFloat("_RecPixelSize", 1.0f / rect.height);
s_Material.SetColor("_BacCol", backgroundColor);
s_Material.SetColor("_ForCol", waveColor);
s_Material.SetColor("_BacCol", BackgroundColor);
s_Material.SetColor("_ForCol", WaveColor);

int mode = -2;

if (m_ChannelMode == ChannelMode.Separate)
mode = -1;
else if (m_ChannelMode == ChannelMode.SpecificChannel)
mode = m_SpecificChannel;
int mode = _channelMode switch
{
ChannelMode.Separate => -1,
ChannelMode.SpecificChannel => _specificChannel,
_ => -2
};

s_Material.SetFloat("_ChanDrawMode", (float)mode);
s_Material.SetFloat("_ChanDrawMode", mode);

Graphics.DrawTexture(rect, m_Texture, s_Material);
Graphics.DrawTexture(rect, _texture, s_Material);
}

public bool ApplyModifications()
{
if (m_Dirty || m_Texture == null)
if (_dirty || _texture == null)
{
m_Flags |= UpdateTexture((int)m_Size.x, m_Channels) ? MessageFlags.TextureChanged : MessageFlags.None;
OnModifications(m_Flags);
m_Flags = MessageFlags.None;
SetFlag(UpdateTexture((int)_size.x, _channels) ? MessageFlags.TextureChanged : MessageFlags.None, true);
OnModifications(_flags);
_flags = MessageFlags.None;

m_Texture.Apply();
m_Dirty = false;
_texture.Apply();
_dirty = false;
return true;
}

return false;
}

public void SetChannelMode(ChannelMode mode, int specificChannelToRender)
{
m_ChannelMode = mode;
m_SpecificChannel = specificChannelToRender;
}

public void SetChannelMode(ChannelMode mode)
public void SetChannelMode(ChannelMode mode, int specificChannelToRender = 0)
{
SetChannelMode(mode, 0);
_channelMode = mode;
_specificChannel = specificChannelToRender;
}

bool UpdateTexture(int width, int channels)
private bool UpdateTexture(int width, int channels)
{
int widthWithChannels = width * channels;
int textureHeight = 1 + widthWithChannels / s_BaseTextureWidth;

Action<bool> updateTexture =
clear =>
{
if (m_Texture == null)
{
m_Texture = new Texture2D(s_BaseTextureWidth, textureHeight, TextureFormat.RGBAHalf, false, true);
m_Texture.filterMode = FilterMode.Point;
clear = false;
}
else
{
m_Texture.Reinitialize(s_BaseTextureWidth, textureHeight);
}
EnsureTextureExists(textureHeight);

if (!clear)
return;
if (width == _samples && channels == _channels && _texture != null)
return false;

var fillColorArray = m_Texture.GetPixels();
for (var i = 0; i < fillColorArray.Length; ++i)
fillColorArray[i] = Color.black;
_samples = width;
_channels = channels;

m_Texture.SetPixels(fillColorArray);
};
_dirty = true;
return true;
}

if (width == m_Samples && channels == m_Channels && m_Texture != null)
private void EnsureTextureExists(int textureHeight)
{
if (_texture == null)
{
return false;
_texture = new Texture2D(s_BaseTextureWidth, textureHeight, TextureFormat.RGBAHalf, false, true)
{
filterMode = FilterMode.Point
};
}
else
{
_texture.Reinitialize(s_BaseTextureWidth, textureHeight);
}

// resample texture here
updateTexture(m_ClearTexture);

m_Samples = width;
m_Channels = channels;

return m_Dirty = true;
}

public void OptimizeForSize(Vector2 newSize)
{
newSize = new Vector2(Mathf.Ceil(newSize.x), Mathf.Ceil(newSize.y));

if (newSize.x != m_Size.x)
if (newSize.x != _size.x)
{
m_Size = newSize;
m_Flags |= MessageFlags.Size;
m_Dirty = true;
_size = newSize;
SetFlag(MessageFlags.Size, true);
_dirty = true;
}
}

protected virtual void OnModifications(MessageFlags changedFlags) {}
protected virtual void OnModifications(MessageFlags changedFlags) { }

public void SetTimeInfo(double start, double length)
{
if (start != m_Start || length != m_Length)
if (start != Start || length != Length)
{
m_Start = start;
m_Length = length;
m_Dirty = true;
m_Flags |= MessageFlags.Start | MessageFlags.Length;
Start = start;
Length = length;
_dirty = true;
SetFlag(MessageFlags.Start | MessageFlags.Length, true);
}
}

public virtual void SetMMWaveData(int interleavedOffset, float[] data)
{
// could be optimized, but profiling shows it isn't the bottleneck at all
for (int i = 0; i < data.Length; interleavedOffset++, i += 2)
Color[] colors = new Color[data.Length / 2];

for (int i = 0, j = 0; i < data.Length; i += 2, j++)
{
int x = interleavedOffset % s_BaseTextureWidth;
int y = interleavedOffset / s_BaseTextureWidth;
m_Texture.SetPixel(x, y, new Color(data[i], data[i + 1], 0.0f, 0.0f));
colors[j] = new Color(data[i], data[i + 1], 0.0f, 0.0f);
}

m_Dirty = true;
m_Flags |= MessageFlags.ContentsChanged;
int x = interleavedOffset % s_BaseTextureWidth;
int y = interleavedOffset / s_BaseTextureWidth;
_texture.SetPixels(x, y, colors.Length, 1, colors);

_dirty = true;
SetFlag(MessageFlags.ContentsChanged, true);

if (updated != null)
updated();
Updated?.Invoke();
}

private void SetFlag(MessageFlags flag, bool value)
{
if (value)
_flags |= flag;
else
_flags &= ~flag;
}
}
}