sharpemu/src/SharpEmu.ShaderCompiler/Resources/MemoryAccessInfo.cs

354 lines
14 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Collections.Generic;
namespace SharpEmu.ShaderCompiler.Resources;
public enum MemoryResourceKind : byte
{
None,
ScalarBuffer,
ScalarAddress,
Buffer,
Flat,
Global,
Scratch,
LocalDataShare,
GlobalDataShare,
Image,
Sampler,
}
public enum MemoryAccess : byte
{
None,
Read,
Write,
Atomic,
}
public enum ImageDimension : byte
{
Unknown,
Dim1D,
Dim1DArray,
Dim2D,
Dim3D,
Dim2DArray,
Dim2DMsaa,
Dim2DMsaaArray,
}
public enum ImageResourceClass : byte
{
None,
Sampled,
Storage,
}
[Flags]
public enum ImageSampleFlags : uint
{
None = 0,
Compare = 1u << 3,
Adjust = 1u << 10,
}
// The decoded facts of one memory access, one entry per accessed dword for scalar
// loads. The tracker fills Resource and Sampler; the planner sets PlanningOnly.
public sealed class MemoryAccessInfo
{
public const uint NoResource = uint.MaxValue;
public uint Pc { get; init; }
public string Opcode { get; init; } = string.Empty;
public MemoryResourceKind Kind { get; init; }
public MemoryAccess Access { get; init; }
public uint Resource { get; set; } = NoResource;
public uint Sampler { get; set; } = NoResource;
public uint Offset { get; init; }
public uint Dmask { get; init; }
public uint DataDwords { get; init; } = 1;
public uint DataBits { get; init; } = 32;
public uint ComponentIndex { get; init; }
public uint ComponentCount { get; init; } = 1;
public uint DataFormat { get; init; }
public uint NumberFormat { get; init; }
public ImageSampleFlags ImageSampleFlags { get; init; }
public ImageDimension ImageDimension { get; init; }
public ImageResourceClass ImageClass { get; init; }
public bool NeedsSampler { get; init; }
public bool AddressIsFull { get; init; }
public bool DataSigned { get; init; }
public bool Typed { get; init; }
public bool Formatted { get; init; }
public bool ImageHasMip { get; init; }
public bool ImageR128 { get; init; }
public bool Glc { get; init; }
public bool Slc { get; init; }
public bool IndexEnabled { get; init; }
public bool OffsetEnabled { get; init; }
public bool PlanningOnly { get; set; }
// A scalar buffer load whose descriptor only exists on the device: the shader reads
// through the descriptor in its registers instead of a host-bound buffer.
public bool DeviceDescriptor { get; set; }
public bool IsAddressKind =>
Kind is MemoryResourceKind.ScalarAddress or MemoryResourceKind.Flat or
MemoryResourceKind.Global or MemoryResourceKind.Scratch;
// The decoded facts of two accesses are the same when every field but the program
// counter and the tracker's patches agree.
public bool SameAccess(MemoryAccessInfo other) =>
Kind == other.Kind && Access == other.Access && Offset == other.Offset && Dmask == other.Dmask &&
DataDwords == other.DataDwords && DataBits == other.DataBits && ComponentIndex == other.ComponentIndex &&
ComponentCount == other.ComponentCount && DataFormat == other.DataFormat && NumberFormat == other.NumberFormat &&
ImageSampleFlags == other.ImageSampleFlags && ImageDimension == other.ImageDimension &&
ImageClass == other.ImageClass && NeedsSampler == other.NeedsSampler && AddressIsFull == other.AddressIsFull &&
DataSigned == other.DataSigned && Typed == other.Typed && Formatted == other.Formatted &&
ImageHasMip == other.ImageHasMip && ImageR128 == other.ImageR128 && Glc == other.Glc && Slc == other.Slc &&
IndexEnabled == other.IndexEnabled && OffsetEnabled == other.OffsetEnabled &&
Resource == other.Resource && Sampler == other.Sampler && PlanningOnly == other.PlanningOnly &&
DeviceDescriptor == other.DeviceDescriptor;
}
// Every memory access of a program, indexed by program counter and component.
public sealed class MemoryAccessTable
{
private readonly List<MemoryAccessInfo> _entries = [];
private readonly Dictionary<(uint Pc, uint Component), int> _indexByPc = [];
public IReadOnlyList<MemoryAccessInfo> Entries => _entries;
public int Count => _entries.Count;
public MemoryAccessInfo this[int index] => _entries[index];
public int Add(MemoryAccessInfo entry)
{
var index = _entries.Count;
_entries.Add(entry);
_indexByPc[(entry.Pc, entry.ComponentIndex)] = index;
return index;
}
public bool TryGetIndex(uint pc, uint component, out int index) =>
_indexByPc.TryGetValue((pc, component), out index);
public MemoryAccessInfo? Find(uint pc, uint component = 0) =>
_indexByPc.TryGetValue((pc, component), out var index) ? _entries[index] : null;
// One entry per memory instruction; scalar loads get one entry per loaded dword.
public static MemoryAccessTable Build(Gen5ShaderProgram program, IReadOnlySet<uint>? fixedFunctionVertexLoads = null)
{
var table = new MemoryAccessTable();
foreach (var instruction in program.Instructions)
{
if (instruction.Control is Gen5BufferMemoryControl && fixedFunctionVertexLoads?.Contains(instruction.Pc) == true)
continue;
switch (instruction.Control)
{
case Gen5ScalarMemoryControl scalar:
AddScalarLoad(table, instruction, scalar);
break;
case Gen5BufferMemoryControl buffer:
table.Add(FromBuffer(instruction, buffer));
break;
case Gen5GlobalMemoryControl global:
table.Add(FromGlobal(instruction, global));
break;
case Gen5ImageControl image:
table.Add(FromImage(instruction, image));
break;
case Gen5DataShareControl share:
if (instruction.Opcode is "DsSwizzleB32" or "DsBpermuteB32")
{
break;
}
table.Add(new MemoryAccessInfo
{
Pc = instruction.Pc,
Opcode = instruction.Opcode,
Kind = share.Gds ? MemoryResourceKind.GlobalDataShare : MemoryResourceKind.LocalDataShare,
Access = DataShareAccess(instruction.Opcode),
Offset = share.SingleOffsetBytes,
});
break;
}
}
return table;
}
private static void AddScalarLoad(MemoryAccessTable table, Gen5ShaderInstruction instruction, Gen5ScalarMemoryControl control)
{
var isBuffer = instruction.Opcode.StartsWith("SBufferLoad", StringComparison.Ordinal);
for (uint component = 0; component < control.DestinationCount; component++)
{
table.Add(new MemoryAccessInfo
{
Pc = instruction.Pc,
Opcode = instruction.Opcode,
Kind = isBuffer ? MemoryResourceKind.ScalarBuffer : MemoryResourceKind.ScalarAddress,
Access = MemoryAccess.Read,
Offset = unchecked((uint)control.ImmediateOffsetBytes + component * sizeof(uint)),
DataDwords = 1,
ComponentIndex = component,
ComponentCount = control.DestinationCount,
});
}
}
private static MemoryAccessInfo FromBuffer(Gen5ShaderInstruction instruction, Gen5BufferMemoryControl control)
{
var opcode = instruction.Opcode;
var access = opcode.Contains("Atomic", StringComparison.Ordinal) ? MemoryAccess.Atomic
: opcode.Contains("Store", StringComparison.Ordinal) ? MemoryAccess.Write
: MemoryAccess.Read;
var (bits, signed) = SubwordBits(opcode);
uint dataFormat = 0;
uint numberFormat = 0;
if (control.Typed)
{
Gfx10UnifiedFormat.TryDecode(control.TypedFormat, out dataFormat, out numberFormat);
}
return new MemoryAccessInfo
{
Pc = instruction.Pc,
Opcode = opcode,
Kind = MemoryResourceKind.Buffer,
Access = access,
Offset = unchecked((uint)control.OffsetBytes),
DataDwords = Math.Max(control.DwordCount, 1u),
DataBits = bits,
ComponentCount = Math.Max(control.DwordCount, 1u),
DataFormat = dataFormat,
NumberFormat = numberFormat,
DataSigned = signed,
Typed = control.Typed,
Formatted = opcode.Contains("Format", StringComparison.Ordinal),
Glc = control.Glc,
Slc = control.Slc,
IndexEnabled = control.IndexEnabled,
OffsetEnabled = control.OffsetEnabled,
};
}
private const uint NullScalarRegister = 125;
private static MemoryAccessInfo FromGlobal(Gen5ShaderInstruction instruction, Gen5GlobalMemoryControl control)
{
var opcode = instruction.Opcode;
var access = opcode.Contains("Atomic", StringComparison.Ordinal) ? MemoryAccess.Atomic
: opcode.Contains("Store", StringComparison.Ordinal) ? MemoryAccess.Write
: MemoryAccess.Read;
var (bits, signed) = SubwordBits(opcode);
return new MemoryAccessInfo
{
Pc = instruction.Pc,
Opcode = opcode,
Kind = opcode.StartsWith("Scratch", StringComparison.Ordinal)
? MemoryResourceKind.Scratch
: control.UsesFlatAddress
? MemoryResourceKind.Flat
: MemoryResourceKind.Global,
Access = access,
Offset = unchecked((uint)control.OffsetBytes),
DataDwords = Math.Max(control.DwordCount, 1u),
DataBits = bits,
ComponentCount = Math.Max(control.DwordCount, 1u),
DataSigned = signed,
AddressIsFull = control.UsesFlatAddress || control.ScalarAddress == NullScalarRegister,
Glc = control.Glc,
Slc = control.Slc,
};
}
private static MemoryAccessInfo FromImage(Gen5ShaderInstruction instruction, Gen5ImageControl control)
{
var opcode = instruction.Opcode;
if (opcode is "ImageBvhIntersectRay" or "ImageBvh64IntersectRay")
{
// The current backends return a deterministic miss for raw GFX10
// BVH traversal. Do not materialize its descriptor as a texture;
// BVH descriptors do not use the regular image descriptor layout.
return new MemoryAccessInfo
{
Pc = instruction.Pc,
Opcode = opcode,
Kind = MemoryResourceKind.None,
Access = MemoryAccess.None,
DataDwords = 4,
ComponentCount = 4,
};
}
var atomic = opcode.StartsWith("ImageAtomic", StringComparison.Ordinal);
var store = opcode.StartsWith("ImageStore", StringComparison.Ordinal);
var sampled = opcode.StartsWith("ImageSample", StringComparison.Ordinal) ||
opcode.StartsWith("ImageGather", StringComparison.Ordinal) ||
opcode == "ImageGetLod";
var compare = (opcode.StartsWith("ImageSampleC", StringComparison.Ordinal) && !opcode.StartsWith("ImageSampleCd", StringComparison.Ordinal)) ||
opcode.StartsWith("ImageGather4C", StringComparison.Ordinal);
var mimgOpcode = instruction.Words.Count != 0
? ((instruction.Words[0] >> 18) & 0x7F) | ((instruction.Words[0] & 1) << 7)
: 0u;
var adjust = mimgOpcode is
0xA0 or 0xA1 or 0xA5 or 0xA6 or 0xA8 or 0xA9 or 0xAD or 0xAE or
0xB0 or 0xB1 or 0xB5 or 0xB6 or 0xB8 or 0xB9 or 0xBD or 0xBE;
return new MemoryAccessInfo
{
Pc = instruction.Pc,
Opcode = opcode,
Kind = MemoryResourceKind.Image,
Access = atomic ? MemoryAccess.Atomic : store ? MemoryAccess.Write : MemoryAccess.Read,
ImageClass = atomic || store ? ImageResourceClass.Storage : ImageResourceClass.Sampled,
NeedsSampler = sampled,
Dmask = control.Dmask,
ImageDimension = DecodeImageDimension(control.Dimension),
ImageSampleFlags =
(compare ? ImageSampleFlags.Compare : ImageSampleFlags.None) |
(adjust ? ImageSampleFlags.Adjust : ImageSampleFlags.None),
ImageHasMip = opcode is "ImageLoadMip" or "ImageStoreMip",
ImageR128 = instruction.Words.Count != 0 && ((instruction.Words[0] >> 15) & 1) != 0,
Glc = control.Glc,
Slc = control.Slc,
};
}
public static ImageDimension DecodeImageDimension(uint dimension) => dimension switch
{
0 => ImageDimension.Dim1D,
1 => ImageDimension.Dim2D,
2 => ImageDimension.Dim3D,
3 => ImageDimension.Dim2DArray,
4 => ImageDimension.Dim1DArray,
5 => ImageDimension.Dim2DArray,
6 => ImageDimension.Dim2DMsaa,
7 => ImageDimension.Dim2DMsaaArray,
_ => ImageDimension.Unknown,
};
private static (uint Bits, bool Signed) SubwordBits(string opcode)
{
if (opcode.Contains("byte", StringComparison.OrdinalIgnoreCase))
{
return (8, opcode.Contains("Sbyte", StringComparison.Ordinal));
}
if (opcode.Contains("short", StringComparison.OrdinalIgnoreCase))
{
return (16, opcode.Contains("Sshort", StringComparison.Ordinal));
}
return (32, false);
}
private static MemoryAccess DataShareAccess(string opcode) =>
Gen5ShaderTranslator.IsDataShareAtomic(opcode) ? MemoryAccess.Atomic
: opcode.StartsWith("DsWrite", StringComparison.Ordinal) ? MemoryAccess.Write
: MemoryAccess.Read;
}