Performance Improvements and Optimization Tweaks (#156)

* Improve Gen5 rendering performance and compatibility

* Pin .NET SDK for locked restore

---------

Co-authored-by: Spooks4576 <Spooks4576@users.noreply.github.com>
This commit is contained in:
Spooks
2026-07-14 11:22:52 -06:00
committed by GitHub
parent 85cc2b9892
commit d8397b022e
10 changed files with 801 additions and 123 deletions
+146 -33
View File
@@ -157,7 +157,7 @@ public static class AgcExports
private static readonly HashSet<uint> _tracedSubmittedDrawOpcodes = new();
private static readonly Dictionary<(ulong Ps, ulong State, Gen5PixelOutputKind Output), byte[]> _pixelSpirvCache = new();
private static readonly Dictionary<
(ulong Es, ulong EsState, ulong Ps, ulong PsState, Gen5PixelOutputKind Output),
(ulong Es, ulong EsState, ulong Ps, ulong PsState, Gen5PixelOutputKind Output, uint Attributes),
(byte[] Vertex, byte[] Pixel)> _graphicsSpirvCache = new();
private static readonly Dictionary<
(ulong Cs, ulong State, uint LocalX, uint LocalY, uint LocalZ),
@@ -2019,8 +2019,16 @@ public static class AgcExports
var gpuState = _submittedGpuStates.GetValue(ctx.Memory, static _ => new SubmittedGpuState());
lock (gpuState.Gate)
{
ParseSubmittedDcb(ctx, gpuState, gpuState.Graphics, commandAddress, dwordCount, tracePackets);
DrainResumableDcbs(ctx, gpuState, tracePackets);
Gen5ShaderScalarEvaluator.BeginGlobalMemoryReadScope();
try
{
ParseSubmittedDcb(ctx, gpuState, gpuState.Graphics, commandAddress, dwordCount, tracePackets);
DrainResumableDcbs(ctx, gpuState, tracePackets);
}
finally
{
Gen5ShaderScalarEvaluator.EndGlobalMemoryReadScope();
}
}
ctx[CpuRegister.Rax] = 0;
@@ -2050,27 +2058,35 @@ public static class AgcExports
var gpuState = _submittedGpuStates.GetValue(ctx.Memory, static _ => new SubmittedGpuState());
lock (gpuState.Gate)
{
for (uint i = 0; i < bufferCount; i++)
Gen5ShaderScalarEvaluator.BeginGlobalMemoryReadScope();
try
{
if (!ctx.TryReadUInt64(addressArray + i * 8, out var commandAddress) ||
commandAddress == 0 ||
!ctx.TryReadUInt32(sizeArray + i * 4, out var dwordCount) ||
dwordCount == 0)
for (uint i = 0; i < bufferCount; i++)
{
continue;
if (!ctx.TryReadUInt64(addressArray + i * 8, out var commandAddress) ||
commandAddress == 0 ||
!ctx.TryReadUInt32(sizeArray + i * 4, out var dwordCount) ||
dwordCount == 0)
{
continue;
}
if (tracePackets)
{
TraceAgc(
$"agc.driver_submit_multi_dcbs index={i}/{bufferCount} " +
$"addr=0x{commandAddress:X16} dwords={dwordCount}");
}
ParseSubmittedDcb(ctx, gpuState, gpuState.Graphics, commandAddress, dwordCount, tracePackets);
}
if (tracePackets)
{
TraceAgc(
$"agc.driver_submit_multi_dcbs index={i}/{bufferCount} " +
$"addr=0x{commandAddress:X16} dwords={dwordCount}");
}
ParseSubmittedDcb(ctx, gpuState, gpuState.Graphics, commandAddress, dwordCount, tracePackets);
DrainResumableDcbs(ctx, gpuState, tracePackets);
}
finally
{
Gen5ShaderScalarEvaluator.EndGlobalMemoryReadScope();
}
DrainResumableDcbs(ctx, gpuState, tracePackets);
}
ctx[CpuRegister.Rax] = 0;
@@ -2118,8 +2134,16 @@ public static class AgcExports
gpuState.ComputeQueues.Add(ownerHandle, queueState);
}
ParseSubmittedDcb(ctx, gpuState, queueState, commandAddress, dwordCount, tracePackets);
DrainResumableDcbs(ctx, gpuState, tracePackets);
Gen5ShaderScalarEvaluator.BeginGlobalMemoryReadScope();
try
{
ParseSubmittedDcb(ctx, gpuState, queueState, commandAddress, dwordCount, tracePackets);
DrainResumableDcbs(ctx, gpuState, tracePackets);
}
finally
{
Gen5ShaderScalarEvaluator.EndGlobalMemoryReadScope();
}
}
ctx[CpuRegister.Rax] = 0;
@@ -3418,7 +3442,8 @@ public static class AgcExports
exportState,
out var exportEvaluation,
out error,
resolveVertexInputs: true) ||
resolveVertexInputs: true,
vertexRecordLimit: indexed ? null : vertexCount) ||
!Gen5ShaderTranslator.TryCreateState(
ctx,
pixelShaderAddress,
@@ -3442,14 +3467,19 @@ public static class AgcExports
HasPixelColorExport(pixelState, target.Slot))
.ToArray();
var outputKind = GetPixelOutputKind(renderTargets.FirstOrDefault().NumberType);
var exportStateFingerprint = ComputeShaderStateFingerprint(exportEvaluation);
var pixelStateFingerprint = ComputeShaderStateFingerprint(pixelEvaluation);
var attributeCount = GetInterpolatedAttributeCount(pixelState);
var exportStateFingerprint = ComputeShaderStructureFingerprint(exportEvaluation);
var pixelStateFingerprint = ComputeShaderStructureFingerprint(pixelEvaluation);
var shaderKey = (
exportShaderAddress,
exportStateFingerprint,
pixelShaderAddress,
pixelStateFingerprint,
outputKind);
outputKind,
attributeCount);
var totalGlobalBuffers =
pixelEvaluation.GlobalMemoryBindings.Count +
exportEvaluation.GlobalMemoryBindings.Count;
(byte[] Vertex, byte[] Pixel) compiled;
lock (_submitTraceGate)
{
@@ -3458,9 +3488,6 @@ public static class AgcExports
if (compiled.Vertex is null || compiled.Pixel is null)
{
var totalGlobalBuffers =
pixelEvaluation.GlobalMemoryBindings.Count +
exportEvaluation.GlobalMemoryBindings.Count;
if (!Gen5SpirvTranslator.TryCompilePixelShader(
pixelState,
pixelEvaluation,
@@ -3468,16 +3495,18 @@ public static class AgcExports
out var pixelShader,
out error,
globalBufferBase: 0,
totalGlobalBufferCount: totalGlobalBuffers,
imageBindingBase: 0) ||
totalGlobalBufferCount: totalGlobalBuffers + 2,
imageBindingBase: 0,
scalarRegisterBufferIndex: totalGlobalBuffers) ||
!Gen5SpirvTranslator.TryCompileVertexShader(
exportState,
exportEvaluation,
out var vertexShader,
out error,
globalBufferBase: pixelEvaluation.GlobalMemoryBindings.Count,
totalGlobalBufferCount: totalGlobalBuffers,
imageBindingBase: pixelEvaluation.ImageBindings.Count))
totalGlobalBufferCount: totalGlobalBuffers + 2,
imageBindingBase: pixelEvaluation.ImageBindings.Count,
scalarRegisterBufferIndex: totalGlobalBuffers + 1))
{
return false;
}
@@ -3529,6 +3558,8 @@ public static class AgcExports
var globalMemoryBindings = pixelEvaluation.GlobalMemoryBindings
.Concat(exportEvaluation.GlobalMemoryBindings)
.Append(CreateScalarRegisterBinding(pixelEvaluation))
.Append(CreateScalarRegisterBinding(exportEvaluation))
.ToArray();
IReadOnlyList<Gen5VertexInputBinding> vertexInputs =
exportEvaluation.VertexInputs ?? [];
@@ -3539,7 +3570,7 @@ public static class AgcExports
primitiveType,
compiled.Vertex,
compiled.Pixel,
GetInterpolatedAttributeCount(pixelState),
attributeCount,
vertexCount,
state.InstanceCount,
indexed ? CreateVulkanIndexBuffer(ctx, state, vertexCount) : null,
@@ -3651,6 +3682,88 @@ public static class AgcExports
return (uint)(maxAttribute + 1);
}
private const int ShaderScalarRegisterCount = 256;
private static Gen5GlobalMemoryBinding CreateScalarRegisterBinding(
Gen5ShaderEvaluation evaluation)
{
var data = new byte[ShaderScalarRegisterCount * sizeof(uint)];
var registers = evaluation.InitialScalarRegisters;
var count = Math.Min(registers.Count, ShaderScalarRegisterCount);
for (var index = 0; index < count; index++)
{
var value = registers[index];
var offset = index * sizeof(uint);
data[offset] = (byte)value;
data[offset + 1] = (byte)(value >> 8);
data[offset + 2] = (byte)(value >> 16);
data[offset + 3] = (byte)(value >> 24);
}
return new Gen5GlobalMemoryBinding(0, 0, [], data);
}
private static ulong ComputeShaderStructureFingerprint(Gen5ShaderEvaluation evaluation)
{
const ulong offsetBasis = 14695981039346656037UL;
const ulong prime = 1099511628211UL;
var hash = offsetBasis;
void Mix(ulong value) => hash = (hash ^ value) * prime;
Mix((ulong)evaluation.GlobalMemoryBindings.Count);
foreach (var binding in evaluation.GlobalMemoryBindings)
{
Mix(binding.ScalarAddress);
Mix((ulong)binding.InstructionPcs.Count);
foreach (var pc in binding.InstructionPcs)
{
Mix(pc);
}
}
Mix((ulong)evaluation.ImageBindings.Count);
foreach (var image in evaluation.ImageBindings)
{
Mix(image.Pc);
Mix((ulong)(uint)image.Opcode.GetHashCode());
foreach (var word in image.ResourceDescriptor)
{
Mix(word);
}
foreach (var word in image.SamplerDescriptor)
{
Mix(word);
}
Mix(image.MipLevel ?? uint.MaxValue);
}
if (evaluation.VertexInputs is { } vertexInputs)
{
Mix((ulong)vertexInputs.Count);
foreach (var input in vertexInputs)
{
Mix(input.Pc);
Mix(input.Location);
Mix(input.ComponentCount);
Mix(input.DataFormat);
Mix(input.NumberFormat);
Mix(input.Stride);
}
}
if (evaluation.ComputeSystemRegisters is { } computeSystemRegisters)
{
Mix(computeSystemRegisters.WorkGroupXRegister ?? uint.MaxValue);
Mix(computeSystemRegisters.WorkGroupYRegister ?? uint.MaxValue);
Mix(computeSystemRegisters.WorkGroupZRegister ?? uint.MaxValue);
Mix(computeSystemRegisters.ThreadGroupSizeRegister ?? uint.MaxValue);
}
return hash;
}
private static ulong ComputeShaderStateFingerprint(Gen5ShaderEvaluation evaluation)
{
const ulong offsetBasis = 14695981039346656037UL;
@@ -13,6 +13,24 @@ internal static class Gen5ShaderScalarEvaluator
private const int ImageDescriptorDwords = 8;
private const int SamplerDescriptorDwords = 4;
private const int MaxGlobalMemoryBindingBytes = 16 * 1024 * 1024;
private static readonly int DefaultGlobalMemoryBindingBytes =
int.TryParse(
Environment.GetEnvironmentVariable("SHARPEMU_GLOBAL_BINDING_BYTES"),
out var configured) && configured >= sizeof(uint)
? Math.Min(configured, MaxGlobalMemoryBindingBytes)
: 1 * 1024 * 1024;
internal static long GlobalMemoryReadCount;
internal static long GlobalMemoryReadBytes;
internal static long GlobalMemoryReadCacheHits;
internal static long GlobalMemoryReadPvmBytes;
internal static long GlobalMemoryReadLibcBytes;
internal static long GlobalMemoryReadReuses;
private const long CrossFrameReadCacheMaxBytes = 1024L * 1024 * 1024;
private static readonly object _crossFrameReadGate = new();
private static readonly Dictionary<(ulong BaseAddress, int SizeBytes), byte[]> _crossFrameReadCache = new();
private static long _crossFrameReadCacheBytes;
private const ulong RdnaWaveMask = 0xFFFF_FFFFUL;
private readonly record struct BufferDescriptor(
@@ -44,7 +62,8 @@ internal static class Gen5ShaderScalarEvaluator
Gen5ShaderState state,
out Gen5ShaderEvaluation evaluation,
out string error,
bool resolveVertexInputs = false)
bool resolveVertexInputs = false,
uint? vertexRecordLimit = null)
{
evaluation = default!;
error = string.Empty;
@@ -255,10 +274,28 @@ internal static class Gen5ShaderScalarEvaluator
if (resolveVertexInputs &&
IsVertexFetchCandidate(instruction, bufferMemory, bufferDescriptor))
{
var vertexReadSize = bufferDescriptor.SizeBytes;
if (vertexRecordLimit is { } recordLimit &&
instruction.Sources.Count > 2 &&
TryEvaluateScalarOperand(
instruction.Sources[2],
scalarRegisters,
out var scalarOffset))
{
var bindingOffset = unchecked((uint)bufferMemory.OffsetBytes + scalarOffset);
var requiredBytes =
(ulong)bindingOffset +
(ulong)(Math.Max(recordLimit, 1u) - 1u) * bufferDescriptor.Stride +
(ulong)bufferMemory.DwordCount * sizeof(uint);
vertexReadSize = Math.Min(
bufferDescriptor.SizeBytes,
Math.Max(requiredBytes, sizeof(uint)));
}
if (!TryReadGlobalMemory(
ctx,
bufferDescriptor.BaseAddress,
bufferDescriptor.SizeBytes,
vertexReadSize,
out var vertexData))
{
error =
@@ -533,22 +570,29 @@ internal static class Gen5ShaderScalarEvaluator
return false;
}
[ThreadStatic]
private static Dictionary<(ulong BaseAddress, int SizeBytes), byte[]>? _globalMemoryReadCache;
internal static void BeginGlobalMemoryReadScope()
{
_globalMemoryReadCache = new Dictionary<(ulong, int), byte[]>();
}
internal static void EndGlobalMemoryReadScope()
{
_globalMemoryReadCache = null;
}
private static bool TryReadGlobalMemory(
CpuContext ctx,
ulong baseAddress,
out byte[] data)
{
for (var size = MaxGlobalMemoryBindingBytes; size >= 4096; size >>= 1)
{
data = GC.AllocateUninitializedArray<byte>(size);
if (ctx.Memory.TryRead(baseAddress, data))
{
return true;
}
}
data = [];
return false;
return TryReadGlobalMemory(
ctx,
baseAddress,
(ulong)DefaultGlobalMemoryBindingBytes,
out data);
}
private static bool TryReadGlobalMemory(
@@ -570,13 +614,74 @@ internal static class Gen5ShaderScalarEvaluator
return false;
}
var cache = _globalMemoryReadCache;
var cacheKey = (baseAddress, (int)cappedSize);
if (cache is not null && cache.TryGetValue(cacheKey, out var cached))
{
Interlocked.Increment(ref GlobalMemoryReadCacheHits);
data = cached;
return true;
}
byte[]? previous;
lock (_crossFrameReadGate)
{
_crossFrameReadCache.TryGetValue(cacheKey, out previous);
}
if (previous is not null && ctx.Memory.TryCompare(baseAddress, previous))
{
Interlocked.Increment(ref GlobalMemoryReadReuses);
if (cache is not null)
{
cache[cacheKey] = previous;
}
data = previous;
return true;
}
var candidateSize = (int)cappedSize;
while (candidateSize >= sizeof(uint))
{
data = GC.AllocateUninitializedArray<byte>(candidateSize);
if (ctx.Memory.TryRead(baseAddress, data) ||
var readFromPvm = ctx.Memory.TryRead(baseAddress, data);
if (readFromPvm ||
KernelMemoryCompatExports.TryReadTrackedLibcHeap(baseAddress, data))
{
Interlocked.Increment(ref GlobalMemoryReadCount);
Interlocked.Add(ref GlobalMemoryReadBytes, data.Length);
if (readFromPvm)
{
Interlocked.Add(ref GlobalMemoryReadPvmBytes, data.Length);
}
else
{
Interlocked.Add(ref GlobalMemoryReadLibcBytes, data.Length);
}
if (cache is not null)
{
cache[cacheKey] = data;
}
lock (_crossFrameReadGate)
{
if (_crossFrameReadCache.TryGetValue(cacheKey, out var replaced))
{
_crossFrameReadCacheBytes -= replaced.Length;
}
if (_crossFrameReadCacheBytes + data.Length > CrossFrameReadCacheMaxBytes)
{
_crossFrameReadCache.Clear();
_crossFrameReadCacheBytes = 0;
}
_crossFrameReadCache[cacheKey] = data;
_crossFrameReadCacheBytes += data.Length;
}
return true;
}
+31 -13
View File
@@ -18,7 +18,8 @@ internal static partial class Gen5SpirvTranslator
out string error,
int globalBufferBase = 0,
int totalGlobalBufferCount = -1,
int imageBindingBase = 0)
int imageBindingBase = 0,
int scalarRegisterBufferIndex = -1)
{
var context = new CompilationContext(
Gen5SpirvStage.Pixel,
@@ -30,7 +31,8 @@ internal static partial class Gen5SpirvTranslator
1,
globalBufferBase,
totalGlobalBufferCount,
imageBindingBase);
imageBindingBase,
scalarRegisterBufferIndex);
return context.TryCompile(out shader, out error);
}
@@ -41,7 +43,8 @@ internal static partial class Gen5SpirvTranslator
out string error,
int globalBufferBase = 0,
int totalGlobalBufferCount = -1,
int imageBindingBase = 0)
int imageBindingBase = 0,
int scalarRegisterBufferIndex = -1)
{
var context = new CompilationContext(
Gen5SpirvStage.Vertex,
@@ -53,7 +56,8 @@ internal static partial class Gen5SpirvTranslator
1,
globalBufferBase,
totalGlobalBufferCount,
imageBindingBase);
imageBindingBase,
scalarRegisterBufferIndex);
return context.TryCompile(out shader, out error);
}
@@ -76,7 +80,8 @@ internal static partial class Gen5SpirvTranslator
Math.Max(localSizeZ, 1),
0,
-1,
0);
0,
-1);
return context.TryCompile(out shader, out error);
}
@@ -93,6 +98,7 @@ internal static partial class Gen5SpirvTranslator
private readonly int _globalBufferBase;
private readonly int _totalGlobalBufferCount;
private readonly int _imageBindingBase;
private readonly int _scalarRegisterBufferIndex;
private readonly List<uint> _interfaces = [];
private readonly Dictionary<uint, uint> _pixelInputs = [];
private readonly Dictionary<uint, uint> _vertexOutputs = [];
@@ -167,7 +173,8 @@ internal static partial class Gen5SpirvTranslator
uint localSizeZ,
int globalBufferBase,
int totalGlobalBufferCount,
int imageBindingBase)
int imageBindingBase,
int scalarRegisterBufferIndex)
{
_stage = stage;
_state = state;
@@ -181,6 +188,7 @@ internal static partial class Gen5SpirvTranslator
? evaluation.GlobalMemoryBindings.Count
: totalGlobalBufferCount;
_imageBindingBase = imageBindingBase;
_scalarRegisterBufferIndex = scalarRegisterBufferIndex;
}
public bool TryCompile(out Gen5SpirvShader shader, out string error)
@@ -767,15 +775,25 @@ internal static partial class Gen5SpirvTranslator
private void EmitInitialState()
{
for (uint index = 0;
index < _evaluation.InitialScalarRegisters.Count &&
index < ScalarRegisterCount;
index++)
if (_scalarRegisterBufferIndex >= 0)
{
var value = _evaluation.InitialScalarRegisters[(int)index];
if (value != 0)
for (uint index = 0; index < ScalarRegisterCount; index++)
{
StoreS(index, UInt(value));
StoreS(index, LoadBufferWord(_scalarRegisterBufferIndex, UInt(index)));
}
}
else
{
for (uint index = 0;
index < _evaluation.InitialScalarRegisters.Count &&
index < ScalarRegisterCount;
index++)
{
var value = _evaluation.InitialScalarRegisters[(int)index];
if (value != 0)
{
StoreS(index, UInt(value));
}
}
}