Skip to content

Commit

Permalink
Merge pull request #5843 from peppy/fix-long-waveform-performance
Browse files Browse the repository at this point in the history
Fix performance when viewing the tail end of a long / zoomed-in waveform
  • Loading branch information
bdach committed Jun 17, 2023
2 parents 7015842 + 1fe574a commit 7fac588
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 22 deletions.
26 changes: 24 additions & 2 deletions osu.Framework.Tests/Visual/Drawables/TestSceneWaveform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ public partial class TestSceneWaveform : FrameworkTestScene
private Track track;
private Waveform waveform;
private Container<Drawable> waveformContainer;
private readonly Bindable<float> zoom = new BindableFloat(1) { MinValue = 0.1f, MaxValue = 20 };
private readonly BindableFloat zoom = new BindableFloat(1) { MinValue = 0.1f, MaxValue = 2000 };

private ScrollContainer<Drawable> scroll;

private ITrackStore store;

Expand Down Expand Up @@ -75,7 +77,7 @@ private void load(Game game, AudioManager audio)
},
},
},
new BasicScrollContainer(Direction.Horizontal)
scroll = new BasicScrollContainer(Direction.Horizontal)
{
RelativeSizeAxes = Axes.Both,
Child = waveformContainer = new FillFlowContainer
Expand Down Expand Up @@ -108,6 +110,26 @@ public void SetUpSteps()
AddStep("Load stereo track", () => loadTrack(true));
}

/// <summary>
/// When zooming in very close – or even zooming in a normal amount on a very long track – the number of points in the waveform
/// can become very high (in the millions).
///
/// In this case, we need to be careful no iteration is performed over the point data. This tests the case of being scrolled to the
/// far end of the waveform, which is the worse-case-scenario and requires special consideration.
/// </summary>
[Test]
public void TestHighZoomEndOfTrackPerformance()
{
TestWaveform graph = null;

AddStep("create waveform", () => waveformContainer.Child = graph = new TestWaveform(track, 1) { Waveform = waveform });
AddUntilStep("wait for load", () => graph.Regenerated);

AddStep("set zoom to highest", () => zoom.Value = zoom.MaxValue);

AddStep("seek to end", () => scroll.ScrollToEnd());
}

[Test]
public void TestMonoTrack()
{
Expand Down
37 changes: 17 additions & 20 deletions osu.Framework/Graphics/Audio/WaveformGraph.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable disable

using System;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -29,8 +27,8 @@ namespace osu.Framework.Graphics.Audio
/// </summary>
public partial class WaveformGraph : Drawable
{
private IShader shader;
private Texture texture;
private IShader shader = null!;
private Texture texture = null!;

[BackgroundDependencyLoader]
private void load(ShaderManager shaders, IRenderer renderer)
Expand Down Expand Up @@ -61,12 +59,12 @@ public float Resolution
}
}

private Waveform waveform;
private Waveform? waveform;

/// <summary>
/// The <see cref="Framework.Audio.Track.Waveform"/> to display.
/// </summary>
public Waveform Waveform
public Waveform? Waveform
{
get => waveform;
set
Expand Down Expand Up @@ -174,10 +172,10 @@ protected override bool OnInvalidate(Invalidation invalidation, InvalidationSour
return result;
}

private CancellationTokenSource cancelSource = new CancellationTokenSource();
private CancellationTokenSource? cancelSource = new CancellationTokenSource();

private long resampledVersion;
private Waveform.Point[] resampledPoints;
private Waveform.Point[]? resampledPoints;
private int? resampledPointCount;
private double resampledMaxHighIntensity;
private double resampledMaxMidIntensity;
Expand All @@ -186,7 +184,7 @@ protected override bool OnInvalidate(Invalidation invalidation, InvalidationSour
private void queueRegeneration() => Scheduler.AddOnce(() =>
{
int requiredPointCount = (int)Math.Max(0, Math.Ceiling(DrawWidth * Scale.X) * Resolution);
if (requiredPointCount == resampledPointCount && !cancelSource.IsCancellationRequested)
if (requiredPointCount == resampledPointCount && cancelSource?.IsCancellationRequested != false)
return;
cancelGeneration();
Expand Down Expand Up @@ -259,10 +257,10 @@ protected override void Dispose(bool isDisposing)

private class WaveformDrawNode : DrawNode
{
private IShader shader;
private Texture texture;
private IShader shader = null!;
private Texture? texture;

private List<Waveform.Point> points;
private List<Waveform.Point>? points;

private Vector2 drawSize;

Expand Down Expand Up @@ -319,7 +317,7 @@ public override void ApplyState()
}
}

private IVertexBatch<TexturedVertex2D> vertexBatch;
private IVertexBatch<TexturedVertex2D>? vertexBatch;

public override void Draw(IRenderer renderer)
{
Expand All @@ -342,17 +340,16 @@ public override void Draw(IRenderer renderer)

float separation = drawSize.X / (points.Count - 1);

for (int i = 0; i < points.Count - 1; i++)
// Equates to making sure that rightX >= localMaskingRectangle.Left at startIndex and leftX <= localMaskingRectangle.Right at endIndex.
// Without this pre-check, very long waveform displays can get slow just from running the loop below (point counts in excess of 1mil).
int startIndex = (int)Math.Clamp(localMaskingRectangle.Left / separation, 0, points.Count - 1);
int endIndex = (int)Math.Clamp(localMaskingRectangle.Right / separation + 1, 0, points.Count - 1);

for (int i = startIndex; i < endIndex; i++)
{
float leftX = i * separation;
float rightX = (i + 1) * separation;

if (rightX < localMaskingRectangle.Left)
continue;

if (leftX > localMaskingRectangle.Right)
break; // X is always increasing

Color4 frequencyColour = baseColour;

// colouring is applied in the order of interest to a viewer.
Expand Down

0 comments on commit 7fac588

Please sign in to comment.