sharpemu/tests/SharpEmu.Libs.Tests/VideoOut/PerformanceOverlayTests.cs

199 lines
7.8 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.Libs.VideoOut;
using Xunit;
namespace SharpEmu.Libs.Tests.VideoOut;
public sealed class PerformanceOverlayTests
{
[Theory]
[InlineData(PerformanceOverlayCorner.TopLeft, 12, 12)]
[InlineData(PerformanceOverlayCorner.TopRight, 1280 - 12 - PerfOverlay.PanelWidth, 12)]
[InlineData(PerformanceOverlayCorner.BottomRight, 1280 - 12 - PerfOverlay.PanelWidth, 720 - 12 - PerfOverlay.PanelHeight)]
[InlineData(PerformanceOverlayCorner.BottomLeft, 12, 720 - 12 - PerfOverlay.PanelHeight)]
public void FullPanelUsesSelectedCorner(PerformanceOverlayCorner corner, int left, int top)
{
var state = new PerformanceOverlayState();
state.Configure(new HostVideoOptions { OverlayCorner = corner }, false);
Assert.Equal(
new OverlayRectangle(left, top, PerfOverlay.PanelWidth, PerfOverlay.PanelHeight),
state.GetRectangle(1280, 720));
}
[Theory]
[InlineData(PerformanceOverlayCorner.TopLeft)]
[InlineData(PerformanceOverlayCorner.TopRight)]
[InlineData(PerformanceOverlayCorner.BottomRight)]
[InlineData(PerformanceOverlayCorner.BottomLeft)]
public void SmallSurfaceClipsPanelInsideItsBounds(PerformanceOverlayCorner corner)
{
var state = new PerformanceOverlayState();
state.Configure(new HostVideoOptions { OverlayCorner = corner }, false);
foreach (var size in new[] { 0, 1, 12, 24, 100, 4096 })
{
var rectangle = state.GetRectangle(size, size);
Assert.InRange(rectangle.Left, 0, size);
Assert.InRange(rectangle.Top, 0, size);
Assert.InRange(rectangle.Left + rectangle.Width, 0, size);
Assert.InRange(rectangle.Top + rectangle.Height, 0, size);
}
}
[Fact]
public void HotkeysChangeOnlyRuntimeState()
{
var options = new HostVideoOptions { OverlayCorner = PerformanceOverlayCorner.TopRight };
var state = new PerformanceOverlayState();
state.Configure(options, false);
state.Toggle();
state.CycleCorner();
Assert.False(state.Enabled);
Assert.Equal(PerformanceOverlayCorner.BottomRight, state.Corner);
Assert.True(options.OverlayEnabled);
Assert.Equal(PerformanceOverlayCorner.TopRight, options.OverlayCorner);
state.Configure(options, false);
Assert.True(state.Enabled);
Assert.Equal(PerformanceOverlayCorner.TopRight, state.Corner);
for (var index = 0; index < 4; index++)
{
state.CycleCorner();
}
Assert.Equal(PerformanceOverlayCorner.TopRight, state.Corner);
}
[Fact]
public void EnvironmentCanStartHiddenWithoutDisablingTheHotkey()
{
var state = new PerformanceOverlayState();
state.Configure(HostVideoOptions.Default, true);
Assert.False(state.DrawOnScreen);
state.Toggle();
Assert.True(state.DrawOnScreen);
}
[Theory]
[InlineData(PerformanceOverlayMode.Full, true, PerfOverlay.PanelHeight)]
[InlineData(PerformanceOverlayMode.Minimal, true, PerfOverlay.MinimalPanelHeight)]
[InlineData(PerformanceOverlayMode.TitleBar, false, PerfOverlay.MinimalPanelHeight)]
public void ModeSelectsPanelOrTitleBar(PerformanceOverlayMode mode, bool onScreen, int height)
{
var state = new PerformanceOverlayState();
state.Configure(new HostVideoOptions { OverlayMode = mode }, false);
Assert.Equal(onScreen, state.DrawOnScreen);
Assert.Equal(height, state.DisplayHeight);
state.Toggle();
Assert.False(state.DrawOnScreen);
}
[Fact]
public void MinimalBottomPanelUsesItsOwnDimensions()
{
var state = new PerformanceOverlayState();
state.Configure(new HostVideoOptions
{
OverlayCorner = PerformanceOverlayCorner.BottomLeft,
OverlayMode = PerformanceOverlayMode.Minimal,
}, false);
Assert.Equal(
new OverlayRectangle(12, 720 - 12 - PerfOverlay.MinimalPanelHeight,
PerfOverlay.MinimalPanelWidth, PerfOverlay.MinimalPanelHeight),
state.GetRectangle(1280, 720));
}
[Theory]
[InlineData(PerformanceOverlayCorner.TopLeft, false, false)]
[InlineData(PerformanceOverlayCorner.TopRight, true, false)]
[InlineData(PerformanceOverlayCorner.BottomRight, true, true)]
[InlineData(PerformanceOverlayCorner.BottomLeft, false, true)]
public void MinimalWidthKeepsThePanelAtItsSelectedCorner(
PerformanceOverlayCorner corner, bool rightAligned, bool bottomAligned)
{
var state = new PerformanceOverlayState();
state.Configure(new HostVideoOptions
{
OverlayCorner = corner,
OverlayMode = PerformanceOverlayMode.Minimal,
}, false);
var rectangle = state.GetRectangle(1280, 720);
Assert.Equal(PerfOverlay.MinimalPanelWidth, state.DisplayWidth);
Assert.Equal(PerfOverlay.MinimalPanelWidth, rectangle.Width);
Assert.Equal(rightAligned ? 1280 - 12 - rectangle.Width : 12, rectangle.Left);
Assert.Equal(bottomAligned ? 720 - 12 - rectangle.Height : 12, rectangle.Top);
var clipped = state.GetRectangle(100, 100);
Assert.Equal(76, clipped.Width);
Assert.Equal(12, clipped.Left);
}
[Fact]
public void SharedPixelBufferCanHoldBothPanelSizes()
{
Assert.True(PerfOverlay.PixelBufferWidth >= PerfOverlay.PanelWidth);
Assert.True(PerfOverlay.PixelBufferWidth >= PerfOverlay.MinimalPanelWidth);
Assert.True(PerfOverlay.PixelBufferHeight >= PerfOverlay.PanelHeight);
Assert.True(PerfOverlay.PixelBufferHeight >= PerfOverlay.MinimalPanelHeight);
}
[Fact]
public void InvalidOptionsUseDisplayDefaults()
{
var options = new HostVideoOptions
{
OverlayCorner = (PerformanceOverlayCorner)100,
OverlayMode = (PerformanceOverlayMode)100,
OverlayEnabled = false,
}.Normalize();
Assert.Equal(PerformanceOverlayCorner.TopRight, options.OverlayCorner);
Assert.Equal(PerformanceOverlayMode.Full, options.OverlayMode);
Assert.False(options.OverlayEnabled);
}
[Fact]
public void TitleBarKeepsPerformanceAndCaptureHint()
{
const string summary = "FPS 60.0 | CPU 10% | GPU 25% | TIME 00:00:10";
Assert.Equal($"Application · {summary} · Press F12 for capture",
SdlHostWindow.FormatWindowTitle("Application", true, summary));
Assert.Equal($"Application · {summary}", SdlHostWindow.FormatWindowTitle("Application", false, summary));
Assert.Equal("Application", SdlHostWindow.FormatWindowTitle("Application", false, ""));
}
[Fact]
public void GpuUsageUsesBusiestValidEngine()
{
var usage = WindowsGpuUsage.IncludeEngine(double.NaN, 0, 30);
usage = WindowsGpuUsage.IncludeEngine(usage, 1, 70);
usage = WindowsGpuUsage.IncludeEngine(usage, 0, 50);
usage = WindowsGpuUsage.IncludeEngine(usage, 0xC0000BC6, 100);
usage = WindowsGpuUsage.IncludeEngine(usage, 0, double.NaN);
usage = WindowsGpuUsage.IncludeEngine(usage, 0, -1);
Assert.Equal(70, usage);
Assert.Equal(100, WindowsGpuUsage.IncludeEngine(usage, 0, 105));
}
[Theory]
[InlineData(double.NaN, "N/A")]
[InlineData(double.PositiveInfinity, "N/A")]
[InlineData(0, "0%")]
[InlineData(100, "100%")]
public void MissingGpuCounterDoesNotReportZero(double value, string label)
{
Assert.Equal(label, PerfOverlay.FormatUsage(value));
}
[Fact]
public void GpuSamplerCanCloseWithAQueuedSample()
{
var sampler = new WindowsGpuUsage();
sampler.RequestSample();
sampler.Dispose();
sampler.RequestSample();
sampler.Dispose();
}
}