163 lines
6.8 KiB
C#
163 lines
6.8 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using SharpEmu.HLE.GuestMemory;
|
|
using SharpEmu.Libs.Tests.Memory.GpuMemory;
|
|
using Xunit;
|
|
|
|
namespace SharpEmu.Libs.Tests.Memory.GuestMemory;
|
|
|
|
[Collection(GpuMemoryStateCollection.Name)]
|
|
public sealed class GuestMemoryProfileTests
|
|
{
|
|
[Fact]
|
|
public void ReadbackDetailsSeparateCallersForTheSameRange()
|
|
{
|
|
var measurements = new GuestMemoryProfile.ReadbackMeasurements();
|
|
var commandRange = new GuestMemoryProfile.ReadbackRange(0x200000, 0x4000, false,
|
|
GuestMemoryProfile.ReadbackSource.CommandMemoryRead);
|
|
var shaderRange = commandRange with { Source = GuestMemoryProfile.ReadbackSource.ShaderResourceRead };
|
|
measurements.Record(commandRange, 608, 11);
|
|
measurements.Record(shaderRange, 780, 7);
|
|
measurements.Record(commandRange, 608, 3);
|
|
|
|
var snapshot = measurements.TakeSnapshot();
|
|
Assert.Equal(2, snapshot.Ranges.Length);
|
|
Assert.Equal(new GuestMemoryProfile.ReadbackMeasurement(2, 2, 1216, 14, 11),
|
|
snapshot.Ranges.Single(entry => entry.Key == commandRange).Value);
|
|
Assert.Equal(new GuestMemoryProfile.ReadbackMeasurement(1, 1, 780, 7, 7),
|
|
snapshot.Ranges.Single(entry => entry.Key == shaderRange).Value);
|
|
Assert.Empty(measurements.TakeSnapshot().Ranges);
|
|
}
|
|
|
|
[Fact]
|
|
public void ReadbackReportRequiresBothSwitchesAndIncludesTheCaller()
|
|
{
|
|
GuestMemoryProfile.WriteReport();
|
|
using var output = new StringWriter();
|
|
var previous = Console.Error;
|
|
try
|
|
{
|
|
Console.SetError(output);
|
|
GuestMemoryProfile.RecordBufferReadback(0x200000, 0x4000, false, 608, 1,
|
|
GuestMemoryProfile.ReadbackSource.ShaderResourceRead);
|
|
GuestMemoryProfile.WriteReport();
|
|
}
|
|
finally
|
|
{
|
|
Console.SetError(previous);
|
|
}
|
|
|
|
var enabled = Environment.GetEnvironmentVariable("SHARPEMU_PROFILE_PERFORMANCE") == "1" &&
|
|
Environment.GetEnvironmentVariable("SHARPEMU_PROFILE_PERFORMANCE_FRAME_TRACE") == "1";
|
|
if (enabled)
|
|
Assert.Contains("source=ShaderResourceRead calls=1 downloads=1 bytes=608", output.ToString());
|
|
else
|
|
Assert.DoesNotContain("[BUFFER_READBACK]", output.ToString());
|
|
}
|
|
|
|
[Fact]
|
|
public void ReadbackDetailsSeparateDownloadsFromCleanRequests()
|
|
{
|
|
var measurements = new GuestMemoryProfile.ReadbackMeasurements();
|
|
var range = new GuestMemoryProfile.ReadbackRange(0x200000, 0x80000, false);
|
|
measurements.Record(range, 4096, 11);
|
|
measurements.Record(range, 0, 3);
|
|
measurements.Record(range with { CpuWrite = true }, 8192, 7);
|
|
|
|
var snapshot = measurements.TakeSnapshot();
|
|
Assert.Equal(new GuestMemoryProfile.ReadbackMeasurement(2, 1, 4096, 14, 11),
|
|
snapshot.Ranges.Single(entry => entry.Key == range).Value);
|
|
Assert.Equal(2, snapshot.Ranges.Length);
|
|
Assert.Equal(0, snapshot.UntrackedCalls);
|
|
Assert.Empty(measurements.TakeSnapshot().Ranges);
|
|
}
|
|
|
|
[Fact]
|
|
public void ReadbackDetailsBoundDistinctRangesAndReportOverflow()
|
|
{
|
|
var measurements = new GuestMemoryProfile.ReadbackMeasurements();
|
|
for (var index = 0; index <= GuestMemoryProfile.ReadbackMeasurements.Capacity; index++)
|
|
measurements.Record(new GuestMemoryProfile.ReadbackRange((ulong)index * 0x80000, 0x80000, false), 1, 1);
|
|
measurements.Record(new GuestMemoryProfile.ReadbackRange(0, 0x80000, false), 1, 1);
|
|
|
|
var snapshot = measurements.TakeSnapshot();
|
|
Assert.Equal(GuestMemoryProfile.ReadbackMeasurements.Capacity, snapshot.Ranges.Length);
|
|
Assert.Equal(1, snapshot.UntrackedCalls);
|
|
Assert.Equal(2, snapshot.Ranges.Single(entry => entry.Key.Address == 0).Value.Calls);
|
|
Assert.Equal(0, measurements.TakeSnapshot().UntrackedCalls);
|
|
}
|
|
|
|
[Fact]
|
|
public void ReportUsesThePerformanceProfileSwitch()
|
|
{
|
|
GuestMemoryProfile.WriteReport();
|
|
using var output = new StringWriter();
|
|
var previous = Console.Error;
|
|
try
|
|
{
|
|
Console.SetError(output);
|
|
using (GuestMemoryProfile.Measure(GuestMemoryProfile.Operation.ReservationHost)) { }
|
|
GuestMemoryProfile.WriteReport();
|
|
}
|
|
finally
|
|
{
|
|
Console.SetError(previous);
|
|
}
|
|
|
|
var enabled = Environment.GetEnvironmentVariable("SHARPEMU_PROFILE_PERFORMANCE") == "1";
|
|
if (enabled)
|
|
Assert.Contains("operation=ReservationHost calls=1 inclusive_ms=", output.ToString());
|
|
else
|
|
Assert.Equal(string.Empty, output.ToString());
|
|
}
|
|
|
|
[Fact]
|
|
public void SnapshotReportsCountTotalAndMaximumThenClearsTheWindow()
|
|
{
|
|
var measurements = new GuestMemoryProfile.Measurements();
|
|
measurements.Record(GuestMemoryProfile.Operation.MappingDrain, 7);
|
|
measurements.Record(GuestMemoryProfile.Operation.MappingDrain, 11);
|
|
|
|
var snapshot = measurements.TakeSnapshot();
|
|
Assert.Equal(new GuestMemoryProfile.Measurement(2, 18, 11),
|
|
snapshot[(int)GuestMemoryProfile.Operation.MappingDrain]);
|
|
Assert.All(measurements.TakeSnapshot(), value => Assert.Equal(default, value));
|
|
Assert.Equal(2, snapshot[(int)GuestMemoryProfile.Operation.MappingDrain].Calls);
|
|
}
|
|
|
|
[Fact]
|
|
public void NestedOperationTotalsRemainSeparate()
|
|
{
|
|
var measurements = new GuestMemoryProfile.Measurements();
|
|
measurements.Record(GuestMemoryProfile.Operation.ReservationSearch, 4);
|
|
measurements.Record(GuestMemoryProfile.Operation.ReservationHost, 9);
|
|
measurements.Record(GuestMemoryProfile.Operation.Reservation, 20);
|
|
|
|
var snapshot = measurements.TakeSnapshot();
|
|
Assert.Equal(4, snapshot[(int)GuestMemoryProfile.Operation.ReservationSearch].Ticks);
|
|
Assert.Equal(9, snapshot[(int)GuestMemoryProfile.Operation.ReservationHost].Ticks);
|
|
Assert.Equal(20, snapshot[(int)GuestMemoryProfile.Operation.Reservation].Ticks);
|
|
}
|
|
|
|
[Fact]
|
|
public void ConcurrentRecordsAndReportsDoNotLoseCalls()
|
|
{
|
|
var measurements = new GuestMemoryProfile.Measurements();
|
|
long calls = 0;
|
|
long ticks = 0;
|
|
Parallel.For(0, 1000, iteration =>
|
|
{
|
|
measurements.Record(GuestMemoryProfile.Operation.MappingApply, 3);
|
|
if (iteration % 10 != 0)
|
|
return;
|
|
var value = measurements.TakeSnapshot()[(int)GuestMemoryProfile.Operation.MappingApply];
|
|
Interlocked.Add(ref calls, value.Calls);
|
|
Interlocked.Add(ref ticks, value.Ticks);
|
|
});
|
|
var remaining = measurements.TakeSnapshot()[(int)GuestMemoryProfile.Operation.MappingApply];
|
|
Assert.Equal(1000, calls + remaining.Calls);
|
|
Assert.Equal(3000, ticks + remaining.Ticks);
|
|
}
|
|
}
|