Compare commits

..

5 Commits

14 changed files with 2423 additions and 373 deletions
@@ -723,6 +723,7 @@ public sealed partial class DirectExecutionBackend
"Vo5V8KAwCmk" or // sceSystemServiceHideSplashScreen
"TywrFKCoLGY" or // sceSaveDataInitialize3
"dyIhnXq-0SM" or // sceSaveDataDirNameSearch
"ZP4e7rlzOUk" or // sceSaveDataMount3
"ERKzksauAJA" or // sceSaveDataDialogGetStatus
"KK3Bdg1RWK0" or // sceSaveDataDialogUpdateStatus
"en7gNVnh878" or // sceSaveDataDialogIsReadyToDisplay
@@ -355,6 +355,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
public GuestThreadRunState State { get; set; }
public ulong ExitValue { get; set; }
public string? BlockReason { get; set; }
public bool HasBlockedContinuation { get; set; }
@@ -1266,7 +1268,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
libraryName.IndexOf("Kernel", StringComparison.OrdinalIgnoreCase) >= 0;
}
private static bool PreferLleForLibcExport(string exportName)
private bool PreferLleForLibcExport(string exportName)
{
if (string.IsNullOrWhiteSpace(exportName))
{
@@ -1287,6 +1289,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
{
return true;
}
if (IsLibcAllocatorExport(exportName))
{
return CanUseLleLibcAllocatorFamily();
}
if (string.Equals(value, "0", StringComparison.Ordinal))
{
return true;
@@ -1298,6 +1304,51 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
return IsSafeLleLibcExport(exportName);
}
private bool CanUseLleLibcAllocatorFamily()
{
return HasUsableLleLibcExport("gQX+4GDQjpM", "malloc") &&
HasUsableLleLibcExport("tIhsqj0qsFE", "free") &&
HasUsableLleLibcExport("2X5agFjKxMc", "calloc") &&
HasUsableLleLibcExport("Y7aJ1uydPMo", "realloc") &&
HasUsableLleLibcExport("Ujf3KzMvRmI", "memalign") &&
HasUsableLleLibcExport("2Btkg8k24Zg", "aligned_alloc") &&
HasUsableLleLibcExport("cVSk9y8URbc", "posix_memalign");
}
private bool HasUsableLleLibcExport(string nid, string exportName)
{
if (TryResolveRuntimeSymbolAddress(nid, out var address) && IsDirectImportTargetUsable(address))
{
return true;
}
foreach (var candidate in EnumerateRuntimeSymbolCandidates(exportName))
{
if (TryResolveRuntimeSymbolAddress(candidate, out address) && IsDirectImportTargetUsable(address))
{
return true;
}
}
return false;
}
private static bool IsLibcAllocatorExport(string exportName)
{
return exportName switch
{
"malloc" or
"free" or
"calloc" or
"realloc" or
"memalign" or
"aligned_alloc" or
"posix_memalign" or
"malloc_usable_size" => true,
_ => false,
};
}
private static bool IsSafeLleLibcExport(string exportName)
{
return exportName switch
@@ -2406,6 +2457,69 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
public bool SupportsGuestContextTransfer => true;
public bool TryJoinThread(
CpuContext callerContext,
ulong threadHandle,
out ulong returnValue,
out string? error)
{
returnValue = 0;
error = null;
if (threadHandle == 0)
{
error = "thread handle is zero";
return false;
}
if (threadHandle == GuestThreadExecution.CurrentGuestThreadHandle)
{
error = "thread cannot join itself";
return false;
}
while (!ActiveForcedGuestExit)
{
Thread? hostThread;
lock (_guestThreadGate)
{
if (!_guestThreads.TryGetValue(threadHandle, out var thread))
{
error = $"unknown guest thread 0x{threadHandle:X16}";
return false;
}
if (thread.State == GuestThreadRunState.Exited)
{
returnValue = thread.ExitValue;
return true;
}
if (thread.State == GuestThreadRunState.Faulted)
{
error =
$"guest thread 0x{threadHandle:X16} faulted: " +
(thread.BlockReason ?? "unknown error");
return false;
}
hostThread = thread.HostThread;
}
if (hostThread is not null &&
!ReferenceEquals(hostThread, Thread.CurrentThread))
{
hostThread.Join(1);
}
else
{
Thread.Sleep(1);
}
}
error = "guest execution stopped while joining thread";
return false;
}
public void Pump(CpuContext callerContext, string reason)
{
_ = callerContext;
@@ -3254,6 +3368,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
switch (exitReason)
{
case GuestNativeCallExitReason.Returned:
thread.ExitValue = thread.Context[CpuRegister.Rax];
thread.State = GuestThreadRunState.Exited;
break;
case GuestNativeCallExitReason.Blocked:
+6
View File
@@ -29,6 +29,12 @@ public interface IGuestThreadScheduler
bool TryStartThread(CpuContext creatorContext, GuestThreadStartRequest request, out string? error);
bool TryJoinThread(
CpuContext callerContext,
ulong threadHandle,
out ulong returnValue,
out string? error);
void Pump(CpuContext callerContext, string reason);
int WakeBlockedThreads(string wakeKey, int maxCount = int.MaxValue);
+553 -28
View File
@@ -68,17 +68,42 @@ public static class AgcExports
private const uint ComputeNumThreadZ = 0x209;
private const uint SpiPsInputCntl0 = 0x191;
private const uint VgtPrimitiveType = 0x242;
private const uint PaScScreenScissorTl = 0x0C;
private const uint PaScScreenScissorBr = 0x0D;
private const uint CbTargetMask = 0x8E;
private const uint PaScWindowOffset = 0x80;
private const uint PaScWindowScissorTl = 0x81;
private const uint PaScWindowScissorBr = 0x82;
private const uint PaScGenericScissorTl = 0x90;
private const uint PaScGenericScissorBr = 0x91;
private const uint PaScVportScissor0Tl = 0x94;
private const uint PaScVportScissor0Br = 0x95;
private const uint PaClVportXScale = 0x10F;
private const uint PaClVportXOffset = 0x110;
private const uint PaClVportYScale = 0x111;
private const uint PaClVportYOffset = 0x112;
private const uint PaScVportZMin0 = 0xB4;
private const uint PaScVportZMax0 = 0xB5;
private const uint CbBlendRed = 0x105;
private const uint CbBlendGreen = 0x106;
private const uint CbBlendBlue = 0x107;
private const uint CbBlendAlpha = 0x108;
private const uint CbColorControl = 0x202;
private const uint CbColor0Base = 0x318;
private const uint CbColorRegisterStride = 15;
private const uint CbColor0Info = 0x31C;
private const uint CbColor0BaseExt = 0x390;
private const uint CbColor0Attrib2 = 0x3B0;
private const uint CbColor0Attrib3 = 0x3B8;
private const uint CbBlend0Control = 0x1E0;
private const uint PaScModeCntl0 = 0x292;
private const int ColorTargetCount = 8;
private const uint PsTextureUserDataRegister = 0xC;
private const uint VsUserDataRegister = 0x4C;
private const uint GsUserDataRegister = 0x8C;
private const uint EsUserDataRegister = 0xCC;
private const uint ComputeUserDataRegister = 0x240;
private const uint NggUserDataScalarRegisterBase = 8;
private const uint Gen5TextureFormatR8G8B8A8Unorm = 56;
private const uint Gen5TextureFormatR16G16B16A16Float = 71;
private const uint Gen5TextureType2D = 9;
@@ -148,9 +173,25 @@ public static class AgcExports
private static readonly RegisterDefaultGroup[] PrimaryRegisterDefaults =
[
new(0, 0, 0xE24F806D, [new(CbColorControl, 0x00CC0010)]),
new(0, 3, 0x0BC65DA4, [new(0x08F, 0)]),
new(0, 4, 0x9E5AD592, [new(0x08E, 0)]),
new(0, 12, 0x6DE4C312, [new(0x203, 0)]),
new(0, 28, 0x1EB8D73A, [new(PaScModeCntl0, 0x00000002)]),
new(0, 31, 0xA20EFC70, [new(PaScWindowOffset, 0)]),
new(0, 58, 0x43FBD769,
[
new(CbBlendRed, 0),
new(CbBlendBlue, 0),
new(CbBlendGreen, 0),
new(CbBlendAlpha, 0),
]),
new(0, 59, 0xEF550356, [new(CbBlend0Control, 0x20010001)]),
new(0, 67, 0x918106BB,
[
new(PaScGenericScissorTl, 0x80000000),
new(PaScGenericScissorBr, 0x40004000),
]),
new(0, 72, 0x38E92C91,
[
new(0x318, 0),
@@ -185,6 +226,11 @@ public static class AgcExports
new(0x0B4, 0),
new(0x0B5, 0),
]),
new(0, 77, 0x078D7060,
[
new(PaScWindowScissorTl, 0x80000000),
new(PaScWindowScissorBr, 0x40004000),
]),
new(1, 13, 0xC918DF3E, [new(0x20C, 0), new(0x20D, 0)]),
new(1, 14, 0xC9751C9C, [new(0x0C8, 0), new(0x0C9, 0)]),
new(1, 18, 0xC9E01B31, [new(0x008, 0), new(0x009, 0)]),
@@ -228,7 +274,9 @@ public static class AgcExports
uint TileMode,
uint Type,
uint BaseLevel,
uint LastLevel)
uint LastLevel,
uint Pitch,
uint DstSelect)
{
public uint MipLevels
{
@@ -271,12 +319,15 @@ public static class AgcExports
VulkanGuestIndexBuffer? IndexBuffer,
IReadOnlyList<TranslatedImageBinding> Textures,
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
IReadOnlyList<RenderTargetDescriptor> RenderTargets);
IReadOnlyList<Gen5VertexInputBinding> VertexInputs,
IReadOnlyList<RenderTargetDescriptor> RenderTargets,
VulkanGuestRenderState RenderState);
private sealed record TranslatedImageBinding(
TextureDescriptor Descriptor,
bool IsStorage,
uint MipLevel);
uint MipLevel,
IReadOnlyList<uint> SamplerDescriptor);
private readonly record struct RenderTargetWriter(
ulong Sequence,
@@ -2767,11 +2818,13 @@ public static class AgcExports
out _);
var globalMemoryBuffers =
CreateVulkanGuestMemoryBuffers(translatedDraw.GlobalMemoryBindings);
VulkanVideoPresenter.SubmitOffscreenTranslatedDraw(
translatedDraw.PixelSpirv,
textures,
globalMemoryBuffers,
translatedDraw.AttributeCount,
var vertexBuffers =
CreateVulkanGuestVertexBuffers(translatedDraw.VertexInputs);
VulkanVideoPresenter.SubmitOffscreenTranslatedDraw(
translatedDraw.PixelSpirv,
textures,
globalMemoryBuffers,
translatedDraw.AttributeCount,
new VulkanGuestRenderTarget(
firstTarget.Address,
firstTarget.Width,
@@ -2782,7 +2835,9 @@ public static class AgcExports
translatedDraw.VertexCount,
translatedDraw.InstanceCount,
translatedDraw.PrimitiveType,
translatedDraw.IndexBuffer);
translatedDraw.IndexBuffer,
vertexBuffers,
translatedDraw.RenderState);
}
else
{
@@ -2873,14 +2928,16 @@ public static class AgcExports
exportShaderAddress,
exportShaderHeader,
state.ShRegisters,
EsUserDataRegister,
SelectExportUserDataRegister(state.ShRegisters),
out var exportState,
out error) ||
out error,
userDataScalarRegisterBase: NggUserDataScalarRegisterBase) ||
!Gen5ShaderScalarEvaluator.TryEvaluate(
ctx,
exportState,
out var exportEvaluation,
out error) ||
out error,
resolveVertexInputs: true) ||
!Gen5ShaderTranslator.TryCreateState(
ctx,
pixelShaderAddress,
@@ -2904,11 +2961,13 @@ public static class AgcExports
HasPixelColorExport(pixelState, target.Slot))
.ToArray();
var outputKind = GetPixelOutputKind(renderTargets.FirstOrDefault().NumberType);
var exportStateFingerprint = ComputeShaderStateFingerprint(exportEvaluation);
var pixelStateFingerprint = ComputeShaderStateFingerprint(pixelEvaluation);
var shaderKey = (
exportShaderAddress,
ComputeShaderStateFingerprint(exportEvaluation),
exportStateFingerprint,
pixelShaderAddress,
ComputeShaderStateFingerprint(pixelEvaluation),
pixelStateFingerprint,
outputKind);
(byte[] Vertex, byte[] Pixel) compiled;
lock (_submitTraceGate)
@@ -2943,6 +3002,18 @@ public static class AgcExports
}
compiled = (vertexShader.Spirv, pixelShader.Spirv);
DumpSpirv(
"vs",
exportShaderAddress,
exportStateFingerprint,
compiled.Vertex,
exportState.Program);
DumpSpirv(
"ps",
pixelShaderAddress,
pixelStateFingerprint,
compiled.Pixel,
pixelState.Program);
lock (_submitTraceGate)
{
_graphicsSpirvCache.TryAdd(shaderKey, compiled);
@@ -2966,12 +3037,15 @@ public static class AgcExports
new TranslatedImageBinding(
texture,
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode),
binding.MipLevel ?? 0));
binding.MipLevel ?? 0,
binding.SamplerDescriptor));
}
var globalMemoryBindings = pixelEvaluation.GlobalMemoryBindings
.Concat(exportEvaluation.GlobalMemoryBindings)
.ToArray();
IReadOnlyList<Gen5VertexInputBinding> vertexInputs =
exportEvaluation.VertexInputs ?? [];
state.UcRegisters.TryGetValue(VgtPrimitiveType, out var primitiveType);
draw = new TranslatedGuestDraw(
exportShaderAddress,
@@ -2985,7 +3059,9 @@ public static class AgcExports
indexed ? CreateVulkanIndexBuffer(ctx, state, vertexCount) : null,
textures,
globalMemoryBindings,
renderTargets);
vertexInputs,
renderTargets,
CreateRenderState(state.CxRegisters, renderTargets.FirstOrDefault()));
return true;
}
@@ -3004,7 +3080,9 @@ public static class AgcExports
var byteOffset = checked((ulong)state.DrawIndexOffset * (uint)bytesPerIndex);
var byteCount = checked((int)(indexCount * (uint)bytesPerIndex));
var data = new byte[byteCount];
return ctx.Memory.TryRead(state.IndexBufferAddress + byteOffset, data)
var address = state.IndexBufferAddress + byteOffset;
return (ctx.Memory.TryRead(address, data) ||
KernelMemoryCompatExports.TryReadTrackedLibcHeap(address, data))
? new VulkanGuestIndexBuffer(data, is32Bit)
: null;
}
@@ -3095,6 +3173,246 @@ public static class AgcExports
return targets;
}
private static VulkanGuestRenderState CreateRenderState(
IReadOnlyDictionary<uint, uint> registers,
RenderTargetDescriptor target)
{
var scissor = DecodeScissor(registers, target.Width, target.Height);
return new VulkanGuestRenderState(
DecodeBlendState(registers, target.Slot),
scissor,
DecodeViewport(registers, target.Width, target.Height, scissor));
}
private static VulkanGuestBlendState DecodeBlendState(
IReadOnlyDictionary<uint, uint> registers,
uint slot)
{
var writeMask = 0xFu;
if (registers.TryGetValue(CbTargetMask, out var targetMask))
{
writeMask = (targetMask >> checked((int)(slot * 4))) & 0xFu;
}
registers.TryGetValue(CbBlend0Control + slot, out var control);
return new VulkanGuestBlendState(
((control >> 30) & 1u) != 0,
control & 0x1Fu,
(control >> 8) & 0x1Fu,
(control >> 5) & 0x7u,
(control >> 16) & 0x1Fu,
(control >> 24) & 0x1Fu,
(control >> 21) & 0x7u,
((control >> 29) & 1u) != 0,
writeMask == 0 ? 0xFu : writeMask);
}
private static VulkanGuestRect? DecodeScissor(
IReadOnlyDictionary<uint, uint> registers,
uint targetWidth,
uint targetHeight)
{
if (targetWidth == 0 || targetHeight == 0)
{
return new VulkanGuestRect(0, 0, 0, 0);
}
var left = 0;
var top = 0;
var right = checked((int)Math.Min(targetWidth, int.MaxValue));
var bottom = checked((int)Math.Min(targetHeight, int.MaxValue));
var windowOffsetX = 0;
var windowOffsetY = 0;
var enableWindowOffset = true;
if (registers.TryGetValue(PaScWindowScissorTl, out var windowScissorTl))
{
enableWindowOffset = (windowScissorTl & 0x80000000u) == 0;
}
if (enableWindowOffset &&
registers.TryGetValue(PaScWindowOffset, out var windowOffset))
{
windowOffsetX = (short)(windowOffset & 0xFFFFu);
windowOffsetY = (short)(windowOffset >> 16);
}
IntersectScissorPair(registers, PaScScreenScissorTl, PaScScreenScissorBr, ref left, ref top, ref right, ref bottom);
IntersectScissorPair(
registers,
PaScWindowScissorTl,
PaScWindowScissorBr,
ref left,
ref top,
ref right,
ref bottom,
windowOffsetX,
windowOffsetY);
IntersectScissorPair(
registers,
PaScGenericScissorTl,
PaScGenericScissorBr,
ref left,
ref top,
ref right,
ref bottom,
windowOffsetX,
windowOffsetY);
var vportScissorEnabled =
!registers.TryGetValue(PaScModeCntl0, out var modeControl) ||
((modeControl >> 1) & 1u) != 0;
if (vportScissorEnabled)
{
IntersectScissorPair(registers, PaScVportScissor0Tl, PaScVportScissor0Br, ref left, ref top, ref right, ref bottom);
}
left = Math.Clamp(left, 0, checked((int)targetWidth));
top = Math.Clamp(top, 0, checked((int)targetHeight));
right = Math.Clamp(right, left, checked((int)targetWidth));
bottom = Math.Clamp(bottom, top, checked((int)targetHeight));
if (left == 0 &&
top == 0 &&
right == (int)targetWidth &&
bottom == (int)targetHeight)
{
return null;
}
return new VulkanGuestRect(
left,
top,
checked((uint)(right - left)),
checked((uint)(bottom - top)));
}
private static VulkanGuestViewport? DecodeViewport(
IReadOnlyDictionary<uint, uint> registers,
uint targetWidth,
uint targetHeight,
VulkanGuestRect? scissor)
{
if (targetWidth == 0 || targetHeight == 0)
{
return new VulkanGuestViewport(0, 0, 0, 0, 0, 1);
}
var minDepth = 0f;
var maxDepth = 1f;
if (registers.TryGetValue(PaScVportZMin0, out var zMinBits) &&
registers.TryGetValue(PaScVportZMax0, out var zMaxBits))
{
var decodedMin = BitConverter.UInt32BitsToSingle(zMinBits);
var decodedMax = BitConverter.UInt32BitsToSingle(zMaxBits);
if (float.IsFinite(decodedMin) &&
float.IsFinite(decodedMax) &&
decodedMax > decodedMin)
{
minDepth = decodedMin;
maxDepth = decodedMax;
}
}
if (TryDecodeFiniteFloat(registers, PaClVportXScale, out var xScale) &&
TryDecodeFiniteFloat(registers, PaClVportXOffset, out var xOffset) &&
TryDecodeFiniteFloat(registers, PaClVportYScale, out var yScale) &&
TryDecodeFiniteFloat(registers, PaClVportYOffset, out var yOffset) &&
xScale > 0f &&
yScale != 0f)
{
return new VulkanGuestViewport(
xOffset - xScale,
yOffset - yScale,
xScale * 2f,
yScale * 2f,
minDepth,
maxDepth);
}
if (scissor is not { } rect)
{
return minDepth == 0f && maxDepth == 1f
? null
: new VulkanGuestViewport(0, 0, targetWidth, targetHeight, minDepth, maxDepth);
}
return new VulkanGuestViewport(
rect.X,
rect.Y,
rect.Width,
rect.Height,
minDepth,
maxDepth);
}
private static bool TryDecodeFiniteFloat(
IReadOnlyDictionary<uint, uint> registers,
uint register,
out float value)
{
value = 0;
if (!registers.TryGetValue(register, out var bits))
{
return false;
}
value = BitConverter.UInt32BitsToSingle(bits);
return float.IsFinite(value);
}
private static void IntersectScissorPair(
IReadOnlyDictionary<uint, uint> registers,
uint tlRegister,
uint brRegister,
ref int left,
ref int top,
ref int right,
ref int bottom,
int offsetX = 0,
int offsetY = 0)
{
if (!TryDecodeScissorPair(registers, tlRegister, brRegister, out var pairLeft, out var pairTop, out var pairRight, out var pairBottom))
{
return;
}
pairLeft += offsetX;
pairTop += offsetY;
pairRight += offsetX;
pairBottom += offsetY;
left = Math.Max(left, pairLeft);
top = Math.Max(top, pairTop);
right = Math.Min(right, pairRight);
bottom = Math.Min(bottom, pairBottom);
}
private static bool TryDecodeScissorPair(
IReadOnlyDictionary<uint, uint> registers,
uint tlRegister,
uint brRegister,
out int left,
out int top,
out int right,
out int bottom)
{
left = 0;
top = 0;
right = 0;
bottom = 0;
if (!registers.TryGetValue(tlRegister, out var tl) ||
!registers.TryGetValue(brRegister, out var br))
{
return false;
}
left = (int)(tl & 0x7FFFu);
top = (int)((tl >> 16) & 0x7FFFu);
right = (int)(br & 0x7FFFu);
bottom = (int)((br >> 16) & 0x7FFFu);
return true;
}
private static void TraceTranslatedGuestDraw(
CpuContext ctx,
SubmittedGpuState gpuState,
@@ -3144,12 +3462,40 @@ public static class AgcExports
$"fmt{texture.Format}/num{texture.NumberType}/tile{texture.TileMode}" +
$"/storage={binding.IsStorage}{target}/{probe}{writer}";
}));
var buffers = string.Join(
',',
draw.GlobalMemoryBindings.Select((binding, index) =>
$"{index}:0x{binding.BaseAddress:X16}:{binding.Data.Length}:" +
Convert.ToHexString(binding.Data.AsSpan(0, Math.Min(binding.Data.Length, 32)))));
var indices = draw.IndexBuffer is { } indexBuffer
? $"{(indexBuffer.Is32Bit ? 32 : 16)}:" +
Convert.ToHexString(indexBuffer.Data.AsSpan(0, Math.Min(indexBuffer.Data.Length, 32)))
: "none";
var vertexInputs = draw.VertexInputs.Count == 0
? "none"
: string.Join(
',',
draw.VertexInputs.Select(input =>
$"{input.Location}:pc=0x{input.Pc:X}:0x{input.BaseAddress:X16}" +
$":stride{input.Stride}:off{input.OffsetBytes}:c{input.ComponentCount}"));
var scissor = draw.RenderState.Scissor is { } drawScissor
? $"{drawScissor.X},{drawScissor.Y},{drawScissor.Width}x{drawScissor.Height}"
: "full";
var viewport = draw.RenderState.Viewport is { } drawViewport
? $"{drawViewport.X:0.###},{drawViewport.Y:0.###}," +
$"{drawViewport.Width:0.###}x{drawViewport.Height:0.###}:" +
$"{drawViewport.MinDepth:0.###}-{drawViewport.MaxDepth:0.###}"
: "full";
var blend = draw.RenderState.Blend;
TraceAgcShader(
$"agc.shader_draw es=0x{draw.ExportShaderAddress:X16} " +
$"ps=0x{draw.PixelShaderAddress:X16} spirv={draw.PixelSpirv.Length} " +
$"primitive=0x{draw.PrimitiveType:X} " +
$"blend={(blend.Enable ? 1 : 0)}:{blend.ColorSrcFactor}/{blend.ColorDstFactor}/{blend.ColorFunc} " +
$"write_mask=0x{blend.WriteMask:X} scissor={scissor} viewport={viewport} " +
$"ps_ena=0x{psInputEna:X8} ps_addr=0x{psInputAddr:X8} " +
$"targets=[{targets}] textures=[{textures}]");
$"targets=[{targets}] textures=[{textures}] " +
$"buffers=[{buffers}] vertex=[{vertexInputs}] indices=[{indices}]");
}
private static IReadOnlyList<VulkanGuestDrawTexture> CreateVulkanGuestDrawTextures(
@@ -3166,6 +3512,7 @@ public static class AgcExports
binding.Descriptor,
binding.IsStorage,
binding.MipLevel,
binding.SamplerDescriptor,
out var texture))
{
textures.Add(texture);
@@ -3193,11 +3540,31 @@ public static class AgcExports
return buffers;
}
private static IReadOnlyList<VulkanGuestVertexBuffer> CreateVulkanGuestVertexBuffers(
IReadOnlyList<Gen5VertexInputBinding> bindings)
{
var buffers = new VulkanGuestVertexBuffer[bindings.Count];
for (var index = 0; index < bindings.Count; index++)
{
var binding = bindings[index];
buffers[index] = new VulkanGuestVertexBuffer(
binding.Location,
binding.ComponentCount,
binding.BaseAddress,
binding.Stride,
binding.OffsetBytes,
binding.Data);
}
return buffers;
}
private static bool TryCreateVulkanGuestDrawTexture(
CpuContext ctx,
TextureDescriptor descriptor,
bool isStorage,
uint mipLevel,
IReadOnlyList<uint> samplerDescriptor,
out VulkanGuestDrawTexture texture)
{
texture = default!;
@@ -3211,9 +3578,12 @@ public static class AgcExports
return true;
}
var sourceWidth = descriptor.TileMode == 0
? Math.Max(descriptor.Width, descriptor.Pitch)
: descriptor.Width;
var sourceByteCount = GetTextureByteCount(
descriptor.Format,
descriptor.Width,
sourceWidth,
descriptor.Height);
if (sourceByteCount == 0 ||
sourceByteCount > MaxPresentedTextureBytes ||
@@ -3246,7 +3616,11 @@ public static class AgcExports
IsFallback: descriptor.Address == 0,
IsStorage: true,
MipLevels: descriptor.MipLevels,
MipLevel: mipLevel);
MipLevel: mipLevel,
Pitch: descriptor.Pitch,
TileMode: descriptor.TileMode,
DstSelect: descriptor.DstSelect,
Sampler: ToVulkanSampler(samplerDescriptor));
return true;
}
@@ -3273,7 +3647,8 @@ public static class AgcExports
TraceAgcShader(
$"agc.texture_source addr=0x{descriptor.Address:X16} " +
$"fmt={descriptor.Format} num={descriptor.NumberType} tile={descriptor.TileMode} " +
$"size={descriptor.Width}x{descriptor.Height} " +
$"size={descriptor.Width}x{descriptor.Height} pitch={descriptor.Pitch} " +
$"dst=0x{descriptor.DstSelect:X3} " +
$"bytes={source.Length} nonzero64={nonZero}");
var rgba = source;
@@ -3287,7 +3662,11 @@ public static class AgcExports
IsFallback: false,
IsStorage: isStorage,
MipLevels: descriptor.MipLevels,
MipLevel: mipLevel);
MipLevel: mipLevel,
Pitch: descriptor.Pitch,
TileMode: descriptor.TileMode,
DstSelect: descriptor.DstSelect,
Sampler: ToVulkanSampler(samplerDescriptor));
return true;
}
@@ -3304,6 +3683,15 @@ public static class AgcExports
MipLevels: 1,
MipLevel: 0);
private static VulkanGuestSampler ToVulkanSampler(IReadOnlyList<uint> descriptor) =>
descriptor.Count >= 4
? new VulkanGuestSampler(
descriptor[0],
descriptor[1],
descriptor[2],
descriptor[3])
: default;
private static byte[] ConvertRgba16FloatToRgba8(ReadOnlySpan<byte> source, uint width, uint height)
{
var destination = new byte[checked((int)((ulong)width * height * 4))];
@@ -3456,7 +3844,8 @@ public static class AgcExports
new TranslatedImageBinding(
texture,
isStorage,
binding.MipLevel ?? 0));
binding.MipLevel ?? 0,
binding.SamplerDescriptor));
hasStorageBinding |= isStorage;
var descriptorState = descriptorValid ? string.Empty : "/invalid-desc";
@@ -3511,6 +3900,12 @@ public static class AgcExports
out computeError))
{
computeSpirv = compiledCompute.Spirv;
DumpSpirv(
"cs",
shaderAddress,
shaderKey.Item2,
computeSpirv,
shaderState.Program);
}
if (computeSpirv is not null)
@@ -3527,6 +3922,7 @@ public static class AgcExports
var globalMemoryBuffers =
CreateVulkanGuestMemoryBuffers(evaluation.GlobalMemoryBindings);
VulkanVideoPresenter.SubmitComputeDispatch(
shaderAddress,
computeSpirv,
textures,
globalMemoryBuffers,
@@ -3601,6 +3997,62 @@ public static class AgcExports
private static string DescribeRegister(uint? register) =>
register.HasValue ? $"s{register.Value}" : "-";
private static uint SelectExportUserDataRegister(
IReadOnlyDictionary<uint, uint> registers)
{
if (HasUserDataRange(registers, GsUserDataRegister))
{
return GsUserDataRegister;
}
if (HasUserDataRange(registers, EsUserDataRegister))
{
return EsUserDataRegister;
}
if (HasUserDataRange(registers, VsUserDataRegister))
{
return VsUserDataRegister;
}
var esValues = CountUserDataValues(registers, EsUserDataRegister);
var vsValues = CountUserDataValues(registers, VsUserDataRegister);
return esValues == 0 && vsValues != 0
? VsUserDataRegister
: EsUserDataRegister;
}
private static bool HasUserDataRange(
IReadOnlyDictionary<uint, uint> registers,
uint startRegister)
{
for (var index = 0u; index < 16; index++)
{
if (registers.ContainsKey(startRegister + index))
{
return true;
}
}
return false;
}
private static int CountUserDataValues(
IReadOnlyDictionary<uint, uint> registers,
uint startRegister)
{
var count = 0;
for (var index = 0u; index < 16; index++)
{
count += registers.TryGetValue(startRegister + index, out var value) &&
value != 0
? 1
: 0;
}
return count;
}
private static uint GetComputeLocalSize(
IReadOnlyDictionary<uint, uint> registers,
uint register)
@@ -3798,7 +4250,31 @@ public static class AgcExports
}
}
return $"probe={reads}/{sampleCount}:{nonzero}:0x{hash:X16}";
var bytesPerTexel = GetTextureBytesPerTexel(texture.Format);
var texels = bytesPerTexel is > 0 and <= 16
? string.Join(
'/',
ProbeTextureTexel(ctx, texture.Address, (int)bytesPerTexel),
ProbeTextureTexel(
ctx,
texture.Address +
(((ulong)(texture.Height / 2) * texture.Width) + (texture.Width / 2)) *
bytesPerTexel,
(int)bytesPerTexel),
ProbeTextureTexel(
ctx,
texture.Address + totalBytes - bytesPerTexel,
(int)bytesPerTexel))
: "unsupported";
return $"probe={reads}/{sampleCount}:{nonzero}:0x{hash:X16}:texels={texels}";
}
private static string ProbeTextureTexel(CpuContext ctx, ulong address, int size)
{
var texel = new byte[size];
return ctx.Memory.TryRead(address, texel)
? Convert.ToHexString(texel)
: "unreadable";
}
private static ulong GetTextureBytesPerTexel(uint format) =>
@@ -3912,9 +4388,10 @@ public static class AgcExports
exportShaderAddress,
exportShaderHeader,
state.ShRegisters,
EsUserDataRegister,
SelectExportUserDataRegister(state.ShRegisters),
out var exportState,
out _) &&
out _,
userDataScalarRegisterBase: NggUserDataScalarRegisterBase) &&
Gen5ShaderTranslator.TryCreateState(
ctx,
pixelShaderAddress,
@@ -4130,6 +4607,10 @@ public static class AgcExports
var type = (fields[3] >> 28) & 0xFu;
var baseLevel = (fields[3] >> 12) & 0xFu;
var lastLevel = (fields[3] >> 16) & 0xFu;
var pitch = fields.Count >= 5
? ((fields[4] >> 13) & 0x3FFFu) + 1
: width;
var dstSelect = fields[3] & 0xFFFu;
if (address == 0 || width == 0 || height == 0)
{
return false;
@@ -4144,7 +4625,9 @@ public static class AgcExports
tileMode,
type,
baseLevel,
lastLevel);
lastLevel,
pitch,
dstSelect);
return true;
}
@@ -4173,7 +4656,9 @@ public static class AgcExports
TileMode: tileMode,
Type: Gen5TextureType2D,
BaseLevel: 0,
LastLevel: 0);
LastLevel: 0,
Pitch: 1,
DstSelect: 0xFAC);
}
private static bool TrySoftwarePresent(
@@ -4882,6 +5367,46 @@ public static class AgcExports
? "none"
: string.Join(',', values.Select(static value => $"{value:X8}"));
private static void DumpSpirv(
string stage,
ulong shaderAddress,
ulong stateFingerprint,
byte[] spirv,
Gen5ShaderProgram program)
{
if (spirv.Length == 0 ||
!string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_DUMP_SPIRV"),
"1",
StringComparison.Ordinal))
{
return;
}
var directory = Path.Combine(AppContext.BaseDirectory, "shader-dumps");
Directory.CreateDirectory(directory);
var name = $"{shaderAddress:X16}-{stateFingerprint:X16}.{stage}";
File.WriteAllBytes(Path.Combine(directory, $"{name}.spv"), spirv);
var lines = new List<string>(program.Instructions.Count + 2)
{
$"address=0x{program.Address:X16}",
"pc words opcode destinations <- sources control",
};
foreach (var instruction in program.Instructions)
{
lines.Add(
$"0x{instruction.Pc:X4} " +
$"{string.Join('_', instruction.Words.Select(static word => $"{word:X8}"))} " +
$"{instruction.Opcode} " +
$"{string.Join(',', instruction.Destinations)} <- " +
$"{string.Join(',', instruction.Sources)} " +
$"{instruction.Control}");
}
File.WriteAllLines(Path.Combine(directory, $"{name}.ir.txt"), lines);
}
private static void TraceCreateShader(ulong destinationAddress, ulong headerAddress, ulong codeAddress, string detail)
{
var isOk = string.Equals(detail, "ok", StringComparison.Ordinal);
+15 -3
View File
@@ -60,7 +60,8 @@ internal sealed record Gen5SpirvShader(
byte[] Spirv,
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
IReadOnlyList<Gen5ImageBinding> ImageBindings,
uint AttributeCount);
uint AttributeCount,
IReadOnlyList<Gen5VertexInputBinding> VertexInputs);
internal readonly record struct Gen5ShaderResourceMapping(
Gen5ShaderResourceKind Kind,
@@ -131,7 +132,8 @@ internal sealed record Gen5ShaderState(
Gen5ShaderProgram Program,
IReadOnlyList<uint> UserData,
Gen5ShaderMetadata? Metadata,
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null);
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null,
uint UserDataScalarRegisterBase = 0);
internal readonly record struct Gen5Operand(Gen5OperandKind Kind, uint Value)
{
@@ -270,6 +272,15 @@ internal sealed record Gen5GlobalMemoryBinding(
IReadOnlyList<uint> InstructionPcs,
byte[] Data);
internal sealed record Gen5VertexInputBinding(
uint Pc,
uint Location,
uint ComponentCount,
ulong BaseAddress,
uint Stride,
uint OffsetBytes,
byte[] Data);
internal sealed record Gen5ShaderEvaluation(
IReadOnlyList<uint> InitialScalarRegisters,
IReadOnlyList<uint> ScalarRegisters,
@@ -277,7 +288,8 @@ internal sealed record Gen5ShaderEvaluation(
IReadOnlyList<Gen5ImageBinding> ImageBindings,
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null,
IReadOnlySet<uint>? RuntimeScalarRegisters = null);
IReadOnlySet<uint>? RuntimeScalarRegisters = null,
IReadOnlyList<Gen5VertexInputBinding>? VertexInputs = null);
internal sealed record Gen5ShaderInstruction(
uint Pc,
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
using System.Buffers.Binary;
using System.Numerics;
@@ -13,6 +14,7 @@ internal static class Gen5ShaderScalarEvaluator
private const int ImageDescriptorDwords = 8;
private const int SamplerDescriptorDwords = 4;
private const int MaxGlobalMemoryBindingBytes = 16 * 1024 * 1024;
private const ulong RdnaWaveMask = 0xFFFF_FFFFUL;
private readonly record struct BufferDescriptor(
ulong BaseAddress,
@@ -40,14 +42,19 @@ internal static class Gen5ShaderScalarEvaluator
CpuContext ctx,
Gen5ShaderState state,
out Gen5ShaderEvaluation evaluation,
out string error)
out string error,
bool resolveVertexInputs = false)
{
evaluation = default!;
error = string.Empty;
var scalarRegisters = new uint[ScalarRegisterCount];
for (var index = 0; index < state.UserData.Count && index < scalarRegisters.Length; index++)
for (var index = 0;
index < state.UserData.Count &&
state.UserDataScalarRegisterBase + (uint)index < scalarRegisters.Length;
index++)
{
scalarRegisters[index] = state.UserData[index];
scalarRegisters[state.UserDataScalarRegisterBase + (uint)index] =
state.UserData[index];
}
if (state.ComputeSystemRegisters is { } computeSystemRegisters)
@@ -55,14 +62,15 @@ internal static class Gen5ShaderScalarEvaluator
computeSystemRegisters.ClearStaticValues(scalarRegisters);
}
var execMask = ulong.MaxValue;
WriteScalarPair(scalarRegisters, 106, ulong.MaxValue, ref execMask);
var execMask = RdnaWaveMask;
WriteScalarPair(scalarRegisters, 106, 0, ref execMask);
WriteScalarPair(scalarRegisters, 126, execMask, ref execMask);
var initialScalarRegisters = (uint[])scalarRegisters.Clone();
var resolved = new List<Gen5ImageBinding>();
var globalMemoryBindings = new List<Gen5GlobalMemoryBinding>();
var globalMemoryByAddress = new Dictionary<(uint ScalarAddress, ulong BaseAddress), Gen5GlobalMemoryBinding>();
var vertexInputBindings = new List<Gen5VertexInputBinding>();
var runtimeScalarRegisters = CollectRuntimeScalarRegisters(state.Program);
var scalarRegisterSnapshots = new Dictionary<uint, IReadOnlyList<uint>>();
var scalarConditionCode = false;
@@ -243,6 +251,42 @@ internal static class Gen5ShaderScalarEvaluator
return false;
}
if (resolveVertexInputs &&
IsVertexFetchCandidate(instruction, bufferMemory, bufferDescriptor))
{
if (!TryReadGlobalMemory(
ctx,
bufferDescriptor.BaseAddress,
bufferDescriptor.SizeBytes,
out var vertexData))
{
error =
$"vertex-buffer-read-failed pc=0x{instruction.Pc:X} " +
$"address=0x{bufferDescriptor.BaseAddress:X16} " +
$"bytes={bufferDescriptor.SizeBytes} " +
$"stride={bufferDescriptor.Stride} records={bufferDescriptor.NumRecords}";
return false;
}
if (!TryCreateVertexInputBinding(
instruction,
bufferMemory,
bufferDescriptor,
vertexData,
(uint)vertexInputBindings.Count,
scalarRegisters,
out var vertexInputBinding))
{
error =
$"vertex-input-binding-failed pc=0x{instruction.Pc:X} " +
$"s{bufferMemory.ScalarResource}";
return false;
}
vertexInputBindings.Add(vertexInputBinding);
continue;
}
var key = (bufferMemory.ScalarResource, bufferDescriptor.BaseAddress);
if (globalMemoryByAddress.TryGetValue(key, out var existingBinding))
{
@@ -259,10 +303,16 @@ internal static class Gen5ShaderScalarEvaluator
bufferDescriptor.SizeBytes,
out var data))
{
var descriptorWords = string.Join(
':',
Enumerable.Range(0, 4).Select(index =>
$"{scalarRegisters[bufferMemory.ScalarResource + (uint)index]:X8}"));
error =
$"buffer-memory-read-failed pc=0x{instruction.Pc:X} " +
$"address=0x{bufferDescriptor.BaseAddress:X16} " +
$"bytes={bufferDescriptor.SizeBytes}";
$"bytes={bufferDescriptor.SizeBytes} " +
$"stride={bufferDescriptor.Stride} records={bufferDescriptor.NumRecords} " +
$"s{bufferMemory.ScalarResource}=[{descriptorWords}]";
return false;
}
@@ -328,10 +378,51 @@ internal static class Gen5ShaderScalarEvaluator
resolved,
globalMemoryBindings,
state.ComputeSystemRegisters,
runtimeScalarRegisters);
runtimeScalarRegisters,
vertexInputBindings);
return true;
}
private static bool TryCreateVertexInputBinding(
Gen5ShaderInstruction instruction,
Gen5BufferMemoryControl control,
BufferDescriptor descriptor,
byte[] data,
uint location,
uint[] scalarRegisters,
out Gen5VertexInputBinding binding)
{
binding = default!;
if (!IsVertexFetchCandidate(instruction, control, descriptor) ||
instruction.Sources.Count <= 2 ||
!TryEvaluateScalarOperand(instruction.Sources[2], scalarRegisters, out var scalarOffset))
{
return false;
}
binding = new Gen5VertexInputBinding(
instruction.Pc,
location,
control.DwordCount,
descriptor.BaseAddress,
descriptor.Stride,
unchecked((uint)control.OffsetBytes + scalarOffset),
data);
return true;
}
private static bool IsVertexFetchCandidate(
Gen5ShaderInstruction instruction,
Gen5BufferMemoryControl control,
BufferDescriptor descriptor) =>
control.IndexEnabled &&
!control.OffsetEnabled &&
control.DwordCount is >= 1 and <= 4 &&
descriptor.BaseAddress != 0 &&
descriptor.Stride != 0 &&
(instruction.Opcode.StartsWith("BufferLoadFormat", StringComparison.Ordinal) ||
instruction.Opcode.StartsWith("TBufferLoadFormat", StringComparison.Ordinal));
private static HashSet<uint> CollectRuntimeScalarRegisters(Gen5ShaderProgram program)
{
var registers = new HashSet<uint>();
@@ -471,10 +562,22 @@ internal static class Gen5ShaderScalarEvaluator
return false;
}
data = GC.AllocateUninitializedArray<byte>((int)cappedSize);
if (ctx.Memory.TryRead(baseAddress, data))
var candidateSize = (int)cappedSize;
while (candidateSize >= sizeof(uint))
{
return true;
data = GC.AllocateUninitializedArray<byte>(candidateSize);
if (ctx.Memory.TryRead(baseAddress, data) ||
KernelMemoryCompatExports.TryReadTrackedLibcHeap(baseAddress, data))
{
return true;
}
if (candidateSize == sizeof(uint))
{
break;
}
candidateSize = Math.Max(candidateSize / 2, sizeof(uint));
}
data = [];
@@ -582,6 +685,49 @@ internal static class Gen5ShaderScalarEvaluator
return true;
}
if (instruction.Opcode is "SBfeU64" or "SBfeI64")
{
if (instruction.Sources.Count < 2 ||
destination.Value >= ScalarRegisterCount - 1 ||
!TryEvaluateScalarOperand64(
instruction.Sources[0],
registers,
execMask,
out var source) ||
!TryEvaluateScalarOperand(
instruction.Sources[1],
registers,
out var control))
{
error = $"scalar-source64 pc=0x{instruction.Pc:X} op={instruction.Opcode}";
return false;
}
var offset = (int)control & 63;
var width = Math.Min(((int)control >> 16) & 0x7F, 64 - offset);
ulong value;
if (width == 0)
{
value = 0;
}
else
{
value = source >> offset;
if (width < 64)
{
value &= ulong.MaxValue >> (64 - width);
if (instruction.Opcode == "SBfeI64")
{
value = unchecked((ulong)((long)(value << (64 - width)) >> (64 - width)));
}
}
}
WriteScalarPair(registers, destination.Value, value, ref execMask);
scalarConditionCode = value != 0;
return true;
}
if (instruction.Opcode is
"SCselectB64" or
"SAndB64" or
@@ -884,9 +1030,9 @@ internal static class Gen5ShaderScalarEvaluator
};
WriteScalarPair(registers, destination.Value, oldExec, ref execMask);
execMask = newExec;
execMask = MaskWaveValue(newExec);
WriteScalarPair(registers, 126, execMask, ref execMask);
scalarConditionCode = newExec != 0;
scalarConditionCode = execMask != 0;
return true;
}
@@ -934,6 +1080,11 @@ internal static class Gen5ShaderScalarEvaluator
return;
}
if (destination == 126)
{
value = MaskWaveValue(value);
}
registers[destination] = (uint)value;
registers[destination + 1] = (uint)(value >> 32);
if (destination == 126)
@@ -942,6 +1093,8 @@ internal static class Gen5ShaderScalarEvaluator
}
}
private static ulong MaskWaveValue(ulong value) => value & RdnaWaveMask;
private static bool TryExecuteScalarCompare(
Gen5ShaderInstruction instruction,
uint[] registers,
+22 -15
View File
@@ -113,7 +113,8 @@ internal static class Gen5ShaderTranslator
uint userDataBaseRegister,
out Gen5ShaderState state,
out string error,
Gen5ComputeSystemRegisters? computeSystemRegisters = null)
Gen5ComputeSystemRegisters? computeSystemRegisters = null,
uint userDataScalarRegisterBase = 0)
{
state = default!;
if (!TryDecodeProgram(ctx, shaderAddress, out var program, out error))
@@ -134,7 +135,12 @@ internal static class Gen5ShaderTranslator
shaderRegisters.TryGetValue(userDataBaseRegister + index, out userData[index]);
}
state = new Gen5ShaderState(program, userData, metadata, computeSystemRegisters);
state = new Gen5ShaderState(
program,
userData,
metadata,
computeSystemRegisters,
userDataScalarRegisterBase);
return true;
}
@@ -179,7 +185,9 @@ internal static class Gen5ShaderTranslator
: string.Empty;
if (state.Metadata is not { } metadata)
{
return $"ud[{userData}]{systemRegisters} metadata=missing";
return
$"ud_base=s{state.UserDataScalarRegisterBase} ud[{userData}]" +
$"{systemRegisters} metadata=missing";
}
var direct = string.Join(
@@ -191,7 +199,8 @@ internal static class Gen5ShaderTranslator
$"{resource.Kind}[{resource.Slot}]@{resource.OffsetDwords}" +
(resource.SizeFlag ? "+" : string.Empty)));
return
$"ud[{userData}]{systemRegisters} metadata[eud={metadata.ExtendedUserDataSizeDwords}," +
$"ud_base=s{state.UserDataScalarRegisterBase} ud[{userData}]" +
$"{systemRegisters} metadata[eud={metadata.ExtendedUserDataSizeDwords}," +
$"srt={metadata.ShaderResourceTableSizeDwords},direct={direct},resources={resources}]";
}
@@ -668,9 +677,8 @@ internal static class Gen5ShaderTranslator
error = string.Empty;
name = opcode switch
{
0x00 => "VCndmaskB32",
0x01 => "VReadlaneB32",
0x02 => "VWritelaneB32",
0x01 => "VCndmaskB32",
0x02 => "VDot2cF32F16",
0x03 => "VAddF32",
0x04 => "VSubF32",
0x05 => "VSubrevF32",
@@ -810,7 +818,7 @@ internal static class Gen5ShaderTranslator
}
: opcode switch
{
0x101 => "VReadlaneB32",
0x101 => "VCndmaskB32",
0x103 => "VAddF32",
0x104 => "VSubF32",
0x108 => "VMulF32",
@@ -1246,17 +1254,16 @@ internal static class Gen5ShaderTranslator
var scalarOffset = (extra >> 25) & 0x7F;
var offset = SignExtend(extra & 0x1FFFFF, 21);
var count = ScalarLoadDwordCount(opcode);
var dynamicOffsetRegister = scalarOffset <= 105 || scalarOffset == 124
? scalarOffset
: (uint?)null;
sources = dynamicOffsetRegister.HasValue
? [Gen5Operand.Scalar(scalarBase), Gen5Operand.Scalar(dynamicOffsetRegister.Value)]
: [Gen5Operand.Scalar(scalarBase)];
sources =
[
Gen5Operand.Scalar(scalarBase),
Gen5Operand.Scalar(scalarOffset),
];
destinations = Enumerable
.Range((int)scalarDestination, checked((int)count))
.Select(index => Gen5Operand.Scalar((uint)index))
.ToArray();
control = new Gen5ScalarMemoryControl(count, offset, dynamicOffsetRegister);
control = new Gen5ScalarMemoryControl(count, offset, scalarOffset);
break;
}
case Gen5ShaderEncoding.Vop1:
+133 -96
View File
@@ -38,7 +38,9 @@ internal static partial class Gen5SpirvTranslator
break;
case "VCndmaskB32":
{
var condition = Load(_boolType, _vcc);
var condition = instruction.Sources.Count > 2
? IsCurrentLaneSet(GetRawSource64(instruction, 2))
: Load(_boolType, _vcc);
result = _module.AddInstruction(
SpirvOp.Select,
_uintType,
@@ -131,9 +133,7 @@ internal static partial class Gen5SpirvTranslator
SpirvOp.ConvertFToU,
_uintType,
GetFloatSource(instruction, 0));
var offset = _module.AddInstruction(
SpirvOp.ShiftLeftLogical,
_uintType,
var offset = ShiftLeftLogical(
BitwiseAnd(GetRawSource(instruction, 1), UInt(3)),
UInt(3));
result = _module.AddInstruction(
@@ -350,9 +350,7 @@ internal static partial class Gen5SpirvTranslator
result = _module.AddInstruction(
SpirvOp.UConvert,
_uintType,
_module.AddInstruction(
SpirvOp.ShiftRightLogical,
_ulongType,
ShiftRightLogical64(
product,
_module.Constant64(_ulongType, 32)));
break;
@@ -415,21 +413,12 @@ internal static partial class Gen5SpirvTranslator
var reverse = instruction.Opcode == "VAshrrevI32";
var left = GetRawSource(instruction, reverse ? 1 : 0);
var right = GetRawSource(instruction, reverse ? 0 : 1);
right = BitwiseAnd(right, UInt(31));
result = Bitcast(
_uintType,
_module.AddInstruction(
SpirvOp.ShiftRightArithmetic,
_intType,
Bitcast(_intType, left),
right));
result = ShiftRightArithmetic(left, right);
break;
}
case "VLshlAddU32":
{
var shifted = _module.AddInstruction(
SpirvOp.ShiftLeftLogical,
_uintType,
var shifted = ShiftLeftLogical(
GetRawSource(instruction, 0),
BitwiseAnd(GetRawSource(instruction, 1), UInt(31)));
result = IAdd(shifted, GetRawSource(instruction, 2));
@@ -437,9 +426,7 @@ internal static partial class Gen5SpirvTranslator
}
case "VLshlOrU32":
{
var shifted = _module.AddInstruction(
SpirvOp.ShiftLeftLogical,
_uintType,
var shifted = ShiftLeftLogical(
GetRawSource(instruction, 0),
BitwiseAnd(GetRawSource(instruction, 1), UInt(31)));
result = BitwiseOr(
@@ -472,11 +459,7 @@ internal static partial class Gen5SpirvTranslator
var added = IAdd(
GetRawSource(instruction, 0),
GetRawSource(instruction, 1));
result = _module.AddInstruction(
SpirvOp.ShiftLeftLogical,
_uintType,
added,
BitwiseAnd(GetRawSource(instruction, 2), UInt(31)));
result = ShiftLeftLogical(added, GetRawSource(instruction, 2));
break;
}
case "VAdd3U32":
@@ -857,7 +840,7 @@ internal static partial class Gen5SpirvTranslator
condition = _module.AddInstruction(operation, _boolType, left, right);
}
Store(_vcc, condition);
StoreWaveMask(106, condition);
if (opcode.StartsWith("VCmpx", StringComparison.Ordinal))
{
var active = _module.AddInstruction(
@@ -865,7 +848,7 @@ internal static partial class Gen5SpirvTranslator
_boolType,
Load(_boolType, _exec),
condition);
Store(_exec, active);
StoreWaveMask(126, active);
}
return true;
@@ -930,7 +913,7 @@ internal static partial class Gen5SpirvTranslator
}
if (instruction.Opcode.EndsWith("B64", StringComparison.Ordinal) ||
instruction.Opcode == "SWqmB64")
instruction.Opcode is "SWqmB64" or "SBfeU64" or "SBfeI64")
{
return TryEmitScalar64(instruction, destination, out error);
}
@@ -1099,11 +1082,7 @@ internal static partial class Gen5SpirvTranslator
_module.AddInstruction(SpirvOp.Not, _uintType, right));
break;
case "SLshlB32":
result = _module.AddInstruction(
SpirvOp.ShiftLeftLogical,
_uintType,
left,
BitwiseAnd(right, UInt(31)));
result = ShiftLeftLogical(left, right);
break;
case "SLshrB32":
result = ShiftRightLogical(
@@ -1111,13 +1090,7 @@ internal static partial class Gen5SpirvTranslator
BitwiseAnd(right, UInt(31)));
break;
case "SAshrI32":
result = Bitcast(
_uintType,
_module.AddInstruction(
SpirvOp.ShiftRightArithmetic,
_intType,
Bitcast(_intType, left),
BitwiseAnd(right, UInt(31))));
result = ShiftRightArithmetic(left, right);
break;
case "SBfmB32":
result = _module.AddInstruction(
@@ -1183,11 +1156,7 @@ internal static partial class Gen5SpirvTranslator
{
var shift = (uint)(instruction.Opcode[5] - '0');
result = IAdd(
_module.AddInstruction(
SpirvOp.ShiftLeftLogical,
_uintType,
left,
UInt(shift)),
ShiftLeftLogical(left, UInt(shift)),
right);
break;
}
@@ -1312,7 +1281,7 @@ internal static partial class Gen5SpirvTranslator
var left = GetRawSource64(instruction, 0);
if (instruction.Opcode.EndsWith("SaveexecB64", StringComparison.Ordinal))
{
var oldExec = LoadS64(126);
var oldExec = BooleanToLaneMask(Load(_boolType, _exec));
var notLeft = _module.AddInstruction(SpirvOp.Not, _ulongType, left);
var newExec = instruction.Opcode switch
{
@@ -1345,7 +1314,6 @@ internal static partial class Gen5SpirvTranslator
StoreS64(destination, oldExec);
StoreS64(126, newExec);
Store(_exec, IsNotZero64(newExec));
Store(_scc, IsNotZero64(newExec));
return true;
}
@@ -1361,19 +1329,108 @@ internal static partial class Gen5SpirvTranslator
var shift = _module.AddInstruction(
SpirvOp.UConvert,
_ulongType,
BitwiseAnd(GetRawSource(instruction, 1), UInt(63)));
var shiftedValue = _module.AddInstruction(
instruction.Opcode == "SLshlB64"
? SpirvOp.ShiftLeftLogical
: SpirvOp.ShiftRightLogical,
_ulongType,
left,
shift);
GetRawSource(instruction, 1));
var shiftedValue = instruction.Opcode == "SLshlB64"
? ShiftLeftLogical64(left, shift)
: ShiftRightLogical64(left, shift);
StoreS64(destination, shiftedValue);
Store(_scc, IsNotZero64(shiftedValue));
return true;
}
if (instruction.Opcode is "SBfeU64" or "SBfeI64")
{
if (instruction.Sources.Count < 2)
{
error = "missing scalar 64-bit bitfield source";
return false;
}
var control = GetRawSource(instruction, 1);
var offset = BitwiseAnd(control, UInt(63));
var requestedWidth = BitwiseAnd(
ShiftRightLogical(control, UInt(16)),
UInt(0x7F));
var remaining = _module.AddInstruction(
SpirvOp.ISub,
_uintType,
UInt(64),
offset);
var width = Ext(
38,
_uintType,
requestedWidth,
remaining);
var offset64 = _module.AddInstruction(
SpirvOp.UConvert,
_ulongType,
offset);
var width64 = _module.AddInstruction(
SpirvOp.UConvert,
_ulongType,
width);
var one64 = _module.Constant64(_ulongType, 1);
var shifted = ShiftRightLogical64(left, offset64);
var partialMask = _module.AddInstruction(
SpirvOp.ISub,
_ulongType,
ShiftLeftLogical64(one64, width64),
one64);
var fullWidth = _module.AddInstruction(
SpirvOp.IEqual,
_boolType,
width,
UInt(64));
var mask = _module.AddInstruction(
SpirvOp.Select,
_ulongType,
fullWidth,
_module.Constant64(_ulongType, ulong.MaxValue),
partialMask);
var extracted = _module.AddInstruction(
SpirvOp.BitwiseAnd,
_ulongType,
shifted,
mask);
if (instruction.Opcode == "SBfeI64")
{
var signShift = _module.AddInstruction(
SpirvOp.ISub,
_uintType,
width,
UInt(1));
var signBit = ShiftLeftLogical64(
one64,
_module.AddInstruction(
SpirvOp.UConvert,
_ulongType,
signShift));
var signExtended = _module.AddInstruction(
SpirvOp.ISub,
_ulongType,
_module.AddInstruction(
SpirvOp.BitwiseXor,
_ulongType,
extracted,
signBit),
signBit);
extracted = _module.AddInstruction(
SpirvOp.Select,
_ulongType,
_module.AddInstruction(
SpirvOp.IEqual,
_boolType,
width,
UInt(0)),
_module.Constant64(_ulongType, 0),
signExtended);
}
StoreS64(destination, extracted);
Store(_scc, IsNotZero64(extracted));
return true;
}
uint value;
if (instruction.Opcode is "SMovB64" or "SWqmB64")
{
@@ -1451,11 +1508,6 @@ internal static partial class Gen5SpirvTranslator
}
StoreS64(destination, value);
if (destination == 126)
{
Store(_exec, IsNotZero64(value));
}
return true;
}
@@ -1472,14 +1524,6 @@ internal static partial class Gen5SpirvTranslator
uint value = operand.Kind switch
{
Gen5OperandKind.VectorRegister => LoadV(operand.Value),
Gen5OperandKind.ScalarRegister when operand.Value == 106 =>
_module.AddInstruction(
SpirvOp.Select,
_uintType,
Load(_boolType, _vcc),
UInt(1),
UInt(0)),
Gen5OperandKind.ScalarRegister when operand.Value == 107 => UInt(0),
Gen5OperandKind.ScalarRegister => LoadS(operand.Value),
Gen5OperandKind.LiteralConstant => UInt(operand.Value),
Gen5OperandKind.EncodedConstant when TryDecodeInlineConstant(
@@ -1584,11 +1628,7 @@ internal static partial class Gen5SpirvTranslator
SpirvOp.UConvert,
_ulongType,
LoadS(register + 1));
high = _module.AddInstruction(
SpirvOp.ShiftLeftLogical,
_ulongType,
high,
_module.Constant64(_ulongType, 32));
high = ShiftLeftLogical64(high, _module.Constant64(_ulongType, 32));
return _module.AddInstruction(SpirvOp.BitwiseOr, _ulongType, low, high);
}
@@ -1597,9 +1637,7 @@ internal static partial class Gen5SpirvTranslator
StoreS(
register,
_module.AddInstruction(SpirvOp.UConvert, _uintType, value));
var high = _module.AddInstruction(
SpirvOp.ShiftRightLogical,
_ulongType,
var high = ShiftRightLogical64(
value,
_module.Constant64(_ulongType, 32));
StoreS(
@@ -1651,12 +1689,19 @@ internal static partial class Gen5SpirvTranslator
{
var left = GetRawSource(instruction, reverse ? 1 : 0);
var right = GetRawSource(instruction, reverse ? 0 : 1);
if (operation is
SpirvOp.ShiftLeftLogical or
SpirvOp.ShiftRightLogical or
SpirvOp.ShiftRightArithmetic)
if (operation == SpirvOp.ShiftLeftLogical)
{
right = BitwiseAnd(right, UInt(31));
return ShiftLeftLogical(left, right);
}
if (operation == SpirvOp.ShiftRightLogical)
{
return ShiftRightLogical(left, right);
}
if (operation == SpirvOp.ShiftRightArithmetic)
{
return ShiftRightArithmetic(left, right);
}
return _module.AddInstruction(operation, _uintType, left, right);
@@ -1881,7 +1926,7 @@ internal static partial class Gen5SpirvTranslator
_boolType,
_module.AddInstruction(SpirvOp.ULessThan, _boolType, partial, left),
_module.AddInstruction(SpirvOp.ULessThan, _boolType, result, partial));
Store(_vcc, carry);
StoreWaveMask(106, carry);
return result;
}
@@ -1912,7 +1957,7 @@ internal static partial class Gen5SpirvTranslator
_boolType,
partial,
borrowIn));
Store(_vcc, borrow);
StoreWaveMask(106, borrow);
return result;
}
@@ -1932,13 +1977,13 @@ internal static partial class Gen5SpirvTranslator
UInt(0)));
if (register == 106)
{
Store(_vcc, carry);
StoreWaveMask(106, carry);
}
return;
}
Store(_vcc, carry);
StoreWaveMask(106, carry);
}
private uint EmitPermlane16(
@@ -1955,21 +2000,13 @@ internal static partial class Gen5SpirvTranslator
_boolType,
localLane,
UInt(8));
var lowShift = _module.AddInstruction(
SpirvOp.ShiftLeftLogical,
_uintType,
localLane,
UInt(2));
var lowShift = ShiftLeftLogical(localLane, UInt(2));
var highLane = _module.AddInstruction(
SpirvOp.ISub,
_uintType,
localLane,
UInt(8));
var highShift = _module.AddInstruction(
SpirvOp.ShiftLeftLogical,
_uintType,
highLane,
UInt(2));
var highShift = ShiftLeftLogical(highLane, UInt(2));
var lowSelector = BitwiseAnd(
ShiftRightLogical(selectorLow, lowShift),
UInt(15));
+361 -43
View File
@@ -8,6 +8,7 @@ internal static partial class Gen5SpirvTranslator
private const uint ScalarRegisterCount = 256;
private const uint VectorRegisterCount = 512;
private const uint LdsDwordCount = 8192;
private const uint RdnaWaveLaneCount = 32;
public static bool TryCompilePixelShader(
Gen5ShaderState state,
@@ -95,6 +96,7 @@ internal static partial class Gen5SpirvTranslator
private readonly List<uint> _interfaces = [];
private readonly Dictionary<uint, uint> _pixelInputs = [];
private readonly Dictionary<uint, uint> _vertexOutputs = [];
private readonly Dictionary<uint, SpirvVertexInput> _vertexInputsByPc = [];
private readonly List<SpirvImageResource> _imageResources = [];
private readonly Dictionary<uint, int> _imageBindingByPc = [];
private readonly Dictionary<uint, int> _bufferBindingByPc = [];
@@ -102,6 +104,7 @@ internal static partial class Gen5SpirvTranslator
private uint _boolType;
private uint _uintType;
private uint _intType;
private uint _longType;
private uint _ulongType;
private uint _floatType;
private uint _vec2Type;
@@ -149,6 +152,11 @@ internal static partial class Gen5SpirvTranslator
ImageComponentKind ComponentKind,
bool IsStorage);
private readonly record struct SpirvVertexInput(
uint Variable,
uint Type,
uint ComponentCount);
public CompilationContext(
Gen5SpirvStage stage,
Gen5ShaderState state,
@@ -282,7 +290,10 @@ internal static partial class Gen5SpirvTranslator
_module.Build(),
_evaluation.GlobalMemoryBindings,
_evaluation.ImageBindings,
attributeCount);
attributeCount,
_stage == Gen5SpirvStage.Vertex
? _evaluation.VertexInputs ?? []
: []);
return true;
}
catch (Exception exception)
@@ -297,10 +308,31 @@ internal static partial class Gen5SpirvTranslator
_module.AddCapability(SpirvCapability.Shader);
_module.AddCapability(SpirvCapability.Int64);
_module.AddCapability(SpirvCapability.ImageQuery);
if (UsesSubgroupShuffle())
if (_evaluation.ImageBindings.Any(
static binding =>
(binding.Opcode.StartsWith(
"ImageSample",
StringComparison.Ordinal) ||
binding.Opcode.StartsWith(
"ImageGather4",
StringComparison.Ordinal)) &&
binding.Opcode.EndsWith("O", StringComparison.Ordinal)))
{
_module.AddCapability(SpirvCapability.ImageGatherExtended);
}
if (UsesSubgroupOperations())
{
_module.AddCapability(SpirvCapability.GroupNonUniform);
_module.AddCapability(SpirvCapability.GroupNonUniformShuffle);
if (UsesSubgroupShuffle())
{
_module.AddCapability(SpirvCapability.GroupNonUniformShuffle);
}
if (UsesWaveControl())
{
_module.AddCapability(SpirvCapability.GroupNonUniformVote);
}
}
_glsl = _module.ImportExtInst("GLSL.std.450");
@@ -308,6 +340,7 @@ internal static partial class Gen5SpirvTranslator
_boolType = _module.TypeBool();
_uintType = _module.TypeInt(32, signed: false);
_intType = _module.TypeInt(32, signed: true);
_longType = _module.TypeInt(64, signed: true);
_ulongType = _module.TypeInt(64, signed: false);
_floatType = _module.TypeFloat(32);
_vec2Type = _module.TypeVector(_floatType, 2);
@@ -379,10 +412,6 @@ internal static partial class Gen5SpirvTranslator
}
var ldsArrayType = _module.TypeArray(_uintType, LdsDwordCount);
_module.AddDecoration(
ldsArrayType,
SpirvDecoration.ArrayStride,
sizeof(uint));
var ldsPointer =
_module.TypePointer(SpirvStorageClass.Workgroup, ldsArrayType);
_workgroupUintPointer =
@@ -456,6 +485,11 @@ internal static partial class Gen5SpirvTranslator
_module.AddCapability(
SpirvCapability.StorageImageWriteWithoutFormat);
}
else if (isStorage && RequiresExtendedStorageImageFormat(format))
{
_module.AddCapability(
SpirvCapability.StorageImageExtendedFormats);
}
var imageType = _module.TypeImage(
componentType,
@@ -493,6 +527,13 @@ internal static partial class Gen5SpirvTranslator
}
}
private static bool RequiresExtendedStorageImageFormat(
SpirvImageFormat format) =>
format is not SpirvImageFormat.Unknown and
not SpirvImageFormat.Rgba32f and
not SpirvImageFormat.Rgba32i and
not SpirvImageFormat.Rgba32ui;
private static (SpirvImageFormat Format, ImageComponentKind Kind)
DecodeImageFormat(IReadOnlyList<uint> descriptor)
{
@@ -557,7 +598,7 @@ internal static partial class Gen5SpirvTranslator
private void DeclareStageInterface()
{
if (UsesSubgroupShuffle())
if (UsesSubgroupOperations())
{
var subgroupPointer =
_module.TypePointer(SpirvStorageClass.Input, _uintType);
@@ -573,6 +614,8 @@ internal static partial class Gen5SpirvTranslator
if (_stage == Gen5SpirvStage.Vertex)
{
DeclareVertexInputs();
var inputPointer =
_module.TypePointer(SpirvStorageClass.Input, _uintType);
_vertexIndexInput = _module.AddGlobalVariable(
@@ -618,7 +661,6 @@ internal static partial class Gen5SpirvTranslator
outputPointer,
SpirvStorageClass.Output);
_module.AddDecoration(variable, SpirvDecoration.Location, parameter);
_module.AddDecoration(variable, SpirvDecoration.NoPerspective);
_vertexOutputs.Add(parameter, variable);
_interfaces.Add(variable);
}
@@ -640,7 +682,6 @@ internal static partial class Gen5SpirvTranslator
inputVec4Pointer,
SpirvStorageClass.Input);
_module.AddDecoration(variable, SpirvDecoration.Location, attribute);
_module.AddDecoration(variable, SpirvDecoration.NoPerspective);
_pixelInputs.Add(attribute, variable);
_interfaces.Add(variable);
}
@@ -691,6 +732,42 @@ internal static partial class Gen5SpirvTranslator
}
}
private void DeclareVertexInputs()
{
foreach (var input in _evaluation.VertexInputs ?? [])
{
var type = input.ComponentCount switch
{
1u => _floatType,
2u => _vec2Type,
3u => _vec3Type,
4u => _vec4Type,
_ => 0u,
};
if (type == 0)
{
continue;
}
var pointer = _module.TypePointer(SpirvStorageClass.Input, type);
var variable = _module.AddGlobalVariable(
pointer,
SpirvStorageClass.Input);
_module.AddName(variable, $"attr{input.Location}");
_module.AddDecoration(
variable,
SpirvDecoration.Location,
input.Location);
_vertexInputsByPc.TryAdd(
input.Pc,
new SpirvVertexInput(
variable,
type,
input.ComponentCount));
_interfaces.Add(variable);
}
}
private void EmitInitialState()
{
for (uint index = 0;
@@ -706,8 +783,16 @@ internal static partial class Gen5SpirvTranslator
}
Store(_scc, _module.ConstantBool(false));
Store(_vcc, _module.ConstantBool(false));
Store(_exec, _module.ConstantBool(true));
if (_subgroupInvocationIdInput != 0)
{
StoreWaveMask(106, _module.ConstantBool(false));
StoreWaveMask(126, _module.ConstantBool(true));
}
else
{
Store(_vcc, _module.ConstantBool(false));
Store(_exec, _module.ConstantBool(true));
}
Store(_programCounter, UInt(0));
Store(_programActive, _module.ConstantBool(true));
@@ -923,10 +1008,10 @@ internal static partial class Gen5SpirvTranslator
{
"SCbranchScc0" => LogicalNot(Load(_boolType, _scc)),
"SCbranchScc1" => Load(_boolType, _scc),
"SCbranchVccz" => LogicalNot(Load(_boolType, _vcc)),
"SCbranchVccnz" => Load(_boolType, _vcc),
"SCbranchExecz" => LogicalNot(Load(_boolType, _exec)),
"SCbranchExecnz" => Load(_boolType, _exec),
"SCbranchVccz" => LogicalNot(SubgroupAny(Load(_boolType, _vcc))),
"SCbranchVccnz" => SubgroupAny(Load(_boolType, _vcc)),
"SCbranchExecz" => LogicalNot(SubgroupAny(Load(_boolType, _exec))),
"SCbranchExecnz" => SubgroupAny(Load(_boolType, _exec)),
_ => 0,
};
return condition != 0;
@@ -1269,6 +1354,19 @@ internal static partial class Gen5SpirvTranslator
out string error)
{
error = string.Empty;
if (_stage == Gen5SpirvStage.Vertex &&
_vertexInputsByPc.TryGetValue(instruction.Pc, out var vertexInput))
{
return TryEmitVertexInputFetch(control, vertexInput, out error);
}
if (_stage == Gen5SpirvStage.Vertex &&
IsFormatBufferLoad(instruction.Opcode))
{
error = $"missing vertex input for {instruction.Opcode} pc=0x{instruction.Pc:X}";
return false;
}
if (!_bufferBindingByPc.TryGetValue(instruction.Pc, out var bindingIndex))
{
error = "missing buffer-memory binding";
@@ -1297,30 +1395,39 @@ internal static partial class Gen5SpirvTranslator
if (instruction.Opcode == "BufferAtomicAdd")
{
var original = _module.AddInstruction(
SpirvOp.AtomicIAdd,
_uintType,
BufferWordPointer(bindingIndex, dwordAddress),
UInt(1),
UInt(0x48),
LoadV(control.VectorData));
if (control.Glc)
EmitExecConditional(() =>
{
StoreV(control.VectorData, original);
}
var original = _module.AddInstruction(
SpirvOp.AtomicIAdd,
_uintType,
BufferWordPointer(bindingIndex, dwordAddress),
UInt(1),
UInt(0x48),
LoadV(control.VectorData));
if (control.Glc)
{
StoreV(control.VectorData, original);
}
});
return true;
}
if (instruction.Opcode.StartsWith("BufferStoreDword", StringComparison.Ordinal))
{
for (uint index = 0; index < control.DwordCount; index++)
EmitExecConditional(() =>
{
var address = index == 0
? dwordAddress
: IAdd(dwordAddress, UInt(index));
StoreBufferWord(bindingIndex, address, LoadV(control.VectorData + index));
}
for (uint index = 0; index < control.DwordCount; index++)
{
var address = index == 0
? dwordAddress
: IAdd(dwordAddress, UInt(index));
StoreBufferWord(
bindingIndex,
address,
LoadV(control.VectorData + index));
}
});
return true;
}
@@ -1345,6 +1452,41 @@ internal static partial class Gen5SpirvTranslator
return true;
}
private static bool IsFormatBufferLoad(string opcode) =>
opcode.StartsWith("BufferLoadFormat", StringComparison.Ordinal) ||
opcode.StartsWith("TBufferLoadFormat", StringComparison.Ordinal);
private bool TryEmitVertexInputFetch(
Gen5BufferMemoryControl control,
SpirvVertexInput input,
out string error)
{
error = string.Empty;
if (control.DwordCount == 0 ||
control.DwordCount > input.ComponentCount)
{
error =
$"invalid vertex input fetch components={control.DwordCount} " +
$"input={input.ComponentCount}";
return false;
}
var loaded = Load(input.Type, input.Variable);
for (uint component = 0; component < control.DwordCount; component++)
{
var value = input.ComponentCount == 1
? loaded
: _module.AddInstruction(
SpirvOp.CompositeExtract,
_floatType,
loaded,
component);
StoreV(control.VectorData + component, Bitcast(_uintType, value));
}
return true;
}
private bool TryEmitImage(
Gen5ShaderInstruction instruction,
Gen5ImageControl image,
@@ -1362,11 +1504,20 @@ internal static partial class Gen5SpirvTranslator
var imageObject = Load(resource.ObjectType, resource.Variable);
if (instruction.Opcode == "ImageGetResinfo")
{
var queryImage = resource.IsStorage
? imageObject
: _module.AddInstruction(
SpirvOp.Image,
resource.ImageType,
imageObject);
var size = _module.AddInstruction(
SpirvOp.ImageQuerySizeLod,
resource.IsStorage
? SpirvOp.ImageQuerySize
: SpirvOp.ImageQuerySizeLod,
_module.TypeVector(_intType, 2),
imageObject,
UInt(0));
resource.IsStorage
? [queryImage]
: [queryImage, UInt(0)]);
uint outputIndex = 0;
for (uint component = 0; component < 4; component++)
{
@@ -1449,11 +1600,12 @@ internal static partial class Gen5SpirvTranslator
}
else
{
_module.AddStatement(
SpirvOp.ImageWrite,
imageObject,
coordinates,
texel);
EmitExecConditional(
() => _module.AddStatement(
SpirvOp.ImageWrite,
imageObject,
coordinates,
texel));
}
return true;
@@ -1476,10 +1628,14 @@ internal static partial class Gen5SpirvTranslator
? BuildClampedIntegerCoordinates(image, 0, width, height)
: BuildIntegerCoordinates(image, 0);
var mipLevel = _evaluation.ImageBindings[bindingIndex].MipLevel ?? 0;
var fetchedImage = _module.AddInstruction(
SpirvOp.Image,
resource.ImageType,
imageObject);
sampled = _module.AddInstruction(
SpirvOp.ImageFetch,
resource.VectorType,
imageObject,
fetchedImage,
coordinates,
2,
UInt(mipLevel));
@@ -1774,6 +1930,11 @@ internal static partial class Gen5SpirvTranslator
_boolType,
lowerInRange,
upperInRange);
inRange = _module.AddInstruction(
SpirvOp.LogicalAnd,
_boolType,
Load(_boolType, _exec),
inRange);
var writeLabel = _module.AllocateId();
var mergeLabel = _module.AllocateId();
_module.AddStatement(SpirvOp.SelectionMerge, mergeLabel, 0);
@@ -1900,6 +2061,12 @@ internal static partial class Gen5SpirvTranslator
SpirvOp.CompositeConstruct,
outputType,
values);
vector = _module.AddInstruction(
SpirvOp.Select,
outputType,
Load(_boolType, _exec),
vector,
Load(outputType, _pixelOutput));
Store(_pixelOutput, vector);
return true;
}
@@ -1945,6 +2112,12 @@ internal static partial class Gen5SpirvTranslator
SpirvOp.CompositeConstruct,
_vec4Type,
components);
outputValue = _module.AddInstruction(
SpirvOp.Select,
_vec4Type,
Load(_boolType, _exec),
outputValue,
Load(_vec4Type, outputVariable));
Store(outputVariable, outputValue);
return true;
}
@@ -2009,7 +2182,18 @@ internal static partial class Gen5SpirvTranslator
private uint LoadV(uint register) => Load(_uintType, VectorPointer(register));
private void StoreS(uint register, uint value) => Store(ScalarPointer(register), value);
private void StoreS(uint register, uint value)
{
Store(ScalarPointer(register), value);
if (register is 106 or 107)
{
Store(_vcc, IsWaveMaskActive(LoadS64(106)));
}
else if (register is 126 or 127)
{
Store(_exec, IsWaveMaskActive(LoadS64(126)));
}
}
private void StoreV(uint register, uint value, bool guardWithExec = true)
{
@@ -2044,12 +2228,49 @@ internal static partial class Gen5SpirvTranslator
private uint IAdd(uint left, uint right) =>
_module.AddInstruction(SpirvOp.IAdd, _uintType, left, right);
private uint ShiftLeftLogical(uint left, uint right) =>
_module.AddInstruction(
SpirvOp.ShiftLeftLogical,
_uintType,
left,
BitwiseAnd(right, UInt(31)));
private uint ShiftRightLogical(uint left, uint right) =>
_module.AddInstruction(SpirvOp.ShiftRightLogical, _uintType, left, right);
_module.AddInstruction(
SpirvOp.ShiftRightLogical,
_uintType,
left,
BitwiseAnd(right, UInt(31)));
private uint ShiftRightArithmetic(uint left, uint right) =>
Bitcast(
_uintType,
_module.AddInstruction(
SpirvOp.ShiftRightArithmetic,
_intType,
Bitcast(_intType, left),
BitwiseAnd(right, UInt(31))));
private uint ShiftLeftLogical64(uint left, uint right) =>
_module.AddInstruction(
SpirvOp.ShiftLeftLogical,
_ulongType,
left,
BitwiseAnd64(right, _module.Constant64(_ulongType, 63)));
private uint ShiftRightLogical64(uint left, uint right) =>
_module.AddInstruction(
SpirvOp.ShiftRightLogical,
_ulongType,
left,
BitwiseAnd64(right, _module.Constant64(_ulongType, 63)));
private uint BitwiseAnd(uint left, uint right) =>
_module.AddInstruction(SpirvOp.BitwiseAnd, _uintType, left, right);
private uint BitwiseAnd64(uint left, uint right) =>
_module.AddInstruction(SpirvOp.BitwiseAnd, _ulongType, left, right);
private uint BitwiseOr(uint left, uint right) =>
_module.AddInstruction(SpirvOp.BitwiseOr, _uintType, left, right);
@@ -2059,6 +2280,86 @@ internal static partial class Gen5SpirvTranslator
private uint LogicalNot(uint value) =>
_module.AddInstruction(SpirvOp.LogicalNot, _boolType, value);
private uint SubgroupAny(uint condition) =>
_subgroupInvocationIdInput == 0
? condition
: _module.AddInstruction(
SpirvOp.GroupNonUniformAny,
_boolType,
UInt(3),
condition);
private uint CurrentLaneBit()
{
if (_subgroupInvocationIdInput == 0)
{
return _module.Constant64(_ulongType, 1);
}
var lane = Load(_uintType, _subgroupInvocationIdInput);
var maskedLane = BitwiseAnd(lane, UInt(RdnaWaveLaneCount - 1));
var shifted = ShiftLeftLogical64(
_module.Constant64(_ulongType, 1),
_module.AddInstruction(
SpirvOp.UConvert,
_ulongType,
maskedLane));
return _module.AddInstruction(
SpirvOp.Select,
_ulongType,
IsCurrentLaneInRdnaWave(),
shifted,
_module.Constant64(_ulongType, 0));
}
private uint IsCurrentLaneInRdnaWave() =>
_module.AddInstruction(
SpirvOp.ULessThan,
_boolType,
Load(_uintType, _subgroupInvocationIdInput),
UInt(RdnaWaveLaneCount));
private uint BooleanToLaneMask(uint condition) =>
_module.AddInstruction(
SpirvOp.Select,
_ulongType,
condition,
CurrentLaneBit(),
_module.Constant64(_ulongType, 0));
private uint IsWaveMaskActive(uint mask) =>
_subgroupInvocationIdInput == 0
? IsNotZero64(mask)
: IsCurrentLaneSet(mask);
private uint IsCurrentLaneSet(uint mask) =>
IsNotZero64(
_module.AddInstruction(
SpirvOp.BitwiseAnd,
_ulongType,
mask,
CurrentLaneBit()));
private void StoreWaveMask(uint register, uint condition) =>
StoreS64(register, BooleanToLaneMask(condition));
private void EmitExecConditional(Action emit)
{
var activeLabel = _module.AllocateId();
var mergeLabel = _module.AllocateId();
var active = Load(_boolType, _exec);
_module.AddStatement(SpirvOp.SelectionMerge, mergeLabel, 0);
_module.AddStatement(
SpirvOp.BranchConditional,
active,
activeLabel,
mergeLabel);
_module.AddLabel(activeLabel);
emit();
_module.AddStatement(SpirvOp.Branch, mergeLabel);
_module.AddLabel(mergeLabel);
}
private bool UsesLds() =>
_state.Program.Instructions.Any(instruction =>
instruction.Control is Gen5DataShareControl);
@@ -2067,6 +2368,23 @@ internal static partial class Gen5SpirvTranslator
_state.Program.Instructions.Any(instruction =>
instruction.Opcode is "VPermlane16B32" or "VPermlanex16B32");
private bool UsesWaveControl() =>
_state.Program.Instructions.Any(instruction =>
instruction.Opcode.Contains("Saveexec", StringComparison.Ordinal) ||
instruction.Opcode.StartsWith("SCbranchExec", StringComparison.Ordinal) ||
instruction.Opcode.StartsWith("SCbranchVcc", StringComparison.Ordinal) ||
instruction.Opcode.StartsWith("VCmpx", StringComparison.Ordinal) ||
instruction.Sources.Any(IsWaveMaskOperand) ||
instruction.Destinations.Any(IsWaveMaskOperand));
private bool UsesSubgroupOperations() =>
_stage == Gen5SpirvStage.Compute &&
(UsesSubgroupShuffle() || UsesWaveControl());
private static bool IsWaveMaskOperand(Gen5Operand operand) =>
operand.Kind == Gen5OperandKind.ScalarRegister &&
operand.Value is 106 or 107 or 126 or 127;
private static bool TryGetVectorDestination(
Gen5ShaderInstruction instruction,
out uint destination)
@@ -62,6 +62,7 @@ internal enum SpirvOp : ushort
ImageDrefGather = 97,
ImageRead = 98,
ImageWrite = 99,
Image = 100,
ImageQuerySizeLod = 103,
ImageQuerySize = 104,
ImageQueryLod = 105,
@@ -173,10 +174,13 @@ internal enum SpirvCapability : uint
Float64 = 10,
Int64 = 11,
Int16 = 22,
ImageGatherExtended = 25,
StorageImageExtendedFormats = 49,
ImageQuery = 50,
StorageImageReadWithoutFormat = 55,
StorageImageWriteWithoutFormat = 56,
GroupNonUniform = 61,
GroupNonUniformVote = 62,
GroupNonUniformBallot = 64,
GroupNonUniformShuffle = 65,
RuntimeDescriptorArray = 5302,
@@ -309,6 +313,18 @@ internal sealed class SpirvModuleBuilder
private readonly Dictionary<(uint Width, bool Signed), uint> _integerTypes = [];
private readonly Dictionary<uint, uint> _floatTypes = [];
private readonly Dictionary<(uint Component, uint Count), uint> _vectorTypes = [];
private readonly Dictionary<
(
uint SampledType,
SpirvImageDim Dimension,
bool Depth,
bool Arrayed,
bool Multisampled,
uint Sampled,
SpirvImageFormat Format
),
uint> _imageTypes = [];
private readonly Dictionary<uint, uint> _sampledImageTypes = [];
private readonly Dictionary<(SpirvStorageClass Storage, uint Type), uint> _pointerTypes = [];
private readonly Dictionary<(uint Element, uint Count), uint> _arrayTypes = [];
private readonly Dictionary<uint, uint> _runtimeArrayTypes = [];
@@ -477,6 +493,19 @@ internal sealed class SpirvModuleBuilder
uint sampled,
SpirvImageFormat format)
{
var key = (
sampledType,
dimension,
depth,
arrayed,
multisampled,
sampled,
format);
if (_imageTypes.TryGetValue(key, out var existing))
{
return existing;
}
var id = AllocateId();
Emit(
_typesConstantsGlobals,
@@ -489,13 +518,20 @@ internal sealed class SpirvModuleBuilder
multisampled ? 1u : 0u,
sampled,
(uint)format);
_imageTypes.Add(key, id);
return id;
}
public uint TypeSampledImage(uint imageType)
{
if (_sampledImageTypes.TryGetValue(imageType, out var existing))
{
return existing;
}
var id = AllocateId();
Emit(_typesConstantsGlobals, SpirvOp.TypeSampledImage, id, imageType);
_sampledImageTypes.Add(imageType, id);
return id;
}
+24 -4
View File
@@ -306,10 +306,6 @@ public static class KernelExports
{
var threadId = ctx[CpuRegister.Rdi];
var returnValueAddress = ctx[CpuRegister.Rsi];
if (returnValueAddress != 0 && !ctx.TryWriteUInt64(returnValueAddress, 0))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (ShouldTracePthread())
{
@@ -317,6 +313,30 @@ public static class KernelExports
$"[LOADER][TRACE] pthread_join: thread=0x{threadId:X16} retval_out=0x{returnValueAddress:X16}");
}
var returnValue = 0UL;
if (GuestThreadExecution.Scheduler is { } scheduler &&
!scheduler.TryJoinThread(ctx, threadId, out returnValue, out var error))
{
Console.Error.WriteLine(
$"[LOADER][ERROR] pthread_join: thread=0x{threadId:X16}: {error}");
var result = string.Equals(
error,
"thread cannot join itself",
StringComparison.Ordinal)
? OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT
: OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
ctx[CpuRegister.Rax] = unchecked((ulong)(int)result);
return (int)result;
}
if (returnValueAddress != 0 &&
!ctx.TryWriteUInt64(returnValueAddress, returnValue))
{
ctx[CpuRegister.Rax] =
unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -99,10 +99,12 @@ public static class KernelMemoryCompatExports
private static readonly object _tlsGate = new();
private static readonly object _ioTraceGate = new();
private static readonly object _statCacheGate = new();
private static readonly object _guestMountGate = new();
private static readonly Dictionary<ulong, DirectAllocation> _directAllocations = new();
private static readonly Dictionary<ulong, LibcHeapAllocation> _libcAllocations = new();
private static readonly Dictionary<ulong, MappedRegion> _mappedRegions = new();
private static readonly Dictionary<ulong, ulong> _tlsModuleBlocks = new();
private static readonly Dictionary<string, string> _guestMounts = new(StringComparer.OrdinalIgnoreCase);
private static readonly HashSet<string> _tracedStatResults = new(StringComparer.Ordinal);
private static readonly HashSet<string> _negativeStatCache = new(StringComparer.OrdinalIgnoreCase);
private static long _nextFileDescriptor = 2;
@@ -153,6 +155,32 @@ public static class KernelMemoryCompatExports
private readonly record struct MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, bool IsDirect, ulong DirectStart);
private readonly record struct BatchMapEntry(ulong Start, ulong Offset, ulong Length, byte Protection, byte Type, int Operation);
public static void RegisterGuestPathMount(string guestMountPoint, string hostRoot)
{
ArgumentException.ThrowIfNullOrWhiteSpace(guestMountPoint);
ArgumentException.ThrowIfNullOrWhiteSpace(hostRoot);
var normalizedMountPoint = NormalizeGuestStatCachePath(guestMountPoint);
if (normalizedMountPoint is null || normalizedMountPoint == "/")
{
throw new ArgumentException("Guest mount point must name a directory.", nameof(guestMountPoint));
}
var normalizedHostRoot = Path.GetFullPath(hostRoot);
Directory.CreateDirectory(normalizedHostRoot);
lock (_guestMountGate)
{
_guestMounts[normalizedMountPoint] = normalizedHostRoot;
}
lock (_statCacheGate)
{
_negativeStatCache.RemoveWhere(path =>
string.Equals(path, normalizedMountPoint, StringComparison.OrdinalIgnoreCase) ||
path.StartsWith(normalizedMountPoint + "/", StringComparison.OrdinalIgnoreCase));
}
}
internal static bool TryAllocateHleData(
CpuContext ctx,
ulong length,
@@ -4033,6 +4061,11 @@ public static class KernelMemoryCompatExports
return guestPath;
}
if (TryResolveRegisteredGuestMount(guestPath, out var mountedPath))
{
return mountedPath;
}
if (guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase))
{
var relative = NormalizeMountRelativePath(guestPath["/devlog/app/".Length..]);
@@ -4131,6 +4164,51 @@ public static class KernelMemoryCompatExports
return guestPath;
}
private static bool TryResolveRegisteredGuestMount(string guestPath, out string hostPath)
{
hostPath = string.Empty;
var normalizedGuestPath = NormalizeGuestStatCachePath(guestPath);
if (normalizedGuestPath is null)
{
return false;
}
string? matchedMountPoint = null;
string? matchedHostRoot = null;
lock (_guestMountGate)
{
foreach (var (mountPoint, hostRoot) in _guestMounts)
{
if ((string.Equals(normalizedGuestPath, mountPoint, StringComparison.OrdinalIgnoreCase) ||
normalizedGuestPath.StartsWith(mountPoint + "/", StringComparison.OrdinalIgnoreCase)) &&
(matchedMountPoint is null || mountPoint.Length > matchedMountPoint.Length))
{
matchedMountPoint = mountPoint;
matchedHostRoot = hostRoot;
}
}
}
if (matchedMountPoint is null || matchedHostRoot is null)
{
return false;
}
var relativePath = normalizedGuestPath[matchedMountPoint.Length..].TrimStart('/');
var candidate = Path.GetFullPath(Path.Combine(
matchedHostRoot,
NormalizeMountRelativePath(relativePath)));
var rootWithSeparator = Path.TrimEndingDirectorySeparator(matchedHostRoot) + Path.DirectorySeparatorChar;
if (!string.Equals(candidate, matchedHostRoot, StringComparison.OrdinalIgnoreCase) &&
!candidate.StartsWith(rootWithSeparator, StringComparison.OrdinalIgnoreCase))
{
return false;
}
hostPath = candidate;
return true;
}
private static string? ResolveApp0Root()
{
var cached = Volatile.Read(ref _cachedApp0Root);
@@ -5310,6 +5388,37 @@ public static class KernelMemoryCompatExports
}
}
internal static bool TryReadTrackedLibcHeap(
ulong address,
Span<byte> destination)
{
if (destination.IsEmpty)
{
return true;
}
var length = (ulong)destination.Length;
lock (_libcAllocGate)
{
foreach (var (allocationAddress, allocation) in _libcAllocations)
{
var allocationSize = (ulong)allocation.Size;
var offset = address >= allocationAddress
? address - allocationAddress
: ulong.MaxValue;
if (offset > allocationSize ||
length > allocationSize - offset)
{
continue;
}
return TryReadHostMemory(address, destination);
}
}
return false;
}
private static bool TryAllocateLibcHeap(ulong requestedSize, nuint alignment, bool zeroFill, out ulong address)
{
address = 0;
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
using System.Buffers.Binary;
using System.Text;
@@ -10,6 +11,8 @@ namespace SharpEmu.Libs.SaveData;
public static class SaveDataExports
{
private const int OrbisSaveDataErrorParameter = unchecked((int)0x809F0000);
private const int OrbisSaveDataErrorExists = unchecked((int)0x809F0007);
private const int OrbisSaveDataErrorNotFound = unchecked((int)0x809F0008);
private const int OrbisSaveDataErrorInternal = unchecked((int)0x809F000B);
private const int SaveDataTitleIdSize = 10;
private const int SaveDataDirNameSize = 32;
@@ -23,6 +26,9 @@ public static class SaveDataExports
private const ulong ResultInfosOffset = 0x20;
private const uint SortKeyFreeBlocks = 5;
private const uint SortOrderDescent = 1;
private const uint MountModeCreate = 1u << 2;
private const uint MountModeCreate2 = 1u << 5;
private const int MountResultSize = 0x40;
private static readonly object _stateGate = new();
private static string? _titleId;
@@ -149,6 +155,95 @@ public static class SaveDataExports
}
}
[SysAbiExport(
Nid = "ZP4e7rlzOUk",
ExportName = "sceSaveDataMount3",
Target = Generation.Gen5,
LibraryName = "libSceSaveData")]
public static int SaveDataMount3(CpuContext ctx)
{
var mountAddress = ctx[CpuRegister.Rdi];
var resultAddress = ctx[CpuRegister.Rsi];
if (mountAddress == 0 || resultAddress == 0)
{
return SetReturn(ctx, OrbisSaveDataErrorParameter);
}
if (!TryReadInt32(ctx, mountAddress, out var userId) ||
!ctx.TryReadUInt64(mountAddress + 0x08, out var dirNameAddress) ||
!ctx.TryReadUInt64(mountAddress + 0x10, out var blocks) ||
!ctx.TryReadUInt64(mountAddress + 0x18, out var systemBlocks) ||
!TryReadUInt32(ctx, mountAddress + 0x20, out var mountMode) ||
!TryReadUInt32(ctx, mountAddress + 0x24, out var resource) ||
!TryReadUInt32(ctx, mountAddress + 0x28, out var mode) ||
dirNameAddress == 0 ||
!TryReadFixedAscii(ctx, dirNameAddress, SaveDataDirNameSize, out var dirName))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (userId < 0 || string.IsNullOrWhiteSpace(dirName))
{
return SetReturn(ctx, OrbisSaveDataErrorParameter);
}
try
{
var titleId = ResolveConfiguredTitleId();
var savePath = Path.Combine(
ResolveTitleSaveRoot(userId, titleId),
SanitizePathSegment(dirName));
var existed = Directory.Exists(savePath);
var create = (mountMode & MountModeCreate) != 0;
var createIfMissing = (mountMode & MountModeCreate2) != 0;
if (!existed && !create && !createIfMissing)
{
return SetReturn(ctx, OrbisSaveDataErrorNotFound);
}
if (existed && create)
{
return SetReturn(ctx, OrbisSaveDataErrorExists);
}
if (!existed)
{
Directory.CreateDirectory(savePath);
}
const string mountPoint = "/savedata0";
KernelMemoryCompatExports.RegisterGuestPathMount(mountPoint, savePath);
Span<byte> result = stackalloc byte[MountResultSize];
result.Clear();
WriteAscii(result[..16], mountPoint);
BinaryPrimitives.WriteUInt32LittleEndian(result[0x1C..], createIfMissing && !existed ? 1u : 0u);
if (!ctx.Memory.TryWrite(resultAddress, result))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceSaveData(
$"mount3 user={userId} title={titleId} dir={dirName} blocks={blocks} " +
$"system_blocks={systemBlocks} mount_mode=0x{mountMode:X} resource={resource} mode={mode} " +
$"mount_point={mountPoint} created={!existed} root='{savePath}'");
return SetReturn(ctx, 0);
}
catch (IOException)
{
return SetReturn(ctx, OrbisSaveDataErrorInternal);
}
catch (UnauthorizedAccessException)
{
return SetReturn(ctx, OrbisSaveDataErrorInternal);
}
catch (ArgumentException)
{
return SetReturn(ctx, OrbisSaveDataErrorParameter);
}
}
private static bool TryReadSearchCond(CpuContext ctx, ulong address, out SearchCond cond)
{
cond = default;
File diff suppressed because it is too large Load Diff