mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-27 05:01:07 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e11d8a15c3 | |||
| 7fac1aa9cd | |||
| c30b4763c6 | |||
| a4f3d3cd7f | |||
| 92526ecacf | |||
| aa152de934 | |||
| 39dd12a11b |
@@ -723,6 +723,7 @@ public sealed partial class DirectExecutionBackend
|
|||||||
"Vo5V8KAwCmk" or // sceSystemServiceHideSplashScreen
|
"Vo5V8KAwCmk" or // sceSystemServiceHideSplashScreen
|
||||||
"TywrFKCoLGY" or // sceSaveDataInitialize3
|
"TywrFKCoLGY" or // sceSaveDataInitialize3
|
||||||
"dyIhnXq-0SM" or // sceSaveDataDirNameSearch
|
"dyIhnXq-0SM" or // sceSaveDataDirNameSearch
|
||||||
|
"ZP4e7rlzOUk" or // sceSaveDataMount3
|
||||||
"ERKzksauAJA" or // sceSaveDataDialogGetStatus
|
"ERKzksauAJA" or // sceSaveDataDialogGetStatus
|
||||||
"KK3Bdg1RWK0" or // sceSaveDataDialogUpdateStatus
|
"KK3Bdg1RWK0" or // sceSaveDataDialogUpdateStatus
|
||||||
"en7gNVnh878" or // sceSaveDataDialogIsReadyToDisplay
|
"en7gNVnh878" or // sceSaveDataDialogIsReadyToDisplay
|
||||||
|
|||||||
@@ -355,6 +355,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
public GuestThreadRunState State { get; set; }
|
public GuestThreadRunState State { get; set; }
|
||||||
|
|
||||||
|
public ulong ExitValue { get; set; }
|
||||||
|
|
||||||
public string? BlockReason { get; set; }
|
public string? BlockReason { get; set; }
|
||||||
|
|
||||||
public bool HasBlockedContinuation { 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;
|
libraryName.IndexOf("Kernel", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool PreferLleForLibcExport(string exportName)
|
private bool PreferLleForLibcExport(string exportName)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(exportName))
|
if (string.IsNullOrWhiteSpace(exportName))
|
||||||
{
|
{
|
||||||
@@ -1287,6 +1289,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (IsLibcAllocatorExport(exportName))
|
||||||
|
{
|
||||||
|
return CanUseLleLibcAllocatorFamily();
|
||||||
|
}
|
||||||
if (string.Equals(value, "0", StringComparison.Ordinal))
|
if (string.Equals(value, "0", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
@@ -1298,6 +1304,51 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
return IsSafeLleLibcExport(exportName);
|
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)
|
private static bool IsSafeLleLibcExport(string exportName)
|
||||||
{
|
{
|
||||||
return exportName switch
|
return exportName switch
|
||||||
@@ -2406,6 +2457,69 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
public bool SupportsGuestContextTransfer => true;
|
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)
|
public void Pump(CpuContext callerContext, string reason)
|
||||||
{
|
{
|
||||||
_ = callerContext;
|
_ = callerContext;
|
||||||
@@ -3254,6 +3368,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
switch (exitReason)
|
switch (exitReason)
|
||||||
{
|
{
|
||||||
case GuestNativeCallExitReason.Returned:
|
case GuestNativeCallExitReason.Returned:
|
||||||
|
thread.ExitValue = thread.Context[CpuRegister.Rax];
|
||||||
thread.State = GuestThreadRunState.Exited;
|
thread.State = GuestThreadRunState.Exited;
|
||||||
break;
|
break;
|
||||||
case GuestNativeCallExitReason.Blocked:
|
case GuestNativeCallExitReason.Blocked:
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ public interface IGuestThreadScheduler
|
|||||||
|
|
||||||
bool TryStartThread(CpuContext creatorContext, GuestThreadStartRequest request, out string? error);
|
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);
|
void Pump(CpuContext callerContext, string reason);
|
||||||
|
|
||||||
int WakeBlockedThreads(string wakeKey, int maxCount = int.MaxValue);
|
int WakeBlockedThreads(string wakeKey, int maxCount = int.MaxValue);
|
||||||
|
|||||||
@@ -68,17 +68,42 @@ public static class AgcExports
|
|||||||
private const uint ComputeNumThreadZ = 0x209;
|
private const uint ComputeNumThreadZ = 0x209;
|
||||||
private const uint SpiPsInputCntl0 = 0x191;
|
private const uint SpiPsInputCntl0 = 0x191;
|
||||||
private const uint VgtPrimitiveType = 0x242;
|
private const uint VgtPrimitiveType = 0x242;
|
||||||
|
private const uint PaScScreenScissorTl = 0x0C;
|
||||||
|
private const uint PaScScreenScissorBr = 0x0D;
|
||||||
private const uint CbTargetMask = 0x8E;
|
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 CbColor0Base = 0x318;
|
||||||
private const uint CbColorRegisterStride = 15;
|
private const uint CbColorRegisterStride = 15;
|
||||||
private const uint CbColor0Info = 0x31C;
|
private const uint CbColor0Info = 0x31C;
|
||||||
private const uint CbColor0BaseExt = 0x390;
|
private const uint CbColor0BaseExt = 0x390;
|
||||||
private const uint CbColor0Attrib2 = 0x3B0;
|
private const uint CbColor0Attrib2 = 0x3B0;
|
||||||
private const uint CbColor0Attrib3 = 0x3B8;
|
private const uint CbColor0Attrib3 = 0x3B8;
|
||||||
|
private const uint CbBlend0Control = 0x1E0;
|
||||||
|
private const uint PaScModeCntl0 = 0x292;
|
||||||
private const int ColorTargetCount = 8;
|
private const int ColorTargetCount = 8;
|
||||||
private const uint PsTextureUserDataRegister = 0xC;
|
private const uint PsTextureUserDataRegister = 0xC;
|
||||||
|
private const uint VsUserDataRegister = 0x4C;
|
||||||
|
private const uint GsUserDataRegister = 0x8C;
|
||||||
private const uint EsUserDataRegister = 0xCC;
|
private const uint EsUserDataRegister = 0xCC;
|
||||||
private const uint ComputeUserDataRegister = 0x240;
|
private const uint ComputeUserDataRegister = 0x240;
|
||||||
|
private const uint NggUserDataScalarRegisterBase = 8;
|
||||||
private const uint Gen5TextureFormatR8G8B8A8Unorm = 56;
|
private const uint Gen5TextureFormatR8G8B8A8Unorm = 56;
|
||||||
private const uint Gen5TextureFormatR16G16B16A16Float = 71;
|
private const uint Gen5TextureFormatR16G16B16A16Float = 71;
|
||||||
private const uint Gen5TextureType2D = 9;
|
private const uint Gen5TextureType2D = 9;
|
||||||
@@ -117,7 +142,7 @@ public static class AgcExports
|
|||||||
private static readonly HashSet<uint> _tracedDcbSizes = new();
|
private static readonly HashSet<uint> _tracedDcbSizes = new();
|
||||||
private static readonly HashSet<(ulong Es, ulong Ps, GuestDrawKind Kind)> _tracedShaderTranslations = new();
|
private static readonly HashSet<(ulong Es, ulong Ps, GuestDrawKind Kind)> _tracedShaderTranslations = new();
|
||||||
private static readonly HashSet<(ulong Es, ulong Ps)> _tracedShaderDecodePairs = new();
|
private static readonly HashSet<(ulong Es, ulong Ps)> _tracedShaderDecodePairs = new();
|
||||||
private static readonly HashSet<(ulong Es, ulong Ps, ulong Target)> _tracedShaderDraws = new();
|
private static readonly HashSet<(ulong Es, ulong Ps, ulong Target, ulong Texture, uint VertexCount)> _tracedShaderDraws = new();
|
||||||
private static readonly HashSet<(ulong Ps, string Error)> _tracedShaderFailures = new();
|
private static readonly HashSet<(ulong Ps, string Error)> _tracedShaderFailures = new();
|
||||||
private static readonly HashSet<(int Handle, int Index, ulong Address, string Path)> _tracedDisplayBuffers = new();
|
private static readonly HashSet<(int Handle, int Index, ulong Address, string Path)> _tracedDisplayBuffers = new();
|
||||||
private static readonly HashSet<ulong> _tracedComputeShaders = new();
|
private static readonly HashSet<ulong> _tracedComputeShaders = new();
|
||||||
@@ -148,9 +173,25 @@ public static class AgcExports
|
|||||||
|
|
||||||
private static readonly RegisterDefaultGroup[] PrimaryRegisterDefaults =
|
private static readonly RegisterDefaultGroup[] PrimaryRegisterDefaults =
|
||||||
[
|
[
|
||||||
|
new(0, 0, 0xE24F806D, [new(CbColorControl, 0x00CC0010)]),
|
||||||
new(0, 3, 0x0BC65DA4, [new(0x08F, 0)]),
|
new(0, 3, 0x0BC65DA4, [new(0x08F, 0)]),
|
||||||
new(0, 4, 0x9E5AD592, [new(0x08E, 0)]),
|
new(0, 4, 0x9E5AD592, [new(0x08E, 0)]),
|
||||||
new(0, 12, 0x6DE4C312, [new(0x203, 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(0, 72, 0x38E92C91,
|
||||||
[
|
[
|
||||||
new(0x318, 0),
|
new(0x318, 0),
|
||||||
@@ -185,6 +226,11 @@ public static class AgcExports
|
|||||||
new(0x0B4, 0),
|
new(0x0B4, 0),
|
||||||
new(0x0B5, 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, 13, 0xC918DF3E, [new(0x20C, 0), new(0x20D, 0)]),
|
||||||
new(1, 14, 0xC9751C9C, [new(0x0C8, 0), new(0x0C9, 0)]),
|
new(1, 14, 0xC9751C9C, [new(0x0C8, 0), new(0x0C9, 0)]),
|
||||||
new(1, 18, 0xC9E01B31, [new(0x008, 0), new(0x009, 0)]),
|
new(1, 18, 0xC9E01B31, [new(0x008, 0), new(0x009, 0)]),
|
||||||
@@ -228,7 +274,9 @@ public static class AgcExports
|
|||||||
uint TileMode,
|
uint TileMode,
|
||||||
uint Type,
|
uint Type,
|
||||||
uint BaseLevel,
|
uint BaseLevel,
|
||||||
uint LastLevel)
|
uint LastLevel,
|
||||||
|
uint Pitch,
|
||||||
|
uint DstSelect)
|
||||||
{
|
{
|
||||||
public uint MipLevels
|
public uint MipLevels
|
||||||
{
|
{
|
||||||
@@ -271,12 +319,15 @@ public static class AgcExports
|
|||||||
VulkanGuestIndexBuffer? IndexBuffer,
|
VulkanGuestIndexBuffer? IndexBuffer,
|
||||||
IReadOnlyList<TranslatedImageBinding> Textures,
|
IReadOnlyList<TranslatedImageBinding> Textures,
|
||||||
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
|
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
|
||||||
IReadOnlyList<RenderTargetDescriptor> RenderTargets);
|
IReadOnlyList<Gen5VertexInputBinding> VertexInputs,
|
||||||
|
IReadOnlyList<RenderTargetDescriptor> RenderTargets,
|
||||||
|
VulkanGuestRenderState RenderState);
|
||||||
|
|
||||||
private sealed record TranslatedImageBinding(
|
private sealed record TranslatedImageBinding(
|
||||||
TextureDescriptor Descriptor,
|
TextureDescriptor Descriptor,
|
||||||
bool IsStorage,
|
bool IsStorage,
|
||||||
uint MipLevel);
|
uint MipLevel,
|
||||||
|
IReadOnlyList<uint> SamplerDescriptor);
|
||||||
|
|
||||||
private readonly record struct RenderTargetWriter(
|
private readonly record struct RenderTargetWriter(
|
||||||
ulong Sequence,
|
ulong Sequence,
|
||||||
@@ -2737,7 +2788,7 @@ public static class AgcExports
|
|||||||
$"ps=0x{(hasPixelShader ? pixelShaderAddress : 0):X16}");
|
$"ps=0x{(hasPixelShader ? pixelShaderAddress : 0):X16}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vertexCount is not (3 or 4 or 6))
|
if (vertexCount == 0 || vertexCount > 1_048_576)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2767,6 +2818,8 @@ public static class AgcExports
|
|||||||
out _);
|
out _);
|
||||||
var globalMemoryBuffers =
|
var globalMemoryBuffers =
|
||||||
CreateVulkanGuestMemoryBuffers(translatedDraw.GlobalMemoryBindings);
|
CreateVulkanGuestMemoryBuffers(translatedDraw.GlobalMemoryBindings);
|
||||||
|
var vertexBuffers =
|
||||||
|
CreateVulkanGuestVertexBuffers(translatedDraw.VertexInputs);
|
||||||
VulkanVideoPresenter.SubmitOffscreenTranslatedDraw(
|
VulkanVideoPresenter.SubmitOffscreenTranslatedDraw(
|
||||||
translatedDraw.PixelSpirv,
|
translatedDraw.PixelSpirv,
|
||||||
textures,
|
textures,
|
||||||
@@ -2782,7 +2835,9 @@ public static class AgcExports
|
|||||||
translatedDraw.VertexCount,
|
translatedDraw.VertexCount,
|
||||||
translatedDraw.InstanceCount,
|
translatedDraw.InstanceCount,
|
||||||
translatedDraw.PrimitiveType,
|
translatedDraw.PrimitiveType,
|
||||||
translatedDraw.IndexBuffer);
|
translatedDraw.IndexBuffer,
|
||||||
|
vertexBuffers,
|
||||||
|
translatedDraw.RenderState);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2817,8 +2872,9 @@ public static class AgcExports
|
|||||||
|
|
||||||
lock (_submitTraceGate)
|
lock (_submitTraceGate)
|
||||||
{
|
{
|
||||||
|
var firstTextureAddress = translatedDraw.Textures.FirstOrDefault()?.Descriptor.Address ?? 0;
|
||||||
if (_tracedShaderDraws.Add(
|
if (_tracedShaderDraws.Add(
|
||||||
(exportShaderAddress, pixelShaderAddress, firstTarget.Address)))
|
(exportShaderAddress, pixelShaderAddress, firstTarget.Address, firstTextureAddress, vertexCount)))
|
||||||
{
|
{
|
||||||
TraceTranslatedGuestDraw(
|
TraceTranslatedGuestDraw(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -2873,14 +2929,16 @@ public static class AgcExports
|
|||||||
exportShaderAddress,
|
exportShaderAddress,
|
||||||
exportShaderHeader,
|
exportShaderHeader,
|
||||||
state.ShRegisters,
|
state.ShRegisters,
|
||||||
EsUserDataRegister,
|
SelectExportUserDataRegister(state.ShRegisters),
|
||||||
out var exportState,
|
out var exportState,
|
||||||
out error) ||
|
out error,
|
||||||
|
userDataScalarRegisterBase: NggUserDataScalarRegisterBase) ||
|
||||||
!Gen5ShaderScalarEvaluator.TryEvaluate(
|
!Gen5ShaderScalarEvaluator.TryEvaluate(
|
||||||
ctx,
|
ctx,
|
||||||
exportState,
|
exportState,
|
||||||
out var exportEvaluation,
|
out var exportEvaluation,
|
||||||
out error) ||
|
out error,
|
||||||
|
resolveVertexInputs: true) ||
|
||||||
!Gen5ShaderTranslator.TryCreateState(
|
!Gen5ShaderTranslator.TryCreateState(
|
||||||
ctx,
|
ctx,
|
||||||
pixelShaderAddress,
|
pixelShaderAddress,
|
||||||
@@ -2904,11 +2962,13 @@ public static class AgcExports
|
|||||||
HasPixelColorExport(pixelState, target.Slot))
|
HasPixelColorExport(pixelState, target.Slot))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
var outputKind = GetPixelOutputKind(renderTargets.FirstOrDefault().NumberType);
|
var outputKind = GetPixelOutputKind(renderTargets.FirstOrDefault().NumberType);
|
||||||
|
var exportStateFingerprint = ComputeShaderStateFingerprint(exportEvaluation);
|
||||||
|
var pixelStateFingerprint = ComputeShaderStateFingerprint(pixelEvaluation);
|
||||||
var shaderKey = (
|
var shaderKey = (
|
||||||
exportShaderAddress,
|
exportShaderAddress,
|
||||||
ComputeShaderStateFingerprint(exportEvaluation),
|
exportStateFingerprint,
|
||||||
pixelShaderAddress,
|
pixelShaderAddress,
|
||||||
ComputeShaderStateFingerprint(pixelEvaluation),
|
pixelStateFingerprint,
|
||||||
outputKind);
|
outputKind);
|
||||||
(byte[] Vertex, byte[] Pixel) compiled;
|
(byte[] Vertex, byte[] Pixel) compiled;
|
||||||
lock (_submitTraceGate)
|
lock (_submitTraceGate)
|
||||||
@@ -2943,6 +3003,18 @@ public static class AgcExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
compiled = (vertexShader.Spirv, pixelShader.Spirv);
|
compiled = (vertexShader.Spirv, pixelShader.Spirv);
|
||||||
|
DumpSpirv(
|
||||||
|
"vs",
|
||||||
|
exportShaderAddress,
|
||||||
|
exportStateFingerprint,
|
||||||
|
compiled.Vertex,
|
||||||
|
exportState.Program);
|
||||||
|
DumpSpirv(
|
||||||
|
"ps",
|
||||||
|
pixelShaderAddress,
|
||||||
|
pixelStateFingerprint,
|
||||||
|
compiled.Pixel,
|
||||||
|
pixelState.Program);
|
||||||
lock (_submitTraceGate)
|
lock (_submitTraceGate)
|
||||||
{
|
{
|
||||||
_graphicsSpirvCache.TryAdd(shaderKey, compiled);
|
_graphicsSpirvCache.TryAdd(shaderKey, compiled);
|
||||||
@@ -2962,16 +3034,24 @@ public static class AgcExports
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TraceAgcShader(
|
||||||
|
$"agc.texture_binding ps=0x{pixelShaderAddress:X16} es=0x{exportShaderAddress:X16} " +
|
||||||
|
$"pc=0x{binding.Pc:X} op={binding.Opcode} storage={(Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode) ? 1 : 0)} " +
|
||||||
|
$"decoded={FormatTextureDescriptor(texture)} " +
|
||||||
|
$"raw={FormatShaderDwords(binding.ResourceDescriptor)} sampler={FormatShaderDwords(binding.SamplerDescriptor)}");
|
||||||
textures.Add(
|
textures.Add(
|
||||||
new TranslatedImageBinding(
|
new TranslatedImageBinding(
|
||||||
texture,
|
texture,
|
||||||
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode),
|
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode),
|
||||||
binding.MipLevel ?? 0));
|
binding.MipLevel ?? 0,
|
||||||
|
binding.SamplerDescriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalMemoryBindings = pixelEvaluation.GlobalMemoryBindings
|
var globalMemoryBindings = pixelEvaluation.GlobalMemoryBindings
|
||||||
.Concat(exportEvaluation.GlobalMemoryBindings)
|
.Concat(exportEvaluation.GlobalMemoryBindings)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
IReadOnlyList<Gen5VertexInputBinding> vertexInputs =
|
||||||
|
exportEvaluation.VertexInputs ?? [];
|
||||||
state.UcRegisters.TryGetValue(VgtPrimitiveType, out var primitiveType);
|
state.UcRegisters.TryGetValue(VgtPrimitiveType, out var primitiveType);
|
||||||
draw = new TranslatedGuestDraw(
|
draw = new TranslatedGuestDraw(
|
||||||
exportShaderAddress,
|
exportShaderAddress,
|
||||||
@@ -2985,7 +3065,9 @@ public static class AgcExports
|
|||||||
indexed ? CreateVulkanIndexBuffer(ctx, state, vertexCount) : null,
|
indexed ? CreateVulkanIndexBuffer(ctx, state, vertexCount) : null,
|
||||||
textures,
|
textures,
|
||||||
globalMemoryBindings,
|
globalMemoryBindings,
|
||||||
renderTargets);
|
vertexInputs,
|
||||||
|
renderTargets,
|
||||||
|
CreateRenderState(state.CxRegisters, renderTargets.FirstOrDefault()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3004,7 +3086,9 @@ public static class AgcExports
|
|||||||
var byteOffset = checked((ulong)state.DrawIndexOffset * (uint)bytesPerIndex);
|
var byteOffset = checked((ulong)state.DrawIndexOffset * (uint)bytesPerIndex);
|
||||||
var byteCount = checked((int)(indexCount * (uint)bytesPerIndex));
|
var byteCount = checked((int)(indexCount * (uint)bytesPerIndex));
|
||||||
var data = new byte[byteCount];
|
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)
|
? new VulkanGuestIndexBuffer(data, is32Bit)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
@@ -3095,6 +3179,246 @@ public static class AgcExports
|
|||||||
return targets;
|
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(
|
private static void TraceTranslatedGuestDraw(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
SubmittedGpuState gpuState,
|
SubmittedGpuState gpuState,
|
||||||
@@ -3144,12 +3468,65 @@ public static class AgcExports
|
|||||||
$"fmt{texture.Format}/num{texture.NumberType}/tile{texture.TileMode}" +
|
$"fmt{texture.Format}/num{texture.NumberType}/tile{texture.TileMode}" +
|
||||||
$"/storage={binding.IsStorage}{target}/{probe}{writer}";
|
$"/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}" +
|
||||||
|
$":fmt{input.DataFormat}/num{input.NumberFormat}"));
|
||||||
|
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 rasterRegisters = new (string Name, uint Offset)[]
|
||||||
|
{
|
||||||
|
("screen_tl", PaScScreenScissorTl),
|
||||||
|
("screen_br", PaScScreenScissorBr),
|
||||||
|
("window_off", PaScWindowOffset),
|
||||||
|
("window_tl", PaScWindowScissorTl),
|
||||||
|
("window_br", PaScWindowScissorBr),
|
||||||
|
("generic_tl", PaScGenericScissorTl),
|
||||||
|
("generic_br", PaScGenericScissorBr),
|
||||||
|
("vport_tl", PaScVportScissor0Tl),
|
||||||
|
("vport_br", PaScVportScissor0Br),
|
||||||
|
("mode", PaScModeCntl0),
|
||||||
|
("xscale", PaClVportXScale),
|
||||||
|
("xoffset", PaClVportXOffset),
|
||||||
|
("yscale", PaClVportYScale),
|
||||||
|
("yoffset", PaClVportYOffset),
|
||||||
|
};
|
||||||
|
var raster = string.Join(
|
||||||
|
',',
|
||||||
|
rasterRegisters.Select(entry =>
|
||||||
|
state.CxRegisters.TryGetValue(entry.Offset, out var value)
|
||||||
|
? $"{entry.Name}=0x{value:X8}"
|
||||||
|
: $"{entry.Name}=missing"));
|
||||||
|
var blend = draw.RenderState.Blend;
|
||||||
TraceAgcShader(
|
TraceAgcShader(
|
||||||
$"agc.shader_draw es=0x{draw.ExportShaderAddress:X16} " +
|
$"agc.shader_draw es=0x{draw.ExportShaderAddress:X16} " +
|
||||||
$"ps=0x{draw.PixelShaderAddress:X16} spirv={draw.PixelSpirv.Length} " +
|
$"ps=0x{draw.PixelShaderAddress:X16} spirv={draw.PixelSpirv.Length} " +
|
||||||
$"primitive=0x{draw.PrimitiveType:X} " +
|
$"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} " +
|
||||||
|
$"raster=[{raster}] " +
|
||||||
$"ps_ena=0x{psInputEna:X8} ps_addr=0x{psInputAddr:X8} " +
|
$"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(
|
private static IReadOnlyList<VulkanGuestDrawTexture> CreateVulkanGuestDrawTextures(
|
||||||
@@ -3166,6 +3543,7 @@ public static class AgcExports
|
|||||||
binding.Descriptor,
|
binding.Descriptor,
|
||||||
binding.IsStorage,
|
binding.IsStorage,
|
||||||
binding.MipLevel,
|
binding.MipLevel,
|
||||||
|
binding.SamplerDescriptor,
|
||||||
out var texture))
|
out var texture))
|
||||||
{
|
{
|
||||||
textures.Add(texture);
|
textures.Add(texture);
|
||||||
@@ -3193,11 +3571,33 @@ public static class AgcExports
|
|||||||
return buffers;
|
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.DataFormat,
|
||||||
|
binding.NumberFormat,
|
||||||
|
binding.BaseAddress,
|
||||||
|
binding.Stride,
|
||||||
|
binding.OffsetBytes,
|
||||||
|
binding.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffers;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryCreateVulkanGuestDrawTexture(
|
private static bool TryCreateVulkanGuestDrawTexture(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
TextureDescriptor descriptor,
|
TextureDescriptor descriptor,
|
||||||
bool isStorage,
|
bool isStorage,
|
||||||
uint mipLevel,
|
uint mipLevel,
|
||||||
|
IReadOnlyList<uint> samplerDescriptor,
|
||||||
out VulkanGuestDrawTexture texture)
|
out VulkanGuestDrawTexture texture)
|
||||||
{
|
{
|
||||||
texture = default!;
|
texture = default!;
|
||||||
@@ -3211,9 +3611,15 @@ public static class AgcExports
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sourceWidth = descriptor.TileMode == 0
|
||||||
|
? GetLinearTexturePitch(
|
||||||
|
Math.Max(descriptor.Width, descriptor.Pitch),
|
||||||
|
descriptor.Height,
|
||||||
|
descriptor.Format)
|
||||||
|
: descriptor.Width;
|
||||||
var sourceByteCount = GetTextureByteCount(
|
var sourceByteCount = GetTextureByteCount(
|
||||||
descriptor.Format,
|
descriptor.Format,
|
||||||
descriptor.Width,
|
sourceWidth,
|
||||||
descriptor.Height);
|
descriptor.Height);
|
||||||
if (sourceByteCount == 0 ||
|
if (sourceByteCount == 0 ||
|
||||||
sourceByteCount > MaxPresentedTextureBytes ||
|
sourceByteCount > MaxPresentedTextureBytes ||
|
||||||
@@ -3246,7 +3652,11 @@ public static class AgcExports
|
|||||||
IsFallback: descriptor.Address == 0,
|
IsFallback: descriptor.Address == 0,
|
||||||
IsStorage: true,
|
IsStorage: true,
|
||||||
MipLevels: descriptor.MipLevels,
|
MipLevels: descriptor.MipLevels,
|
||||||
MipLevel: mipLevel);
|
MipLevel: mipLevel,
|
||||||
|
Pitch: sourceWidth,
|
||||||
|
TileMode: descriptor.TileMode,
|
||||||
|
DstSelect: descriptor.DstSelect,
|
||||||
|
Sampler: ToVulkanSampler(samplerDescriptor));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3273,7 +3683,8 @@ public static class AgcExports
|
|||||||
TraceAgcShader(
|
TraceAgcShader(
|
||||||
$"agc.texture_source addr=0x{descriptor.Address:X16} " +
|
$"agc.texture_source addr=0x{descriptor.Address:X16} " +
|
||||||
$"fmt={descriptor.Format} num={descriptor.NumberType} tile={descriptor.TileMode} " +
|
$"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}");
|
$"bytes={source.Length} nonzero64={nonZero}");
|
||||||
|
|
||||||
var rgba = source;
|
var rgba = source;
|
||||||
@@ -3287,7 +3698,11 @@ public static class AgcExports
|
|||||||
IsFallback: false,
|
IsFallback: false,
|
||||||
IsStorage: isStorage,
|
IsStorage: isStorage,
|
||||||
MipLevels: descriptor.MipLevels,
|
MipLevels: descriptor.MipLevels,
|
||||||
MipLevel: mipLevel);
|
MipLevel: mipLevel,
|
||||||
|
Pitch: sourceWidth,
|
||||||
|
TileMode: descriptor.TileMode,
|
||||||
|
DstSelect: descriptor.DstSelect,
|
||||||
|
Sampler: ToVulkanSampler(samplerDescriptor));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3304,6 +3719,15 @@ public static class AgcExports
|
|||||||
MipLevels: 1,
|
MipLevels: 1,
|
||||||
MipLevel: 0);
|
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)
|
private static byte[] ConvertRgba16FloatToRgba8(ReadOnlySpan<byte> source, uint width, uint height)
|
||||||
{
|
{
|
||||||
var destination = new byte[checked((int)((ulong)width * height * 4))];
|
var destination = new byte[checked((int)((ulong)width * height * 4))];
|
||||||
@@ -3456,7 +3880,8 @@ public static class AgcExports
|
|||||||
new TranslatedImageBinding(
|
new TranslatedImageBinding(
|
||||||
texture,
|
texture,
|
||||||
isStorage,
|
isStorage,
|
||||||
binding.MipLevel ?? 0));
|
binding.MipLevel ?? 0,
|
||||||
|
binding.SamplerDescriptor));
|
||||||
hasStorageBinding |= isStorage;
|
hasStorageBinding |= isStorage;
|
||||||
|
|
||||||
var descriptorState = descriptorValid ? string.Empty : "/invalid-desc";
|
var descriptorState = descriptorValid ? string.Empty : "/invalid-desc";
|
||||||
@@ -3511,6 +3936,12 @@ public static class AgcExports
|
|||||||
out computeError))
|
out computeError))
|
||||||
{
|
{
|
||||||
computeSpirv = compiledCompute.Spirv;
|
computeSpirv = compiledCompute.Spirv;
|
||||||
|
DumpSpirv(
|
||||||
|
"cs",
|
||||||
|
shaderAddress,
|
||||||
|
shaderKey.Item2,
|
||||||
|
computeSpirv,
|
||||||
|
shaderState.Program);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (computeSpirv is not null)
|
if (computeSpirv is not null)
|
||||||
@@ -3527,6 +3958,7 @@ public static class AgcExports
|
|||||||
var globalMemoryBuffers =
|
var globalMemoryBuffers =
|
||||||
CreateVulkanGuestMemoryBuffers(evaluation.GlobalMemoryBindings);
|
CreateVulkanGuestMemoryBuffers(evaluation.GlobalMemoryBindings);
|
||||||
VulkanVideoPresenter.SubmitComputeDispatch(
|
VulkanVideoPresenter.SubmitComputeDispatch(
|
||||||
|
shaderAddress,
|
||||||
computeSpirv,
|
computeSpirv,
|
||||||
textures,
|
textures,
|
||||||
globalMemoryBuffers,
|
globalMemoryBuffers,
|
||||||
@@ -3601,6 +4033,62 @@ public static class AgcExports
|
|||||||
private static string DescribeRegister(uint? register) =>
|
private static string DescribeRegister(uint? register) =>
|
||||||
register.HasValue ? $"s{register.Value}" : "-";
|
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(
|
private static uint GetComputeLocalSize(
|
||||||
IReadOnlyDictionary<uint, uint> registers,
|
IReadOnlyDictionary<uint, uint> registers,
|
||||||
uint register)
|
uint register)
|
||||||
@@ -3798,7 +4286,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) =>
|
private static ulong GetTextureBytesPerTexel(uint format) =>
|
||||||
@@ -3850,6 +4362,28 @@ public static class AgcExports
|
|||||||
: checked(((ulong)width + 3) / 4 * (((ulong)height + 3) / 4) * blockBytes);
|
: checked(((ulong)width + 3) / 4 * (((ulong)height + 3) / 4) * blockBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static uint GetLinearTexturePitch(uint pitch, uint height, uint format)
|
||||||
|
{
|
||||||
|
var bytesPerTexel = GetTextureBytesPerTexel(format);
|
||||||
|
if (bytesPerTexel == 0 || height == 0)
|
||||||
|
{
|
||||||
|
return pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pitchAlignment = Math.Max(8UL, 64UL / bytesPerTexel);
|
||||||
|
var alignedPitch = AlignUp(pitch, pitchAlignment);
|
||||||
|
var sliceAlignment = Math.Max(64UL, 256UL / bytesPerTexel);
|
||||||
|
while ((alignedPitch * height) % sliceAlignment != 0)
|
||||||
|
{
|
||||||
|
alignedPitch += pitchAlignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return checked((uint)alignedPitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong AlignUp(ulong value, ulong alignment) =>
|
||||||
|
(value + alignment - 1) & ~(alignment - 1);
|
||||||
|
|
||||||
private static void TraceShaderTranslationMiss(
|
private static void TraceShaderTranslationMiss(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
SubmittedDcbState state,
|
SubmittedDcbState state,
|
||||||
@@ -3912,9 +4446,10 @@ public static class AgcExports
|
|||||||
exportShaderAddress,
|
exportShaderAddress,
|
||||||
exportShaderHeader,
|
exportShaderHeader,
|
||||||
state.ShRegisters,
|
state.ShRegisters,
|
||||||
EsUserDataRegister,
|
SelectExportUserDataRegister(state.ShRegisters),
|
||||||
out var exportState,
|
out var exportState,
|
||||||
out _) &&
|
out _,
|
||||||
|
userDataScalarRegisterBase: NggUserDataScalarRegisterBase) &&
|
||||||
Gen5ShaderTranslator.TryCreateState(
|
Gen5ShaderTranslator.TryCreateState(
|
||||||
ctx,
|
ctx,
|
||||||
pixelShaderAddress,
|
pixelShaderAddress,
|
||||||
@@ -4117,6 +4652,10 @@ public static class AgcExports
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GFX10/RDNA2 T# layout: WIDTH is split across word1[31:30] (lo 2 bits)
|
||||||
|
// and word2[11:0] (hi 12 bits); FORMAT is the combined 9-bit field at
|
||||||
|
// word1[28:20]. Verified against Kyty's decode of the same game
|
||||||
|
// descriptors (fmt=56=8_8_8_8_UNORM, extent 1280x720, sw_mode 27).
|
||||||
// GNM T# exposes a 38-bit baseaddr256 field, but RPCSX and the
|
// GNM T# exposes a 38-bit baseaddr256 field, but RPCSX and the
|
||||||
// Demon's Souls descriptors both show that only the low 32 bits are
|
// Demon's Souls descriptors both show that only the low 32 bits are
|
||||||
// part of the guest GPU VA. The upper baseaddr bits carry resource
|
// part of the guest GPU VA. The upper baseaddr bits carry resource
|
||||||
@@ -4130,6 +4669,10 @@ public static class AgcExports
|
|||||||
var type = (fields[3] >> 28) & 0xFu;
|
var type = (fields[3] >> 28) & 0xFu;
|
||||||
var baseLevel = (fields[3] >> 12) & 0xFu;
|
var baseLevel = (fields[3] >> 12) & 0xFu;
|
||||||
var lastLevel = (fields[3] >> 16) & 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)
|
if (address == 0 || width == 0 || height == 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -4144,7 +4687,9 @@ public static class AgcExports
|
|||||||
tileMode,
|
tileMode,
|
||||||
type,
|
type,
|
||||||
baseLevel,
|
baseLevel,
|
||||||
lastLevel);
|
lastLevel,
|
||||||
|
pitch,
|
||||||
|
dstSelect);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4173,7 +4718,9 @@ public static class AgcExports
|
|||||||
TileMode: tileMode,
|
TileMode: tileMode,
|
||||||
Type: Gen5TextureType2D,
|
Type: Gen5TextureType2D,
|
||||||
BaseLevel: 0,
|
BaseLevel: 0,
|
||||||
LastLevel: 0);
|
LastLevel: 0,
|
||||||
|
Pitch: 1,
|
||||||
|
DstSelect: 0xFAC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TrySoftwarePresent(
|
private static bool TrySoftwarePresent(
|
||||||
@@ -4882,6 +5429,52 @@ public static class AgcExports
|
|||||||
? "none"
|
? "none"
|
||||||
: string.Join(',', values.Select(static value => $"{value:X8}"));
|
: string.Join(',', values.Select(static value => $"{value:X8}"));
|
||||||
|
|
||||||
|
private static string FormatTextureDescriptor(TextureDescriptor descriptor) =>
|
||||||
|
$"addr=0x{descriptor.Address:X16} {descriptor.Width}x{descriptor.Height} " +
|
||||||
|
$"fmt={descriptor.Format} num={descriptor.NumberType} tile={descriptor.TileMode} " +
|
||||||
|
$"type={descriptor.Type} levels={descriptor.BaseLevel}-{descriptor.LastLevel} " +
|
||||||
|
$"pitch={descriptor.Pitch} dst=0x{descriptor.DstSelect:X3}";
|
||||||
|
|
||||||
|
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)
|
private static void TraceCreateShader(ulong destinationAddress, ulong headerAddress, ulong codeAddress, string detail)
|
||||||
{
|
{
|
||||||
var isOk = string.Equals(detail, "ok", StringComparison.Ordinal);
|
var isOk = string.Equals(detail, "ok", StringComparison.Ordinal);
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ internal sealed record Gen5SpirvShader(
|
|||||||
byte[] Spirv,
|
byte[] Spirv,
|
||||||
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
|
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
|
||||||
IReadOnlyList<Gen5ImageBinding> ImageBindings,
|
IReadOnlyList<Gen5ImageBinding> ImageBindings,
|
||||||
uint AttributeCount);
|
uint AttributeCount,
|
||||||
|
IReadOnlyList<Gen5VertexInputBinding> VertexInputs);
|
||||||
|
|
||||||
internal readonly record struct Gen5ShaderResourceMapping(
|
internal readonly record struct Gen5ShaderResourceMapping(
|
||||||
Gen5ShaderResourceKind Kind,
|
Gen5ShaderResourceKind Kind,
|
||||||
@@ -131,7 +132,8 @@ internal sealed record Gen5ShaderState(
|
|||||||
Gen5ShaderProgram Program,
|
Gen5ShaderProgram Program,
|
||||||
IReadOnlyList<uint> UserData,
|
IReadOnlyList<uint> UserData,
|
||||||
Gen5ShaderMetadata? Metadata,
|
Gen5ShaderMetadata? Metadata,
|
||||||
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null);
|
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null,
|
||||||
|
uint UserDataScalarRegisterBase = 0);
|
||||||
|
|
||||||
internal readonly record struct Gen5Operand(Gen5OperandKind Kind, uint Value)
|
internal readonly record struct Gen5Operand(Gen5OperandKind Kind, uint Value)
|
||||||
{
|
{
|
||||||
@@ -270,6 +272,17 @@ internal sealed record Gen5GlobalMemoryBinding(
|
|||||||
IReadOnlyList<uint> InstructionPcs,
|
IReadOnlyList<uint> InstructionPcs,
|
||||||
byte[] Data);
|
byte[] Data);
|
||||||
|
|
||||||
|
internal sealed record Gen5VertexInputBinding(
|
||||||
|
uint Pc,
|
||||||
|
uint Location,
|
||||||
|
uint ComponentCount,
|
||||||
|
uint DataFormat,
|
||||||
|
uint NumberFormat,
|
||||||
|
ulong BaseAddress,
|
||||||
|
uint Stride,
|
||||||
|
uint OffsetBytes,
|
||||||
|
byte[] Data);
|
||||||
|
|
||||||
internal sealed record Gen5ShaderEvaluation(
|
internal sealed record Gen5ShaderEvaluation(
|
||||||
IReadOnlyList<uint> InitialScalarRegisters,
|
IReadOnlyList<uint> InitialScalarRegisters,
|
||||||
IReadOnlyList<uint> ScalarRegisters,
|
IReadOnlyList<uint> ScalarRegisters,
|
||||||
@@ -277,7 +290,8 @@ internal sealed record Gen5ShaderEvaluation(
|
|||||||
IReadOnlyList<Gen5ImageBinding> ImageBindings,
|
IReadOnlyList<Gen5ImageBinding> ImageBindings,
|
||||||
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
|
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
|
||||||
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null,
|
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null,
|
||||||
IReadOnlySet<uint>? RuntimeScalarRegisters = null);
|
IReadOnlySet<uint>? RuntimeScalarRegisters = null,
|
||||||
|
IReadOnlyList<Gen5VertexInputBinding>? VertexInputs = null);
|
||||||
|
|
||||||
internal sealed record Gen5ShaderInstruction(
|
internal sealed record Gen5ShaderInstruction(
|
||||||
uint Pc,
|
uint Pc,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
using SharpEmu.HLE;
|
using SharpEmu.HLE;
|
||||||
|
using SharpEmu.Libs.Kernel;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@@ -13,12 +14,15 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
private const int ImageDescriptorDwords = 8;
|
private const int ImageDescriptorDwords = 8;
|
||||||
private const int SamplerDescriptorDwords = 4;
|
private const int SamplerDescriptorDwords = 4;
|
||||||
private const int MaxGlobalMemoryBindingBytes = 16 * 1024 * 1024;
|
private const int MaxGlobalMemoryBindingBytes = 16 * 1024 * 1024;
|
||||||
|
private const ulong RdnaWaveMask = 0xFFFF_FFFFUL;
|
||||||
|
|
||||||
private readonly record struct BufferDescriptor(
|
private readonly record struct BufferDescriptor(
|
||||||
ulong BaseAddress,
|
ulong BaseAddress,
|
||||||
uint Stride,
|
uint Stride,
|
||||||
uint NumRecords,
|
uint NumRecords,
|
||||||
ulong SizeBytes);
|
ulong SizeBytes,
|
||||||
|
uint NumberFormat,
|
||||||
|
uint DataFormat);
|
||||||
|
|
||||||
public static bool TryResolveImageBindings(
|
public static bool TryResolveImageBindings(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
@@ -40,14 +44,19 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
Gen5ShaderState state,
|
Gen5ShaderState state,
|
||||||
out Gen5ShaderEvaluation evaluation,
|
out Gen5ShaderEvaluation evaluation,
|
||||||
out string error)
|
out string error,
|
||||||
|
bool resolveVertexInputs = false)
|
||||||
{
|
{
|
||||||
evaluation = default!;
|
evaluation = default!;
|
||||||
error = string.Empty;
|
error = string.Empty;
|
||||||
var scalarRegisters = new uint[ScalarRegisterCount];
|
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)
|
if (state.ComputeSystemRegisters is { } computeSystemRegisters)
|
||||||
@@ -55,14 +64,15 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
computeSystemRegisters.ClearStaticValues(scalarRegisters);
|
computeSystemRegisters.ClearStaticValues(scalarRegisters);
|
||||||
}
|
}
|
||||||
|
|
||||||
var execMask = ulong.MaxValue;
|
var execMask = RdnaWaveMask;
|
||||||
WriteScalarPair(scalarRegisters, 106, ulong.MaxValue, ref execMask);
|
WriteScalarPair(scalarRegisters, 106, 0, ref execMask);
|
||||||
WriteScalarPair(scalarRegisters, 126, execMask, ref execMask);
|
WriteScalarPair(scalarRegisters, 126, execMask, ref execMask);
|
||||||
var initialScalarRegisters = (uint[])scalarRegisters.Clone();
|
var initialScalarRegisters = (uint[])scalarRegisters.Clone();
|
||||||
|
|
||||||
var resolved = new List<Gen5ImageBinding>();
|
var resolved = new List<Gen5ImageBinding>();
|
||||||
var globalMemoryBindings = new List<Gen5GlobalMemoryBinding>();
|
var globalMemoryBindings = new List<Gen5GlobalMemoryBinding>();
|
||||||
var globalMemoryByAddress = new Dictionary<(uint ScalarAddress, ulong BaseAddress), Gen5GlobalMemoryBinding>();
|
var globalMemoryByAddress = new Dictionary<(uint ScalarAddress, ulong BaseAddress), Gen5GlobalMemoryBinding>();
|
||||||
|
var vertexInputBindings = new List<Gen5VertexInputBinding>();
|
||||||
var runtimeScalarRegisters = CollectRuntimeScalarRegisters(state.Program);
|
var runtimeScalarRegisters = CollectRuntimeScalarRegisters(state.Program);
|
||||||
var scalarRegisterSnapshots = new Dictionary<uint, IReadOnlyList<uint>>();
|
var scalarRegisterSnapshots = new Dictionary<uint, IReadOnlyList<uint>>();
|
||||||
var scalarConditionCode = false;
|
var scalarConditionCode = false;
|
||||||
@@ -243,6 +253,42 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
return false;
|
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);
|
var key = (bufferMemory.ScalarResource, bufferDescriptor.BaseAddress);
|
||||||
if (globalMemoryByAddress.TryGetValue(key, out var existingBinding))
|
if (globalMemoryByAddress.TryGetValue(key, out var existingBinding))
|
||||||
{
|
{
|
||||||
@@ -259,10 +305,16 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
bufferDescriptor.SizeBytes,
|
bufferDescriptor.SizeBytes,
|
||||||
out var data))
|
out var data))
|
||||||
{
|
{
|
||||||
|
var descriptorWords = string.Join(
|
||||||
|
':',
|
||||||
|
Enumerable.Range(0, 4).Select(index =>
|
||||||
|
$"{scalarRegisters[bufferMemory.ScalarResource + (uint)index]:X8}"));
|
||||||
error =
|
error =
|
||||||
$"buffer-memory-read-failed pc=0x{instruction.Pc:X} " +
|
$"buffer-memory-read-failed pc=0x{instruction.Pc:X} " +
|
||||||
$"address=0x{bufferDescriptor.BaseAddress:X16} " +
|
$"address=0x{bufferDescriptor.BaseAddress:X16} " +
|
||||||
$"bytes={bufferDescriptor.SizeBytes}";
|
$"bytes={bufferDescriptor.SizeBytes} " +
|
||||||
|
$"stride={bufferDescriptor.Stride} records={bufferDescriptor.NumRecords} " +
|
||||||
|
$"s{bufferMemory.ScalarResource}=[{descriptorWords}]";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,10 +380,58 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
resolved,
|
resolved,
|
||||||
globalMemoryBindings,
|
globalMemoryBindings,
|
||||||
state.ComputeSystemRegisters,
|
state.ComputeSystemRegisters,
|
||||||
runtimeScalarRegisters);
|
runtimeScalarRegisters,
|
||||||
|
vertexInputBindings);
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bindingData = data;
|
||||||
|
var bindingStride = descriptor.Stride;
|
||||||
|
var bindingOffset = unchecked((uint)control.OffsetBytes + scalarOffset);
|
||||||
|
var bindingDataFormat = descriptor.DataFormat;
|
||||||
|
var bindingNumberFormat = descriptor.NumberFormat;
|
||||||
|
binding = new Gen5VertexInputBinding(
|
||||||
|
instruction.Pc,
|
||||||
|
location,
|
||||||
|
control.DwordCount,
|
||||||
|
bindingDataFormat,
|
||||||
|
bindingNumberFormat,
|
||||||
|
descriptor.BaseAddress,
|
||||||
|
bindingStride,
|
||||||
|
bindingOffset,
|
||||||
|
bindingData);
|
||||||
|
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)
|
private static HashSet<uint> CollectRuntimeScalarRegisters(Gen5ShaderProgram program)
|
||||||
{
|
{
|
||||||
var registers = new HashSet<uint>();
|
var registers = new HashSet<uint>();
|
||||||
@@ -471,12 +571,24 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = GC.AllocateUninitializedArray<byte>((int)cappedSize);
|
var candidateSize = (int)cappedSize;
|
||||||
if (ctx.Memory.TryRead(baseAddress, data))
|
while (candidateSize >= sizeof(uint))
|
||||||
|
{
|
||||||
|
data = GC.AllocateUninitializedArray<byte>(candidateSize);
|
||||||
|
if (ctx.Memory.TryRead(baseAddress, data) ||
|
||||||
|
KernelMemoryCompatExports.TryReadTrackedLibcHeap(baseAddress, data))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (candidateSize == sizeof(uint))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidateSize = Math.Max(candidateSize / 2, sizeof(uint));
|
||||||
|
}
|
||||||
|
|
||||||
data = [];
|
data = [];
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -582,6 +694,49 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
return true;
|
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
|
if (instruction.Opcode is
|
||||||
"SCselectB64" or
|
"SCselectB64" or
|
||||||
"SAndB64" or
|
"SAndB64" or
|
||||||
@@ -657,7 +812,11 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
"SFF1I32B32" => left == 0 ? uint.MaxValue : (uint)BitOperations.TrailingZeroCount(left),
|
"SFF1I32B32" => left == 0 ? uint.MaxValue : (uint)BitOperations.TrailingZeroCount(left),
|
||||||
_ => registers[destination.Value] | (1u << ((int)left & 31)),
|
_ => registers[destination.Value] | (1u << ((int)left & 31)),
|
||||||
};
|
};
|
||||||
|
if (instruction.Opcode != "SBitset1B32")
|
||||||
|
{
|
||||||
scalarConditionCode = registers[destination.Value] != 0;
|
scalarConditionCode = registers[destination.Value] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,13 +842,15 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
}
|
}
|
||||||
case "SSubU32":
|
case "SSubU32":
|
||||||
result = left - right;
|
result = left - right;
|
||||||
scalarConditionCode = left >= right;
|
scalarConditionCode = right > left;
|
||||||
break;
|
break;
|
||||||
case "SAddI32":
|
case "SAddI32":
|
||||||
result = unchecked((uint)((int)left + (int)right));
|
result = unchecked((uint)((int)left + (int)right));
|
||||||
|
scalarConditionCode = SignedAddOverflow(left, right, result);
|
||||||
break;
|
break;
|
||||||
case "SSubI32":
|
case "SSubI32":
|
||||||
result = unchecked((uint)((int)left - (int)right));
|
result = unchecked((uint)((int)left - (int)right));
|
||||||
|
scalarConditionCode = SignedSubOverflow(left, right, result);
|
||||||
break;
|
break;
|
||||||
case "SAddcU32":
|
case "SAddcU32":
|
||||||
{
|
{
|
||||||
@@ -700,23 +861,27 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
}
|
}
|
||||||
case "SSubbU32":
|
case "SSubbU32":
|
||||||
{
|
{
|
||||||
var borrow = scalarConditionCode ? 0UL : 1UL;
|
var borrow = scalarConditionCode ? 1UL : 0UL;
|
||||||
var subtrahend = (ulong)right + borrow;
|
var subtrahend = (ulong)right + borrow;
|
||||||
result = unchecked(left - (uint)subtrahend);
|
result = unchecked(left - (uint)subtrahend);
|
||||||
scalarConditionCode = left >= subtrahend;
|
scalarConditionCode = subtrahend > left;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "SMinI32":
|
case "SMinI32":
|
||||||
result = unchecked((uint)Math.Min((int)left, (int)right));
|
result = unchecked((uint)Math.Min((int)left, (int)right));
|
||||||
|
scalarConditionCode = (int)left < (int)right;
|
||||||
break;
|
break;
|
||||||
case "SMinU32":
|
case "SMinU32":
|
||||||
result = Math.Min(left, right);
|
result = Math.Min(left, right);
|
||||||
|
scalarConditionCode = left < right;
|
||||||
break;
|
break;
|
||||||
case "SMaxI32":
|
case "SMaxI32":
|
||||||
result = unchecked((uint)Math.Max((int)left, (int)right));
|
result = unchecked((uint)Math.Max((int)left, (int)right));
|
||||||
|
scalarConditionCode = (int)left > (int)right;
|
||||||
break;
|
break;
|
||||||
case "SMaxU32":
|
case "SMaxU32":
|
||||||
result = Math.Max(left, right);
|
result = Math.Max(left, right);
|
||||||
|
scalarConditionCode = left > right;
|
||||||
break;
|
break;
|
||||||
case "SCselectB32":
|
case "SCselectB32":
|
||||||
result = scalarConditionCode ? left : right;
|
result = scalarConditionCode ? left : right;
|
||||||
@@ -780,6 +945,7 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
var offset = (int)right & 31;
|
var offset = (int)right & 31;
|
||||||
var width = Math.Min(((int)right >> 16) & 0x7F, 32 - offset);
|
var width = Math.Min(((int)right >> 16) & 0x7F, 32 - offset);
|
||||||
result = width == 0 ? 0 : left >> offset & (uint.MaxValue >> (32 - width));
|
result = width == 0 ? 0 : left >> offset & (uint.MaxValue >> (32 - width));
|
||||||
|
scalarConditionCode = result != 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "SBfeI32":
|
case "SBfeI32":
|
||||||
@@ -789,23 +955,41 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
result = width == 0
|
result = width == 0
|
||||||
? 0
|
? 0
|
||||||
: unchecked((uint)(((int)(left << (32 - width - offset))) >> (32 - width)));
|
: unchecked((uint)(((int)(left << (32 - width - offset))) >> (32 - width)));
|
||||||
|
scalarConditionCode = result != 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "SAbsdiffI32":
|
case "SAbsdiffI32":
|
||||||
result = unchecked((uint)Math.Abs((long)(int)left - (int)right));
|
result = unchecked((uint)Math.Abs((long)(int)left - (int)right));
|
||||||
|
scalarConditionCode = result != 0;
|
||||||
break;
|
break;
|
||||||
case "SLshl1AddU32":
|
case "SLshl1AddU32":
|
||||||
result = (left << 1) + right;
|
{
|
||||||
|
var wide = ((ulong)left << 1) + right;
|
||||||
|
result = (uint)wide;
|
||||||
|
scalarConditionCode = wide > uint.MaxValue;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case "SLshl2AddU32":
|
case "SLshl2AddU32":
|
||||||
result = (left << 2) + right;
|
{
|
||||||
|
var wide = ((ulong)left << 2) + right;
|
||||||
|
result = (uint)wide;
|
||||||
|
scalarConditionCode = wide > uint.MaxValue;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case "SLshl3AddU32":
|
case "SLshl3AddU32":
|
||||||
result = (left << 3) + right;
|
{
|
||||||
|
var wide = ((ulong)left << 3) + right;
|
||||||
|
result = (uint)wide;
|
||||||
|
scalarConditionCode = wide > uint.MaxValue;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case "SLshl4AddU32":
|
case "SLshl4AddU32":
|
||||||
result = (left << 4) + right;
|
{
|
||||||
|
var wide = ((ulong)left << 4) + right;
|
||||||
|
result = (uint)wide;
|
||||||
|
scalarConditionCode = wide > uint.MaxValue;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case "SPackLlB32B16":
|
case "SPackLlB32B16":
|
||||||
result = (left & 0xFFFFu) | (right << 16);
|
result = (left & 0xFFFFu) | (right << 16);
|
||||||
break;
|
break;
|
||||||
@@ -847,7 +1031,8 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
"SNandSaveexecB64" or
|
"SNandSaveexecB64" or
|
||||||
"SNorSaveexecB64" or
|
"SNorSaveexecB64" or
|
||||||
"SXnorSaveexecB64" or
|
"SXnorSaveexecB64" or
|
||||||
"SAndn1SaveexecB64"))
|
"SAndn1SaveexecB64" or
|
||||||
|
"SOrn1SaveexecB64"))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -875,18 +1060,19 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
"SAndSaveexecB64" => oldExec & source,
|
"SAndSaveexecB64" => oldExec & source,
|
||||||
"SOrSaveexecB64" => oldExec | source,
|
"SOrSaveexecB64" => oldExec | source,
|
||||||
"SXorSaveexecB64" => oldExec ^ source,
|
"SXorSaveexecB64" => oldExec ^ source,
|
||||||
"SAndn1SaveexecB64" => ~oldExec & source,
|
"SAndn1SaveexecB64" => ~source & oldExec,
|
||||||
"SAndn2SaveexecB64" => oldExec & ~source,
|
"SAndn2SaveexecB64" => source & ~oldExec,
|
||||||
"SOrn2SaveexecB64" => oldExec | ~source,
|
"SOrn1SaveexecB64" => ~source | oldExec,
|
||||||
"SNandSaveexecB64" => ~(oldExec & source),
|
"SOrn2SaveexecB64" => source | ~oldExec,
|
||||||
"SNorSaveexecB64" => ~(oldExec | source),
|
"SNandSaveexecB64" => ~(source & oldExec),
|
||||||
|
"SNorSaveexecB64" => ~(source | oldExec),
|
||||||
_ => ~(oldExec ^ source),
|
_ => ~(oldExec ^ source),
|
||||||
};
|
};
|
||||||
|
|
||||||
WriteScalarPair(registers, destination.Value, oldExec, ref execMask);
|
WriteScalarPair(registers, destination.Value, oldExec, ref execMask);
|
||||||
execMask = newExec;
|
execMask = MaskWaveValue(newExec);
|
||||||
WriteScalarPair(registers, 126, execMask, ref execMask);
|
WriteScalarPair(registers, 126, execMask, ref execMask);
|
||||||
scalarConditionCode = newExec != 0;
|
scalarConditionCode = execMask != 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -934,6 +1120,11 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (destination == 126)
|
||||||
|
{
|
||||||
|
value = MaskWaveValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
registers[destination] = (uint)value;
|
registers[destination] = (uint)value;
|
||||||
registers[destination + 1] = (uint)(value >> 32);
|
registers[destination + 1] = (uint)(value >> 32);
|
||||||
if (destination == 126)
|
if (destination == 126)
|
||||||
@@ -942,6 +1133,14 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ulong MaskWaveValue(ulong value) => value & RdnaWaveMask;
|
||||||
|
|
||||||
|
private static bool SignedAddOverflow(uint left, uint right, uint result) =>
|
||||||
|
((left ^ result) & (right ^ result) & 0x80000000u) != 0;
|
||||||
|
|
||||||
|
private static bool SignedSubOverflow(uint left, uint right, uint result) =>
|
||||||
|
((left ^ right) & (left ^ result) & 0x80000000u) != 0;
|
||||||
|
|
||||||
private static bool TryExecuteScalarCompare(
|
private static bool TryExecuteScalarCompare(
|
||||||
Gen5ShaderInstruction instruction,
|
Gen5ShaderInstruction instruction,
|
||||||
uint[] registers,
|
uint[] registers,
|
||||||
@@ -1263,7 +1462,7 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
word2 == 0 &&
|
word2 == 0 &&
|
||||||
word3 == 0)
|
word3 == 0)
|
||||||
{
|
{
|
||||||
descriptor = new BufferDescriptor(0, 0, 0, 0);
|
descriptor = new BufferDescriptor(0, 0, 0, 0, 0, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1275,19 +1474,64 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
descriptor = new BufferDescriptor(0, 0, 0, 0);
|
descriptor = new BufferDescriptor(0, 0, 0, 0, 0, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseAddress = word0 | ((ulong)(word1 & 0x0FFFu) << 32);
|
var baseAddress = word0 | ((ulong)(word1 & 0xFFFFu) << 32);
|
||||||
var stride = (word1 >> 16) & 0x3FFFu;
|
var stride = (word1 >> 16) & 0x3FFFu;
|
||||||
|
var unifiedFormat = (word3 >> 12) & 0x7Fu;
|
||||||
|
var (dataFormat, numberFormat) =
|
||||||
|
DecodeGfx10BufferFormat(unifiedFormat);
|
||||||
var sizeBytes = stride == 0
|
var sizeBytes = stride == 0
|
||||||
? word2
|
? word2
|
||||||
: (ulong)stride * word2;
|
: (ulong)stride * word2;
|
||||||
descriptor = new BufferDescriptor(baseAddress, stride, word2, sizeBytes);
|
descriptor = new BufferDescriptor(baseAddress, stride, word2, sizeBytes, numberFormat, dataFormat);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static (uint DataFormat, uint NumberFormat)
|
||||||
|
DecodeGfx10BufferFormat(uint format) =>
|
||||||
|
format switch
|
||||||
|
{
|
||||||
|
0 => (0, 0),
|
||||||
|
>= 1 and <= 6 => (1, format - 1),
|
||||||
|
>= 7 and <= 13 => (2, DecodeUnifiedNumber(format - 7, 7)),
|
||||||
|
>= 14 and <= 19 => (3, format - 14),
|
||||||
|
>= 20 and <= 22 => (4, DecodeIntegerOrFloatNumber(format - 20)),
|
||||||
|
>= 23 and <= 29 => (5, DecodeUnifiedNumber(format - 23, 7)),
|
||||||
|
>= 30 and <= 36 => (6, DecodeUnifiedNumber(format - 30, 7)),
|
||||||
|
>= 37 and <= 43 => (7, DecodeUnifiedNumber(format - 37, 7)),
|
||||||
|
>= 44 and <= 49 => (8, format - 44),
|
||||||
|
>= 50 and <= 55 => (9, format - 50),
|
||||||
|
>= 56 and <= 61 => (10, format - 56),
|
||||||
|
>= 62 and <= 64 => (11, DecodeIntegerOrFloatNumber(format - 62)),
|
||||||
|
>= 65 and <= 71 => (12, DecodeUnifiedNumber(format - 65, 7)),
|
||||||
|
>= 72 and <= 74 => (13, DecodeIntegerOrFloatNumber(format - 72)),
|
||||||
|
>= 75 and <= 77 => (14, DecodeIntegerOrFloatNumber(format - 75)),
|
||||||
|
128 => (1, 9),
|
||||||
|
129 => (3, 9),
|
||||||
|
130 => (10, 9),
|
||||||
|
132 => (34, 7),
|
||||||
|
133 => (16, 0),
|
||||||
|
134 => (17, 0),
|
||||||
|
135 => (18, 0),
|
||||||
|
136 => (19, 0),
|
||||||
|
140 => (4, 7),
|
||||||
|
_ => (0, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static uint DecodeUnifiedNumber(uint offset, uint formatCount) =>
|
||||||
|
offset == formatCount - 1 ? 7u : offset;
|
||||||
|
|
||||||
|
private static uint DecodeIntegerOrFloatNumber(uint offset) =>
|
||||||
|
offset switch
|
||||||
|
{
|
||||||
|
0 => 4,
|
||||||
|
1 => 5,
|
||||||
|
_ => 7,
|
||||||
|
};
|
||||||
|
|
||||||
private static bool TryReadUserDataScalarLoad(
|
private static bool TryReadUserDataScalarLoad(
|
||||||
Gen5ShaderState state,
|
Gen5ShaderState state,
|
||||||
Gen5ShaderInstruction instruction,
|
Gen5ShaderInstruction instruction,
|
||||||
|
|||||||
@@ -113,7 +113,8 @@ internal static class Gen5ShaderTranslator
|
|||||||
uint userDataBaseRegister,
|
uint userDataBaseRegister,
|
||||||
out Gen5ShaderState state,
|
out Gen5ShaderState state,
|
||||||
out string error,
|
out string error,
|
||||||
Gen5ComputeSystemRegisters? computeSystemRegisters = null)
|
Gen5ComputeSystemRegisters? computeSystemRegisters = null,
|
||||||
|
uint userDataScalarRegisterBase = 0)
|
||||||
{
|
{
|
||||||
state = default!;
|
state = default!;
|
||||||
if (!TryDecodeProgram(ctx, shaderAddress, out var program, out error))
|
if (!TryDecodeProgram(ctx, shaderAddress, out var program, out error))
|
||||||
@@ -134,7 +135,12 @@ internal static class Gen5ShaderTranslator
|
|||||||
shaderRegisters.TryGetValue(userDataBaseRegister + index, out userData[index]);
|
shaderRegisters.TryGetValue(userDataBaseRegister + index, out userData[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
state = new Gen5ShaderState(program, userData, metadata, computeSystemRegisters);
|
state = new Gen5ShaderState(
|
||||||
|
program,
|
||||||
|
userData,
|
||||||
|
metadata,
|
||||||
|
computeSystemRegisters,
|
||||||
|
userDataScalarRegisterBase);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +185,9 @@ internal static class Gen5ShaderTranslator
|
|||||||
: string.Empty;
|
: string.Empty;
|
||||||
if (state.Metadata is not { } metadata)
|
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(
|
var direct = string.Join(
|
||||||
@@ -191,7 +199,8 @@ internal static class Gen5ShaderTranslator
|
|||||||
$"{resource.Kind}[{resource.Slot}]@{resource.OffsetDwords}" +
|
$"{resource.Kind}[{resource.Slot}]@{resource.OffsetDwords}" +
|
||||||
(resource.SizeFlag ? "+" : string.Empty)));
|
(resource.SizeFlag ? "+" : string.Empty)));
|
||||||
return
|
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}]";
|
$"srt={metadata.ShaderResourceTableSizeDwords},direct={direct},resources={resources}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,6 +452,7 @@ internal static class Gen5ShaderTranslator
|
|||||||
0x2A => "SNorSaveexecB64",
|
0x2A => "SNorSaveexecB64",
|
||||||
0x2B => "SXnorSaveexecB64",
|
0x2B => "SXnorSaveexecB64",
|
||||||
0x37 => "SAndn1SaveexecB64",
|
0x37 => "SAndn1SaveexecB64",
|
||||||
|
0x38 => "SOrn1SaveexecB64",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -668,9 +678,8 @@ internal static class Gen5ShaderTranslator
|
|||||||
error = string.Empty;
|
error = string.Empty;
|
||||||
name = opcode switch
|
name = opcode switch
|
||||||
{
|
{
|
||||||
0x00 => "VCndmaskB32",
|
0x01 => "VCndmaskB32",
|
||||||
0x01 => "VReadlaneB32",
|
0x02 => "VDot2cF32F16",
|
||||||
0x02 => "VWritelaneB32",
|
|
||||||
0x03 => "VAddF32",
|
0x03 => "VAddF32",
|
||||||
0x04 => "VSubF32",
|
0x04 => "VSubF32",
|
||||||
0x05 => "VSubrevF32",
|
0x05 => "VSubrevF32",
|
||||||
@@ -810,7 +819,7 @@ internal static class Gen5ShaderTranslator
|
|||||||
}
|
}
|
||||||
: opcode switch
|
: opcode switch
|
||||||
{
|
{
|
||||||
0x101 => "VReadlaneB32",
|
0x101 => "VCndmaskB32",
|
||||||
0x103 => "VAddF32",
|
0x103 => "VAddF32",
|
||||||
0x104 => "VSubF32",
|
0x104 => "VSubF32",
|
||||||
0x108 => "VMulF32",
|
0x108 => "VMulF32",
|
||||||
@@ -1246,17 +1255,16 @@ internal static class Gen5ShaderTranslator
|
|||||||
var scalarOffset = (extra >> 25) & 0x7F;
|
var scalarOffset = (extra >> 25) & 0x7F;
|
||||||
var offset = SignExtend(extra & 0x1FFFFF, 21);
|
var offset = SignExtend(extra & 0x1FFFFF, 21);
|
||||||
var count = ScalarLoadDwordCount(opcode);
|
var count = ScalarLoadDwordCount(opcode);
|
||||||
var dynamicOffsetRegister = scalarOffset <= 105 || scalarOffset == 124
|
sources =
|
||||||
? scalarOffset
|
[
|
||||||
: (uint?)null;
|
Gen5Operand.Scalar(scalarBase),
|
||||||
sources = dynamicOffsetRegister.HasValue
|
Gen5Operand.Scalar(scalarOffset),
|
||||||
? [Gen5Operand.Scalar(scalarBase), Gen5Operand.Scalar(dynamicOffsetRegister.Value)]
|
];
|
||||||
: [Gen5Operand.Scalar(scalarBase)];
|
|
||||||
destinations = Enumerable
|
destinations = Enumerable
|
||||||
.Range((int)scalarDestination, checked((int)count))
|
.Range((int)scalarDestination, checked((int)count))
|
||||||
.Select(index => Gen5Operand.Scalar((uint)index))
|
.Select(index => Gen5Operand.Scalar((uint)index))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
control = new Gen5ScalarMemoryControl(count, offset, dynamicOffsetRegister);
|
control = new Gen5ScalarMemoryControl(count, offset, scalarOffset);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Gen5ShaderEncoding.Vop1:
|
case Gen5ShaderEncoding.Vop1:
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
break;
|
break;
|
||||||
case "VCndmaskB32":
|
case "VCndmaskB32":
|
||||||
{
|
{
|
||||||
var condition = Load(_boolType, _vcc);
|
var condition = instruction.Sources.Count > 2
|
||||||
|
? IsCurrentLaneSet(GetRawSource64(instruction, 2))
|
||||||
|
: Load(_boolType, _vcc);
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
SpirvOp.Select,
|
SpirvOp.Select,
|
||||||
_uintType,
|
_uintType,
|
||||||
@@ -131,9 +133,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
SpirvOp.ConvertFToU,
|
SpirvOp.ConvertFToU,
|
||||||
_uintType,
|
_uintType,
|
||||||
GetFloatSource(instruction, 0));
|
GetFloatSource(instruction, 0));
|
||||||
var offset = _module.AddInstruction(
|
var offset = ShiftLeftLogical(
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
BitwiseAnd(GetRawSource(instruction, 1), UInt(3)),
|
BitwiseAnd(GetRawSource(instruction, 1), UInt(3)),
|
||||||
UInt(3));
|
UInt(3));
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
@@ -212,12 +212,26 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
case "VSinF32":
|
case "VSinF32":
|
||||||
result = EmitFloatResult(
|
result = EmitFloatResult(
|
||||||
instruction,
|
instruction,
|
||||||
Ext(13, _floatType, GetFloatSource(instruction, 0)));
|
Ext(
|
||||||
|
13,
|
||||||
|
_floatType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.FMul,
|
||||||
|
_floatType,
|
||||||
|
GetFloatSource(instruction, 0),
|
||||||
|
Float(MathF.Tau))));
|
||||||
break;
|
break;
|
||||||
case "VCosF32":
|
case "VCosF32":
|
||||||
result = EmitFloatResult(
|
result = EmitFloatResult(
|
||||||
instruction,
|
instruction,
|
||||||
Ext(14, _floatType, GetFloatSource(instruction, 0)));
|
Ext(
|
||||||
|
14,
|
||||||
|
_floatType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.FMul,
|
||||||
|
_floatType,
|
||||||
|
GetFloatSource(instruction, 0),
|
||||||
|
Float(MathF.Tau))));
|
||||||
break;
|
break;
|
||||||
case "VAddF32":
|
case "VAddF32":
|
||||||
result = EmitFloatBinary(instruction, SpirvOp.FAdd);
|
result = EmitFloatBinary(instruction, SpirvOp.FAdd);
|
||||||
@@ -350,9 +364,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
SpirvOp.UConvert,
|
SpirvOp.UConvert,
|
||||||
_uintType,
|
_uintType,
|
||||||
_module.AddInstruction(
|
ShiftRightLogical64(
|
||||||
SpirvOp.ShiftRightLogical,
|
|
||||||
_ulongType,
|
|
||||||
product,
|
product,
|
||||||
_module.Constant64(_ulongType, 32)));
|
_module.Constant64(_ulongType, 32)));
|
||||||
break;
|
break;
|
||||||
@@ -415,21 +427,12 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var reverse = instruction.Opcode == "VAshrrevI32";
|
var reverse = instruction.Opcode == "VAshrrevI32";
|
||||||
var left = GetRawSource(instruction, reverse ? 1 : 0);
|
var left = GetRawSource(instruction, reverse ? 1 : 0);
|
||||||
var right = GetRawSource(instruction, reverse ? 0 : 1);
|
var right = GetRawSource(instruction, reverse ? 0 : 1);
|
||||||
right = BitwiseAnd(right, UInt(31));
|
result = ShiftRightArithmetic(left, right);
|
||||||
result = Bitcast(
|
|
||||||
_uintType,
|
|
||||||
_module.AddInstruction(
|
|
||||||
SpirvOp.ShiftRightArithmetic,
|
|
||||||
_intType,
|
|
||||||
Bitcast(_intType, left),
|
|
||||||
right));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "VLshlAddU32":
|
case "VLshlAddU32":
|
||||||
{
|
{
|
||||||
var shifted = _module.AddInstruction(
|
var shifted = ShiftLeftLogical(
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
GetRawSource(instruction, 0),
|
GetRawSource(instruction, 0),
|
||||||
BitwiseAnd(GetRawSource(instruction, 1), UInt(31)));
|
BitwiseAnd(GetRawSource(instruction, 1), UInt(31)));
|
||||||
result = IAdd(shifted, GetRawSource(instruction, 2));
|
result = IAdd(shifted, GetRawSource(instruction, 2));
|
||||||
@@ -437,9 +440,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
case "VLshlOrU32":
|
case "VLshlOrU32":
|
||||||
{
|
{
|
||||||
var shifted = _module.AddInstruction(
|
var shifted = ShiftLeftLogical(
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
GetRawSource(instruction, 0),
|
GetRawSource(instruction, 0),
|
||||||
BitwiseAnd(GetRawSource(instruction, 1), UInt(31)));
|
BitwiseAnd(GetRawSource(instruction, 1), UInt(31)));
|
||||||
result = BitwiseOr(
|
result = BitwiseOr(
|
||||||
@@ -472,11 +473,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var added = IAdd(
|
var added = IAdd(
|
||||||
GetRawSource(instruction, 0),
|
GetRawSource(instruction, 0),
|
||||||
GetRawSource(instruction, 1));
|
GetRawSource(instruction, 1));
|
||||||
result = _module.AddInstruction(
|
result = ShiftLeftLogical(added, GetRawSource(instruction, 2));
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
added,
|
|
||||||
BitwiseAnd(GetRawSource(instruction, 2), UInt(31)));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "VAdd3U32":
|
case "VAdd3U32":
|
||||||
@@ -652,13 +649,29 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
width);
|
width);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "VBfiB32":
|
||||||
|
{
|
||||||
|
var mask = GetRawSource(instruction, 0);
|
||||||
|
var insert = GetRawSource(instruction, 1);
|
||||||
|
var source = GetRawSource(instruction, 2);
|
||||||
|
result = _module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseOr,
|
||||||
|
_uintType,
|
||||||
|
BitwiseAnd(mask, insert),
|
||||||
|
BitwiseAnd(
|
||||||
|
_module.AddInstruction(SpirvOp.Not, _uintType, mask),
|
||||||
|
source));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "VCvtPkrtzF16F32":
|
case "VCvtPkrtzF16F32":
|
||||||
{
|
{
|
||||||
|
var first = TruncateFloat32ForPack(GetFloatSource(instruction, 0));
|
||||||
|
var second = TruncateFloat32ForPack(GetFloatSource(instruction, 1));
|
||||||
var vector = _module.AddInstruction(
|
var vector = _module.AddInstruction(
|
||||||
SpirvOp.CompositeConstruct,
|
SpirvOp.CompositeConstruct,
|
||||||
_vec2Type,
|
_vec2Type,
|
||||||
GetFloatSource(instruction, 0),
|
first,
|
||||||
GetFloatSource(instruction, 1));
|
second);
|
||||||
result = Ext(58, _uintType, vector);
|
result = Ext(58, _uintType, vector);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -857,7 +870,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
condition = _module.AddInstruction(operation, _boolType, left, right);
|
condition = _module.AddInstruction(operation, _boolType, left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
Store(_vcc, condition);
|
StoreWaveMask(106, condition);
|
||||||
if (opcode.StartsWith("VCmpx", StringComparison.Ordinal))
|
if (opcode.StartsWith("VCmpx", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
var active = _module.AddInstruction(
|
var active = _module.AddInstruction(
|
||||||
@@ -865,7 +878,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_boolType,
|
_boolType,
|
||||||
Load(_boolType, _exec),
|
Load(_boolType, _exec),
|
||||||
condition);
|
condition);
|
||||||
Store(_exec, active);
|
StoreWaveMask(126, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -930,7 +943,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (instruction.Opcode.EndsWith("B64", StringComparison.Ordinal) ||
|
if (instruction.Opcode.EndsWith("B64", StringComparison.Ordinal) ||
|
||||||
instruction.Opcode == "SWqmB64")
|
instruction.Opcode is "SWqmB64" or "SBfeU64" or "SBfeI64")
|
||||||
{
|
{
|
||||||
return TryEmitScalar64(instruction, destination, out error);
|
return TryEmitScalar64(instruction, destination, out error);
|
||||||
}
|
}
|
||||||
@@ -962,6 +975,16 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
StoreS(destination, result);
|
StoreS(destination, result);
|
||||||
Store(_scc, IsNotZero(result));
|
Store(_scc, IsNotZero(result));
|
||||||
return true;
|
return true;
|
||||||
|
case "SBitset1B32":
|
||||||
|
result = _module.AddInstruction(
|
||||||
|
SpirvOp.BitFieldInsert,
|
||||||
|
_uintType,
|
||||||
|
LoadS(destination),
|
||||||
|
UInt(1),
|
||||||
|
BitwiseAnd(left, UInt(31)),
|
||||||
|
UInt(1));
|
||||||
|
StoreS(destination, result);
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
if (instruction.Sources.Count < 2)
|
if (instruction.Sources.Count < 2)
|
||||||
@@ -988,13 +1011,14 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
left,
|
left,
|
||||||
right);
|
right);
|
||||||
Store(_scc, _module.AddInstruction(
|
Store(_scc, _module.AddInstruction(
|
||||||
SpirvOp.UGreaterThanEqual,
|
SpirvOp.UGreaterThan,
|
||||||
_boolType,
|
_boolType,
|
||||||
left,
|
right,
|
||||||
right));
|
left));
|
||||||
break;
|
break;
|
||||||
case "SAddI32":
|
case "SAddI32":
|
||||||
result = IAdd(left, right);
|
result = IAdd(left, right);
|
||||||
|
Store(_scc, SignedAddOverflow(left, right, result));
|
||||||
break;
|
break;
|
||||||
case "SSubI32":
|
case "SSubI32":
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
@@ -1002,6 +1026,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_uintType,
|
_uintType,
|
||||||
left,
|
left,
|
||||||
right);
|
right);
|
||||||
|
Store(_scc, SignedSubOverflow(left, right, result));
|
||||||
break;
|
break;
|
||||||
case "SAddcU32":
|
case "SAddcU32":
|
||||||
{
|
{
|
||||||
@@ -1038,8 +1063,8 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
SpirvOp.Select,
|
SpirvOp.Select,
|
||||||
_uintType,
|
_uintType,
|
||||||
Load(_boolType, _scc),
|
Load(_boolType, _scc),
|
||||||
UInt(0),
|
UInt(1),
|
||||||
UInt(1));
|
UInt(0));
|
||||||
var partial = _module.AddInstruction(
|
var partial = _module.AddInstruction(
|
||||||
SpirvOp.ISub,
|
SpirvOp.ISub,
|
||||||
_uintType,
|
_uintType,
|
||||||
@@ -1050,23 +1075,31 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_uintType,
|
_uintType,
|
||||||
partial,
|
partial,
|
||||||
borrow);
|
borrow);
|
||||||
var firstNoBorrow = _module.AddInstruction(
|
var firstBorrow = _module.AddInstruction(
|
||||||
SpirvOp.UGreaterThanEqual,
|
SpirvOp.UGreaterThan,
|
||||||
_boolType,
|
_boolType,
|
||||||
left,
|
right,
|
||||||
right);
|
left);
|
||||||
var secondNoBorrow = _module.AddInstruction(
|
var secondBorrow = _module.AddInstruction(
|
||||||
SpirvOp.UGreaterThanEqual,
|
SpirvOp.LogicalAnd,
|
||||||
_boolType,
|
_boolType,
|
||||||
partial,
|
_module.AddInstruction(
|
||||||
borrow);
|
SpirvOp.IEqual,
|
||||||
|
_boolType,
|
||||||
|
borrow,
|
||||||
|
UInt(1)),
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.IEqual,
|
||||||
|
_boolType,
|
||||||
|
right,
|
||||||
|
left));
|
||||||
Store(
|
Store(
|
||||||
_scc,
|
_scc,
|
||||||
_module.AddInstruction(
|
_module.AddInstruction(
|
||||||
SpirvOp.LogicalAnd,
|
SpirvOp.LogicalOr,
|
||||||
_boolType,
|
_boolType,
|
||||||
firstNoBorrow,
|
firstBorrow,
|
||||||
secondNoBorrow));
|
secondBorrow));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "SMulI32":
|
case "SMulI32":
|
||||||
@@ -1078,6 +1111,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
break;
|
break;
|
||||||
case "SAndB32":
|
case "SAndB32":
|
||||||
result = BitwiseAnd(left, right);
|
result = BitwiseAnd(left, right);
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
case "SOrB32":
|
case "SOrB32":
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
@@ -1085,6 +1119,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_uintType,
|
_uintType,
|
||||||
left,
|
left,
|
||||||
right);
|
right);
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
case "SXorB32":
|
case "SXorB32":
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
@@ -1092,32 +1127,64 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_uintType,
|
_uintType,
|
||||||
left,
|
left,
|
||||||
right);
|
right);
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
case "SAndn2B32":
|
case "SAndn2B32":
|
||||||
result = BitwiseAnd(
|
result = BitwiseAnd(
|
||||||
left,
|
left,
|
||||||
_module.AddInstruction(SpirvOp.Not, _uintType, right));
|
_module.AddInstruction(SpirvOp.Not, _uintType, right));
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
case "SLshlB32":
|
case "SOrn2B32":
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
SpirvOp.ShiftLeftLogical,
|
SpirvOp.BitwiseOr,
|
||||||
_uintType,
|
_uintType,
|
||||||
left,
|
left,
|
||||||
BitwiseAnd(right, UInt(31)));
|
_module.AddInstruction(SpirvOp.Not, _uintType, right));
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
|
break;
|
||||||
|
case "SNandB32":
|
||||||
|
result = _module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_uintType,
|
||||||
|
BitwiseAnd(left, right));
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
|
break;
|
||||||
|
case "SNorB32":
|
||||||
|
result = _module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_uintType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseOr,
|
||||||
|
_uintType,
|
||||||
|
left,
|
||||||
|
right));
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
|
break;
|
||||||
|
case "SXnorB32":
|
||||||
|
result = _module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_uintType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseXor,
|
||||||
|
_uintType,
|
||||||
|
left,
|
||||||
|
right));
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
|
break;
|
||||||
|
case "SLshlB32":
|
||||||
|
result = ShiftLeftLogical(left, right);
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
case "SLshrB32":
|
case "SLshrB32":
|
||||||
result = ShiftRightLogical(
|
result = ShiftRightLogical(
|
||||||
left,
|
left,
|
||||||
BitwiseAnd(right, UInt(31)));
|
BitwiseAnd(right, UInt(31)));
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
case "SAshrI32":
|
case "SAshrI32":
|
||||||
result = Bitcast(
|
result = ShiftRightArithmetic(left, right);
|
||||||
_uintType,
|
Store(_scc, IsNotZero(result));
|
||||||
_module.AddInstruction(
|
|
||||||
SpirvOp.ShiftRightArithmetic,
|
|
||||||
_intType,
|
|
||||||
Bitcast(_intType, left),
|
|
||||||
BitwiseAnd(right, UInt(31))));
|
|
||||||
break;
|
break;
|
||||||
case "SBfmB32":
|
case "SBfmB32":
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
@@ -1160,6 +1227,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
left,
|
left,
|
||||||
offset,
|
offset,
|
||||||
width);
|
width);
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "SCselectB32":
|
case "SCselectB32":
|
||||||
@@ -1172,9 +1240,47 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
break;
|
break;
|
||||||
case "SMinU32":
|
case "SMinU32":
|
||||||
result = Ext(38, _uintType, left, right);
|
result = Ext(38, _uintType, left, right);
|
||||||
|
Store(
|
||||||
|
_scc,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.ULessThan,
|
||||||
|
_boolType,
|
||||||
|
left,
|
||||||
|
right));
|
||||||
|
break;
|
||||||
|
case "SMinI32":
|
||||||
|
result = Bitcast(
|
||||||
|
_uintType,
|
||||||
|
Ext(39, _intType, Bitcast(_intType, left), Bitcast(_intType, right)));
|
||||||
|
Store(
|
||||||
|
_scc,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.SLessThan,
|
||||||
|
_boolType,
|
||||||
|
Bitcast(_intType, left),
|
||||||
|
Bitcast(_intType, right)));
|
||||||
break;
|
break;
|
||||||
case "SMaxU32":
|
case "SMaxU32":
|
||||||
result = Ext(41, _uintType, left, right);
|
result = Ext(41, _uintType, left, right);
|
||||||
|
Store(
|
||||||
|
_scc,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.UGreaterThan,
|
||||||
|
_boolType,
|
||||||
|
left,
|
||||||
|
right));
|
||||||
|
break;
|
||||||
|
case "SMaxI32":
|
||||||
|
result = Bitcast(
|
||||||
|
_uintType,
|
||||||
|
Ext(42, _intType, Bitcast(_intType, left), Bitcast(_intType, right)));
|
||||||
|
Store(
|
||||||
|
_scc,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.SGreaterThan,
|
||||||
|
_boolType,
|
||||||
|
Bitcast(_intType, left),
|
||||||
|
Bitcast(_intType, right)));
|
||||||
break;
|
break;
|
||||||
case "SLshl1AddU32":
|
case "SLshl1AddU32":
|
||||||
case "SLshl2AddU32":
|
case "SLshl2AddU32":
|
||||||
@@ -1183,11 +1289,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
{
|
{
|
||||||
var shift = (uint)(instruction.Opcode[5] - '0');
|
var shift = (uint)(instruction.Opcode[5] - '0');
|
||||||
result = IAdd(
|
result = IAdd(
|
||||||
_module.AddInstruction(
|
ShiftLeftLogical(left, UInt(shift)),
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
left,
|
|
||||||
UInt(shift)),
|
|
||||||
right);
|
right);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1312,7 +1414,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var left = GetRawSource64(instruction, 0);
|
var left = GetRawSource64(instruction, 0);
|
||||||
if (instruction.Opcode.EndsWith("SaveexecB64", StringComparison.Ordinal))
|
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 notLeft = _module.AddInstruction(SpirvOp.Not, _ulongType, left);
|
||||||
var newExec = instruction.Opcode switch
|
var newExec = instruction.Opcode switch
|
||||||
{
|
{
|
||||||
@@ -1323,17 +1425,55 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
"SXorSaveexecB64" => _module.AddInstruction(
|
"SXorSaveexecB64" => _module.AddInstruction(
|
||||||
SpirvOp.BitwiseXor, _ulongType, oldExec, left),
|
SpirvOp.BitwiseXor, _ulongType, oldExec, left),
|
||||||
"SAndn2SaveexecB64" => _module.AddInstruction(
|
"SAndn2SaveexecB64" => _module.AddInstruction(
|
||||||
SpirvOp.BitwiseAnd, _ulongType, oldExec, notLeft),
|
|
||||||
"SAndn1SaveexecB64" => _module.AddInstruction(
|
|
||||||
SpirvOp.BitwiseAnd,
|
SpirvOp.BitwiseAnd,
|
||||||
_ulongType,
|
_ulongType,
|
||||||
|
left,
|
||||||
_module.AddInstruction(
|
_module.AddInstruction(
|
||||||
SpirvOp.Not,
|
SpirvOp.Not,
|
||||||
_ulongType,
|
_ulongType,
|
||||||
|
oldExec)),
|
||||||
|
"SAndn1SaveexecB64" => _module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseAnd,
|
||||||
|
_ulongType,
|
||||||
|
notLeft,
|
||||||
|
oldExec),
|
||||||
|
"SOrn1SaveexecB64" => _module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseOr,
|
||||||
|
_ulongType,
|
||||||
|
notLeft,
|
||||||
oldExec),
|
oldExec),
|
||||||
left),
|
|
||||||
"SOrn2SaveexecB64" => _module.AddInstruction(
|
"SOrn2SaveexecB64" => _module.AddInstruction(
|
||||||
SpirvOp.BitwiseOr, _ulongType, oldExec, notLeft),
|
SpirvOp.BitwiseOr,
|
||||||
|
_ulongType,
|
||||||
|
left,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_ulongType,
|
||||||
|
oldExec)),
|
||||||
|
"SNandSaveexecB64" => _module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_ulongType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseAnd,
|
||||||
|
_ulongType,
|
||||||
|
left,
|
||||||
|
oldExec)),
|
||||||
|
"SNorSaveexecB64" => _module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_ulongType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseOr,
|
||||||
|
_ulongType,
|
||||||
|
left,
|
||||||
|
oldExec)),
|
||||||
|
"SXnorSaveexecB64" => _module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_ulongType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseXor,
|
||||||
|
_ulongType,
|
||||||
|
left,
|
||||||
|
oldExec)),
|
||||||
_ => 0u,
|
_ => 0u,
|
||||||
};
|
};
|
||||||
if (newExec == 0)
|
if (newExec == 0)
|
||||||
@@ -1345,7 +1485,6 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
|
|
||||||
StoreS64(destination, oldExec);
|
StoreS64(destination, oldExec);
|
||||||
StoreS64(126, newExec);
|
StoreS64(126, newExec);
|
||||||
Store(_exec, IsNotZero64(newExec));
|
|
||||||
Store(_scc, IsNotZero64(newExec));
|
Store(_scc, IsNotZero64(newExec));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1361,19 +1500,108 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var shift = _module.AddInstruction(
|
var shift = _module.AddInstruction(
|
||||||
SpirvOp.UConvert,
|
SpirvOp.UConvert,
|
||||||
_ulongType,
|
_ulongType,
|
||||||
BitwiseAnd(GetRawSource(instruction, 1), UInt(63)));
|
GetRawSource(instruction, 1));
|
||||||
var shiftedValue = _module.AddInstruction(
|
var shiftedValue = instruction.Opcode == "SLshlB64"
|
||||||
instruction.Opcode == "SLshlB64"
|
? ShiftLeftLogical64(left, shift)
|
||||||
? SpirvOp.ShiftLeftLogical
|
: ShiftRightLogical64(left, shift);
|
||||||
: SpirvOp.ShiftRightLogical,
|
|
||||||
_ulongType,
|
|
||||||
left,
|
|
||||||
shift);
|
|
||||||
StoreS64(destination, shiftedValue);
|
StoreS64(destination, shiftedValue);
|
||||||
Store(_scc, IsNotZero64(shiftedValue));
|
Store(_scc, IsNotZero64(shiftedValue));
|
||||||
return true;
|
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;
|
uint value;
|
||||||
if (instruction.Opcode is "SMovB64" or "SWqmB64")
|
if (instruction.Opcode is "SMovB64" or "SWqmB64")
|
||||||
{
|
{
|
||||||
@@ -1451,9 +1679,20 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
StoreS64(destination, value);
|
StoreS64(destination, value);
|
||||||
if (destination == 126)
|
if (instruction.Opcode is
|
||||||
|
"SNotB64" or
|
||||||
|
"SAndB64" or
|
||||||
|
"SOrB64" or
|
||||||
|
"SXorB64" or
|
||||||
|
"SAndn1B64" or
|
||||||
|
"SAndn2B64" or
|
||||||
|
"SOrn1B64" or
|
||||||
|
"SOrn2B64" or
|
||||||
|
"SNandB64" or
|
||||||
|
"SNorB64" or
|
||||||
|
"SXnorB64")
|
||||||
{
|
{
|
||||||
Store(_exec, IsNotZero64(value));
|
Store(_scc, IsNotZero64(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -1472,14 +1711,6 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
uint value = operand.Kind switch
|
uint value = operand.Kind switch
|
||||||
{
|
{
|
||||||
Gen5OperandKind.VectorRegister => LoadV(operand.Value),
|
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.ScalarRegister => LoadS(operand.Value),
|
||||||
Gen5OperandKind.LiteralConstant => UInt(operand.Value),
|
Gen5OperandKind.LiteralConstant => UInt(operand.Value),
|
||||||
Gen5OperandKind.EncodedConstant when TryDecodeInlineConstant(
|
Gen5OperandKind.EncodedConstant when TryDecodeInlineConstant(
|
||||||
@@ -1584,11 +1815,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
SpirvOp.UConvert,
|
SpirvOp.UConvert,
|
||||||
_ulongType,
|
_ulongType,
|
||||||
LoadS(register + 1));
|
LoadS(register + 1));
|
||||||
high = _module.AddInstruction(
|
high = ShiftLeftLogical64(high, _module.Constant64(_ulongType, 32));
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_ulongType,
|
|
||||||
high,
|
|
||||||
_module.Constant64(_ulongType, 32));
|
|
||||||
return _module.AddInstruction(SpirvOp.BitwiseOr, _ulongType, low, high);
|
return _module.AddInstruction(SpirvOp.BitwiseOr, _ulongType, low, high);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1597,9 +1824,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
StoreS(
|
StoreS(
|
||||||
register,
|
register,
|
||||||
_module.AddInstruction(SpirvOp.UConvert, _uintType, value));
|
_module.AddInstruction(SpirvOp.UConvert, _uintType, value));
|
||||||
var high = _module.AddInstruction(
|
var high = ShiftRightLogical64(
|
||||||
SpirvOp.ShiftRightLogical,
|
|
||||||
_ulongType,
|
|
||||||
value,
|
value,
|
||||||
_module.Constant64(_ulongType, 32));
|
_module.Constant64(_ulongType, 32));
|
||||||
StoreS(
|
StoreS(
|
||||||
@@ -1651,12 +1876,19 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
{
|
{
|
||||||
var left = GetRawSource(instruction, reverse ? 1 : 0);
|
var left = GetRawSource(instruction, reverse ? 1 : 0);
|
||||||
var right = GetRawSource(instruction, reverse ? 0 : 1);
|
var right = GetRawSource(instruction, reverse ? 0 : 1);
|
||||||
if (operation is
|
if (operation == SpirvOp.ShiftLeftLogical)
|
||||||
SpirvOp.ShiftLeftLogical or
|
|
||||||
SpirvOp.ShiftRightLogical or
|
|
||||||
SpirvOp.ShiftRightArithmetic)
|
|
||||||
{
|
{
|
||||||
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);
|
return _module.AddInstruction(operation, _uintType, left, right);
|
||||||
@@ -1881,7 +2113,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_boolType,
|
_boolType,
|
||||||
_module.AddInstruction(SpirvOp.ULessThan, _boolType, partial, left),
|
_module.AddInstruction(SpirvOp.ULessThan, _boolType, partial, left),
|
||||||
_module.AddInstruction(SpirvOp.ULessThan, _boolType, result, partial));
|
_module.AddInstruction(SpirvOp.ULessThan, _boolType, result, partial));
|
||||||
Store(_vcc, carry);
|
StoreWaveMask(106, carry);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1912,7 +2144,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_boolType,
|
_boolType,
|
||||||
partial,
|
partial,
|
||||||
borrowIn));
|
borrowIn));
|
||||||
Store(_vcc, borrow);
|
StoreWaveMask(106, borrow);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1932,13 +2164,13 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
UInt(0)));
|
UInt(0)));
|
||||||
if (register == 106)
|
if (register == 106)
|
||||||
{
|
{
|
||||||
Store(_vcc, carry);
|
StoreWaveMask(106, carry);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Store(_vcc, carry);
|
StoreWaveMask(106, carry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint EmitPermlane16(
|
private uint EmitPermlane16(
|
||||||
@@ -1955,21 +2187,13 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_boolType,
|
_boolType,
|
||||||
localLane,
|
localLane,
|
||||||
UInt(8));
|
UInt(8));
|
||||||
var lowShift = _module.AddInstruction(
|
var lowShift = ShiftLeftLogical(localLane, UInt(2));
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
localLane,
|
|
||||||
UInt(2));
|
|
||||||
var highLane = _module.AddInstruction(
|
var highLane = _module.AddInstruction(
|
||||||
SpirvOp.ISub,
|
SpirvOp.ISub,
|
||||||
_uintType,
|
_uintType,
|
||||||
localLane,
|
localLane,
|
||||||
UInt(8));
|
UInt(8));
|
||||||
var highShift = _module.AddInstruction(
|
var highShift = ShiftLeftLogical(highLane, UInt(2));
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
highLane,
|
|
||||||
UInt(2));
|
|
||||||
var lowSelector = BitwiseAnd(
|
var lowSelector = BitwiseAnd(
|
||||||
ShiftRightLogical(selectorLow, lowShift),
|
ShiftRightLogical(selectorLow, lowShift),
|
||||||
UInt(15));
|
UInt(15));
|
||||||
@@ -2030,6 +2254,14 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
return Bitcast(_uintType, value);
|
return Bitcast(_uintType, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private uint TruncateFloat32ForPack(uint value)
|
||||||
|
{
|
||||||
|
var raw = BitwiseAnd(
|
||||||
|
Bitcast(_uintType, value),
|
||||||
|
UInt(0xFFFF_E000));
|
||||||
|
return Bitcast(_floatType, raw);
|
||||||
|
}
|
||||||
|
|
||||||
private uint Ext(uint operation, uint resultType, params uint[] operands)
|
private uint Ext(uint operation, uint resultType, params uint[] operands)
|
||||||
{
|
{
|
||||||
var values = new uint[2 + operands.Length];
|
var values = new uint[2 + operands.Length];
|
||||||
@@ -2049,6 +2281,53 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
value,
|
value,
|
||||||
_module.Constant64(_ulongType, 0));
|
_module.Constant64(_ulongType, 0));
|
||||||
|
|
||||||
|
private uint SignBit(uint value) =>
|
||||||
|
ShiftRightLogical(value, UInt(31));
|
||||||
|
|
||||||
|
private uint SignedAddOverflow(uint left, uint right, uint result)
|
||||||
|
{
|
||||||
|
var leftSign = SignBit(left);
|
||||||
|
var rightSign = SignBit(right);
|
||||||
|
var resultSign = SignBit(result);
|
||||||
|
var sameSourceSign = _module.AddInstruction(
|
||||||
|
SpirvOp.IEqual,
|
||||||
|
_boolType,
|
||||||
|
leftSign,
|
||||||
|
rightSign);
|
||||||
|
var resultSignChanged = _module.AddInstruction(
|
||||||
|
SpirvOp.INotEqual,
|
||||||
|
_boolType,
|
||||||
|
leftSign,
|
||||||
|
resultSign);
|
||||||
|
return _module.AddInstruction(
|
||||||
|
SpirvOp.LogicalAnd,
|
||||||
|
_boolType,
|
||||||
|
sameSourceSign,
|
||||||
|
resultSignChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint SignedSubOverflow(uint left, uint right, uint result)
|
||||||
|
{
|
||||||
|
var leftSign = SignBit(left);
|
||||||
|
var rightSign = SignBit(right);
|
||||||
|
var resultSign = SignBit(result);
|
||||||
|
var differentSourceSign = _module.AddInstruction(
|
||||||
|
SpirvOp.INotEqual,
|
||||||
|
_boolType,
|
||||||
|
leftSign,
|
||||||
|
rightSign);
|
||||||
|
var resultSignChanged = _module.AddInstruction(
|
||||||
|
SpirvOp.INotEqual,
|
||||||
|
_boolType,
|
||||||
|
leftSign,
|
||||||
|
resultSign);
|
||||||
|
return _module.AddInstruction(
|
||||||
|
SpirvOp.LogicalAnd,
|
||||||
|
_boolType,
|
||||||
|
differentSourceSign,
|
||||||
|
resultSignChanged);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryDecodeInlineConstant(uint encoded, out uint value)
|
private static bool TryDecodeInlineConstant(uint encoded, out uint value)
|
||||||
{
|
{
|
||||||
if (encoded == 125)
|
if (encoded == 125)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
private const uint ScalarRegisterCount = 256;
|
private const uint ScalarRegisterCount = 256;
|
||||||
private const uint VectorRegisterCount = 512;
|
private const uint VectorRegisterCount = 512;
|
||||||
private const uint LdsDwordCount = 8192;
|
private const uint LdsDwordCount = 8192;
|
||||||
|
private const uint RdnaWaveLaneCount = 32;
|
||||||
|
|
||||||
public static bool TryCompilePixelShader(
|
public static bool TryCompilePixelShader(
|
||||||
Gen5ShaderState state,
|
Gen5ShaderState state,
|
||||||
@@ -95,6 +96,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
private readonly List<uint> _interfaces = [];
|
private readonly List<uint> _interfaces = [];
|
||||||
private readonly Dictionary<uint, uint> _pixelInputs = [];
|
private readonly Dictionary<uint, uint> _pixelInputs = [];
|
||||||
private readonly Dictionary<uint, uint> _vertexOutputs = [];
|
private readonly Dictionary<uint, uint> _vertexOutputs = [];
|
||||||
|
private readonly Dictionary<uint, SpirvVertexInput> _vertexInputsByPc = [];
|
||||||
private readonly List<SpirvImageResource> _imageResources = [];
|
private readonly List<SpirvImageResource> _imageResources = [];
|
||||||
private readonly Dictionary<uint, int> _imageBindingByPc = [];
|
private readonly Dictionary<uint, int> _imageBindingByPc = [];
|
||||||
private readonly Dictionary<uint, int> _bufferBindingByPc = [];
|
private readonly Dictionary<uint, int> _bufferBindingByPc = [];
|
||||||
@@ -102,6 +104,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
private uint _boolType;
|
private uint _boolType;
|
||||||
private uint _uintType;
|
private uint _uintType;
|
||||||
private uint _intType;
|
private uint _intType;
|
||||||
|
private uint _longType;
|
||||||
private uint _ulongType;
|
private uint _ulongType;
|
||||||
private uint _floatType;
|
private uint _floatType;
|
||||||
private uint _vec2Type;
|
private uint _vec2Type;
|
||||||
@@ -149,6 +152,11 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
ImageComponentKind ComponentKind,
|
ImageComponentKind ComponentKind,
|
||||||
bool IsStorage);
|
bool IsStorage);
|
||||||
|
|
||||||
|
private readonly record struct SpirvVertexInput(
|
||||||
|
uint Variable,
|
||||||
|
uint Type,
|
||||||
|
uint ComponentCount);
|
||||||
|
|
||||||
public CompilationContext(
|
public CompilationContext(
|
||||||
Gen5SpirvStage stage,
|
Gen5SpirvStage stage,
|
||||||
Gen5ShaderState state,
|
Gen5ShaderState state,
|
||||||
@@ -282,7 +290,10 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_module.Build(),
|
_module.Build(),
|
||||||
_evaluation.GlobalMemoryBindings,
|
_evaluation.GlobalMemoryBindings,
|
||||||
_evaluation.ImageBindings,
|
_evaluation.ImageBindings,
|
||||||
attributeCount);
|
attributeCount,
|
||||||
|
_stage == Gen5SpirvStage.Vertex
|
||||||
|
? _evaluation.VertexInputs ?? []
|
||||||
|
: []);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
@@ -297,17 +308,39 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_module.AddCapability(SpirvCapability.Shader);
|
_module.AddCapability(SpirvCapability.Shader);
|
||||||
_module.AddCapability(SpirvCapability.Int64);
|
_module.AddCapability(SpirvCapability.Int64);
|
||||||
_module.AddCapability(SpirvCapability.ImageQuery);
|
_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.GroupNonUniform);
|
||||||
|
if (UsesSubgroupShuffle())
|
||||||
|
{
|
||||||
_module.AddCapability(SpirvCapability.GroupNonUniformShuffle);
|
_module.AddCapability(SpirvCapability.GroupNonUniformShuffle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (UsesWaveControl())
|
||||||
|
{
|
||||||
|
_module.AddCapability(SpirvCapability.GroupNonUniformVote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_glsl = _module.ImportExtInst("GLSL.std.450");
|
_glsl = _module.ImportExtInst("GLSL.std.450");
|
||||||
_voidType = _module.TypeVoid();
|
_voidType = _module.TypeVoid();
|
||||||
_boolType = _module.TypeBool();
|
_boolType = _module.TypeBool();
|
||||||
_uintType = _module.TypeInt(32, signed: false);
|
_uintType = _module.TypeInt(32, signed: false);
|
||||||
_intType = _module.TypeInt(32, signed: true);
|
_intType = _module.TypeInt(32, signed: true);
|
||||||
|
_longType = _module.TypeInt(64, signed: true);
|
||||||
_ulongType = _module.TypeInt(64, signed: false);
|
_ulongType = _module.TypeInt(64, signed: false);
|
||||||
_floatType = _module.TypeFloat(32);
|
_floatType = _module.TypeFloat(32);
|
||||||
_vec2Type = _module.TypeVector(_floatType, 2);
|
_vec2Type = _module.TypeVector(_floatType, 2);
|
||||||
@@ -379,10 +412,6 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ldsArrayType = _module.TypeArray(_uintType, LdsDwordCount);
|
var ldsArrayType = _module.TypeArray(_uintType, LdsDwordCount);
|
||||||
_module.AddDecoration(
|
|
||||||
ldsArrayType,
|
|
||||||
SpirvDecoration.ArrayStride,
|
|
||||||
sizeof(uint));
|
|
||||||
var ldsPointer =
|
var ldsPointer =
|
||||||
_module.TypePointer(SpirvStorageClass.Workgroup, ldsArrayType);
|
_module.TypePointer(SpirvStorageClass.Workgroup, ldsArrayType);
|
||||||
_workgroupUintPointer =
|
_workgroupUintPointer =
|
||||||
@@ -456,6 +485,11 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_module.AddCapability(
|
_module.AddCapability(
|
||||||
SpirvCapability.StorageImageWriteWithoutFormat);
|
SpirvCapability.StorageImageWriteWithoutFormat);
|
||||||
}
|
}
|
||||||
|
else if (isStorage && RequiresExtendedStorageImageFormat(format))
|
||||||
|
{
|
||||||
|
_module.AddCapability(
|
||||||
|
SpirvCapability.StorageImageExtendedFormats);
|
||||||
|
}
|
||||||
|
|
||||||
var imageType = _module.TypeImage(
|
var imageType = _module.TypeImage(
|
||||||
componentType,
|
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)
|
private static (SpirvImageFormat Format, ImageComponentKind Kind)
|
||||||
DecodeImageFormat(IReadOnlyList<uint> descriptor)
|
DecodeImageFormat(IReadOnlyList<uint> descriptor)
|
||||||
{
|
{
|
||||||
@@ -557,7 +598,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
|
|
||||||
private void DeclareStageInterface()
|
private void DeclareStageInterface()
|
||||||
{
|
{
|
||||||
if (UsesSubgroupShuffle())
|
if (UsesSubgroupOperations())
|
||||||
{
|
{
|
||||||
var subgroupPointer =
|
var subgroupPointer =
|
||||||
_module.TypePointer(SpirvStorageClass.Input, _uintType);
|
_module.TypePointer(SpirvStorageClass.Input, _uintType);
|
||||||
@@ -573,6 +614,8 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
|
|
||||||
if (_stage == Gen5SpirvStage.Vertex)
|
if (_stage == Gen5SpirvStage.Vertex)
|
||||||
{
|
{
|
||||||
|
DeclareVertexInputs();
|
||||||
|
|
||||||
var inputPointer =
|
var inputPointer =
|
||||||
_module.TypePointer(SpirvStorageClass.Input, _uintType);
|
_module.TypePointer(SpirvStorageClass.Input, _uintType);
|
||||||
_vertexIndexInput = _module.AddGlobalVariable(
|
_vertexIndexInput = _module.AddGlobalVariable(
|
||||||
@@ -618,7 +661,6 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
outputPointer,
|
outputPointer,
|
||||||
SpirvStorageClass.Output);
|
SpirvStorageClass.Output);
|
||||||
_module.AddDecoration(variable, SpirvDecoration.Location, parameter);
|
_module.AddDecoration(variable, SpirvDecoration.Location, parameter);
|
||||||
_module.AddDecoration(variable, SpirvDecoration.NoPerspective);
|
|
||||||
_vertexOutputs.Add(parameter, variable);
|
_vertexOutputs.Add(parameter, variable);
|
||||||
_interfaces.Add(variable);
|
_interfaces.Add(variable);
|
||||||
}
|
}
|
||||||
@@ -640,7 +682,6 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
inputVec4Pointer,
|
inputVec4Pointer,
|
||||||
SpirvStorageClass.Input);
|
SpirvStorageClass.Input);
|
||||||
_module.AddDecoration(variable, SpirvDecoration.Location, attribute);
|
_module.AddDecoration(variable, SpirvDecoration.Location, attribute);
|
||||||
_module.AddDecoration(variable, SpirvDecoration.NoPerspective);
|
|
||||||
_pixelInputs.Add(attribute, variable);
|
_pixelInputs.Add(attribute, variable);
|
||||||
_interfaces.Add(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()
|
private void EmitInitialState()
|
||||||
{
|
{
|
||||||
for (uint index = 0;
|
for (uint index = 0;
|
||||||
@@ -706,8 +783,16 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
Store(_scc, _module.ConstantBool(false));
|
Store(_scc, _module.ConstantBool(false));
|
||||||
|
if (_subgroupInvocationIdInput != 0)
|
||||||
|
{
|
||||||
|
StoreWaveMask(106, _module.ConstantBool(false));
|
||||||
|
StoreWaveMask(126, _module.ConstantBool(true));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
Store(_vcc, _module.ConstantBool(false));
|
Store(_vcc, _module.ConstantBool(false));
|
||||||
Store(_exec, _module.ConstantBool(true));
|
Store(_exec, _module.ConstantBool(true));
|
||||||
|
}
|
||||||
Store(_programCounter, UInt(0));
|
Store(_programCounter, UInt(0));
|
||||||
Store(_programActive, _module.ConstantBool(true));
|
Store(_programActive, _module.ConstantBool(true));
|
||||||
|
|
||||||
@@ -923,10 +1008,10 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
{
|
{
|
||||||
"SCbranchScc0" => LogicalNot(Load(_boolType, _scc)),
|
"SCbranchScc0" => LogicalNot(Load(_boolType, _scc)),
|
||||||
"SCbranchScc1" => Load(_boolType, _scc),
|
"SCbranchScc1" => Load(_boolType, _scc),
|
||||||
"SCbranchVccz" => LogicalNot(Load(_boolType, _vcc)),
|
"SCbranchVccz" => LogicalNot(SubgroupAny(Load(_boolType, _vcc))),
|
||||||
"SCbranchVccnz" => Load(_boolType, _vcc),
|
"SCbranchVccnz" => SubgroupAny(Load(_boolType, _vcc)),
|
||||||
"SCbranchExecz" => LogicalNot(Load(_boolType, _exec)),
|
"SCbranchExecz" => LogicalNot(SubgroupAny(Load(_boolType, _exec))),
|
||||||
"SCbranchExecnz" => Load(_boolType, _exec),
|
"SCbranchExecnz" => SubgroupAny(Load(_boolType, _exec)),
|
||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
return condition != 0;
|
return condition != 0;
|
||||||
@@ -1269,6 +1354,19 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
out string error)
|
out string error)
|
||||||
{
|
{
|
||||||
error = string.Empty;
|
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))
|
if (!_bufferBindingByPc.TryGetValue(instruction.Pc, out var bindingIndex))
|
||||||
{
|
{
|
||||||
error = "missing buffer-memory binding";
|
error = "missing buffer-memory binding";
|
||||||
@@ -1296,6 +1394,8 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var dwordAddress = ShiftRightLogical(byteAddress, UInt(2));
|
var dwordAddress = ShiftRightLogical(byteAddress, UInt(2));
|
||||||
|
|
||||||
if (instruction.Opcode == "BufferAtomicAdd")
|
if (instruction.Opcode == "BufferAtomicAdd")
|
||||||
|
{
|
||||||
|
EmitExecConditional(() =>
|
||||||
{
|
{
|
||||||
var original = _module.AddInstruction(
|
var original = _module.AddInstruction(
|
||||||
SpirvOp.AtomicIAdd,
|
SpirvOp.AtomicIAdd,
|
||||||
@@ -1308,19 +1408,26 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
{
|
{
|
||||||
StoreV(control.VectorData, original);
|
StoreV(control.VectorData, original);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instruction.Opcode.StartsWith("BufferStoreDword", StringComparison.Ordinal))
|
if (instruction.Opcode.StartsWith("BufferStoreDword", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
EmitExecConditional(() =>
|
||||||
{
|
{
|
||||||
for (uint index = 0; index < control.DwordCount; index++)
|
for (uint index = 0; index < control.DwordCount; index++)
|
||||||
{
|
{
|
||||||
var address = index == 0
|
var address = index == 0
|
||||||
? dwordAddress
|
? dwordAddress
|
||||||
: IAdd(dwordAddress, UInt(index));
|
: IAdd(dwordAddress, UInt(index));
|
||||||
StoreBufferWord(bindingIndex, address, LoadV(control.VectorData + index));
|
StoreBufferWord(
|
||||||
|
bindingIndex,
|
||||||
|
address,
|
||||||
|
LoadV(control.VectorData + index));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1345,6 +1452,41 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
return true;
|
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(
|
private bool TryEmitImage(
|
||||||
Gen5ShaderInstruction instruction,
|
Gen5ShaderInstruction instruction,
|
||||||
Gen5ImageControl image,
|
Gen5ImageControl image,
|
||||||
@@ -1362,11 +1504,20 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var imageObject = Load(resource.ObjectType, resource.Variable);
|
var imageObject = Load(resource.ObjectType, resource.Variable);
|
||||||
if (instruction.Opcode == "ImageGetResinfo")
|
if (instruction.Opcode == "ImageGetResinfo")
|
||||||
{
|
{
|
||||||
|
var queryImage = resource.IsStorage
|
||||||
|
? imageObject
|
||||||
|
: _module.AddInstruction(
|
||||||
|
SpirvOp.Image,
|
||||||
|
resource.ImageType,
|
||||||
|
imageObject);
|
||||||
var size = _module.AddInstruction(
|
var size = _module.AddInstruction(
|
||||||
SpirvOp.ImageQuerySizeLod,
|
resource.IsStorage
|
||||||
|
? SpirvOp.ImageQuerySize
|
||||||
|
: SpirvOp.ImageQuerySizeLod,
|
||||||
_module.TypeVector(_intType, 2),
|
_module.TypeVector(_intType, 2),
|
||||||
imageObject,
|
resource.IsStorage
|
||||||
UInt(0));
|
? [queryImage]
|
||||||
|
: [queryImage, UInt(0)]);
|
||||||
uint outputIndex = 0;
|
uint outputIndex = 0;
|
||||||
for (uint component = 0; component < 4; component++)
|
for (uint component = 0; component < 4; component++)
|
||||||
{
|
{
|
||||||
@@ -1449,11 +1600,12 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_module.AddStatement(
|
EmitExecConditional(
|
||||||
|
() => _module.AddStatement(
|
||||||
SpirvOp.ImageWrite,
|
SpirvOp.ImageWrite,
|
||||||
imageObject,
|
imageObject,
|
||||||
coordinates,
|
coordinates,
|
||||||
texel);
|
texel));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -1476,10 +1628,14 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
? BuildClampedIntegerCoordinates(image, 0, width, height)
|
? BuildClampedIntegerCoordinates(image, 0, width, height)
|
||||||
: BuildIntegerCoordinates(image, 0);
|
: BuildIntegerCoordinates(image, 0);
|
||||||
var mipLevel = _evaluation.ImageBindings[bindingIndex].MipLevel ?? 0;
|
var mipLevel = _evaluation.ImageBindings[bindingIndex].MipLevel ?? 0;
|
||||||
|
var fetchedImage = _module.AddInstruction(
|
||||||
|
SpirvOp.Image,
|
||||||
|
resource.ImageType,
|
||||||
|
imageObject);
|
||||||
sampled = _module.AddInstruction(
|
sampled = _module.AddInstruction(
|
||||||
SpirvOp.ImageFetch,
|
SpirvOp.ImageFetch,
|
||||||
resource.VectorType,
|
resource.VectorType,
|
||||||
imageObject,
|
fetchedImage,
|
||||||
coordinates,
|
coordinates,
|
||||||
2,
|
2,
|
||||||
UInt(mipLevel));
|
UInt(mipLevel));
|
||||||
@@ -1774,6 +1930,11 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_boolType,
|
_boolType,
|
||||||
lowerInRange,
|
lowerInRange,
|
||||||
upperInRange);
|
upperInRange);
|
||||||
|
inRange = _module.AddInstruction(
|
||||||
|
SpirvOp.LogicalAnd,
|
||||||
|
_boolType,
|
||||||
|
Load(_boolType, _exec),
|
||||||
|
inRange);
|
||||||
var writeLabel = _module.AllocateId();
|
var writeLabel = _module.AllocateId();
|
||||||
var mergeLabel = _module.AllocateId();
|
var mergeLabel = _module.AllocateId();
|
||||||
_module.AddStatement(SpirvOp.SelectionMerge, mergeLabel, 0);
|
_module.AddStatement(SpirvOp.SelectionMerge, mergeLabel, 0);
|
||||||
@@ -1900,6 +2061,12 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
SpirvOp.CompositeConstruct,
|
SpirvOp.CompositeConstruct,
|
||||||
outputType,
|
outputType,
|
||||||
values);
|
values);
|
||||||
|
vector = _module.AddInstruction(
|
||||||
|
SpirvOp.Select,
|
||||||
|
outputType,
|
||||||
|
Load(_boolType, _exec),
|
||||||
|
vector,
|
||||||
|
Load(outputType, _pixelOutput));
|
||||||
Store(_pixelOutput, vector);
|
Store(_pixelOutput, vector);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1945,6 +2112,12 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
SpirvOp.CompositeConstruct,
|
SpirvOp.CompositeConstruct,
|
||||||
_vec4Type,
|
_vec4Type,
|
||||||
components);
|
components);
|
||||||
|
outputValue = _module.AddInstruction(
|
||||||
|
SpirvOp.Select,
|
||||||
|
_vec4Type,
|
||||||
|
Load(_boolType, _exec),
|
||||||
|
outputValue,
|
||||||
|
Load(_vec4Type, outputVariable));
|
||||||
Store(outputVariable, outputValue);
|
Store(outputVariable, outputValue);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2009,7 +2182,18 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
|
|
||||||
private uint LoadV(uint register) => Load(_uintType, VectorPointer(register));
|
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)
|
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) =>
|
private uint IAdd(uint left, uint right) =>
|
||||||
_module.AddInstruction(SpirvOp.IAdd, _uintType, left, 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) =>
|
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) =>
|
private uint BitwiseAnd(uint left, uint right) =>
|
||||||
_module.AddInstruction(SpirvOp.BitwiseAnd, _uintType, left, 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) =>
|
private uint BitwiseOr(uint left, uint right) =>
|
||||||
_module.AddInstruction(SpirvOp.BitwiseOr, _uintType, left, right);
|
_module.AddInstruction(SpirvOp.BitwiseOr, _uintType, left, right);
|
||||||
|
|
||||||
@@ -2059,6 +2280,86 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
private uint LogicalNot(uint value) =>
|
private uint LogicalNot(uint value) =>
|
||||||
_module.AddInstruction(SpirvOp.LogicalNot, _boolType, 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() =>
|
private bool UsesLds() =>
|
||||||
_state.Program.Instructions.Any(instruction =>
|
_state.Program.Instructions.Any(instruction =>
|
||||||
instruction.Control is Gen5DataShareControl);
|
instruction.Control is Gen5DataShareControl);
|
||||||
@@ -2067,6 +2368,23 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_state.Program.Instructions.Any(instruction =>
|
_state.Program.Instructions.Any(instruction =>
|
||||||
instruction.Opcode is "VPermlane16B32" or "VPermlanex16B32");
|
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(
|
private static bool TryGetVectorDestination(
|
||||||
Gen5ShaderInstruction instruction,
|
Gen5ShaderInstruction instruction,
|
||||||
out uint destination)
|
out uint destination)
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ internal enum SpirvOp : ushort
|
|||||||
ImageDrefGather = 97,
|
ImageDrefGather = 97,
|
||||||
ImageRead = 98,
|
ImageRead = 98,
|
||||||
ImageWrite = 99,
|
ImageWrite = 99,
|
||||||
|
Image = 100,
|
||||||
ImageQuerySizeLod = 103,
|
ImageQuerySizeLod = 103,
|
||||||
ImageQuerySize = 104,
|
ImageQuerySize = 104,
|
||||||
ImageQueryLod = 105,
|
ImageQueryLod = 105,
|
||||||
@@ -173,10 +174,13 @@ internal enum SpirvCapability : uint
|
|||||||
Float64 = 10,
|
Float64 = 10,
|
||||||
Int64 = 11,
|
Int64 = 11,
|
||||||
Int16 = 22,
|
Int16 = 22,
|
||||||
|
ImageGatherExtended = 25,
|
||||||
|
StorageImageExtendedFormats = 49,
|
||||||
ImageQuery = 50,
|
ImageQuery = 50,
|
||||||
StorageImageReadWithoutFormat = 55,
|
StorageImageReadWithoutFormat = 55,
|
||||||
StorageImageWriteWithoutFormat = 56,
|
StorageImageWriteWithoutFormat = 56,
|
||||||
GroupNonUniform = 61,
|
GroupNonUniform = 61,
|
||||||
|
GroupNonUniformVote = 62,
|
||||||
GroupNonUniformBallot = 64,
|
GroupNonUniformBallot = 64,
|
||||||
GroupNonUniformShuffle = 65,
|
GroupNonUniformShuffle = 65,
|
||||||
RuntimeDescriptorArray = 5302,
|
RuntimeDescriptorArray = 5302,
|
||||||
@@ -309,6 +313,18 @@ internal sealed class SpirvModuleBuilder
|
|||||||
private readonly Dictionary<(uint Width, bool Signed), uint> _integerTypes = [];
|
private readonly Dictionary<(uint Width, bool Signed), uint> _integerTypes = [];
|
||||||
private readonly Dictionary<uint, uint> _floatTypes = [];
|
private readonly Dictionary<uint, uint> _floatTypes = [];
|
||||||
private readonly Dictionary<(uint Component, uint Count), uint> _vectorTypes = [];
|
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<(SpirvStorageClass Storage, uint Type), uint> _pointerTypes = [];
|
||||||
private readonly Dictionary<(uint Element, uint Count), uint> _arrayTypes = [];
|
private readonly Dictionary<(uint Element, uint Count), uint> _arrayTypes = [];
|
||||||
private readonly Dictionary<uint, uint> _runtimeArrayTypes = [];
|
private readonly Dictionary<uint, uint> _runtimeArrayTypes = [];
|
||||||
@@ -477,6 +493,19 @@ internal sealed class SpirvModuleBuilder
|
|||||||
uint sampled,
|
uint sampled,
|
||||||
SpirvImageFormat format)
|
SpirvImageFormat format)
|
||||||
{
|
{
|
||||||
|
var key = (
|
||||||
|
sampledType,
|
||||||
|
dimension,
|
||||||
|
depth,
|
||||||
|
arrayed,
|
||||||
|
multisampled,
|
||||||
|
sampled,
|
||||||
|
format);
|
||||||
|
if (_imageTypes.TryGetValue(key, out var existing))
|
||||||
|
{
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
var id = AllocateId();
|
var id = AllocateId();
|
||||||
Emit(
|
Emit(
|
||||||
_typesConstantsGlobals,
|
_typesConstantsGlobals,
|
||||||
@@ -489,13 +518,20 @@ internal sealed class SpirvModuleBuilder
|
|||||||
multisampled ? 1u : 0u,
|
multisampled ? 1u : 0u,
|
||||||
sampled,
|
sampled,
|
||||||
(uint)format);
|
(uint)format);
|
||||||
|
_imageTypes.Add(key, id);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint TypeSampledImage(uint imageType)
|
public uint TypeSampledImage(uint imageType)
|
||||||
{
|
{
|
||||||
|
if (_sampledImageTypes.TryGetValue(imageType, out var existing))
|
||||||
|
{
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
var id = AllocateId();
|
var id = AllocateId();
|
||||||
Emit(_typesConstantsGlobals, SpirvOp.TypeSampledImage, id, imageType);
|
Emit(_typesConstantsGlobals, SpirvOp.TypeSampledImage, id, imageType);
|
||||||
|
_sampledImageTypes.Add(imageType, id);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -306,10 +306,6 @@ public static class KernelExports
|
|||||||
{
|
{
|
||||||
var threadId = ctx[CpuRegister.Rdi];
|
var threadId = ctx[CpuRegister.Rdi];
|
||||||
var returnValueAddress = ctx[CpuRegister.Rsi];
|
var returnValueAddress = ctx[CpuRegister.Rsi];
|
||||||
if (returnValueAddress != 0 && !ctx.TryWriteUInt64(returnValueAddress, 0))
|
|
||||||
{
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ShouldTracePthread())
|
if (ShouldTracePthread())
|
||||||
{
|
{
|
||||||
@@ -317,6 +313,30 @@ public static class KernelExports
|
|||||||
$"[LOADER][TRACE] pthread_join: thread=0x{threadId:X16} retval_out=0x{returnValueAddress:X16}");
|
$"[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;
|
ctx[CpuRegister.Rax] = 0;
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,10 +99,12 @@ public static class KernelMemoryCompatExports
|
|||||||
private static readonly object _tlsGate = new();
|
private static readonly object _tlsGate = new();
|
||||||
private static readonly object _ioTraceGate = new();
|
private static readonly object _ioTraceGate = new();
|
||||||
private static readonly object _statCacheGate = 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, DirectAllocation> _directAllocations = new();
|
||||||
private static readonly Dictionary<ulong, LibcHeapAllocation> _libcAllocations = new();
|
private static readonly Dictionary<ulong, LibcHeapAllocation> _libcAllocations = new();
|
||||||
private static readonly Dictionary<ulong, MappedRegion> _mappedRegions = new();
|
private static readonly Dictionary<ulong, MappedRegion> _mappedRegions = new();
|
||||||
private static readonly Dictionary<ulong, ulong> _tlsModuleBlocks = 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> _tracedStatResults = new(StringComparer.Ordinal);
|
||||||
private static readonly HashSet<string> _negativeStatCache = new(StringComparer.OrdinalIgnoreCase);
|
private static readonly HashSet<string> _negativeStatCache = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private static long _nextFileDescriptor = 2;
|
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 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);
|
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(
|
internal static bool TryAllocateHleData(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
ulong length,
|
ulong length,
|
||||||
@@ -4033,6 +4061,11 @@ public static class KernelMemoryCompatExports
|
|||||||
return guestPath;
|
return guestPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TryResolveRegisteredGuestMount(guestPath, out var mountedPath))
|
||||||
|
{
|
||||||
|
return mountedPath;
|
||||||
|
}
|
||||||
|
|
||||||
if (guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase))
|
if (guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var relative = NormalizeMountRelativePath(guestPath["/devlog/app/".Length..]);
|
var relative = NormalizeMountRelativePath(guestPath["/devlog/app/".Length..]);
|
||||||
@@ -4131,6 +4164,51 @@ public static class KernelMemoryCompatExports
|
|||||||
return guestPath;
|
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()
|
private static string? ResolveApp0Root()
|
||||||
{
|
{
|
||||||
var cached = Volatile.Read(ref _cachedApp0Root);
|
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)
|
private static bool TryAllocateLibcHeap(ulong requestedSize, nuint alignment, bool zeroFill, out ulong address)
|
||||||
{
|
{
|
||||||
address = 0;
|
address = 0;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
using SharpEmu.HLE;
|
using SharpEmu.HLE;
|
||||||
|
using SharpEmu.Libs.Kernel;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -10,6 +11,8 @@ namespace SharpEmu.Libs.SaveData;
|
|||||||
public static class SaveDataExports
|
public static class SaveDataExports
|
||||||
{
|
{
|
||||||
private const int OrbisSaveDataErrorParameter = unchecked((int)0x809F0000);
|
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 OrbisSaveDataErrorInternal = unchecked((int)0x809F000B);
|
||||||
private const int SaveDataTitleIdSize = 10;
|
private const int SaveDataTitleIdSize = 10;
|
||||||
private const int SaveDataDirNameSize = 32;
|
private const int SaveDataDirNameSize = 32;
|
||||||
@@ -23,6 +26,9 @@ public static class SaveDataExports
|
|||||||
private const ulong ResultInfosOffset = 0x20;
|
private const ulong ResultInfosOffset = 0x20;
|
||||||
private const uint SortKeyFreeBlocks = 5;
|
private const uint SortKeyFreeBlocks = 5;
|
||||||
private const uint SortOrderDescent = 1;
|
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 readonly object _stateGate = new();
|
||||||
private static string? _titleId;
|
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)
|
private static bool TryReadSearchCond(CpuContext ctx, ulong address, out SearchCond cond)
|
||||||
{
|
{
|
||||||
cond = default;
|
cond = default;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user