Compare commits

...

5 Commits

12 changed files with 1343 additions and 101 deletions
@@ -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:
+6
View File
@@ -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);
+75 -7
View File
@@ -142,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();
@@ -2788,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;
} }
@@ -2872,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,
@@ -3033,6 +3034,11 @@ 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,
@@ -3477,7 +3483,8 @@ public static class AgcExports
',', ',',
draw.VertexInputs.Select(input => draw.VertexInputs.Select(input =>
$"{input.Location}:pc=0x{input.Pc:X}:0x{input.BaseAddress:X16}" + $"{input.Location}:pc=0x{input.Pc:X}:0x{input.BaseAddress:X16}" +
$":stride{input.Stride}:off{input.OffsetBytes}:c{input.ComponentCount}")); $":stride{input.Stride}:off{input.OffsetBytes}:c{input.ComponentCount}" +
$":fmt{input.DataFormat}/num{input.NumberFormat}"));
var scissor = draw.RenderState.Scissor is { } drawScissor var scissor = draw.RenderState.Scissor is { } drawScissor
? $"{drawScissor.X},{drawScissor.Y},{drawScissor.Width}x{drawScissor.Height}" ? $"{drawScissor.X},{drawScissor.Y},{drawScissor.Width}x{drawScissor.Height}"
: "full"; : "full";
@@ -3486,6 +3493,29 @@ public static class AgcExports
$"{drawViewport.Width:0.###}x{drawViewport.Height:0.###}:" + $"{drawViewport.Width:0.###}x{drawViewport.Height:0.###}:" +
$"{drawViewport.MinDepth:0.###}-{drawViewport.MaxDepth:0.###}" $"{drawViewport.MinDepth:0.###}-{drawViewport.MaxDepth:0.###}"
: "full"; : "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; var blend = draw.RenderState.Blend;
TraceAgcShader( TraceAgcShader(
$"agc.shader_draw es=0x{draw.ExportShaderAddress:X16} " + $"agc.shader_draw es=0x{draw.ExportShaderAddress:X16} " +
@@ -3493,6 +3523,7 @@ public static class AgcExports
$"primitive=0x{draw.PrimitiveType:X} " + $"primitive=0x{draw.PrimitiveType:X} " +
$"blend={(blend.Enable ? 1 : 0)}:{blend.ColorSrcFactor}/{blend.ColorDstFactor}/{blend.ColorFunc} " + $"blend={(blend.Enable ? 1 : 0)}:{blend.ColorSrcFactor}/{blend.ColorDstFactor}/{blend.ColorFunc} " +
$"write_mask=0x{blend.WriteMask:X} scissor={scissor} viewport={viewport} " + $"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}]"); $"buffers=[{buffers}] vertex=[{vertexInputs}] indices=[{indices}]");
@@ -3550,6 +3581,8 @@ public static class AgcExports
buffers[index] = new VulkanGuestVertexBuffer( buffers[index] = new VulkanGuestVertexBuffer(
binding.Location, binding.Location,
binding.ComponentCount, binding.ComponentCount,
binding.DataFormat,
binding.NumberFormat,
binding.BaseAddress, binding.BaseAddress,
binding.Stride, binding.Stride,
binding.OffsetBytes, binding.OffsetBytes,
@@ -3579,7 +3612,10 @@ public static class AgcExports
} }
var sourceWidth = descriptor.TileMode == 0 var sourceWidth = descriptor.TileMode == 0
? Math.Max(descriptor.Width, descriptor.Pitch) ? GetLinearTexturePitch(
Math.Max(descriptor.Width, descriptor.Pitch),
descriptor.Height,
descriptor.Format)
: descriptor.Width; : descriptor.Width;
var sourceByteCount = GetTextureByteCount( var sourceByteCount = GetTextureByteCount(
descriptor.Format, descriptor.Format,
@@ -3617,7 +3653,7 @@ public static class AgcExports
IsStorage: true, IsStorage: true,
MipLevels: descriptor.MipLevels, MipLevels: descriptor.MipLevels,
MipLevel: mipLevel, MipLevel: mipLevel,
Pitch: descriptor.Pitch, Pitch: sourceWidth,
TileMode: descriptor.TileMode, TileMode: descriptor.TileMode,
DstSelect: descriptor.DstSelect, DstSelect: descriptor.DstSelect,
Sampler: ToVulkanSampler(samplerDescriptor)); Sampler: ToVulkanSampler(samplerDescriptor));
@@ -3663,7 +3699,7 @@ public static class AgcExports
IsStorage: isStorage, IsStorage: isStorage,
MipLevels: descriptor.MipLevels, MipLevels: descriptor.MipLevels,
MipLevel: mipLevel, MipLevel: mipLevel,
Pitch: descriptor.Pitch, Pitch: sourceWidth,
TileMode: descriptor.TileMode, TileMode: descriptor.TileMode,
DstSelect: descriptor.DstSelect, DstSelect: descriptor.DstSelect,
Sampler: ToVulkanSampler(samplerDescriptor)); Sampler: ToVulkanSampler(samplerDescriptor));
@@ -4326,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,
@@ -4594,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
@@ -5367,6 +5429,12 @@ 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( private static void DumpSpirv(
string stage, string stage,
ulong shaderAddress, ulong shaderAddress,
+2
View File
@@ -276,6 +276,8 @@ internal sealed record Gen5VertexInputBinding(
uint Pc, uint Pc,
uint Location, uint Location,
uint ComponentCount, uint ComponentCount,
uint DataFormat,
uint NumberFormat,
ulong BaseAddress, ulong BaseAddress,
uint Stride, uint Stride,
uint OffsetBytes, uint OffsetBytes,
@@ -20,7 +20,9 @@ internal static class Gen5ShaderScalarEvaluator
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,
@@ -400,14 +402,21 @@ internal static class Gen5ShaderScalarEvaluator
return false; 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( binding = new Gen5VertexInputBinding(
instruction.Pc, instruction.Pc,
location, location,
control.DwordCount, control.DwordCount,
bindingDataFormat,
bindingNumberFormat,
descriptor.BaseAddress, descriptor.BaseAddress,
descriptor.Stride, bindingStride,
unchecked((uint)control.OffsetBytes + scalarOffset), bindingOffset,
data); bindingData);
return true; return true;
} }
@@ -803,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)),
}; };
scalarConditionCode = registers[destination.Value] != 0; if (instruction.Opcode != "SBitset1B32")
{
scalarConditionCode = registers[destination.Value] != 0;
}
return true; return true;
} }
@@ -829,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":
{ {
@@ -846,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;
@@ -926,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":
@@ -935,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; {
break; var wide = ((ulong)left << 1) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SLshl2AddU32": case "SLshl2AddU32":
result = (left << 2) + right; {
break; var wide = ((ulong)left << 2) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SLshl3AddU32": case "SLshl3AddU32":
result = (left << 3) + right; {
break; var wide = ((ulong)left << 3) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SLshl4AddU32": case "SLshl4AddU32":
result = (left << 4) + right; {
break; var wide = ((ulong)left << 4) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SPackLlB32B16": case "SPackLlB32B16":
result = (left & 0xFFFFu) | (right << 16); result = (left & 0xFFFFu) | (right << 16);
break; break;
@@ -993,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;
} }
@@ -1021,11 +1060,12 @@ 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),
}; };
@@ -1095,6 +1135,12 @@ internal static class Gen5ShaderScalarEvaluator
private static ulong MaskWaveValue(ulong value) => value & RdnaWaveMask; 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,
@@ -1416,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;
} }
@@ -1428,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,
@@ -452,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,
}; };
+267 -25
View File
@@ -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);
@@ -635,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;
} }
@@ -945,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)
@@ -971,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(
@@ -985,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":
{ {
@@ -1021,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,
@@ -1033,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":
@@ -1061,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(
@@ -1068,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(
@@ -1075,22 +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;
case "SOrn2B32":
result = _module.AddInstruction(
SpirvOp.BitwiseOr,
_uintType,
left,
_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; break;
case "SLshlB32": case "SLshlB32":
result = ShiftLeftLogical(left, right); 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 = ShiftRightArithmetic(left, right); result = ShiftRightArithmetic(left, right);
Store(_scc, IsNotZero(result));
break; break;
case "SBfmB32": case "SBfmB32":
result = _module.AddInstruction( result = _module.AddInstruction(
@@ -1133,6 +1227,7 @@ internal static partial class Gen5SpirvTranslator
left, left,
offset, offset,
width); width);
Store(_scc, IsNotZero(result));
break; break;
} }
case "SCselectB32": case "SCselectB32":
@@ -1145,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":
@@ -1292,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), oldExec)),
left), "SAndn1SaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseAnd,
_ulongType,
notLeft,
oldExec),
"SOrn1SaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseOr,
_ulongType,
notLeft,
oldExec),
"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)
@@ -1508,6 +1679,22 @@ internal static partial class Gen5SpirvTranslator
} }
StoreS64(destination, value); StoreS64(destination, value);
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(_scc, IsNotZero64(value));
}
return true; return true;
} }
@@ -2067,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];
@@ -2086,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)
+24 -4
View File
@@ -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);
@@ -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;
@@ -50,6 +50,8 @@ internal sealed record VulkanGuestMemoryBuffer(
internal sealed record VulkanGuestVertexBuffer( internal sealed record VulkanGuestVertexBuffer(
uint Location, uint Location,
uint ComponentCount, uint ComponentCount,
uint DataFormat,
uint NumberFormat,
ulong BaseAddress, ulong BaseAddress,
uint Stride, uint Stride,
uint OffsetBytes, uint OffsetBytes,
@@ -163,6 +165,8 @@ internal static unsafe class VulkanVideoPresenter
private static readonly object _gate = new(); private static readonly object _gate = new();
private static readonly Queue<object> _pendingGuestWork = new(); private static readonly Queue<object> _pendingGuestWork = new();
private static readonly Dictionary<ulong, uint> _availableGuestImages = new(); private static readonly Dictionary<ulong, uint> _availableGuestImages = new();
private static readonly HashSet<(ulong Address, uint Width, uint Height)>
_tracedGuestImageSubmissions = [];
private static Thread? _thread; private static Thread? _thread;
private static Presentation? _latestPresentation; private static Presentation? _latestPresentation;
private static byte[]? _copyFragmentSpirv; private static byte[]? _copyFragmentSpirv;
@@ -558,44 +562,38 @@ internal static unsafe class VulkanVideoPresenter
uint height, uint height,
uint pitchInPixel) uint pitchInPixel)
{ {
uint format; var traceSubmission = false;
lock (_gate) lock (_gate)
{ {
if (_closed || if (_closed ||
!_availableGuestImages.TryGetValue(address, out format)) !_availableGuestImages.ContainsKey(address))
{ {
return false; return false;
} }
traceSubmission =
_tracedGuestImageSubmissions.Add((address, width, height));
var sequence = (_latestPresentation?.Sequence ?? 0) + 1;
_latestPresentation = new Presentation(
null,
width,
height,
sequence,
GuestDrawKind.None,
TranslatedDraw: null,
RequiredGuestWorkSequence: 0,
IsSplash: false,
GuestImageAddress: address);
} }
var effectivePitch = pitchInPixel == 0 ? width : pitchInPixel; if (traceSubmission)
Console.Error.WriteLine(
$"[LOADER][TRACE] vk.submit_guest_image addr=0x{address:X16} " +
$"size={width}x{height} pitch={effectivePitch}");
if (!TryGetCopyFragmentShader(out var fragmentSpirv))
{ {
return false; var effectivePitch = pitchInPixel == 0 ? width : pitchInPixel;
Console.Error.WriteLine(
$"[LOADER][TRACE] vk.submit_guest_image addr=0x{address:X16} " +
$"size={width}x{height} pitch={effectivePitch}");
} }
SubmitTranslatedDraw(
fragmentSpirv,
[
new VulkanGuestDrawTexture(
address,
width,
height,
format,
NumberType: 0,
[],
IsFallback: false,
IsStorage: false),
],
[],
width,
height,
attributeCount: 1);
return true; return true;
} }
@@ -807,7 +805,8 @@ internal static unsafe class VulkanVideoPresenter
GuestDrawKind DrawKind, GuestDrawKind DrawKind,
VulkanTranslatedGuestDraw? TranslatedDraw, VulkanTranslatedGuestDraw? TranslatedDraw,
long RequiredGuestWorkSequence, long RequiredGuestWorkSequence,
bool IsSplash); bool IsSplash,
ulong GuestImageAddress = 0);
private sealed class Presenter : IDisposable private sealed class Presenter : IDisposable
{ {
@@ -855,11 +854,16 @@ internal static unsafe class VulkanVideoPresenter
private bool _firstGuestDrawPresented; private bool _firstGuestDrawPresented;
private bool _splashPresented; private bool _splashPresented;
private bool _swapchainRecreateDeferred; private bool _swapchainRecreateDeferred;
private bool _tracedPresentedSwapchain;
private bool _swapchainReadbackPending;
private int _directPresentationCount;
private readonly Dictionary<ulong, GuestImageResource> _guestImages = new(); private readonly Dictionary<ulong, GuestImageResource> _guestImages = new();
private readonly HashSet<(ulong Address, uint Width, uint Height, Format Format)> _tracedTextureCacheHits = new(); private readonly HashSet<(ulong Address, uint Width, uint Height, Format Format)> _tracedTextureCacheHits = new();
private readonly HashSet<(ulong Address, uint Width, uint Height, Format Format)> _tracedTextureUploads = new(); private readonly HashSet<(ulong Address, uint Width, uint Height, Format Format)> _tracedTextureUploads = new();
private readonly HashSet<(ulong Address, uint Width, uint Height, uint Format)> _dumpedTextures = new();
private readonly HashSet<(ulong Address, int Size)> _tracedGlobalBuffers = new(); private readonly HashSet<(ulong Address, int Size)> _tracedGlobalBuffers = new();
private readonly HashSet<ulong> _tracedGuestImageContents = new(); private readonly HashSet<ulong> _tracedGuestImageContents = new();
private readonly Dictionary<ulong, int> _tracedGuestWriteCounts = new();
private int _tracedVertexBufferCount; private int _tracedVertexBufferCount;
private readonly Dictionary<byte[], Pipeline> _computePipelines = private readonly Dictionary<byte[], Pipeline> _computePipelines =
new(ReferenceEqualityComparer.Instance); new(ReferenceEqualityComparer.Instance);
@@ -922,6 +926,8 @@ internal static unsafe class VulkanVideoPresenter
public ulong Size; public ulong Size;
public uint Location; public uint Location;
public uint ComponentCount; public uint ComponentCount;
public uint DataFormat;
public uint NumberFormat;
public uint Stride; public uint Stride;
public uint OffsetBytes; public uint OffsetBytes;
} }
@@ -1361,7 +1367,10 @@ internal static unsafe class VulkanVideoPresenter
ImageColorSpace = surfaceFormat.ColorSpace, ImageColorSpace = surfaceFormat.ColorSpace,
ImageExtent = _extent, ImageExtent = _extent,
ImageArrayLayers = 1, ImageArrayLayers = 1,
ImageUsage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.ColorAttachmentBit, ImageUsage =
ImageUsageFlags.TransferDstBit |
ImageUsageFlags.TransferSrcBit |
ImageUsageFlags.ColorAttachmentBit,
ImageSharingMode = SharingMode.Exclusive, ImageSharingMode = SharingMode.Exclusive,
PreTransform = capabilities.CurrentTransform, PreTransform = capabilities.CurrentTransform,
CompositeAlpha = compositeAlpha, CompositeAlpha = compositeAlpha,
@@ -2233,7 +2242,10 @@ internal static unsafe class VulkanVideoPresenter
{ {
Location = vertexBuffer.Location, Location = vertexBuffer.Location,
Binding = (uint)index, Binding = (uint)index,
Format = ToVkVertexFormat(vertexBuffer.ComponentCount), Format = ToVkVertexFormat(
vertexBuffer.DataFormat,
vertexBuffer.NumberFormat,
vertexBuffer.ComponentCount),
Offset = 0, Offset = 0,
}; };
} }
@@ -2420,8 +2432,7 @@ internal static unsafe class VulkanVideoPresenter
var vkFormat = GetTextureFormat(texture.Format, texture.NumberType); var vkFormat = GetTextureFormat(texture.Format, texture.NumberType);
if (texture.Address != 0 && if (texture.Address != 0 &&
_guestImages.TryGetValue(texture.Address, out var guestImage) && _guestImages.TryGetValue(texture.Address, out var guestImage) &&
guestImage.Width == texture.Width && IsCompatibleGuestImageAlias(texture, guestImage) &&
guestImage.Height == texture.Height &&
IsCompatibleViewFormat(guestImage.Format, vkFormat) && IsCompatibleViewFormat(guestImage.Format, vkFormat) &&
TryGetOrCreateGuestImageView( TryGetOrCreateGuestImageView(
guestImage, guestImage,
@@ -2440,6 +2451,16 @@ internal static unsafe class VulkanVideoPresenter
$"image_format={guestImage.Format} view_format={vkFormat}"); $"image_format={guestImage.Format} view_format={vkFormat}");
} }
if (guestImage.Width != texture.Width ||
guestImage.Height != texture.Height)
{
TraceVulkanShader(
$"vk.texture_cache_alias addr=0x{texture.Address:X16} " +
$"texture={texture.Width}x{texture.Height} " +
$"image={guestImage.Width}x{guestImage.Height} " +
$"tile={texture.TileMode} format={vkFormat}");
}
return new TextureResource return new TextureResource
{ {
Address = texture.Address, Address = texture.Address,
@@ -2457,6 +2478,27 @@ internal static unsafe class VulkanVideoPresenter
return CreateTextureResource(texture); return CreateTextureResource(texture);
} }
private static bool IsCompatibleGuestImageAlias(
VulkanGuestDrawTexture texture,
GuestImageResource guestImage)
{
if (guestImage.Width == texture.Width &&
guestImage.Height == texture.Height)
{
return true;
}
if (texture.TileMode == 0 ||
texture.Width == 0 ||
texture.Height == 0)
{
return false;
}
return texture.Width <= guestImage.Width &&
texture.Height <= guestImage.Height;
}
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private TextureResource ResolveStorageImageResource(VulkanGuestDrawTexture texture) private TextureResource ResolveStorageImageResource(VulkanGuestDrawTexture texture)
{ {
@@ -2673,6 +2715,7 @@ internal static unsafe class VulkanVideoPresenter
var pixels = texture.RgbaPixels.Length == (int)expectedSize var pixels = texture.RgbaPixels.Length == (int)expectedSize
? texture.RgbaPixels ? texture.RgbaPixels
: CreateFallbackTexturePixels(texture.Format, rowLength, height, expectedSize); : CreateFallbackTexturePixels(texture.Format, rowLength, height, expectedSize);
DumpTextureUpload(texture, pixels, rowLength, width, height);
var uploadPixels = texture.Format == 13 var uploadPixels = texture.Format == 13
? ExpandRgb32Pixels(pixels) ? ExpandRgb32Pixels(pixels)
: pixels; : pixels;
@@ -2754,6 +2797,102 @@ internal static unsafe class VulkanVideoPresenter
}; };
} }
private void DumpTextureUpload(
VulkanGuestDrawTexture texture,
byte[] pixels,
uint rowLength,
uint width,
uint height)
{
if (!string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_DUMP_TEXTURES"),
"1",
StringComparison.Ordinal) ||
texture.IsFallback ||
texture.IsStorage ||
GetTextureBytesPerPixel(texture.Format) != 4 ||
width == 0 ||
height == 0 ||
!_dumpedTextures.Add((texture.Address, width, height, texture.Format)))
{
return;
}
var rowBytes = checked((int)rowLength * 4);
var visibleRowBytes = checked((int)width * 4);
if (pixels.Length < checked(rowBytes * (int)height))
{
return;
}
var directory = Path.Combine(AppContext.BaseDirectory, "texture-dumps");
Directory.CreateDirectory(directory);
var path = Path.Combine(
directory,
$"tex-{texture.Address:X16}-{width}x{height}-fmt{texture.Format}-row{rowLength}.bmp");
WriteRgbaBmp(path, pixels, rowBytes, visibleRowBytes, (int)width, (int)height);
}
private static void WriteRgbaBmp(
string path,
byte[] rgba,
int sourceRowBytes,
int visibleRowBytes,
int width,
int height)
{
const int fileHeaderSize = 14;
const int infoHeaderSize = 40;
const int bytesPerPixel = 4;
var pixelBytes = checked(width * height * bytesPerPixel);
var fileSize = fileHeaderSize + infoHeaderSize + pixelBytes;
var output = new byte[fileSize];
output[0] = (byte)'B';
output[1] = (byte)'M';
WriteUInt32(output, 2, (uint)fileSize);
WriteUInt32(output, 10, fileHeaderSize + infoHeaderSize);
WriteUInt32(output, 14, infoHeaderSize);
WriteInt32(output, 18, width);
WriteInt32(output, 22, -height);
WriteUInt16(output, 26, 1);
WriteUInt16(output, 28, 32);
WriteUInt32(output, 34, (uint)pixelBytes);
var destinationOffset = fileHeaderSize + infoHeaderSize;
for (var y = 0; y < height; y++)
{
var sourceOffset = y * sourceRowBytes;
for (var x = 0; x < visibleRowBytes; x += bytesPerPixel)
{
var destination = destinationOffset + y * visibleRowBytes + x;
output[destination + 0] = rgba[sourceOffset + x + 2];
output[destination + 1] = rgba[sourceOffset + x + 1];
output[destination + 2] = rgba[sourceOffset + x + 0];
output[destination + 3] = rgba[sourceOffset + x + 3];
}
}
File.WriteAllBytes(path, output);
}
private static void WriteUInt16(byte[] output, int offset, ushort value)
{
output[offset + 0] = (byte)value;
output[offset + 1] = (byte)(value >> 8);
}
private static void WriteUInt32(byte[] output, int offset, uint value)
{
output[offset + 0] = (byte)value;
output[offset + 1] = (byte)(value >> 8);
output[offset + 2] = (byte)(value >> 16);
output[offset + 3] = (byte)(value >> 24);
}
private static void WriteInt32(byte[] output, int offset, int value) =>
WriteUInt32(output, offset, unchecked((uint)value));
private Sampler CreateSampler(VulkanGuestSampler sampler) private Sampler CreateSampler(VulkanGuestSampler sampler)
{ {
var minLod = DecodeSamplerMipFilter(sampler) == 0 var minLod = DecodeSamplerMipFilter(sampler) == 0
@@ -2872,6 +3011,7 @@ internal static unsafe class VulkanVideoPresenter
$"vk.vertex_buffer loc={guestBuffer.Location} " + $"vk.vertex_buffer loc={guestBuffer.Location} " +
$"base=0x{guestBuffer.BaseAddress:X16} stride={guestBuffer.Stride} " + $"base=0x{guestBuffer.BaseAddress:X16} stride={guestBuffer.Stride} " +
$"offset={guestBuffer.OffsetBytes} comps={guestBuffer.ComponentCount} " + $"offset={guestBuffer.OffsetBytes} comps={guestBuffer.ComponentCount} " +
$"fmt={guestBuffer.DataFormat}/num={guestBuffer.NumberFormat} " +
$"bytes={guestBuffer.Data.Length}"); $"bytes={guestBuffer.Data.Length}");
} }
@@ -2882,6 +3022,8 @@ internal static unsafe class VulkanVideoPresenter
Size = size, Size = size,
Location = guestBuffer.Location, Location = guestBuffer.Location,
ComponentCount = guestBuffer.ComponentCount, ComponentCount = guestBuffer.ComponentCount,
DataFormat = guestBuffer.DataFormat,
NumberFormat = guestBuffer.NumberFormat,
Stride = guestBuffer.Stride, Stride = guestBuffer.Stride,
OffsetBytes = guestBuffer.OffsetBytes, OffsetBytes = guestBuffer.OffsetBytes,
}; };
@@ -2931,7 +3073,83 @@ internal static unsafe class VulkanVideoPresenter
_ => PrimitiveTopology.TriangleList, _ => PrimitiveTopology.TriangleList,
}; };
private static Format ToVkVertexFormat(uint componentCount) => private static Format ToVkVertexFormat(
uint dataFormat,
uint numberFormat,
uint componentCount) =>
(dataFormat, numberFormat) switch
{
(1, 0) => Format.R8Unorm,
(1, 1) => Format.R8SNorm,
(1, 4) => Format.R8Uint,
(1, 5) => Format.R8Sint,
(1, 9) => Format.R8Srgb,
(2, 0) => Format.R16Unorm,
(2, 1) => Format.R16SNorm,
(2, 4) => Format.R16Uint,
(2, 5) => Format.R16Sint,
(2, 7) => Format.R16Sfloat,
(3, 0) => Format.R8G8Unorm,
(3, 1) => Format.R8G8SNorm,
(3, 4) => Format.R8G8Uint,
(3, 5) => Format.R8G8Sint,
(3, 9) => Format.R8G8Srgb,
(4, 4) => Format.R32Uint,
(4, 5) => Format.R32Sint,
(4, 7) => Format.R32Sfloat,
(5, 0) => Format.R16G16Unorm,
(5, 1) => Format.R16G16SNorm,
(5, 2) => Format.R16G16Uscaled,
(5, 3) => Format.R16G16Sscaled,
(5, 4) => Format.R16G16Uint,
(5, 5) => Format.R16G16Sint,
(5, 7) => Format.R16G16Sfloat,
(6, 7) => Format.B10G11R11UfloatPack32,
(7, 7) => Format.B10G11R11UfloatPack32,
(8, 0) => Format.A2B10G10R10UnormPack32,
(8, 1) => Format.A2B10G10R10SNormPack32,
(8, 2) => Format.A2B10G10R10UscaledPack32,
(8, 3) => Format.A2B10G10R10SscaledPack32,
(8, 4) => Format.A2B10G10R10UintPack32,
(8, 5) => Format.A2B10G10R10SintPack32,
(9, 0) => Format.A2R10G10B10UnormPack32,
(9, 1) => Format.A2R10G10B10SNormPack32,
(9, 2) => Format.A2R10G10B10UscaledPack32,
(9, 3) => Format.A2R10G10B10SscaledPack32,
(9, 4) => Format.A2R10G10B10UintPack32,
(9, 5) => Format.A2R10G10B10SintPack32,
(10, 0) => Format.R8G8B8A8Unorm,
(10, 1) => Format.R8G8B8A8SNorm,
(10, 2) => Format.R8G8B8A8Uscaled,
(10, 3) => Format.R8G8B8A8Sscaled,
(10, 4) => Format.R8G8B8A8Uint,
(10, 5) => Format.R8G8B8A8Sint,
(10, 9) => Format.R8G8B8A8Srgb,
(11, 4) => Format.R32G32Uint,
(11, 5) => Format.R32G32Sint,
(11, 7) => Format.R32G32Sfloat,
(12, 0) => Format.R16G16B16A16Unorm,
(12, 1) => Format.R16G16B16A16SNorm,
(12, 2) => Format.R16G16B16A16Uscaled,
(12, 3) => Format.R16G16B16A16Sscaled,
(12, 4) => Format.R16G16B16A16Uint,
(12, 5) => Format.R16G16B16A16Sint,
(12, 6) => Format.R16G16B16A16SNorm,
(12, 7) => Format.R16G16B16A16Sfloat,
(13, 4) => Format.R32G32B32Uint,
(13, 5) => Format.R32G32B32Sint,
(13, 7) => Format.R32G32B32Sfloat,
(14, 4) => Format.R32G32B32A32Uint,
(14, 5) => Format.R32G32B32A32Sint,
(14, 7) => Format.R32G32B32A32Sfloat,
(16, 0) => Format.B5G6R5UnormPack16,
(17, 0) => Format.R5G5B5A1UnormPack16,
(19, 0) => Format.R4G4B4A4UnormPack16,
(34, 7) => Format.E5B9G9R9UfloatPack32,
_ => ToVkFloatVertexFormat(componentCount),
};
private static Format ToVkFloatVertexFormat(uint componentCount) =>
componentCount switch componentCount switch
{ {
1 => Format.R32Sfloat, 1 => Format.R32Sfloat,
@@ -3317,7 +3535,7 @@ internal static unsafe class VulkanVideoPresenter
{ {
_stagingBuffer = CreateBuffer( _stagingBuffer = CreateBuffer(
size, size,
BufferUsageFlags.TransferSrcBit, BufferUsageFlags.TransferSrcBit | BufferUsageFlags.TransferDstBit,
MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit, MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit,
out _stagingMemory); out _stagingMemory);
_stagingSize = size; _stagingSize = size;
@@ -3588,6 +3806,27 @@ internal static unsafe class VulkanVideoPresenter
_availableGuestImages[target.Address] = guestTextureFormat; _availableGuestImages[target.Address] = guestTextureFormat;
} }
} }
if (ShouldTraceGuestImageWriteForDiagnostics(target.Address))
{
var writeCount = _tracedGuestWriteCounts.TryGetValue(
target.Address,
out var previousCount)
? previousCount + 1
: 1;
_tracedGuestWriteCounts[target.Address] = writeCount;
if (writeCount <= 3)
{
_commandBuffer = _presentationCommandBuffer;
Check(
_vk.QueueWaitIdle(_queue),
"vkQueueWaitIdle(guest write trace)");
Console.Error.WriteLine(
$"[LOADER][TRACE] vk.guest_write_sample " +
$"addr=0x{target.Address:X16} write={writeCount} " +
$"ps_bytes={work.Draw.PixelSpirv.Length}");
TraceGuestImageContents(target);
}
}
TraceVulkanShader( TraceVulkanShader(
$"vk.offscreen_draw addr=0x{target.Address:X16} " + $"vk.offscreen_draw addr=0x{target.Address:X16} " +
$"size={target.Width}x{target.Height} format={target.Format} " + $"size={target.Width}x{target.Height} format={target.Format} " +
@@ -4051,7 +4290,8 @@ internal static unsafe class VulkanVideoPresenter
if (presentation.Pixels is null && if (presentation.Pixels is null &&
presentation.DrawKind != GuestDrawKind.FullscreenBarycentric && presentation.DrawKind != GuestDrawKind.FullscreenBarycentric &&
presentation.TranslatedDraw is null) presentation.TranslatedDraw is null &&
presentation.GuestImageAddress == 0)
{ {
return; return;
} }
@@ -4074,6 +4314,28 @@ internal static unsafe class VulkanVideoPresenter
} }
TranslatedDrawResources? translatedResources = null; TranslatedDrawResources? translatedResources = null;
GuestImageResource? presentedGuestImage = null;
if (presentation.GuestImageAddress != 0 &&
(!_guestImages.TryGetValue(
presentation.GuestImageAddress,
out presentedGuestImage) ||
!presentedGuestImage.Initialized))
{
return;
}
if (presentedGuestImage is not null)
{
_directPresentationCount++;
if (ShouldTracePresentedGuestImageContentsForDiagnostics() &&
_directPresentationCount is 1 or 30 or 120)
{
Console.Error.WriteLine(
$"[LOADER][TRACE] vk.present_sample frame={_directPresentationCount} " +
$"addr=0x{presentedGuestImage.Address:X16}");
TraceGuestImageContents(presentedGuestImage);
}
}
if (presentation.TranslatedDraw is { } translatedDraw) if (presentation.TranslatedDraw is { } translatedDraw)
{ {
try try
@@ -4175,6 +4437,11 @@ internal static unsafe class VulkanVideoPresenter
_vk.CmdEndRenderPass(_commandBuffer); _vk.CmdEndRenderPass(_commandBuffer);
waitStage = PipelineStageFlags.ColorAttachmentOutputBit; waitStage = PipelineStageFlags.ColorAttachmentOutputBit;
} }
else if (presentedGuestImage is not null)
{
RecordGuestImageBlit(imageIndex, presentedGuestImage);
waitStage = PipelineStageFlags.TransferBit;
}
else if (translatedResources is not null) else if (translatedResources is not null)
{ {
RecordTranslatedDraw(imageIndex, translatedResources); RecordTranslatedDraw(imageIndex, translatedResources);
@@ -4231,6 +4498,10 @@ internal static unsafe class VulkanVideoPresenter
CheckSwapchainResult(presentResult, "vkQueuePresentKHR"); CheckSwapchainResult(presentResult, "vkQueuePresentKHR");
recreateAfterPresent |= presentResult == Result.SuboptimalKhr; recreateAfterPresent |= presentResult == Result.SuboptimalKhr;
Check(_vk.QueueWaitIdle(_queue), "vkQueueWaitIdle"); Check(_vk.QueueWaitIdle(_queue), "vkQueueWaitIdle");
if (_swapchainReadbackPending)
{
TraceSwapchainReadback();
}
CollectCompletedGuestSubmissions(waitForOldest: false); CollectCompletedGuestSubmissions(waitForOldest: false);
if (translatedResources is not null) if (translatedResources is not null)
{ {
@@ -4259,8 +4530,11 @@ internal static unsafe class VulkanVideoPresenter
{ {
_firstGuestDrawPresented = true; _firstGuestDrawPresented = true;
Console.Error.WriteLine( Console.Error.WriteLine(
$"[LOADER][INFO] Vulkan VideoOut presented translated guest draw: " + $"[LOADER][INFO] Vulkan VideoOut presented guest frame: " +
(presentation.TranslatedDraw is null (presentedGuestImage is not null
? $"image=0x{presentedGuestImage.Address:X16} " +
$"{presentedGuestImage.Width}x{presentedGuestImage.Height}"
: presentation.TranslatedDraw is null
? $"{presentation.DrawKind}" ? $"{presentation.DrawKind}"
: $"shader textures={presentation.TranslatedDraw.Textures.Count}")); : $"shader textures={presentation.TranslatedDraw.Textures.Count}"));
} }
@@ -4760,7 +5034,23 @@ internal static unsafe class VulkanVideoPresenter
private static bool ShouldTraceGuestImageAddressForDiagnostics(ulong address) private static bool ShouldTraceGuestImageAddressForDiagnostics(ulong address)
{ {
var addresses = Environment.GetEnvironmentVariable("SHARPEMU_TRACE_GUEST_IMAGE_ADDRS"); return AddressListContains(
"SHARPEMU_TRACE_GUEST_IMAGE_ADDRS",
address);
}
private static bool ShouldTraceGuestImageWriteForDiagnostics(ulong address)
{
return AddressListContains(
"SHARPEMU_TRACE_GUEST_WRITES",
address);
}
private static bool AddressListContains(
string environmentVariable,
ulong address)
{
var addresses = Environment.GetEnvironmentVariable(environmentVariable);
if (string.IsNullOrWhiteSpace(addresses)) if (string.IsNullOrWhiteSpace(addresses))
{ {
return false; return false;
@@ -4770,6 +5060,11 @@ internal static unsafe class VulkanVideoPresenter
[',', ';', ' ', '\t'], [',', ';', ' ', '\t'],
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{ {
if (token == "*")
{
return true;
}
var span = token.AsSpan(); var span = token.AsSpan();
if (span.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) if (span.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{ {
@@ -5105,6 +5400,234 @@ internal static unsafe class VulkanVideoPresenter
&toPresent); &toPresent);
} }
private void RecordGuestImageBlit(
uint imageIndex,
GuestImageResource source)
{
var traceDestination =
ShouldTracePresentedGuestImageContentsForDiagnostics() &&
!_tracedPresentedSwapchain;
_tracedPresentedSwapchain |= traceDestination;
BeginDebugLabel(
_commandBuffer,
$"SharpEmu present image 0x{source.Address:X16}");
var sourceToTransfer = new ImageMemoryBarrier
{
SType = StructureType.ImageMemoryBarrier,
SrcAccessMask = AccessFlags.ShaderReadBit,
DstAccessMask = AccessFlags.TransferReadBit,
OldLayout = ImageLayout.ShaderReadOnlyOptimal,
NewLayout = ImageLayout.TransferSrcOptimal,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = source.Image,
SubresourceRange = ColorSubresourceRange(),
};
var destinationToTransfer = new ImageMemoryBarrier
{
SType = StructureType.ImageMemoryBarrier,
SrcAccessMask = _imageInitialized[imageIndex]
? AccessFlags.MemoryReadBit
: 0,
DstAccessMask = AccessFlags.TransferWriteBit,
OldLayout = _imageInitialized[imageIndex]
? ImageLayout.PresentSrcKhr
: ImageLayout.Undefined,
NewLayout = ImageLayout.TransferDstOptimal,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = _swapchainImages[imageIndex],
SubresourceRange = ColorSubresourceRange(),
};
var barriers = stackalloc ImageMemoryBarrier[2];
barriers[0] = sourceToTransfer;
barriers[1] = destinationToTransfer;
_vk.CmdPipelineBarrier(
_commandBuffer,
PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.TransferBit,
0,
0,
null,
0,
null,
2,
barriers);
var sourceOffsets = new ImageBlit.SrcOffsetsBuffer
{
Element0 = new Offset3D(0, 0, 0),
Element1 = new Offset3D(
checked((int)source.Width),
checked((int)source.Height),
1),
};
var destinationOffsets = new ImageBlit.DstOffsetsBuffer
{
Element0 = new Offset3D(0, 0, 0),
Element1 = new Offset3D(
checked((int)_extent.Width),
checked((int)_extent.Height),
1),
};
var region = new ImageBlit
{
SrcSubresource = new ImageSubresourceLayers(
ImageAspectFlags.ColorBit,
0,
0,
1),
SrcOffsets = sourceOffsets,
DstSubresource = new ImageSubresourceLayers(
ImageAspectFlags.ColorBit,
0,
0,
1),
DstOffsets = destinationOffsets,
};
_vk.CmdBlitImage(
_commandBuffer,
source.Image,
ImageLayout.TransferSrcOptimal,
_swapchainImages[imageIndex],
ImageLayout.TransferDstOptimal,
1,
&region,
Filter.Nearest);
if (traceDestination)
{
var destinationToReadback = new ImageMemoryBarrier
{
SType = StructureType.ImageMemoryBarrier,
SrcAccessMask = AccessFlags.TransferWriteBit,
DstAccessMask = AccessFlags.TransferReadBit,
OldLayout = ImageLayout.TransferDstOptimal,
NewLayout = ImageLayout.TransferSrcOptimal,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = _swapchainImages[imageIndex],
SubresourceRange = ColorSubresourceRange(),
};
_vk.CmdPipelineBarrier(
_commandBuffer,
PipelineStageFlags.TransferBit,
PipelineStageFlags.TransferBit,
0,
0,
null,
0,
null,
1,
&destinationToReadback);
var copyRegion = new BufferImageCopy
{
ImageSubresource = new ImageSubresourceLayers
{
AspectMask = ImageAspectFlags.ColorBit,
LayerCount = 1,
},
ImageExtent = new Extent3D(_extent.Width, _extent.Height, 1),
};
_vk.CmdCopyImageToBuffer(
_commandBuffer,
_swapchainImages[imageIndex],
ImageLayout.TransferSrcOptimal,
_stagingBuffer,
1,
&copyRegion);
_swapchainReadbackPending = true;
}
var sourceToShaderRead = new ImageMemoryBarrier
{
SType = StructureType.ImageMemoryBarrier,
SrcAccessMask = AccessFlags.TransferReadBit,
DstAccessMask = AccessFlags.ShaderReadBit,
OldLayout = ImageLayout.TransferSrcOptimal,
NewLayout = ImageLayout.ShaderReadOnlyOptimal,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = source.Image,
SubresourceRange = ColorSubresourceRange(),
};
var destinationToPresent = new ImageMemoryBarrier
{
SType = StructureType.ImageMemoryBarrier,
SrcAccessMask = traceDestination
? AccessFlags.TransferReadBit
: AccessFlags.TransferWriteBit,
DstAccessMask = AccessFlags.MemoryReadBit,
OldLayout = traceDestination
? ImageLayout.TransferSrcOptimal
: ImageLayout.TransferDstOptimal,
NewLayout = ImageLayout.PresentSrcKhr,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = _swapchainImages[imageIndex],
SubresourceRange = ColorSubresourceRange(),
};
barriers[0] = sourceToShaderRead;
barriers[1] = destinationToPresent;
_vk.CmdPipelineBarrier(
_commandBuffer,
PipelineStageFlags.TransferBit,
PipelineStageFlags.AllCommandsBit,
0,
0,
null,
0,
null,
2,
barriers);
EndDebugLabel(_commandBuffer);
}
private void TraceSwapchainReadback()
{
_swapchainReadbackPending = false;
var byteCount = checked((ulong)_extent.Width * _extent.Height * 4);
void* mapped;
Check(
_vk.MapMemory(_device, _stagingMemory, 0, byteCount, 0, &mapped),
"vkMapMemory(swapchain readback)");
try
{
var bytes = new ReadOnlySpan<byte>(mapped, checked((int)byteCount));
var nonzeroBytes = 0L;
var nonblackPixels = 0L;
ulong hash = 14695981039346656037UL;
for (var offset = 0; offset < bytes.Length; offset += 4)
{
var b0 = bytes[offset];
var b1 = bytes[offset + 1];
var b2 = bytes[offset + 2];
var b3 = bytes[offset + 3];
nonzeroBytes += b0 == 0 ? 0 : 1;
nonzeroBytes += b1 == 0 ? 0 : 1;
nonzeroBytes += b2 == 0 ? 0 : 1;
nonzeroBytes += b3 == 0 ? 0 : 1;
nonblackPixels += b0 != 0 || b1 != 0 || b2 != 0 ? 1 : 0;
hash = (hash ^ b0) * 1099511628211UL;
hash = (hash ^ b1) * 1099511628211UL;
hash = (hash ^ b2) * 1099511628211UL;
hash = (hash ^ b3) * 1099511628211UL;
}
Console.Error.WriteLine(
$"[LOADER][TRACE] vk.swapchain_image size={_extent.Width}x{_extent.Height} " +
$"format={_swapchainFormat} nonzero_bytes={nonzeroBytes}/{byteCount} " +
$"nonblack_pixels={nonblackPixels}/{(ulong)_extent.Width * _extent.Height} " +
$"hash=0x{hash:X16}");
}
finally
{
_vk.UnmapMemory(_device, _stagingMemory);
}
}
private Extent2D ChooseExtent(SurfaceCapabilitiesKHR capabilities) private Extent2D ChooseExtent(SurfaceCapabilitiesKHR capabilities)
{ {
if (capabilities.CurrentExtent.Width != uint.MaxValue) if (capabilities.CurrentExtent.Width != uint.MaxValue)