Skip to content
Open
Show file tree
Hide file tree
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
155 changes: 155 additions & 0 deletions osu.Framework/Graphics/Containers/CullableFlowContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;

namespace osu.Framework.Graphics.Containers
{
/// <summary>
/// This is vertical only
/// </summary>
/// <typeparam name="T"></typeparam>
public partial class CullableFlowContainer<T> : Container<T> where T : Drawable
{
public ScrollContainer<Drawable>? ScrollContainer { get; set; }

public float Spacing { get; set; } = 8;
public float CullPadding { get; set; } = 250;

public float ItemSize { get; set; } = 40;

private readonly List<T> items = new();
private readonly HashSet<T> filteredItems = new();
public readonly HashSet<T> VisibleItems = new();

public IEnumerable<T> Items => items;

public CullableFlowContainer()
{
// TODO: Maybe make one that supports having a Direction prop in the future

// do NOT use AutoSizeAxes = Axes.Y
// we manually compute the height
RelativeSizeAxes = Axes.X;
}

public void Sort(Comparison<T> comparison) => items.Sort(comparison);

public void SetFiltered(T drawable, bool filtered)
{
if (filtered)
{
filteredItems.Add(drawable);

if (VisibleItems.Contains(drawable))
{
base.Remove(drawable, false);
VisibleItems.Remove(drawable);
}
}
else
{
filteredItems.Remove(drawable);
}
}

public override void Add(T drawable) => items.Add(drawable);

public new void AddRange(IEnumerable<T> drawables) => items.AddRange(drawables);

public override bool Remove(T drawable, bool disposeImmediately)
{
items.Remove(drawable);
filteredItems.Remove(drawable);

if (VisibleItems.Contains(drawable))
{
VisibleItems.Remove(drawable);
return base.Remove(drawable, disposeImmediately);
}

if (disposeImmediately) drawable.Dispose();
return true;
}

public override void Clear(bool disposeChildren)
{
if (disposeChildren)
{
foreach (var item in items)
item.Dispose();
}

items.Clear();
filteredItems.Clear();
VisibleItems.Clear();
base.Clear(false);
}

private ScrollContainer<Drawable>? getScrollContainer()
{
if (ScrollContainer != null) return ScrollContainer;

CompositeDrawable parent = Parent;

while (parent != null)
{
if (parent is ScrollContainer<Drawable> scroll)
return ScrollContainer = scroll;

parent = parent.Parent;
}

return null;
}

protected override void Update()
{
base.Update();

var scroll = getScrollContainer();
if (scroll == null) return;

float current = (float)scroll.Current;
float scrollHeight = scroll.DrawHeight;

float pos = 0f;

for (int i = 0; i < items.Count; i++)
{
var item = items[i];

if (filteredItems.Contains(item))
continue;

// ItemSize here is only a fallback
float size = item.DrawHeight > 0 ? item.DrawHeight : (item.Height > 0 ? item.Height : ItemSize);

item.Y = pos;
pos += size + Spacing;

bool inBounds = item.Y + size >= current - CullPadding &&
item.Y <= current + scrollHeight + CullPadding;

if (inBounds)
{
if (!VisibleItems.Contains(item))
{
base.Add(item);
VisibleItems.Add(item);
}
}
else
{
if (VisibleItems.Contains(item))
{
base.Remove(item, false);
VisibleItems.Remove(item);
}
}
}

if (pos > 0) pos -= Spacing;

Height = pos;
}
}
}
6 changes: 4 additions & 2 deletions osu.Framework/Graphics/Containers/DelayedLoadUnloadWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ namespace osu.Framework.Graphics.Containers
{
public partial class DelayedLoadUnloadWrapper : DelayedLoadWrapper
{
public bool AllowUnloading { get; set; } = true;

private readonly double timeBeforeUnload;

public DelayedLoadUnloadWrapper(Func<Drawable> createContentFunction, double timeBeforeLoad = 500, double timeBeforeUnload = 1000)
Expand All @@ -30,7 +32,7 @@ public DelayedLoadUnloadWrapper(Func<Drawable> createContentFunction, double tim

private ScheduledDelegate unloadSchedule;

protected bool ShouldUnloadContent => timeBeforeUnload == 0 || timeHidden > timeBeforeUnload;
protected bool ShouldUnloadContent => AllowUnloading && (timeBeforeUnload == 0 || timeHidden > timeBeforeUnload);

private ScheduledDelegate scheduledUnloadCheckRegistration;

Expand Down Expand Up @@ -104,7 +106,7 @@ private void checkForUnload()
Debug.Assert(Content.LoadState >= LoadState.Ready);

// This code can be expensive, so only run if we haven't yet loaded.
if (IsIntersecting)
if (IsIntersecting || !AllowUnloading)
timeHidden = 0;
else
timeHidden += unloadClock.ElapsedFrameTime;
Expand Down
29 changes: 29 additions & 0 deletions osu.Framework/Graphics/Transforms/DuringTransform.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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.

using System;

namespace osu.Framework.Graphics.Transforms
{
internal class DuringTransform<T> : Transform<bool, T>
where T : class, ITransformable
{
private static ulong id;

private readonly Action<T> action;

public override string TargetMember { get; } = $"During_{System.Threading.Interlocked.Increment(ref id)}";

public DuringTransform(Action<T> action)
{
this.action = action;

StartValue = false;
EndValue = true;
}

protected override void Apply(T d, double time) => action(d);

protected override void ReadIntoStartValue(T d) { }
}
}
48 changes: 48 additions & 0 deletions osu.Framework/Graphics/Transforms/TransformSequence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,54 @@ public TransformSequence<T> Delay(double delay, params Generator[] childGenerato
return Append(childGenerators);
}

public TransformSequence<T> During(Action action)
{
ArgumentNullException.ThrowIfNull(action);

return During(_ => action());
}

public TransformSequence<T> During(Action<T> action)
{
ArgumentNullException.ThrowIfNull(action);

var transform = new DuringTransform<T>(action);

using (origin.BeginAbsoluteSequence(currentTime, false))
{
origin.PopulateTransform(transform, true, Math.Max(0, endTime - currentTime), Easing.None);

origin.AddTransform(transform);
}

Add(transform);
return this;
}

public TransformSequence<T> During(double duration, Action action)
{
ArgumentNullException.ThrowIfNull(action);

return During(duration, _ => action());
}

public TransformSequence<T> During(double duration, Action<T> action)
{
ArgumentNullException.ThrowIfNull(action);

var transform = new DuringTransform<T>(action);

using (origin.BeginAbsoluteSequence(currentTime, false))
{
origin.PopulateTransform(transform, true, duration, Easing.None);

origin.AddTransform(transform);
}

Add(transform);
return this;
}

/// <summary>
/// Registers a callback <paramref name="function"/> which is triggered once all <see cref="Transform"/>s in this
/// <see cref="TransformSequence{T}"/> complete successfully.
Expand Down
3 changes: 3 additions & 0 deletions osu.Framework/Graphics/Video/FFmpegFuncs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public unsafe class FFmpegFuncs

public delegate AVCodecContext* AvcodecAllocContext3Delegate(AVCodec* codec);

public delegate AVBufferRef* AvBufferRefDelegate(AVBufferRef* buf);

public delegate void AvcodecFreeContextDelegate(AVCodecContext** avctx);

public delegate int AvcodecParametersToContextDelegate(AVCodecContext* codec, AVCodecParameters* par);
Expand Down Expand Up @@ -120,6 +122,7 @@ public unsafe class FFmpegFuncs
public AvCodecIsDecoderDelegate av_codec_is_decoder;
public AvcodecGetHwConfigDelegate avcodec_get_hw_config;
public AvcodecAllocContext3Delegate avcodec_alloc_context3;
public AvBufferRefDelegate av_buffer_ref;
public AvcodecFreeContextDelegate avcodec_free_context;
public AvcodecParametersToContextDelegate avcodec_parameters_to_context;
public AvcodecOpen2Delegate avcodec_open2;
Expand Down
71 changes: 56 additions & 15 deletions osu.Framework/Graphics/Video/VideoDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ public unsafe class VideoDecoder : IDisposable
/// </summary>
public readonly Bindable<HardwareVideoDecoder> TargetHardwareVideoDecoders = new Bindable<HardwareVideoDecoder>();

private static readonly Dictionary<AVHWDeviceType, IntPtr> hw_device_contexts = new();

// libav-context-related
private AVFormatContext* formatContext;
private AVIOContext* ioContext;
Expand Down Expand Up @@ -111,23 +113,57 @@ public unsafe class VideoDecoder : IDisposable

static VideoDecoder()
{
if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux)
PreloadLibraries();
}

public static void PreloadLibraries()
{
string[] libraries = ["avutil", "avcodec", "avformat", "swscale"];

// FFmpeg.AutoGen doesn't load libraries as RTLD_GLOBAL, so we must load them ourselves to fix inter-library dependencies
// otherwise they would fallback to the system-installed libraries that can differ in version installed.
foreach (string name in libraries)
{
void loadVersionedLibraryGlobally(string name)
int version = FFmpeg.AutoGen.ffmpeg.LibraryVersionMap[name];

switch (RuntimeInfo.OS)
{
int version = FFmpeg.AutoGen.ffmpeg.LibraryVersionMap[name];
Library.Load($"lib{name}.so.{version}", Library.LoadFlags.RTLD_LAZY | Library.LoadFlags.RTLD_GLOBAL);
case RuntimeInfo.Platform.Linux:
Library.Load($"lib{name}.so.{version}", Library.LoadFlags.RTLD_LAZY | Library.LoadFlags.RTLD_GLOBAL);
break;

case RuntimeInfo.Platform.macOS:
NativeLibrary.Load($"{name}.{version}", RuntimeInfo.EntryAssembly, DllImportSearchPath.UseDllDirectoryForDependencies | DllImportSearchPath.SafeDirectories);
break;

case RuntimeInfo.Platform.Windows:
NativeLibrary.Load($"{name}-{version}", RuntimeInfo.EntryAssembly, DllImportSearchPath.UseDllDirectoryForDependencies | DllImportSearchPath.SafeDirectories);
break;
}
}

foreach (AVHWDeviceType hwDeviceType in Enum.GetValues<AVHWDeviceType>())
{
if (hwDeviceType == AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)
continue;

// FFmpeg.AutoGen doesn't load libraries as RTLD_GLOBAL, so we must load them ourselves to fix inter-library dependencies
// otherwise they would fallback to the system-installed libraries that can differ in version installed.
loadVersionedLibraryGlobally("avutil");
loadVersionedLibraryGlobally("avcodec");
loadVersionedLibraryGlobally("avformat");
loadVersionedLibraryGlobally("swscale");
AVBufferRef* hwDeviceCtx = null;
if (FFmpeg.AutoGen.ffmpeg.av_hwdevice_ctx_create(&hwDeviceCtx, hwDeviceType, null, null, 0) == 0)
hw_device_contexts[hwDeviceType] = (IntPtr)hwDeviceCtx;
}
}

public static void UnloadLibraries()
{
foreach (var (_, ptr) in hw_device_contexts)
{
var ctx = (AVBufferRef*)ptr;
FFmpeg.AutoGen.ffmpeg.av_buffer_unref(&ctx);
}

hw_device_contexts.Clear();
}

/// <summary>
/// Creates a new video decoder that decodes the given video file.
/// </summary>
Expand Down Expand Up @@ -418,15 +454,19 @@ private void recreateCodecContext()
// initialize hardware decode context.
if (hwDeviceType != AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)
{
int hwDeviceCreateResult = ffmpeg.av_hwdevice_ctx_create(&codecContext->hw_device_ctx, hwDeviceType, null, null, 0);

if (hwDeviceCreateResult < 0)
if (!hw_device_contexts.TryGetValue(hwDeviceType, out var cachedCtx))
{
Logger.Log($"Couldn't create hardware video decoder context {hwDeviceType} for codec {decoder.Name}: {getErrorMessage(hwDeviceCreateResult)}");
Logger.Log($"Couldn't create hardware device context found for {hwDeviceType}, skipping {decoder.Name}");
continue;
}

Logger.Log($"Successfully opened hardware video decoder context {hwDeviceType} for codec {decoder.Name}");
codecContext->hw_device_ctx = ffmpeg.av_buffer_ref((AVBufferRef*)cachedCtx);

if (codecContext->hw_device_ctx == null)
{
Logger.Log($"Failed to reference hardware device context for {hwDeviceType}, skipping {decoder.Name}");
continue;
}
}

int openCodecResult = ffmpeg.avcodec_open2(codecContext, decoder.Pointer, null);
Expand Down Expand Up @@ -873,6 +913,7 @@ protected virtual FFmpegFuncs CreateFuncs()
av_codec_is_decoder = FFmpeg.AutoGen.ffmpeg.av_codec_is_decoder,
avcodec_get_hw_config = FFmpeg.AutoGen.ffmpeg.avcodec_get_hw_config,
avcodec_alloc_context3 = FFmpeg.AutoGen.ffmpeg.avcodec_alloc_context3,
av_buffer_ref = FFmpeg.AutoGen.ffmpeg.av_buffer_ref,
avcodec_free_context = FFmpeg.AutoGen.ffmpeg.avcodec_free_context,
avcodec_parameters_to_context = FFmpeg.AutoGen.ffmpeg.avcodec_parameters_to_context,
avcodec_open2 = FFmpeg.AutoGen.ffmpeg.avcodec_open2,
Expand Down