mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-30 22:49:53 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e11d8a15c3 | |||
| 7fac1aa9cd | |||
| c30b4763c6 | |||
| a4f3d3cd7f | |||
| 92526ecacf |
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
®ion,
|
||||||
|
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,
|
||||||
|
©Region);
|
||||||
|
_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)
|
||||||
|
|||||||
Reference in New Issue
Block a user