Compare commits

..

25 Commits

Author SHA1 Message Date
ParantezTech b1bba8d99d [loader] cut import overhead 2026-07-10 01:04:59 +03:00
Berk fe1a592496 [shader-decoder-part1-hotfix] New format support, maintenance8 support, robustness improvements, and bug fixes. This commit includes updates to the AGC exports, Gen5 SPIR-V translator, and Vulkan video presenter to enhance compatibility and performance. (#31) 2026-07-07 21:06:04 +03:00
Berk 9d4dad2c24 [kernel] Added KernelExceptionCompatExports and fstat, pthread_equal (#30) 2026-07-07 21:05:54 +03:00
Foued Attar 38d94d8e54 [vulkan] enable required PhysicalDeviceFeatures for translated shaders (#29)
- Enable VertexPipelineStoresAndAtomics/FragmentStoresAndAtomics:
  fixes vkCreateGraphicsPipelines() rejecting guestBuffers storage
  descriptor as NonWritable in vertex/fragment stages.
- Enable ShaderInt64: fixes vkCreateShaderModule() rejecting SPIR-V
  using 64-bit integer capability.
- Query GetPhysicalDeviceFeatures first and only enable what the GPU
  actually reports as supported, with a warning fallback otherwise.

Also adds optional Vulkan Validation Layers (SHARPEMU_VK_VALIDATION=1)
to surface these VUID errors during development instead of silent
VK_ERROR_DEVICE_LOST.
2026-07-06 16:56:03 +03:00
ParantezTech 61a1abdb13 Merge remote-tracking branch 'refs/remotes/origin/main' 2026-07-06 09:53:04 +03:00
ParantezTech 88452218cb update readme 2026-07-06 09:52:46 +03:00
Berk 427a511e48 [unity-fixes] support for Media/Modules/*.prx files (#27) 2026-07-05 22:10:37 +03:00
Berk e103795768 [agc] new agc exports for agc improvements (sceAgcDcbDrawIndex, sceAgcDriverGetResourceRegistrationMaxNameLength, sceAgcDriverGetDefaultOwner, sceAgcDriverRegisterResource, sceAgcDriverUnknown_KRzWekV120(?)) (#26) 2026-07-05 22:10:15 +03:00
Berk 24c6e44800 [ngs2 implements] Implement NGS2 exports (#25) 2026-07-05 22:09:50 +03:00
Berk 3a30d6419f [pad improvements] Added support for scePadSetVibrationMode (#24) 2026-07-05 22:09:36 +03:00
Berk 2d8a795f23 [sceAudio] added simple audio output implementation, needs to be improved (#23) 2026-07-05 22:09:25 +03:00
Berk adb2acdb39 [sceMsgDialog] implemented sceMsgDialogInitialize (#22) 2026-07-05 22:09:14 +03:00
Berk 5e5bead02c Avplayer implements (#21) 2026-07-05 22:09:04 +03:00
Berk e98f02966a [savedata-improvements-2] added sceSaveDataCreateTransactionResource (#20) 2026-07-05 22:08:53 +03:00
ParantezTech 9ab1e2f21a reuse 2026-07-04 19:14:44 +03:00
ParantezTech 340eb9e0ab [dotnet] configure local nuget package cache 2026-07-04 19:12:18 +03:00
Berk 03e3d25e85 [fixes-assets] Add SharpEmu icon and fix glfw dependency (temporary fix) (#19)
* [fixes-assets] Add SharpEmu icon and fix glfw dependency (temporary fix)

* [reuse] I forgot to add comma

* [readme] update logo
2026-07-04 17:05:09 +03:00
Berk 2bc466b10e [gpu-hotfix1] Cache improvements have been made (6x performance gain) (#18) 2026-07-04 15:19:32 +03:00
Berk 45d0be474b Savedata fix 1 (#17)
* [scePad] Just format code

* [sceSaveData] user folder sometimes appeared in the previous folder has been fixed.
2026-07-04 15:18:21 +03:00
ParantezTech 5bb91eff9b [scePad] Just format code 2026-07-04 15:10:48 +03:00
Vlad Denisov f4df0ed4bd [padExports]: add basic gamepad mappings (#16) 2026-07-04 14:40:43 +03:00
ParantezTech 23691a2bdc [readme] update screenshot image for dreaming sarah 2026-07-04 14:18:51 +03:00
ParantezTech 5c5ed8c064 [readme] update screenshot and add new image for dreaming sarah after shader decoder updates 2026-07-04 14:03:30 +03:00
Berk a4748ce266 [shader-decoder-part1] Implemented a shader decoder (Part 1) (#12)
* [shader-decoder-part1] Implemented a shader decoder for Gen5 shaders, including IR generation, metadata reading, scalar evaluation, and SPIR-V translation. Updated related exports and video output components to support the new shader decoding functionality.

* [shader decoder] correct RDNA2 operands, fixing synchronization problems

* [shader-decoder] RDNA2 decoder improvements

* [shader-decoder] fix RDNA2 shift masking and sprite draws

* [shader-decoder] improve RDNA2 shader decoder to support more instructions and fix some issues with the previous implementation.
2026-07-04 13:51:08 +03:00
Berk 5617646c8a [ci] fix workflow dispatch and push triggers for main branch (#15) 2026-07-04 13:48:29 +03:00
43 changed files with 3829 additions and 690 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ indent_size = 4
indent_style = space indent_style = space
tab_width = 4 tab_width = 4
[*.{md,json,yml,props,csproj}] [*.{md,json,yml,xml,props,csproj}]
indent_size = 2 indent_size = 2
tab_width = 2 tab_width = 2
Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

+1 -1
View File
@@ -126,7 +126,7 @@ jobs:
needs: needs:
- init - init
- build - build
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
+1
View File
@@ -26,6 +26,7 @@ arm64/
*.userosscache *.userosscache
*.sln.docstates *.sln.docstates
.packages
packages/ packages/
*.nupkg *.nupkg
.nuget/ .nuget/
+1 -1
View File
@@ -2,7 +2,6 @@
Copyright (C) 2026 SharpEmu Emulator Project Copyright (C) 2026 SharpEmu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later SPDX-License-Identifier: GPL-2.0-or-later
--> -->
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
@@ -10,6 +9,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<ItemGroup> <ItemGroup>
<PackageVersion Include="Iced" Version="1.21.0" /> <PackageVersion Include="Iced" Version="1.21.0" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" /> <PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.23.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Windowing" Version="2.23.0" /> <PackageVersion Include="Silk.NET.Windowing" Version="2.23.0" />
</ItemGroup> </ItemGroup>
BIN
View File
Binary file not shown.
+3 -1
View File
@@ -3,12 +3,14 @@ version = 1
[[annotations]] [[annotations]]
path = [ path = [
"REUSE.toml", "REUSE.toml",
"nuget.config",
"global.json", "global.json",
"**/packages.lock.json", "**/packages.lock.json",
"scripts/ps5_names.txt", "scripts/ps5_names.txt",
"src/SharpEmu.HLE/Aerolib/aerolib.bin", "src/SharpEmu.HLE/Aerolib/aerolib.bin",
"_logs/**", "_logs/**",
".github/images/**" ".github/images/**",
"assets/images/**"
] ]
precedence = "aggregate" precedence = "aggregate"
SPDX-FileCopyrightText = "SharpEmu Emulator Project" SPDX-FileCopyrightText = "SharpEmu Emulator Project"
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="globalPackagesFolder" value=".packages" />
</config>
<packageSources>
<clear />
<add key="Nuget" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="Nuget">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
+6 -1
View File
@@ -16,7 +16,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RuntimeIdentifiers>win-x64;linux-x64;osx-arm64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;linux-x64;osx-arm64</RuntimeIdentifiers>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile> <PublishSingleFile>true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> <!-- <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> -->
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile> <EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -30,6 +30,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile> <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-x64' Or '$(RuntimeIdentifier)' == ''">
<ApplicationIcon>..\..\assets\images\SharpEmu.ico</ApplicationIcon>
<Win32Icon>..\..\assets\images\SharpEmu.ico</Win32Icon>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="..\..\LICENSE.txt"> <Content Include="..\..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+11
View File
@@ -81,6 +81,7 @@
"dependencies": { "dependencies": {
"SharpEmu.HLE": "[1.0.0, )", "SharpEmu.HLE": "[1.0.0, )",
"Silk.NET.Vulkan": "[2.23.0, )", "Silk.NET.Vulkan": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.EXT": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )", "Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
"Silk.NET.Windowing": "[2.23.0, )" "Silk.NET.Windowing": "[2.23.0, )"
} }
@@ -103,6 +104,16 @@
"Silk.NET.Core": "2.23.0" "Silk.NET.Core": "2.23.0"
} }
}, },
"Silk.NET.Vulkan.Extensions.EXT": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
"resolved": "2.23.0",
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
"dependencies": {
"Silk.NET.Core": "2.23.0",
"Silk.NET.Vulkan": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.KHR": { "Silk.NET.Vulkan.Extensions.KHR": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.23.0, )", "requested": "[2.23.0, )",
@@ -338,10 +338,15 @@ public sealed partial class DirectExecutionBackend
{ {
orbisGen2Result = DispatchBootstrapBridge(); orbisGen2Result = DispatchBootstrapBridge();
} }
else if (string.Equals(importStubEntry.Nid, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal)) else if (string.Equals(importStubEntry.Nid, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal) ||
string.Equals(importStubEntry.Nid, "LwG8g3niqwA", StringComparison.Ordinal))
{ {
orbisGen2Result = DispatchKernelDynlibDlsym(); orbisGen2Result = DispatchKernelDynlibDlsym();
} }
else if (string.Equals(importStubEntry.Nid, "r8mvOaWdi28", StringComparison.Ordinal))
{
orbisGen2Result = DispatchIl2CppApiLookupSymbol();
}
else if (importStubEntry.Export is { } cachedExport && else if (importStubEntry.Export is { } cachedExport &&
(cachedExport.Target & cpuContext.TargetGeneration) != 0) (cachedExport.Target & cpuContext.TargetGeneration) != 0)
{ {
@@ -628,25 +633,56 @@ public sealed partial class DirectExecutionBackend
"ASoW5WE-UPo" or // sceKernelAprSubmitCommandBufferAndGetResult "ASoW5WE-UPo" or // sceKernelAprSubmitCommandBufferAndGetResult
"rqwFKI4PAiM" or // sceKernelAprWaitCommandBuffer "rqwFKI4PAiM" or // sceKernelAprWaitCommandBuffer
"eE4Szl8sil8" or // sceKernelAprSubmitCommandBuffer "eE4Szl8sil8" or // sceKernelAprSubmitCommandBuffer
"qvMUCyyaCSI"; // sceKernelAprSubmitCommandBufferAndGetId "qvMUCyyaCSI" or // sceKernelAprSubmitCommandBufferAndGetId
"Q2V+iqvjgC0" or // vsnprintf
"q1cHNfGycLI" or // scePadRead
"xk0AcarP3V4" or // scePadOpen
"yH17Q6NWtVg" or // sceUserServiceGetEvent
"D-CzAxQL0XI" or // sceUserServiceGetPlatformPrivacySetting
"K-jXhbt2gn4"; // scePthreadMutexTrylock
private bool ShouldLogImportResult(string nid, OrbisGen2Result result) private bool ShouldLogImportResult(string nid, OrbisGen2Result result)
{ {
var resultValue = unchecked((int)result);
if (resultValue > 0)
{
return false;
}
var expectedFileProbeMiss = var expectedFileProbeMiss =
result == OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND && result == OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND &&
IsExpectedFileProbeNotFoundNid(nid); IsExpectedFileProbeNotFoundNid(nid);
var expectedTimedWaitTimeout = var expectedTimedWaitTimeout =
string.Equals(nid, "27bAgiJmOh0", StringComparison.Ordinal) && string.Equals(nid, "27bAgiJmOh0", StringComparison.Ordinal) &&
unchecked((int)result) == 60; unchecked((int)result) == 60;
var expectedEqueueTimeout =
string.Equals(nid, "fzyMKs9kim0", StringComparison.Ordinal) &&
result == OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
var expectedMutexTrylockBusy = var expectedMutexTrylockBusy =
string.Equals(nid, "K-jXhbt2gn4", StringComparison.Ordinal) && string.Equals(nid, "K-jXhbt2gn4", StringComparison.Ordinal) &&
result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY; result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
if (!expectedFileProbeMiss && !expectedTimedWaitTimeout && !expectedMutexTrylockBusy) var expectedUserServiceNoEvent =
string.Equals(nid, "yH17Q6NWtVg", StringComparison.Ordinal) &&
resultValue == unchecked((int)0x80960007);
var expectedPrivacyInvalidParameter =
string.Equals(nid, "D-CzAxQL0XI", StringComparison.Ordinal) &&
resultValue == unchecked((int)0x80960009);
if (!expectedFileProbeMiss &&
!expectedTimedWaitTimeout &&
!expectedEqueueTimeout &&
!expectedMutexTrylockBusy &&
!expectedUserServiceNoEvent &&
!expectedPrivacyInvalidParameter)
{ {
return true; return true;
} }
var key = nid + "\0" + (int)result; if (!ShouldLogExpectedImportResults())
{
return false;
}
var key = nid + "\0" + resultValue;
int count; int count;
lock (_importResultLogSampleGate) lock (_importResultLogSampleGate)
{ {
@@ -658,6 +694,12 @@ public sealed partial class DirectExecutionBackend
return count <= 8 || count % 10000 == 0; return count <= 8 || count % 10000 == 0;
} }
private static bool ShouldLogExpectedImportResults() =>
string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_EXPECTED_IMPORT_RESULTS"),
"1",
StringComparison.Ordinal);
private static bool IsExpectedFileProbeNotFoundNid(string nid) => private static bool IsExpectedFileProbeNotFoundNid(string nid) =>
nid is nid is
"eV9wAD2riIA" or // sceKernelStat "eV9wAD2riIA" or // sceKernelStat
@@ -1180,8 +1222,11 @@ public sealed partial class DirectExecutionBackend
cpuContext[CpuRegister.Rax] = 18446744073709551615uL; cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
return OrbisGen2Result.ORBIS_GEN2_OK; return OrbisGen2Result.ORBIS_GEN2_OK;
} }
if (!TryResolveRuntimeSymbolAddress(symbolName, out var resolvedAddress)) if (!TryResolveRuntimeSymbolAddress(symbolName, out var resolvedAddress) &&
!TryResolveRuntimeSymbolAlias(symbolName, out resolvedAddress))
{ {
Console.Error.WriteLine(
$"[LOADER][WARN] sceKernelDlsym failed: handle=0x{cpuContext[CpuRegister.Rdi]:X} symbol='{symbolName}'");
cpuContext[CpuRegister.Rax] = 18446744073709551615uL; cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
return OrbisGen2Result.ORBIS_GEN2_OK; return OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -1194,6 +1239,62 @@ public sealed partial class DirectExecutionBackend
return OrbisGen2Result.ORBIS_GEN2_OK; return OrbisGen2Result.ORBIS_GEN2_OK;
} }
private bool TryResolveRuntimeSymbolAlias(string symbolName, out ulong address)
{
address = 0;
var alias = symbolName switch
{
"scriptingGetMem" => "malloc",
"scriptingFreeMem" => "free",
"scriptingRealloc" => "realloc",
"scriptingCalloc" => "calloc",
_ => null,
};
return alias != null && TryResolveRuntimeSymbolAddress(alias, out address);
}
private OrbisGen2Result DispatchIl2CppApiLookupSymbol()
{
var cpuContext = ActiveCpuContext;
if (cpuContext == null)
{
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var symbolNameAddress = cpuContext[CpuRegister.Rdi];
var outputAddress = cpuContext[CpuRegister.Rsi];
if (!TryReadAsciiZ(symbolNameAddress, 512, out var symbolName) ||
outputAddress == 0 ||
!TryResolveIl2CppApiAddress(symbolName, out var resolvedAddress) ||
!TryWriteUInt64Compat(outputAddress, resolvedAddress))
{
Console.Error.WriteLine(
$"[LOADER][WARN] il2cpp_api_lookup_symbol failed: name='{symbolName}' out=0x{outputAddress:X16}");
if (outputAddress != 0)
{
_ = TryWriteUInt64Compat(outputAddress, 0);
}
cpuContext[CpuRegister.Rax] = ulong.MaxValue;
return OrbisGen2Result.ORBIS_GEN2_OK;
}
cpuContext[CpuRegister.Rax] = 0;
return OrbisGen2Result.ORBIS_GEN2_OK;
}
private bool TryResolveIl2CppApiAddress(string symbolName, out ulong address)
{
if (TryResolveRuntimeSymbolAddress(symbolName, out address))
{
return true;
}
return Aerolib.Instance.TryGetByExportName(symbolName, out var symbol) &&
TryResolveRuntimeSymbolAddress(symbol.Nid, out address);
}
private OrbisGen2Result DispatchBootstrapBridge() private OrbisGen2Result DispatchBootstrapBridge()
{ {
var cpuContext = ActiveCpuContext; var cpuContext = ActiveCpuContext;
+161 -54
View File
@@ -11,7 +11,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
{ {
private readonly ReaderWriterLockSlim _gate = new(LockRecursionPolicy.SupportsRecursion); private readonly ReaderWriterLockSlim _gate = new(LockRecursionPolicy.SupportsRecursion);
private readonly object _guestAllocationGate = new(); private readonly object _guestAllocationGate = new();
private readonly object _allocationSearchHintGate = new();
private readonly List<MemoryRegion> _regions = new(); private readonly List<MemoryRegion> _regions = new();
private readonly Dictionary<(ulong DesiredAddress, ulong Alignment, bool Executable), ulong> _allocationSearchHints = new();
private readonly Dictionary<ulong, ProgramHeaderFlags> _pageProtections = new(); private readonly Dictionary<ulong, ProgramHeaderFlags> _pageProtections = new();
private bool _disposed; private bool _disposed;
private const ulong PageSize = 0x1000; private const ulong PageSize = 0x1000;
@@ -19,7 +21,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private const ulong GuestAllocationArenaSize = 0x0100_0000; private const ulong GuestAllocationArenaSize = 0x0100_0000;
private const ulong GuestAllocationArenaStartOffset = PageSize; private const ulong GuestAllocationArenaStartOffset = PageSize;
private const ulong LargeDataReserveThreshold = 0x4000_0000UL; // 1 GiB private const ulong LargeDataReserveThreshold = 0x4000_0000UL; // 1 GiB
private const ulong LazyReservePrimeBytes = 0x5000_0000UL; // 1.25 GiB private const ulong FullCommitRegionLimit = 4UL << 30;
private const ulong DefaultLazyReservePrimeBytes = 0x0400_0000UL; // 64 MiB
private const ulong LazyReservePrimeChunkBytes = 0x0200_0000UL; // 32 MiB private const ulong LazyReservePrimeChunkBytes = 0x0200_0000UL; // 32 MiB
private const uint MEM_COMMIT = 0x1000; private const uint MEM_COMMIT = 0x1000;
@@ -35,6 +38,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private ulong _guestAllocationArenaBase; private ulong _guestAllocationArenaBase;
private ulong _guestAllocationOffset; private ulong _guestAllocationOffset;
private static readonly ulong LazyReservePrimeBytes = ResolveLazyReservePrimeBytes();
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
private static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, uint flAllocationType, uint flProtect); private static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, uint flAllocationType, uint flProtect);
@@ -81,7 +85,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterWriteLock(); _gate.EnterWriteLock();
try try
{ {
_regions.Add(new MemoryRegion InsertRegionSorted(new MemoryRegion
{ {
VirtualAddress = actualAddress, VirtualAddress = actualAddress,
Size = alignedSize, Size = alignedSize,
@@ -96,7 +100,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
} }
var allocationKind = executable ? "executable memory" : "data memory"; var allocationKind = executable ? "executable memory" : "data memory";
Console.Error.WriteLine($"[VMEM] Allocated exact {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes)"); TraceVmem($"Allocated exact {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes)");
return true; return true;
} }
@@ -110,7 +114,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
var protection = executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; var protection = executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
var allocationType = MEM_COMMIT | MEM_RESERVE; var allocationType = MEM_COMMIT | MEM_RESERVE;
var reservedOnly = false; var reservedOnly = false;
var preferReserveOnly = !executable && alignedSize >= LargeDataReserveThreshold; var preferReserveOnly = !executable &&
alignedSize >= LargeDataReserveThreshold &&
alignedSize > FullCommitRegionLimit;
void* result = null; void* result = null;
if (preferReserveOnly) if (preferReserveOnly)
@@ -139,7 +145,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
throw new InvalidOperationException($"Failed to allocate exact mapping at 0x{desiredAddress:X16} ({alignedSize} bytes)"); throw new InvalidOperationException($"Failed to allocate exact mapping at 0x{desiredAddress:X16} ({alignedSize} bytes)");
} }
Console.Error.WriteLine($"[VMEM] Could not allocate at 0x{desiredAddress:X16}, trying any address..."); TraceVmem($"Could not allocate at 0x{desiredAddress:X16}, trying any address...");
result = VirtualAlloc(null, (nuint)alignedSize, allocationType, protection); result = VirtualAlloc(null, (nuint)alignedSize, allocationType, protection);
if (result == null) if (result == null)
@@ -193,12 +199,12 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
lazyPrimeState = committedBytes == primeBytes lazyPrimeState = committedBytes == primeBytes
? $"ok:{committedBytes:X}" ? $"ok:{committedBytes:X}"
: $"partial:{committedBytes:X}/{primeBytes:X}"; : $"partial:{committedBytes:X}/{primeBytes:X}";
Console.Error.WriteLine($"[VMEM] Primed lazy region: 0x{actualAddress:X16} - 0x{actualAddress + committedBytes:X16} ({committedBytes} bytes)"); TraceVmem($"Primed lazy region: 0x{actualAddress:X16} - 0x{actualAddress + committedBytes:X16} ({committedBytes} bytes)");
} }
else else
{ {
lazyPrimeState = $"fail:{primeBytes:X}"; lazyPrimeState = $"fail:{primeBytes:X}";
Console.Error.WriteLine($"[VMEM] Failed to prime lazy region at 0x{actualAddress:X16} ({primeBytes} bytes), continuing with on-demand commit"); TraceVmem($"Failed to prime lazy region at 0x{actualAddress:X16} ({primeBytes} bytes), continuing with on-demand commit");
} }
} }
else else
@@ -210,7 +216,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterWriteLock(); _gate.EnterWriteLock();
try try
{ {
_regions.Add(new MemoryRegion InsertRegionSorted(new MemoryRegion
{ {
VirtualAddress = actualAddress, VirtualAddress = actualAddress,
Size = alignedSize, Size = alignedSize,
@@ -227,7 +233,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
var allocationKind = reservedOnly var allocationKind = reservedOnly
? "reserved data memory (lazy commit)" ? "reserved data memory (lazy commit)"
: (executable ? "executable memory" : "data memory"); : (executable ? "executable memory" : "data memory");
Console.Error.WriteLine($"[VMEM] Allocated {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes) lazy_prime={lazyPrimeState}"); TraceVmem($"Allocated {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes) lazy_prime={lazyPrimeState}");
return actualAddress; return actualAddress;
} }
@@ -247,7 +253,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
var alignedSize = AlignUp(size, PageSize); var alignedSize = AlignUp(size, PageSize);
var effectiveAlignment = Math.Max(PageSize, alignment == 0 ? PageSize : alignment); var effectiveAlignment = Math.Max(PageSize, alignment == 0 ? PageSize : alignment);
var cursor = AlignUp(desiredAddress, effectiveAlignment); var requestedCursor = AlignUp(desiredAddress, effectiveAlignment);
var cursor = GetAllocationSearchCursor(desiredAddress, requestedCursor, effectiveAlignment, executable);
for (var attempt = 0; attempt < 0x10000; attempt++) for (var attempt = 0; attempt < 0x10000; attempt++)
{ {
@@ -267,6 +274,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
actualAddress = AllocateAt(cursor, alignedSize, executable, allowAlternative: false); actualAddress = AllocateAt(cursor, alignedSize, executable, allowAlternative: false);
if (actualAddress == cursor) if (actualAddress == cursor)
{ {
UpdateAllocationSearchCursor(desiredAddress, effectiveAlignment, executable, actualAddress + alignedSize);
return true; return true;
} }
@@ -334,6 +342,10 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
} }
_regions.Clear(); _regions.Clear();
_pageProtections.Clear(); _pageProtections.Clear();
lock (_allocationSearchHintGate)
{
_allocationSearchHints.Clear();
}
} }
finally finally
{ {
@@ -390,7 +402,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
ApplySegmentProtection(mapStart, mapEnd, protection); ApplySegmentProtection(mapStart, mapEnd, protection);
Console.Error.WriteLine($"[VMEM] Mapped segment: 0x{virtualAddress:X16} - 0x{virtualAddress + memorySize:X16} (file: {fileData.Length} bytes, prot: {protection})"); TraceVmem($"Mapped segment: 0x{virtualAddress:X16} - 0x{virtualAddress + memorySize:X16} (file: {fileData.Length} bytes, prot: {protection})");
} }
finally finally
{ {
@@ -473,9 +485,13 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock(); _gate.EnterReadLock();
try try
{ {
foreach (var region in _regions) var region = FindRegion(virtualAddress, (ulong)destination.Length);
{ if (region is not null &&
if (TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset)) TryResolveRegionOffset(
virtualAddress,
(ulong)destination.Length,
region,
out var offset))
{ {
var srcPtr = (void*)(region.VirtualAddress + offset); var srcPtr = (void*)(region.VirtualAddress + offset);
if (destination.IsEmpty) if (destination.IsEmpty)
@@ -494,9 +510,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
if (!CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region)) if (!CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region))
{ {
requiresExclusiveAccess = true; requiresExclusiveAccess = true;
break;
} }
else
{
fixed (byte* destPtr = destination) fixed (byte* destPtr = destination)
{ {
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length); Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length);
@@ -533,9 +549,13 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock(); _gate.EnterReadLock();
try try
{ {
foreach (var region in _regions) var region = FindRegion(virtualAddress, (ulong)source.Length);
{ if (region is not null &&
if (TryResolveRegionOffset(virtualAddress, (ulong)source.Length, region, out var offset)) TryResolveRegionOffset(
virtualAddress,
(ulong)source.Length,
region,
out var offset))
{ {
var destPtr = (void*)(region.VirtualAddress + offset); var destPtr = (void*)(region.VirtualAddress + offset);
if (source.IsEmpty) if (source.IsEmpty)
@@ -554,9 +574,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
if (!CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region)) if (!CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region))
{ {
requiresExclusiveAccess = true; requiresExclusiveAccess = true;
break;
} }
else
{
fixed (byte* srcPtr = source) fixed (byte* srcPtr = source)
{ {
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)source.Length, (nuint)source.Length); Buffer.MemoryCopy(srcPtr, destPtr, (nuint)source.Length, (nuint)source.Length);
@@ -589,13 +609,14 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private bool TryReadExclusive(ulong virtualAddress, Span<byte> destination) private bool TryReadExclusive(ulong virtualAddress, Span<byte> destination)
{ {
foreach (var region in _regions) var region = FindRegion(virtualAddress, (ulong)destination.Length);
if (region is not null &&
TryResolveRegionOffset(
virtualAddress,
(ulong)destination.Length,
region,
out var offset))
{ {
if (!TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset))
{
continue;
}
var srcPtr = (void*)(region.VirtualAddress + offset); var srcPtr = (void*)(region.VirtualAddress + offset);
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region)) if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
{ {
@@ -637,13 +658,14 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private bool TryWriteExclusive(ulong virtualAddress, ReadOnlySpan<byte> source) private bool TryWriteExclusive(ulong virtualAddress, ReadOnlySpan<byte> source)
{ {
foreach (var region in _regions) var region = FindRegion(virtualAddress, (ulong)source.Length);
if (region is not null &&
TryResolveRegionOffset(
virtualAddress,
(ulong)source.Length,
region,
out var offset))
{ {
if (!TryResolveRegionOffset(virtualAddress, (ulong)source.Length, region, out var offset))
{
continue;
}
var destPtr = (void*)(region.VirtualAddress + offset); var destPtr = (void*)(region.VirtualAddress + offset);
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region)) if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
{ {
@@ -699,15 +721,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock(); _gate.EnterReadLock();
try try
{ {
foreach (var region in _regions) return FindRegion(virtualAddress, 1) is not null
{ ? (void*)virtualAddress
if (virtualAddress >= region.VirtualAddress && : null;
virtualAddress < region.VirtualAddress + region.Size)
{
return (void*)virtualAddress;
}
}
return null;
} }
finally finally
{ {
@@ -720,14 +736,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock(); _gate.EnterReadLock();
try try
{ {
foreach (var region in _regions) return FindRegion(virtualAddress, size) is not null;
{
if (TryResolveRegionOffset(virtualAddress, size, region, out _))
{
return true;
}
}
return false;
} }
finally finally
{ {
@@ -737,14 +746,48 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private MemoryRegion? FindRegion(ulong address, ulong size) private MemoryRegion? FindRegion(ulong address, ulong size)
{ {
foreach (var region in _regions) var low = 0;
var high = _regions.Count - 1;
MemoryRegion? candidate = null;
while (low <= high)
{ {
if (TryResolveRegionOffset(address, size, region, out _)) var middle = low + ((high - low) >> 1);
var region = _regions[middle];
if (region.VirtualAddress <= address)
{ {
return region; candidate = region;
low = middle + 1;
}
else
{
high = middle - 1;
} }
} }
return null;
return candidate is not null &&
TryResolveRegionOffset(address, size, candidate, out _)
? candidate
: null;
}
private void InsertRegionSorted(MemoryRegion region)
{
var low = 0;
var high = _regions.Count;
while (low < high)
{
var middle = low + ((high - low) >> 1);
if (_regions[middle].VirtualAddress < region.VirtualAddress)
{
low = middle + 1;
}
else
{
high = middle;
}
}
_regions.Insert(low, region);
} }
private bool TryGetOverlappingRegionEnd(ulong address, ulong size, out ulong overlapEnd) private bool TryGetOverlappingRegionEnd(ulong address, ulong size, out ulong overlapEnd)
@@ -762,6 +805,16 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
foreach (var region in _regions) foreach (var region in _regions)
{ {
var regionEnd = region.VirtualAddress + region.Size; var regionEnd = region.VirtualAddress + region.Size;
if (region.VirtualAddress >= end)
{
break;
}
if (regionEnd <= address)
{
continue;
}
if (address < regionEnd && region.VirtualAddress < end) if (address < regionEnd && region.VirtualAddress < end)
{ {
overlapEnd = Math.Max(overlapEnd, regionEnd); overlapEnd = Math.Max(overlapEnd, regionEnd);
@@ -776,6 +829,37 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
return overlapEnd != 0; return overlapEnd != 0;
} }
private ulong GetAllocationSearchCursor(
ulong desiredAddress,
ulong requestedCursor,
ulong alignment,
bool executable)
{
lock (_allocationSearchHintGate)
{
var key = (desiredAddress, alignment, executable);
if (_allocationSearchHints.TryGetValue(key, out var hintedCursor) &&
hintedCursor > requestedCursor)
{
return AlignUp(hintedCursor, alignment);
}
}
return requestedCursor;
}
private void UpdateAllocationSearchCursor(
ulong desiredAddress,
ulong alignment,
bool executable,
ulong nextCursor)
{
lock (_allocationSearchHintGate)
{
_allocationSearchHints[(desiredAddress, alignment, executable)] = AlignUp(nextCursor, alignment);
}
}
private static bool TryResolveRegionOffset(ulong address, ulong size, MemoryRegion region, out ulong offset) private static bool TryResolveRegionOffset(ulong address, ulong size, MemoryRegion region, out ulong offset)
{ {
offset = 0; offset = 0;
@@ -944,6 +1028,29 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
return checked((value + mask) & ~mask); return checked((value + mask) & ~mask);
} }
private static ulong ResolveLazyReservePrimeBytes()
{
var configured = Environment.GetEnvironmentVariable("SHARPEMU_LAZY_RESERVE_PRIME_MB");
if (ulong.TryParse(configured, out var megabytes))
{
return megabytes == 0
? 0
: checked(Math.Min(megabytes, 4096UL) * 1024UL * 1024UL);
}
return DefaultLazyReservePrimeBytes;
}
private static void TraceVmem(string message)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_VMEM"), "1", StringComparison.Ordinal))
{
return;
}
Console.Error.WriteLine($"[VMEM] {message}");
}
public void Dispose() public void Dispose()
{ {
if (!_disposed) if (!_disposed)
+4 -2
View File
@@ -543,6 +543,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
{ {
Path.Combine(ebootDirectory, "sce_module"), Path.Combine(ebootDirectory, "sce_module"),
Path.Combine(ebootDirectory, "sce_modules"), Path.Combine(ebootDirectory, "sce_modules"),
Path.Combine(ebootDirectory, "Media", "Modules"),
} }
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.Where(Directory.Exists) .Where(Directory.Exists)
@@ -554,7 +555,9 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
} }
var allModulePaths = moduleDirectories var allModulePaths = moduleDirectories
.SelectMany(Directory.EnumerateFiles) .SelectMany(directory => Directory
.EnumerateFiles(directory)
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase))
.Where(path => .Where(path =>
{ {
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path);
@@ -562,7 +565,6 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
string.Equals(extension, ".sprx", StringComparison.OrdinalIgnoreCase); string.Equals(extension, ".sprx", StringComparison.OrdinalIgnoreCase);
}) })
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
.ToArray(); .ToArray();
var modulePaths = allModulePaths var modulePaths = allModulePaths
+11
View File
@@ -72,6 +72,7 @@
"dependencies": { "dependencies": {
"SharpEmu.HLE": "[1.0.0, )", "SharpEmu.HLE": "[1.0.0, )",
"Silk.NET.Vulkan": "[2.23.0, )", "Silk.NET.Vulkan": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.EXT": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )", "Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
"Silk.NET.Windowing": "[2.23.0, )" "Silk.NET.Windowing": "[2.23.0, )"
} }
@@ -88,6 +89,16 @@
"Silk.NET.Core": "2.23.0" "Silk.NET.Core": "2.23.0"
} }
}, },
"Silk.NET.Vulkan.Extensions.EXT": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
"resolved": "2.23.0",
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
"dependencies": {
"Silk.NET.Core": "2.23.0",
"Silk.NET.Vulkan": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.KHR": { "Silk.NET.Vulkan.Extensions.KHR": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.23.0, )", "requested": "[2.23.0, )",
+254 -18
View File
@@ -142,7 +142,7 @@ public static class AgcExports
private static readonly HashSet<uint> _tracedDcbSizes = new(); private static readonly HashSet<uint> _tracedDcbSizes = new();
private static readonly HashSet<(ulong Es, ulong Ps, GuestDrawKind Kind)> _tracedShaderTranslations = new(); private static readonly HashSet<(ulong Es, ulong Ps, GuestDrawKind Kind)> _tracedShaderTranslations = new();
private static readonly HashSet<(ulong Es, ulong Ps)> _tracedShaderDecodePairs = new(); private static readonly HashSet<(ulong Es, ulong Ps)> _tracedShaderDecodePairs = new();
private static readonly HashSet<(ulong Es, ulong Ps, ulong Target)> _tracedShaderDraws = new(); private static readonly HashSet<(ulong Es, ulong Ps, ulong Target, ulong Texture, uint VertexCount)> _tracedShaderDraws = new();
private static readonly HashSet<(ulong Ps, string Error)> _tracedShaderFailures = new(); private static readonly HashSet<(ulong Ps, string Error)> _tracedShaderFailures = new();
private static readonly HashSet<(int Handle, int Index, ulong Address, string Path)> _tracedDisplayBuffers = new(); private static readonly HashSet<(int Handle, int Index, ulong Address, string Path)> _tracedDisplayBuffers = new();
private static readonly HashSet<ulong> _tracedComputeShaders = new(); private static readonly HashSet<ulong> _tracedComputeShaders = new();
@@ -155,6 +155,16 @@ public static class AgcExports
(ulong Cs, ulong State, uint LocalX, uint LocalY, uint LocalZ), (ulong Cs, ulong State, uint LocalX, uint LocalY, uint LocalZ),
byte[]> _computeSpirvCache = new(); byte[]> _computeSpirvCache = new();
private static readonly Dictionary<ulong, ulong> _shaderHeadersByCode = new(); private static readonly Dictionary<ulong, ulong> _shaderHeadersByCode = new();
private static readonly bool _traceAgc = string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"),
"1",
StringComparison.Ordinal);
private static readonly bool _traceAgcShader =
_traceAgc ||
string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC_SHADER"),
"1",
StringComparison.Ordinal);
private static long _dcbWriteDataTraceCount; private static long _dcbWriteDataTraceCount;
private static long _dcbWaitRegMemTraceCount; private static long _dcbWaitRegMemTraceCount;
private static long _createShaderTraceCount; private static long _createShaderTraceCount;
@@ -1194,6 +1204,51 @@ public static class AgcExports
return ReturnPointer(ctx, commandAddress); return ReturnPointer(ctx, commandAddress);
} }
[SysAbiExport(
Nid = "q88lQ+GP5Yk",
ExportName = "sceAgcDcbDrawIndex",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbDrawIndex(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var indexCount = (uint)ctx[CpuRegister.Rsi];
var indexAddress = ctx[CpuRegister.Rdx];
var modifier = (uint)ctx[CpuRegister.Rcx];
if (commandBufferAddress == 0 || modifier != 0x4000_0000)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var baseCommand) ||
!TryWriteUInt32(ctx, baseCommand, Pm4(3, ItIndexBase, 0)) ||
!TryWriteUInt32(ctx, baseCommand + 4, (uint)indexAddress) ||
!TryWriteUInt32(ctx, baseCommand + 8, (uint)(indexAddress >> 32)) ||
!TryWriteUInt32(ctx, baseCommand + 12, Pm4(2, ItIndexBufferSize, 0)) ||
!TryWriteUInt32(ctx, baseCommand + 16, indexCount))
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var drawCommand) ||
!TryWriteUInt32(ctx, drawCommand, Pm4(5, ItDrawIndex2, 0)) ||
!TryWriteUInt32(ctx, drawCommand + 4, indexCount) ||
!TryWriteUInt32(ctx, drawCommand + 8, 0) ||
!TryWriteUInt32(ctx, drawCommand + 12, 0) ||
!TryWriteUInt32(ctx, drawCommand + 16, 0))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.dcb_draw_index buf=0x{commandBufferAddress:X16} " +
$"base=0x{baseCommand:X16} draw=0x{drawCommand:X16} " +
$"count={indexCount} index=0x{indexAddress:X16}");
return ReturnPointer(ctx, drawCommand);
}
[SysAbiExport( [SysAbiExport(
Nid = "Yw0jKSqop+E", Nid = "Yw0jKSqop+E",
ExportName = "sceAgcDcbDrawIndexAuto", ExportName = "sceAgcDcbDrawIndexAuto",
@@ -1996,6 +2051,88 @@ public static class AgcExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "uJziRsODk1c",
ExportName = "sceAgcDriverGetResourceRegistrationMaxNameLength",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DriverGetResourceRegistrationMaxNameLength(CpuContext ctx)
{
var outAddress = ctx[CpuRegister.Rdi];
if (outAddress == 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!TryWriteUInt32(ctx, outAddress, 256))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceAgc($"agc.driver_get_resource_registration_max_name_length out=0x{outAddress:X16} value=256");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
private const uint DefaultAgcOwner = 1;
[SysAbiExport(
Nid = "F0ZXt5q0ZTA",
ExportName = "sceAgcDriverGetDefaultOwner",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DriverGetDefaultOwner(CpuContext ctx)
{
var ownerAddress = ctx[CpuRegister.Rdi];
if (ownerAddress == 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!TryWriteUInt32(ctx, ownerAddress, DefaultAgcOwner))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceAgc($"agc.driver_get_default_owner out=0x{ownerAddress:X16} owner={DefaultAgcOwner}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "W5z4eZrjEas",
ExportName = "sceAgcDriverRegisterResource",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DriverRegisterResource(CpuContext ctx)
{
var resourceAddress = ctx[CpuRegister.Rdi];
var owner = (uint)ctx[CpuRegister.Rsi];
var nameAddress = ctx[CpuRegister.Rdx];
var type = (uint)ctx[CpuRegister.R8];
var flags = (uint)ctx[CpuRegister.R9];
TraceAgc(
$"agc.driver_register_resource resource=0x{resourceAddress:X16} owner={owner} " +
$"name=0x{nameAddress:X16} type={type} flags={flags}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "-KRzWekV120",
ExportName = "sceAgcDriverUnknown_KRzWekV120",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DriverUnknownKRzWekV120(CpuContext ctx)
{
TraceAgc(
$"agc.driver_unknown_krz rdi=0x{ctx[CpuRegister.Rdi]:X16} " +
$"rsi=0x{ctx[CpuRegister.Rsi]:X16} rdx=0x{ctx[CpuRegister.Rdx]:X16} " +
$"rcx=0x{ctx[CpuRegister.Rcx]:X16} r8=0x{ctx[CpuRegister.R8]:X16} r9=0x{ctx[CpuRegister.R9]:X16}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport( [SysAbiExport(
Nid = "h9z6+0hEydk", Nid = "h9z6+0hEydk",
ExportName = "sceAgcSuspendPoint", ExportName = "sceAgcSuspendPoint",
@@ -2788,7 +2925,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 +3009,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 +3171,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,
@@ -3466,7 +3609,7 @@ public static class AgcExports
',', ',',
draw.GlobalMemoryBindings.Select((binding, index) => draw.GlobalMemoryBindings.Select((binding, index) =>
$"{index}:0x{binding.BaseAddress:X16}:{binding.Data.Length}:" + $"{index}:0x{binding.BaseAddress:X16}:{binding.Data.Length}:" +
Convert.ToHexString(binding.Data.AsSpan(0, Math.Min(binding.Data.Length, 32))))); Convert.ToHexString(binding.Data.AsSpan(0, Math.Min(binding.Data.Length, 256)))));
var indices = draw.IndexBuffer is { } indexBuffer var indices = draw.IndexBuffer is { } indexBuffer
? $"{(indexBuffer.Is32Bit ? 32 : 16)}:" + ? $"{(indexBuffer.Is32Bit ? 32 : 16)}:" +
Convert.ToHexString(indexBuffer.Data.AsSpan(0, Math.Min(indexBuffer.Data.Length, 32))) Convert.ToHexString(indexBuffer.Data.AsSpan(0, Math.Min(indexBuffer.Data.Length, 32)))
@@ -3477,7 +3620,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 +3630,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 +3660,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 +3718,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,
@@ -3574,12 +3744,15 @@ public static class AgcExports
descriptor.Width > 8192 || descriptor.Width > 8192 ||
descriptor.Height > 8192) descriptor.Height > 8192)
{ {
texture = CreateFallbackGuestDrawTexture(isStorage); texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
return true; return true;
} }
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,
@@ -3589,7 +3762,32 @@ public static class AgcExports
sourceByteCount > MaxPresentedTextureBytes || sourceByteCount > MaxPresentedTextureBytes ||
sourceByteCount > int.MaxValue) sourceByteCount > int.MaxValue)
{ {
texture = CreateFallbackGuestDrawTexture(isStorage); texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
return true;
}
if (!isStorage &&
descriptor.Address != 0 &&
VulkanVideoPresenter.IsGuestImageAvailable(
descriptor.Address,
descriptor.Format,
descriptor.NumberType))
{
texture = new VulkanGuestDrawTexture(
descriptor.Address,
descriptor.Width,
descriptor.Height,
descriptor.Format,
descriptor.NumberType,
[],
IsFallback: false,
IsStorage: false,
MipLevels: descriptor.MipLevels,
MipLevel: mipLevel,
Pitch: sourceWidth,
TileMode: descriptor.TileMode,
DstSelect: descriptor.DstSelect,
Sampler: ToVulkanSampler(samplerDescriptor));
return true; return true;
} }
@@ -3617,7 +3815,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));
@@ -3627,7 +3825,7 @@ public static class AgcExports
var source = new byte[(int)sourceByteCount]; var source = new byte[(int)sourceByteCount];
if (!ctx.Memory.TryRead(descriptor.Address, source)) if (!ctx.Memory.TryRead(descriptor.Address, source))
{ {
texture = CreateFallbackGuestDrawTexture(isStorage); texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
return true; return true;
} }
@@ -3663,25 +3861,32 @@ 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));
return true; return true;
} }
private static VulkanGuestDrawTexture CreateFallbackGuestDrawTexture(bool isStorage = false) => private static VulkanGuestDrawTexture CreateFallbackGuestDrawTexture(
new( bool isStorage,
uint format,
uint numberType)
{
var fallbackFormat = format == 0 ? 10u : format;
var fallbackNumberType = numberType;
return new(
0, 0,
1, 1,
1, 1,
56, fallbackFormat,
NumberType: 0, fallbackNumberType,
[0, 0, 0, 255], [0, 0, 0, 255],
IsFallback: true, IsFallback: true,
IsStorage: isStorage, IsStorage: isStorage,
MipLevels: 1, MipLevels: 1,
MipLevel: 0); MipLevel: 0);
}
private static VulkanGuestSampler ToVulkanSampler(IReadOnlyList<uint> descriptor) => private static VulkanGuestSampler ToVulkanSampler(IReadOnlyList<uint> descriptor) =>
descriptor.Count >= 4 descriptor.Count >= 4
@@ -4326,6 +4531,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 +4821,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
@@ -5343,7 +5574,7 @@ public static class AgcExports
private static void TraceAgc(string message) private static void TraceAgc(string message)
{ {
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal)) if (!_traceAgc)
{ {
return; return;
} }
@@ -5353,8 +5584,7 @@ public static class AgcExports
private static void TraceAgcShader(string message) private static void TraceAgcShader(string message)
{ {
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal) && if (!_traceAgcShader)
!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC_SHADER"), "1", StringComparison.Ordinal))
{ {
return; return;
} }
@@ -5367,6 +5597,12 @@ public static class AgcExports
? "none" ? "none"
: string.Join(',', values.Select(static value => $"{value:X8}")); : string.Join(',', values.Select(static value => $"{value:X8}"));
private static string FormatTextureDescriptor(TextureDescriptor descriptor) =>
$"addr=0x{descriptor.Address:X16} {descriptor.Width}x{descriptor.Height} " +
$"fmt={descriptor.Format} num={descriptor.NumberType} tile={descriptor.TileMode} " +
$"type={descriptor.Type} levels={descriptor.BaseLevel}-{descriptor.LastLevel} " +
$"pitch={descriptor.Pitch} dst=0x{descriptor.DstSelect:X3}";
private static void DumpSpirv( private static void DumpSpirv(
string stage, string stage,
ulong shaderAddress, ulong shaderAddress,
+2
View File
@@ -276,6 +276,8 @@ internal sealed record Gen5VertexInputBinding(
uint Pc, uint Pc,
uint Location, uint Location,
uint ComponentCount, uint ComponentCount,
uint DataFormat,
uint NumberFormat,
ulong BaseAddress, ulong BaseAddress,
uint Stride, uint Stride,
uint OffsetBytes, uint OffsetBytes,
@@ -20,7 +20,9 @@ internal static class Gen5ShaderScalarEvaluator
ulong BaseAddress, ulong BaseAddress,
uint Stride, uint Stride,
uint NumRecords, uint NumRecords,
ulong SizeBytes); ulong SizeBytes,
uint NumberFormat,
uint DataFormat);
public static bool TryResolveImageBindings( public static bool TryResolveImageBindings(
CpuContext ctx, CpuContext ctx,
@@ -400,14 +402,21 @@ internal static class Gen5ShaderScalarEvaluator
return false; return false;
} }
var bindingData = data;
var bindingStride = descriptor.Stride;
var bindingOffset = unchecked((uint)control.OffsetBytes + scalarOffset);
var bindingDataFormat = descriptor.DataFormat;
var bindingNumberFormat = descriptor.NumberFormat;
binding = new Gen5VertexInputBinding( binding = new Gen5VertexInputBinding(
instruction.Pc, instruction.Pc,
location, location,
control.DwordCount, control.DwordCount,
bindingDataFormat,
bindingNumberFormat,
descriptor.BaseAddress, descriptor.BaseAddress,
descriptor.Stride, bindingStride,
unchecked((uint)control.OffsetBytes + scalarOffset), bindingOffset,
data); bindingData);
return true; return true;
} }
@@ -803,7 +812,11 @@ internal static class Gen5ShaderScalarEvaluator
"SFF1I32B32" => left == 0 ? uint.MaxValue : (uint)BitOperations.TrailingZeroCount(left), "SFF1I32B32" => left == 0 ? uint.MaxValue : (uint)BitOperations.TrailingZeroCount(left),
_ => registers[destination.Value] | (1u << ((int)left & 31)), _ => registers[destination.Value] | (1u << ((int)left & 31)),
}; };
if (instruction.Opcode != "SBitset1B32")
{
scalarConditionCode = registers[destination.Value] != 0; 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; {
var wide = ((ulong)left << 1) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break; break;
}
case "SLshl2AddU32": case "SLshl2AddU32":
result = (left << 2) + right; {
var wide = ((ulong)left << 2) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break; break;
}
case "SLshl3AddU32": case "SLshl3AddU32":
result = (left << 3) + right; {
var wide = ((ulong)left << 3) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break; break;
}
case "SLshl4AddU32": case "SLshl4AddU32":
result = (left << 4) + right; {
var wide = ((ulong)left << 4) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break; break;
}
case "SPackLlB32B16": case "SPackLlB32B16":
result = (left & 0xFFFFu) | (right << 16); result = (left & 0xFFFFu) | (right << 16);
break; break;
@@ -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,
+48 -3
View File
@@ -4,6 +4,7 @@
using SharpEmu.HLE; using SharpEmu.HLE;
using SharpEmu.Libs.VideoOut; using SharpEmu.Libs.VideoOut;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
namespace SharpEmu.Libs.Agc; namespace SharpEmu.Libs.Agc;
@@ -13,6 +14,14 @@ internal static class Gen5ShaderTranslator
private const int MaxInstructions = 4096; private const int MaxInstructions = 4096;
private const int MinimumUserDataDwords = 16; private const int MinimumUserDataDwords = 16;
private const int MaximumUserDataDwords = 256; private const int MaximumUserDataDwords = 256;
private static readonly ConditionalWeakTable<object, ShaderDecodeCache> _decodeCaches = new();
private sealed class ShaderDecodeCache
{
public object Gate { get; } = new();
public Dictionary<ulong, Gen5ShaderProgram> Programs { get; } = new();
public Dictionary<ulong, Gen5ShaderMetadata?> Metadata { get; } = new();
}
private static readonly uint[] FullscreenBarycentricEs = private static readonly uint[] FullscreenBarycentricEs =
[ [
@@ -117,18 +126,53 @@ internal static class Gen5ShaderTranslator
uint userDataScalarRegisterBase = 0) uint userDataScalarRegisterBase = 0)
{ {
state = default!; state = default!;
if (!TryDecodeProgram(ctx, shaderAddress, out var program, out error)) error = string.Empty;
var cache = _decodeCaches.GetValue(ctx.Memory, static _ => new ShaderDecodeCache());
Gen5ShaderProgram? program;
lock (cache.Gate)
{
cache.Programs.TryGetValue(shaderAddress, out program);
}
if (program is null)
{
if (!TryDecodeProgram(ctx, shaderAddress, out program, out error))
{ {
return false; return false;
} }
lock (cache.Gate)
{
cache.Programs.TryAdd(shaderAddress, program);
}
}
Gen5ShaderMetadata? metadata = null; Gen5ShaderMetadata? metadata = null;
if (shaderHeaderAddress != 0 && if (shaderHeaderAddress != 0)
Gen5ShaderMetadataReader.TryRead(ctx, shaderHeaderAddress, out var decodedMetadata)) {
var metadataCached = false;
lock (cache.Gate)
{
metadataCached = cache.Metadata.TryGetValue(shaderHeaderAddress, out metadata);
}
if (!metadataCached)
{
if (Gen5ShaderMetadataReader.TryRead(
ctx,
shaderHeaderAddress,
out var decodedMetadata))
{ {
metadata = decodedMetadata; metadata = decodedMetadata;
} }
lock (cache.Gate)
{
cache.Metadata.TryAdd(shaderHeaderAddress, metadata);
}
}
}
var userData = new uint[GetUserDataDwordCount(metadata)]; var userData = new uint[GetUserDataDwordCount(metadata)];
for (uint index = 0; index < userData.Length; index++) for (uint index = 0; index < userData.Length; index++)
{ {
@@ -452,6 +496,7 @@ internal static class Gen5ShaderTranslator
0x2A => "SNorSaveexecB64", 0x2A => "SNorSaveexecB64",
0x2B => "SXnorSaveexecB64", 0x2B => "SXnorSaveexecB64",
0x37 => "SAndn1SaveexecB64", 0x37 => "SAndn1SaveexecB64",
0x38 => "SOrn1SaveexecB64",
_ => string.Empty, _ => string.Empty,
}; };
+266 -24
View File
@@ -212,12 +212,26 @@ internal static partial class Gen5SpirvTranslator
case "VSinF32": case "VSinF32":
result = EmitFloatResult( result = EmitFloatResult(
instruction, instruction,
Ext(13, _floatType, GetFloatSource(instruction, 0))); Ext(
13,
_floatType,
_module.AddInstruction(
SpirvOp.FMul,
_floatType,
GetFloatSource(instruction, 0),
Float(MathF.Tau))));
break; break;
case "VCosF32": case "VCosF32":
result = EmitFloatResult( result = EmitFloatResult(
instruction, instruction,
Ext(14, _floatType, GetFloatSource(instruction, 0))); Ext(
14,
_floatType,
_module.AddInstruction(
SpirvOp.FMul,
_floatType,
GetFloatSource(instruction, 0),
Float(MathF.Tau))));
break; break;
case "VAddF32": case "VAddF32":
result = EmitFloatBinary(instruction, SpirvOp.FAdd); result = EmitFloatBinary(instruction, SpirvOp.FAdd);
@@ -635,13 +649,29 @@ internal static partial class Gen5SpirvTranslator
width); width);
break; break;
} }
case "VBfiB32":
{
var mask = GetRawSource(instruction, 0);
var insert = GetRawSource(instruction, 1);
var source = GetRawSource(instruction, 2);
result = _module.AddInstruction(
SpirvOp.BitwiseOr,
_uintType,
BitwiseAnd(mask, insert),
BitwiseAnd(
_module.AddInstruction(SpirvOp.Not, _uintType, mask),
source));
break;
}
case "VCvtPkrtzF16F32": case "VCvtPkrtzF16F32":
{ {
var first = TruncateFloat32ForPack(GetFloatSource(instruction, 0));
var second = TruncateFloat32ForPack(GetFloatSource(instruction, 1));
var vector = _module.AddInstruction( var vector = _module.AddInstruction(
SpirvOp.CompositeConstruct, SpirvOp.CompositeConstruct,
_vec2Type, _vec2Type,
GetFloatSource(instruction, 0), first,
GetFloatSource(instruction, 1)); second);
result = Ext(58, _uintType, vector); result = Ext(58, _uintType, vector);
break; break;
} }
@@ -945,6 +975,16 @@ internal static partial class Gen5SpirvTranslator
StoreS(destination, result); StoreS(destination, result);
Store(_scc, IsNotZero(result)); Store(_scc, IsNotZero(result));
return true; return true;
case "SBitset1B32":
result = _module.AddInstruction(
SpirvOp.BitFieldInsert,
_uintType,
LoadS(destination),
UInt(1),
BitwiseAnd(left, UInt(31)),
UInt(1));
StoreS(destination, result);
return true;
default: default:
{ {
if (instruction.Sources.Count < 2) if (instruction.Sources.Count < 2)
@@ -971,13 +1011,14 @@ internal static partial class Gen5SpirvTranslator
left, left,
right); right);
Store(_scc, _module.AddInstruction( Store(_scc, _module.AddInstruction(
SpirvOp.UGreaterThanEqual, SpirvOp.UGreaterThan,
_boolType, _boolType,
left, right,
right)); left));
break; break;
case "SAddI32": case "SAddI32":
result = IAdd(left, right); result = IAdd(left, right);
Store(_scc, SignedAddOverflow(left, right, result));
break; break;
case "SSubI32": case "SSubI32":
result = _module.AddInstruction( result = _module.AddInstruction(
@@ -985,6 +1026,7 @@ internal static partial class Gen5SpirvTranslator
_uintType, _uintType,
left, left,
right); right);
Store(_scc, SignedSubOverflow(left, right, result));
break; break;
case "SAddcU32": case "SAddcU32":
{ {
@@ -1021,8 +1063,8 @@ internal static partial class Gen5SpirvTranslator
SpirvOp.Select, SpirvOp.Select,
_uintType, _uintType,
Load(_boolType, _scc), Load(_boolType, _scc),
UInt(0), UInt(1),
UInt(1)); UInt(0));
var partial = _module.AddInstruction( var partial = _module.AddInstruction(
SpirvOp.ISub, SpirvOp.ISub,
_uintType, _uintType,
@@ -1033,23 +1075,31 @@ internal static partial class Gen5SpirvTranslator
_uintType, _uintType,
partial, partial,
borrow); borrow);
var firstNoBorrow = _module.AddInstruction( var firstBorrow = _module.AddInstruction(
SpirvOp.UGreaterThanEqual, SpirvOp.UGreaterThan,
_boolType, _boolType,
left, right,
right); left);
var secondNoBorrow = _module.AddInstruction( var secondBorrow = _module.AddInstruction(
SpirvOp.UGreaterThanEqual, SpirvOp.LogicalAnd,
_boolType, _boolType,
partial, _module.AddInstruction(
borrow); SpirvOp.IEqual,
_boolType,
borrow,
UInt(1)),
_module.AddInstruction(
SpirvOp.IEqual,
_boolType,
right,
left));
Store( Store(
_scc, _scc,
_module.AddInstruction( _module.AddInstruction(
SpirvOp.LogicalAnd, SpirvOp.LogicalOr,
_boolType, _boolType,
firstNoBorrow, firstBorrow,
secondNoBorrow)); secondBorrow));
break; break;
} }
case "SMulI32": case "SMulI32":
@@ -1061,6 +1111,7 @@ internal static partial class Gen5SpirvTranslator
break; break;
case "SAndB32": case "SAndB32":
result = BitwiseAnd(left, right); result = BitwiseAnd(left, right);
Store(_scc, IsNotZero(result));
break; break;
case "SOrB32": case "SOrB32":
result = _module.AddInstruction( result = _module.AddInstruction(
@@ -1068,6 +1119,7 @@ internal static partial class Gen5SpirvTranslator
_uintType, _uintType,
left, left,
right); right);
Store(_scc, IsNotZero(result));
break; break;
case "SXorB32": case "SXorB32":
result = _module.AddInstruction( result = _module.AddInstruction(
@@ -1075,22 +1127,64 @@ internal static partial class Gen5SpirvTranslator
_uintType, _uintType,
left, left,
right); right);
Store(_scc, IsNotZero(result));
break; break;
case "SAndn2B32": case "SAndn2B32":
result = BitwiseAnd( result = BitwiseAnd(
left, left,
_module.AddInstruction(SpirvOp.Not, _uintType, right)); _module.AddInstruction(SpirvOp.Not, _uintType, right));
Store(_scc, IsNotZero(result));
break;
case "SOrn2B32":
result = _module.AddInstruction(
SpirvOp.BitwiseOr,
_uintType,
left,
_module.AddInstruction(SpirvOp.Not, _uintType, right));
Store(_scc, IsNotZero(result));
break;
case "SNandB32":
result = _module.AddInstruction(
SpirvOp.Not,
_uintType,
BitwiseAnd(left, right));
Store(_scc, IsNotZero(result));
break;
case "SNorB32":
result = _module.AddInstruction(
SpirvOp.Not,
_uintType,
_module.AddInstruction(
SpirvOp.BitwiseOr,
_uintType,
left,
right));
Store(_scc, IsNotZero(result));
break;
case "SXnorB32":
result = _module.AddInstruction(
SpirvOp.Not,
_uintType,
_module.AddInstruction(
SpirvOp.BitwiseXor,
_uintType,
left,
right));
Store(_scc, IsNotZero(result));
break; break;
case "SLshlB32": case "SLshlB32":
result = ShiftLeftLogical(left, right); result = ShiftLeftLogical(left, right);
Store(_scc, IsNotZero(result));
break; break;
case "SLshrB32": case "SLshrB32":
result = ShiftRightLogical( result = ShiftRightLogical(
left, left,
BitwiseAnd(right, UInt(31))); BitwiseAnd(right, UInt(31)));
Store(_scc, IsNotZero(result));
break; break;
case "SAshrI32": case "SAshrI32":
result = ShiftRightArithmetic(left, right); result = ShiftRightArithmetic(left, right);
Store(_scc, IsNotZero(result));
break; break;
case "SBfmB32": case "SBfmB32":
result = _module.AddInstruction( result = _module.AddInstruction(
@@ -1133,6 +1227,7 @@ internal static partial class Gen5SpirvTranslator
left, left,
offset, offset,
width); width);
Store(_scc, IsNotZero(result));
break; break;
} }
case "SCselectB32": case "SCselectB32":
@@ -1145,9 +1240,47 @@ internal static partial class Gen5SpirvTranslator
break; break;
case "SMinU32": case "SMinU32":
result = Ext(38, _uintType, left, right); result = Ext(38, _uintType, left, right);
Store(
_scc,
_module.AddInstruction(
SpirvOp.ULessThan,
_boolType,
left,
right));
break;
case "SMinI32":
result = Bitcast(
_uintType,
Ext(39, _intType, Bitcast(_intType, left), Bitcast(_intType, right)));
Store(
_scc,
_module.AddInstruction(
SpirvOp.SLessThan,
_boolType,
Bitcast(_intType, left),
Bitcast(_intType, right)));
break; break;
case "SMaxU32": case "SMaxU32":
result = Ext(41, _uintType, left, right); result = Ext(41, _uintType, left, right);
Store(
_scc,
_module.AddInstruction(
SpirvOp.UGreaterThan,
_boolType,
left,
right));
break;
case "SMaxI32":
result = Bitcast(
_uintType,
Ext(42, _intType, Bitcast(_intType, left), Bitcast(_intType, right)));
Store(
_scc,
_module.AddInstruction(
SpirvOp.SGreaterThan,
_boolType,
Bitcast(_intType, left),
Bitcast(_intType, right)));
break; break;
case "SLshl1AddU32": case "SLshl1AddU32":
case "SLshl2AddU32": case "SLshl2AddU32":
@@ -1292,17 +1425,55 @@ internal static partial class Gen5SpirvTranslator
"SXorSaveexecB64" => _module.AddInstruction( "SXorSaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseXor, _ulongType, oldExec, left), SpirvOp.BitwiseXor, _ulongType, oldExec, left),
"SAndn2SaveexecB64" => _module.AddInstruction( "SAndn2SaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseAnd, _ulongType, oldExec, notLeft),
"SAndn1SaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseAnd, SpirvOp.BitwiseAnd,
_ulongType, _ulongType,
left,
_module.AddInstruction( _module.AddInstruction(
SpirvOp.Not, SpirvOp.Not,
_ulongType, _ulongType,
oldExec)),
"SAndn1SaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseAnd,
_ulongType,
notLeft,
oldExec),
"SOrn1SaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseOr,
_ulongType,
notLeft,
oldExec), oldExec),
left),
"SOrn2SaveexecB64" => _module.AddInstruction( "SOrn2SaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseOr, _ulongType, oldExec, notLeft), SpirvOp.BitwiseOr,
_ulongType,
left,
_module.AddInstruction(
SpirvOp.Not,
_ulongType,
oldExec)),
"SNandSaveexecB64" => _module.AddInstruction(
SpirvOp.Not,
_ulongType,
_module.AddInstruction(
SpirvOp.BitwiseAnd,
_ulongType,
left,
oldExec)),
"SNorSaveexecB64" => _module.AddInstruction(
SpirvOp.Not,
_ulongType,
_module.AddInstruction(
SpirvOp.BitwiseOr,
_ulongType,
left,
oldExec)),
"SXnorSaveexecB64" => _module.AddInstruction(
SpirvOp.Not,
_ulongType,
_module.AddInstruction(
SpirvOp.BitwiseXor,
_ulongType,
left,
oldExec)),
_ => 0u, _ => 0u,
}; };
if (newExec == 0) if (newExec == 0)
@@ -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)
+92 -36
View File
@@ -467,9 +467,6 @@ internal static partial class Gen5SpirvTranslator
_imageBindingByPc.TryAdd(binding.Pc, index); _imageBindingByPc.TryAdd(binding.Pc, index);
var isStorage = var isStorage =
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode); Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode);
var isDepth =
binding.Opcode.Contains("SampleC", StringComparison.Ordinal) ||
binding.Opcode.Contains("Gather4C", StringComparison.Ordinal);
var (format, componentKind) = var (format, componentKind) =
DecodeImageFormat(binding.ResourceDescriptor); DecodeImageFormat(binding.ResourceDescriptor);
var componentType = componentKind switch var componentType = componentKind switch
@@ -494,7 +491,7 @@ internal static partial class Gen5SpirvTranslator
var imageType = _module.TypeImage( var imageType = _module.TypeImage(
componentType, componentType,
SpirvImageDim.Dim2D, SpirvImageDim.Dim2D,
depth: isDepth, depth: false,
arrayed: false, arrayed: false,
multisampled: false, multisampled: false,
sampled: isStorage ? 2u : 1u, sampled: isStorage ? 2u : 1u,
@@ -1661,18 +1658,14 @@ internal static partial class Gen5SpirvTranslator
var offset = hasOffset ? BuildImageOffset(image, 0) : 0u; var offset = hasOffset ? BuildImageOffset(image, 0) : 0u;
var imageOperands = var imageOperands =
(explicitLod ? 2u : 0u) | (hasOffset ? 0x10u : 0u); (explicitLod ? 2u : 0u) | (hasOffset ? 0x10u : 0u);
var reference = hasCompare
? Bitcast(_floatType, LoadV(image.GetAddressRegister(hasOffset ? 1 : 0)))
: 0u;
var operands = new List<uint> var operands = new List<uint>
{ {
imageObject, imageObject,
coordinates, coordinates,
}; };
if (hasCompare)
{
operands.Add(
Bitcast(
_floatType,
LoadV(image.GetAddressRegister(hasOffset ? 1 : 0))));
}
if (imageOperands != 0) if (imageOperands != 0)
{ {
@@ -1689,29 +1682,14 @@ internal static partial class Gen5SpirvTranslator
} }
sampled = _module.AddInstruction( sampled = _module.AddInstruction(
hasCompare explicitLod
? explicitLod
? SpirvOp.ImageSampleDrefExplicitLod
: SpirvOp.ImageSampleDrefImplicitLod
: explicitLod
? SpirvOp.ImageSampleExplicitLod ? SpirvOp.ImageSampleExplicitLod
: SpirvOp.ImageSampleImplicitLod, : SpirvOp.ImageSampleImplicitLod,
hasCompare ? resource.ComponentType : resource.VectorType, resource.VectorType,
[.. operands]); [.. operands]);
if (hasCompare) if (hasCompare)
{ {
var scalar = sampled; sampled = EmitManualDepthCompare(resource, sampled, reference);
sampled = _module.AddInstruction(
SpirvOp.CompositeConstruct,
resource.VectorType,
scalar,
scalar,
scalar,
resource.ComponentKind == ImageComponentKind.Float
? Float(1)
: resource.ComponentKind == ImageComponentKind.Uint
? UInt(1)
: _module.Constant(_intType, 1));
} }
} }
else if (instruction.Opcode.StartsWith( else if (instruction.Opcode.StartsWith(
@@ -1725,6 +1703,9 @@ internal static partial class Gen5SpirvTranslator
var start = (hasOffset ? 1 : 0) + (hasCompare ? 1 : 0); var start = (hasOffset ? 1 : 0) + (hasCompare ? 1 : 0);
var coordinates = BuildFloatCoordinates(image, start); var coordinates = BuildFloatCoordinates(image, start);
var offset = hasOffset ? BuildImageOffset(image, 0) : 0u; var offset = hasOffset ? BuildImageOffset(image, 0) : 0u;
var reference = hasCompare
? Bitcast(_floatType, LoadV(image.GetAddressRegister(hasOffset ? 1 : 0)))
: 0u;
var operands = new List<uint> var operands = new List<uint>
{ {
imageObject, imageObject,
@@ -1732,10 +1713,7 @@ internal static partial class Gen5SpirvTranslator
}; };
if (hasCompare) if (hasCompare)
{ {
operands.Add( operands.Add(UInt(0));
Bitcast(
_floatType,
LoadV(image.GetAddressRegister(hasOffset ? 1 : 0))));
} }
else else
{ {
@@ -1756,11 +1734,28 @@ internal static partial class Gen5SpirvTranslator
} }
sampled = _module.AddInstruction( sampled = _module.AddInstruction(
hasCompare SpirvOp.ImageGather,
? SpirvOp.ImageDrefGather
: SpirvOp.ImageGather,
resource.VectorType, resource.VectorType,
[.. operands]); [.. operands]);
if (hasCompare)
{
var compared = new uint[4];
for (var component = 0u; component < 4; component++)
{
var texel = _module.AddInstruction(
SpirvOp.CompositeExtract,
resource.ComponentType,
sampled,
component);
compared[component] = EmitDepthCompareScalar(resource, texel, reference);
}
sampled = _module.AddInstruction(
SpirvOp.CompositeConstruct,
resource.VectorType,
compared);
}
writeAllComponents = true; writeAllComponents = true;
} }
else else
@@ -1794,6 +1789,67 @@ internal static partial class Gen5SpirvTranslator
return true; return true;
} }
private uint EmitDepthCompareScalar(
SpirvImageResource resource,
uint texel,
uint reference)
{
var texelAsFloat = resource.ComponentKind switch
{
ImageComponentKind.Uint => _module.AddInstruction(
SpirvOp.ConvertUToF, _floatType, texel),
ImageComponentKind.Sint => _module.AddInstruction(
SpirvOp.ConvertSToF, _floatType, texel),
_ => texel,
};
var passes = _module.AddInstruction(
SpirvOp.FOrdLessThanEqual,
_boolType,
reference,
texelAsFloat);
return _module.AddInstruction(
SpirvOp.Select,
resource.ComponentType,
passes,
resource.ComponentKind switch
{
ImageComponentKind.Uint => UInt(1),
ImageComponentKind.Sint => _module.Constant(_intType, 1),
_ => Float(1),
},
resource.ComponentKind switch
{
ImageComponentKind.Uint => UInt(0),
ImageComponentKind.Sint => _module.Constant(_intType, 0),
_ => Float(0),
});
}
private uint EmitManualDepthCompare(
SpirvImageResource resource,
uint sampledVector,
uint reference)
{
var texel = _module.AddInstruction(
SpirvOp.CompositeExtract,
resource.ComponentType,
sampledVector,
0u);
var scalar = EmitDepthCompareScalar(resource, texel, reference);
return _module.AddInstruction(
SpirvOp.CompositeConstruct,
resource.VectorType,
scalar,
scalar,
scalar,
resource.ComponentKind switch
{
ImageComponentKind.Uint => UInt(1),
ImageComponentKind.Sint => _module.Constant(_intType, 1),
_ => Float(1),
});
}
private uint BuildFloatCoordinates(Gen5ImageControl image, int start) private uint BuildFloatCoordinates(Gen5ImageControl image, int start)
{ {
var x = Bitcast( var x = Bitcast(
+87 -12
View File
@@ -24,6 +24,7 @@ public static class AmprExports
private const uint KernelEventQueueRecordType = 2; private const uint KernelEventQueueRecordType = 2;
private const uint WriteAddressRecordType = 3; private const uint WriteAddressRecordType = 3;
private static readonly ConcurrentDictionary<ulong, CommandBufferState> _commandBuffers = new(); private static readonly ConcurrentDictionary<ulong, CommandBufferState> _commandBuffers = new();
private static readonly ConcurrentDictionary<string, Lazy<CachedHostFile>> _hostFileCache = new(StringComparer.OrdinalIgnoreCase);
private static readonly bool _traceAmpr = private static readonly bool _traceAmpr =
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal); string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal);
private static readonly bool _traceAmprReads = private static readonly bool _traceAmprReads =
@@ -37,6 +38,23 @@ public static class AmprExports
public ulong WriteOffset; public ulong WriteOffset;
} }
private sealed class CachedHostFile
{
public CachedHostFile(string path)
{
Stream = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite | FileShare.Delete,
bufferSize: 1024 * 1024,
FileOptions.RandomAccess);
}
public object Gate { get; } = new();
public FileStream Stream { get; }
}
[SysAbiExport( [SysAbiExport(
Nid = "8aI7R7WaOlc", Nid = "8aI7R7WaOlc",
ExportName = "sceAmprCommandBufferConstructor", ExportName = "sceAmprCommandBufferConstructor",
@@ -249,7 +267,7 @@ public static class AmprExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
if (!AmprFileRegistry.TryGetHostPath(fileId, out var hostPath) || !File.Exists(hostPath)) if (!AmprFileRegistry.TryGetHostPath(fileId, out var hostPath))
{ {
TraceAmprRead(ctx, commandBuffer, fileId, destination, size, fileOffset, bytesRead: 0, hostPath, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND); TraceAmprRead(ctx, commandBuffer, fileId, destination, size, fileOffset, bytesRead: 0, hostPath, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
@@ -634,24 +652,43 @@ public static class AmprExports
try try
{ {
using var stream = new FileStream( if (!TryGetCachedHostFile(hostPath, out var cachedFile, out var openResult))
hostPath, {
FileMode.Open, return openResult;
FileAccess.Read, }
FileShare.ReadWrite | FileShare.Delete,
ChunkSize, long fileLength;
FileOptions.SequentialScan); lock (cachedFile.Gate)
if (fileOffset >= (ulong)stream.Length) {
fileLength = cachedFile.Stream.Length;
}
if (fileOffset >= (ulong)fileLength)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
stream.Position = unchecked((long)fileOffset);
while (bytesRead < size) while (bytesRead < size)
{ {
if (bytesRead > ulong.MaxValue - fileOffset)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var absoluteOffset = fileOffset + bytesRead;
if (absoluteOffset > long.MaxValue)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var request = (int)Math.Min((ulong)buffer.Length, size - bytesRead); var request = (int)Math.Min((ulong)buffer.Length, size - bytesRead);
var read = stream.Read(buffer, 0, request); int read;
lock (cachedFile.Gate)
{
cachedFile.Stream.Position = unchecked((long)absoluteOffset);
read = cachedFile.Stream.Read(buffer, 0, request);
}
if (read <= 0) if (read <= 0)
{ {
break; break;
@@ -681,6 +718,44 @@ public static class AmprExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static bool TryGetCachedHostFile(string hostPath, out CachedHostFile file, out int result)
{
file = null!;
result = (int)OrbisGen2Result.ORBIS_GEN2_OK;
string cachePath;
try
{
cachePath = Path.GetFullPath(hostPath);
}
catch
{
cachePath = hostPath;
}
var lazy = _hostFileCache.GetOrAdd(
cachePath,
static path => new Lazy<CachedHostFile>(() => new CachedHostFile(path), isThreadSafe: true));
try
{
file = lazy.Value;
return true;
}
catch (UnauthorizedAccessException)
{
_hostFileCache.TryRemove(cachePath, out _);
result = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
return false;
}
catch (IOException)
{
_hostFileCache.TryRemove(cachePath, out _);
result = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
return false;
}
}
private static bool AppendReadFileRecord( private static bool AppendReadFileRecord(
CpuContext ctx, CpuContext ctx,
ulong commandBuffer, ulong commandBuffer,
@@ -0,0 +1,92 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Collections.Concurrent;
using System.Threading;
namespace SharpEmu.Libs.Audio;
public static class AudioOutExports
{
private static readonly ConcurrentDictionary<int, PortState> Ports = new();
private static int _nextPortHandle;
private sealed record PortState(int UserId, int Type, uint BufferLength, uint Frequency, int Format);
[SysAbiExport(
Nid = "JfEPXVxhFqA",
ExportName = "sceAudioOutInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAudioOut")]
public static int AudioOutInit(CpuContext ctx) => SetReturn(ctx, 0);
[SysAbiExport(
Nid = "ekNvsT22rsY",
ExportName = "sceAudioOutOpen",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAudioOut")]
public static int AudioOutOpen(CpuContext ctx)
{
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
var type = unchecked((int)ctx[CpuRegister.Rsi]);
var bufferLength = unchecked((uint)ctx[CpuRegister.Rcx]);
var frequency = unchecked((uint)ctx[CpuRegister.R8]);
var format = unchecked((int)ctx[CpuRegister.R9]);
if (bufferLength == 0 || frequency == 0)
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
var handle = Interlocked.Increment(ref _nextPortHandle);
Ports[handle] = new PortState(userId, type, bufferLength, frequency, format);
return SetReturn(ctx, handle);
}
[SysAbiExport(
Nid = "s1--uE9mBFw",
ExportName = "sceAudioOutClose",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAudioOut")]
public static int AudioOutClose(CpuContext ctx)
{
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
return SetReturn(
ctx,
Ports.TryRemove(handle, out _)
? 0
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
[SysAbiExport(
Nid = "QOQtbeDqsT4",
ExportName = "sceAudioOutOutput",
Target = Generation.Gen5,
LibraryName = "libSceAudioOut")]
public static int AudioOutOutput(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "b+uAV89IlxE",
ExportName = "sceAudioOutSetVolume",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAudioOut")]
public static int AudioOutSetVolume(CpuContext ctx)
{
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
return SetReturn(
ctx,
Ports.ContainsKey(handle)
? 0
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
private static int SetReturn(CpuContext ctx, int result)
{
ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
}
}
@@ -0,0 +1,77 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
namespace SharpEmu.Libs.AvPlayer;
public static class AvPlayerExports
{
private const int InvalidParameters = unchecked((int)0x806A0001);
private static readonly object StateGate = new();
private static readonly HashSet<ulong> Players = new();
[SysAbiExport(
Nid = "aS66RI0gGgo",
ExportName = "sceAvPlayerInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAvPlayer")]
public static int AvPlayerInit(CpuContext ctx)
{
if (ctx[CpuRegister.Rdi] == 0 ||
!KernelMemoryCompatExports.TryAllocateHleData(ctx, 0x40, 16, out var handle))
{
ctx[CpuRegister.Rax] = 0;
return 0;
}
lock (StateGate)
{
Players.Add(handle);
}
ctx[CpuRegister.Rax] = handle;
return unchecked((int)handle);
}
[SysAbiExport(
Nid = "HD1YKVU26-M",
ExportName = "sceAvPlayerPostInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAvPlayer")]
public static int AvPlayerPostInit(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
var dataAddress = ctx[CpuRegister.Rsi];
lock (StateGate)
{
return SetReturn(
ctx,
handle != 0 && dataAddress != 0 && Players.Contains(handle)
? 0
: InvalidParameters);
}
}
[SysAbiExport(
Nid = "NkJwDzKmIlw",
ExportName = "sceAvPlayerClose",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAvPlayer")]
public static int AvPlayerClose(CpuContext ctx)
{
lock (StateGate)
{
return SetReturn(
ctx,
Players.Remove(ctx[CpuRegister.Rdi]) ? 0 : InvalidParameters);
}
}
private static int SetReturn(CpuContext ctx, int result)
{
ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
}
}
@@ -0,0 +1,27 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Threading;
namespace SharpEmu.Libs.CommonDialog;
public static class MsgDialogExports
{
private const int AlreadyInitialized = unchecked((int)0x80B80002);
private static int _initialized;
[SysAbiExport(
Nid = "lDqxaY1UbEo",
ExportName = "sceMsgDialogInitialize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceMsgDialog")]
public static int MsgDialogInitialize(CpuContext ctx)
{
var result = Interlocked.Exchange(ref _initialized, 1) == 0
? 0
: AlreadyInitialized;
ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
}
}
@@ -276,6 +276,22 @@ public static class KernelEventQueueCompatExports
var outCountAddress = ctx[CpuRegister.Rcx]; var outCountAddress = ctx[CpuRegister.Rcx];
var timeoutAddress = ctx[CpuRegister.R8]; var timeoutAddress = ctx[CpuRegister.R8];
if (!IsValidEqueue(handle))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (eventsAddress == 0 || eventCapacity < 1)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
uint timeoutUsec = 0;
if (timeoutAddress != 0 && !TryReadUInt32(ctx, timeoutAddress, out timeoutUsec))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var deliveredCount = DequeueEvents(ctx, handle, eventsAddress, eventCapacity); var deliveredCount = DequeueEvents(ctx, handle, eventsAddress, eventCapacity);
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, (uint)deliveredCount)) if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, (uint)deliveredCount))
{ {
@@ -300,6 +316,41 @@ public static class KernelEventQueueCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
if (timeoutAddress != 0 && ctx.TryReadUInt64(timeoutAddress, out var timeoutRaw))
{
var timeoutMicros = timeoutRaw & 0xFFFF_FFFFUL;
var deadline = Environment.TickCount64 +
Math.Max(1L, (long)Math.Min(timeoutMicros / 1000, int.MaxValue));
lock (_eventQueueGate)
{
while (!HasPendingEvents(handle))
{
var remaining = deadline - Environment.TickCount64;
if (remaining <= 0)
{
break;
}
Monitor.Wait(_eventQueueGate, (int)Math.Min(remaining, 100));
}
}
deliveredCount = DequeueEvents(ctx, handle, eventsAddress, eventCapacity);
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, (uint)deliveredCount))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (deliveredCount > 0)
{
TraceEventQueue(ctx, "wait-timed-deliver", handle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
TraceEventQueue(ctx, "wait-timeout", handle);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
}
TraceEventQueue(ctx, "wait", handle); TraceEventQueue(ctx, "wait", handle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -330,6 +381,7 @@ public static class KernelEventQueueCompatExports
queue.AddLast(queuedEvent); queue.AddLast(queuedEvent);
queued = true; queued = true;
Monitor.PulseAll(_eventQueueGate);
} }
if (queued) if (queued)
@@ -494,7 +546,9 @@ public static class KernelEventQueueCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return deliveredCount > 0
? (int)OrbisGen2Result.ORBIS_GEN2_OK
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
} }
private static bool HasPendingEvents(ulong handle) private static bool HasPendingEvents(ulong handle)
@@ -612,4 +666,17 @@ public static class KernelEventQueueCompatExports
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
return ctx.Memory.TryWrite(address, buffer); return ctx.Memory.TryWrite(address, buffer);
} }
private static bool TryReadUInt32(CpuContext ctx, ulong address, out uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
if (!ctx.Memory.TryRead(address, buffer))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
return true;
}
} }
@@ -0,0 +1,65 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
namespace SharpEmu.Libs.Kernel;
public static class KernelExceptionCompatExports
{
private static readonly HashSet<int> AllowedSignals = new() { 1, 4, 8, 10, 11, 30 };
private static readonly Dictionary<int, ulong> _installedHandlers = new();
private static readonly object _gate = new();
[SysAbiExport(
Nid = "WkwEd3N7w0Y",
ExportName = "sceKernelInstallExceptionHandler",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int InstallExceptionHandler(CpuContext ctx)
{
var signum = unchecked((int)ctx[CpuRegister.Rdi]);
var handler = ctx[CpuRegister.Rsi];
if (!AllowedSignals.Contains(signum))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_gate)
{
if (_installedHandlers.ContainsKey(signum))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS;
}
_installedHandlers[signum] = handler;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Qhv5ARAoOEc",
ExportName = "sceKernelRemoveExceptionHandler",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int RemoveExceptionHandler(CpuContext ctx)
{
var signum = unchecked((int)ctx[CpuRegister.Rdi]);
if (!AllowedSignals.Contains(signum))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_gate)
{
_installedHandlers.Remove(signum);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}
@@ -365,6 +365,13 @@ public static class KernelExports
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int KernelOpen(CpuContext ctx) => KernelMemoryCompatExports.KernelOpenUnderscore(ctx); public static int KernelOpen(CpuContext ctx) => KernelMemoryCompatExports.KernelOpenUnderscore(ctx);
[SysAbiExport(
Nid = "mqQMh1zPPT8",
ExportName = "fstat",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Fstat(CpuContext ctx) => KernelMemoryCompatExports.KernelFstat(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "hcuQgD53UxM", Nid = "hcuQgD53UxM",
ExportName = "printf", ExportName = "printf",
@@ -5,9 +5,9 @@ using SharpEmu.HLE;
using SharpEmu.Libs.Ampr; using SharpEmu.Libs.Ampr;
using System.Buffers; using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Linq; using System.Linq;
using System.Globalization; using System.Globalization;
@@ -103,10 +103,12 @@ public static class KernelMemoryCompatExports
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, string> _mappedRegionNames = 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 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 readonly ConcurrentDictionary<string, ulong> _aprFileSizeCache = new(StringComparer.OrdinalIgnoreCase);
private static long _nextFileDescriptor = 2; private static long _nextFileDescriptor = 2;
private static ulong _nextPhysicalAddress; private static ulong _nextPhysicalAddress;
private static ulong _nextVirtualAddress; private static ulong _nextVirtualAddress;
@@ -231,6 +233,25 @@ public static class KernelMemoryCompatExports
return true; return true;
} }
internal static void RegisterReservedVirtualRange(ulong address, ulong length)
{
if (address == 0 || length == 0)
{
return;
}
lock (_memoryGate)
{
_mappedRegions[address] = new MappedRegion(
address,
length,
Protection: 0,
IsFlexible: false,
IsDirect: false,
DirectStart: 0);
}
}
[SysAbiExport( [SysAbiExport(
Nid = "8zTFvBIAIN8", Nid = "8zTFvBIAIN8",
ExportName = "memset", ExportName = "memset",
@@ -1322,6 +1343,7 @@ public static class KernelMemoryCompatExports
if (IsMutatingOpen(flags)) if (IsMutatingOpen(flags))
{ {
InvalidateNegativeStatCacheForPathAndAncestors(guestPath); InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
InvalidateAprFileSizeCache(hostPath);
} }
LogOpenTrace($"_open file path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} fd={fd}"); LogOpenTrace($"_open file path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} fd={fd}");
@@ -1530,6 +1552,7 @@ public static class KernelMemoryCompatExports
File.Delete(hostPath); File.Delete(hostPath);
InvalidateNegativeStatCacheForPathAndAncestors(guestPath); InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
InvalidateAprFileSizeCache(hostPath);
AddNegativeStatCacheForGuestPath(guestPath); AddNegativeStatCacheForGuestPath(guestPath);
LogOpenTrace($"unlink path='{guestPath}' host='{hostPath}'"); LogOpenTrace($"unlink path='{guestPath}' host='{hostPath}'");
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
@@ -2435,6 +2458,36 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "hwVSPCmp5tM",
ExportName = "sceKernelCheckedReleaseDirectMemory",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelCheckedReleaseDirectMemory(CpuContext ctx)
{
var start = ctx[CpuRegister.Rdi];
var length = ctx[CpuRegister.Rsi];
if (length == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_memoryGate)
{
if (!_directAllocations.TryGetValue(start, out var allocation) ||
allocation.Length != length)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
_directAllocations.Remove(start);
_nextPhysicalAddress = GetDirectMemoryHighWaterMarkLocked();
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport( [SysAbiExport(
Nid = "L-Q3LEjIbgA", Nid = "L-Q3LEjIbgA",
ExportName = "sceKernelMapDirectMemory", ExportName = "sceKernelMapDirectMemory",
@@ -2546,8 +2599,11 @@ public static class KernelMemoryCompatExports
var length = ctx[CpuRegister.Rsi]; var length = ctx[CpuRegister.Rsi];
var protection = unchecked((int)ctx[CpuRegister.Rdx]); var protection = unchecked((int)ctx[CpuRegister.Rdx]);
var flags = ctx[CpuRegister.Rcx]; var flags = ctx[CpuRegister.Rcx];
if (ShouldTraceDirectMemory())
{
Console.Error.WriteLine( Console.Error.WriteLine(
$"[LOADER][TRACE] map_flexible: inout=0x{inOutAddressPointer:X16} len=0x{length:X16} prot=0x{protection:X8} flags=0x{flags:X16}"); $"[LOADER][TRACE] map_flexible: inout=0x{inOutAddressPointer:X16} len=0x{length:X16} prot=0x{protection:X8} flags=0x{flags:X16}");
}
if (inOutAddressPointer == 0 || length == 0) if (inOutAddressPointer == 0 || length == 0)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
@@ -2577,8 +2633,11 @@ public static class KernelMemoryCompatExports
: AllocateMappedGuestAddress(ctx, length, 0x1000UL); : AllocateMappedGuestAddress(ctx, length, 0x1000UL);
} }
if (ShouldTraceDirectMemory())
{
Console.Error.WriteLine( Console.Error.WriteLine(
$"[LOADER][TRACE] map_flexible reserve: requested=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} mapped=0x{mappedAddress:X16}"); $"[LOADER][TRACE] map_flexible reserve: requested=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} mapped=0x{mappedAddress:X16}");
}
if (mappedAddress == 0) if (mappedAddress == 0)
{ {
@@ -2614,6 +2673,16 @@ public static class KernelMemoryCompatExports
return KernelMapNamedFlexibleMemory(ctx); return KernelMapNamedFlexibleMemory(ctx);
} }
[SysAbiExport(
Nid = "4h6F1LLbTiw",
ExportName = "sceKernelMapFlexibleMemoryInternal",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelMapFlexibleMemoryInternal(CpuContext ctx)
{
return KernelMapNamedFlexibleMemory(ctx);
}
[SysAbiExport( [SysAbiExport(
Nid = "2SKEx6bSq-4", Nid = "2SKEx6bSq-4",
ExportName = "sceKernelBatchMap", ExportName = "sceKernelBatchMap",
@@ -2667,6 +2736,44 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "DGMG3JshrZU",
ExportName = "sceKernelSetVirtualRangeName",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelSetVirtualRangeName(CpuContext ctx)
{
var address = ctx[CpuRegister.Rdi];
var length = ctx[CpuRegister.Rsi];
var nameAddress = ctx[CpuRegister.Rdx];
if (nameAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (!TryReadCString(ctx, nameAddress, OrbisKernelMaximumNameLength, out var nameBytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var name = Encoding.UTF8.GetString(nameBytes);
lock (_memoryGate)
{
if (!TryFindVirtualQueryRegionLocked(address, findNext: false, out var region) ||
length > region.Length ||
address < region.Address ||
length > region.Address + region.Length - address)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
_mappedRegionNames[region.Address] = name;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport( [SysAbiExport(
Nid = "rVjRvHJ0X6c", Nid = "rVjRvHJ0X6c",
ExportName = "sceKernelVirtualQuery", ExportName = "sceKernelVirtualQuery",
@@ -2724,11 +2831,16 @@ public static class KernelMemoryCompatExports
BinaryPrimitives.WriteInt32LittleEndian(payload[28..32], memoryType); BinaryPrimitives.WriteInt32LittleEndian(payload[28..32], memoryType);
payload[32] = unchecked((byte)stateFlags); payload[32] = unchecked((byte)stateFlags);
var name = region.IsDirect string name;
lock (_memoryGate)
{
name = _mappedRegionNames.GetValueOrDefault(region.Address)
?? (region.IsDirect
? "direct" ? "direct"
: region.IsFlexible : region.IsFlexible
? "flexible" ? "flexible"
: string.Empty; : string.Empty);
}
if (!string.IsNullOrEmpty(name)) if (!string.IsNullOrEmpty(name))
{ {
var nameBytes = Encoding.ASCII.GetBytes(name); var nameBytes = Encoding.ASCII.GetBytes(name);
@@ -3870,118 +3982,17 @@ public static class KernelMemoryCompatExports
ulong alignment, ulong alignment,
out ulong mappedAddress) out ulong mappedAddress)
{ {
mappedAddress = 0;
if (length == 0)
{
return false;
}
try
{
object memoryObject = ctx.Memory;
MethodInfo? allocateAt = null;
MethodInfo? allocateAtOrAbove = null;
var allocateAtHasAllowAlternativeArg = false;
for (var depth = 0; depth < 4; depth++)
{
foreach (var candidate in memoryObject.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
var parameters = candidate.GetParameters();
if (string.Equals(candidate.Name, "TryAllocateAtOrAbove", StringComparison.Ordinal) &&
parameters.Length == 5 &&
parameters[0].ParameterType == typeof(ulong) &&
parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool) &&
parameters[3].ParameterType == typeof(ulong) &&
parameters[4].ParameterType == typeof(ulong).MakeByRefType())
{
allocateAtOrAbove = candidate;
}
else if (string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal))
{
if (parameters.Length == 3 &&
parameters[0].ParameterType == typeof(ulong) &&
parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool))
{
allocateAt = candidate;
allocateAtHasAllowAlternativeArg = false;
}
else if (parameters.Length == 4 &&
parameters[0].ParameterType == typeof(ulong) &&
parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool) &&
parameters[3].ParameterType == typeof(bool))
{
allocateAt = candidate;
allocateAtHasAllowAlternativeArg = true;
}
}
if (allocateAtOrAbove is not null && allocateAt is not null)
{
break;
}
}
if (allocateAtOrAbove is not null || allocateAt is not null)
{
break;
}
var innerProperty = memoryObject.GetType().GetProperty("Inner", BindingFlags.Public | BindingFlags.Instance);
if (innerProperty is null)
{
break;
}
var innerValue = innerProperty.GetValue(memoryObject);
if (innerValue is null || ReferenceEquals(innerValue, memoryObject))
{
break;
}
memoryObject = innerValue;
}
var executable = (protection & OrbisProtCpuExec) != 0; var executable = (protection & OrbisProtCpuExec) != 0;
if (allocateAtOrAbove is not null) return KernelVirtualRangeAllocator.TryReserve(
{ ctx,
var searchArgs = new object[] { desiredAddress, length, executable, alignment, 0UL }; desiredAddress,
var searchResult = allocateAtOrAbove.Invoke(memoryObject, searchArgs); length,
if (searchResult is bool trueValue && trueValue && executable,
searchArgs[4] is ulong searchedAddress && searchedAddress != 0) alignment,
{ allowSearch: true,
mappedAddress = searchedAddress; allowAllocateAtAlternative: false,
return true; "reserve range",
} out mappedAddress);
}
if (allocateAt is null)
{
Console.Error.WriteLine($"[LOADER][TRACE] reserve range: AllocateAt missing on {ctx.Memory.GetType().FullName}");
return false;
}
var invokeArgs = allocateAtHasAllowAlternativeArg
? new object[] { desiredAddress, length, executable, false }
: new object[] { desiredAddress, length, executable };
var result = allocateAt.Invoke(memoryObject, invokeArgs);
if (result is not ulong allocated || allocated == 0)
{
var resultType = result?.GetType().FullName ?? "null";
Console.Error.WriteLine($"[LOADER][TRACE] reserve range: AllocateAt returned {resultType} value={result ?? "null"}");
return false;
}
mappedAddress = allocated;
return true;
}
catch
{
Console.Error.WriteLine("[LOADER][TRACE] reserve range threw while invoking AllocateAt");
return false;
}
} }
private static bool IsMappedGuestRangeAvailable( private static bool IsMappedGuestRangeAvailable(
@@ -4134,6 +4145,20 @@ public static class KernelMemoryCompatExports
var app0Root = ResolveApp0Root(); var app0Root = ResolveApp0Root();
if (!string.IsNullOrWhiteSpace(app0Root)) if (!string.IsNullOrWhiteSpace(app0Root))
{ {
if (string.Equals(guestPath, "$", StringComparison.Ordinal) ||
string.Equals(guestPath, "$/", StringComparison.Ordinal) ||
string.Equals(guestPath, "$\\", StringComparison.Ordinal))
{
return app0Root;
}
if (guestPath.StartsWith("$/", StringComparison.Ordinal) ||
guestPath.StartsWith("$\\", StringComparison.Ordinal))
{
var relative = NormalizeMountRelativePath(guestPath[2..]);
return Path.Combine(app0Root, relative);
}
if (string.Equals(guestPath, "/app0", StringComparison.OrdinalIgnoreCase) || if (string.Equals(guestPath, "/app0", StringComparison.OrdinalIgnoreCase) ||
string.Equals(guestPath, "app0", StringComparison.OrdinalIgnoreCase)) string.Equals(guestPath, "app0", StringComparison.OrdinalIgnoreCase))
{ {
@@ -5796,22 +5821,40 @@ public static class KernelMemoryCompatExports
private static bool TryGetAprFileSize(string hostPath, out ulong size) private static bool TryGetAprFileSize(string hostPath, out ulong size)
{ {
size = 0; size = 0;
string cachePath;
try try
{ {
var fileInfo = new FileInfo(hostPath); cachePath = Path.GetFullPath(hostPath);
}
catch
{
cachePath = hostPath;
}
if (_aprFileSizeCache.TryGetValue(cachePath, out size))
{
return true;
}
try
{
var fileInfo = new FileInfo(cachePath);
if (fileInfo.Exists) if (fileInfo.Exists)
{ {
var length = fileInfo.Length; var length = fileInfo.Length;
size = length < 0 ? 0UL : unchecked((ulong)length); size = length < 0 ? 0UL : unchecked((ulong)length);
_aprFileSizeCache.TryAdd(cachePath, size);
return true; return true;
} }
if (!new DirectoryInfo(hostPath).Exists) if (!new DirectoryInfo(cachePath).Exists)
{ {
return false; return false;
} }
size = 65536; size = 65536;
_aprFileSizeCache.TryAdd(cachePath, size);
return true; return true;
} }
catch catch
@@ -6110,6 +6153,20 @@ public static class KernelMemoryCompatExports
} }
} }
private static void InvalidateAprFileSizeCache(string hostPath)
{
try
{
hostPath = Path.GetFullPath(hostPath);
}
catch
{
// The cache key remains the original path when normalization fails.
}
_aprFileSizeCache.TryRemove(hostPath, out _);
}
private static string PreviewIoBytes(byte[] buffer, int count, int maxBytes) private static string PreviewIoBytes(byte[] buffer, int count, int maxBytes)
{ {
if (count <= 0) if (count <= 0)
@@ -95,6 +95,13 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "7Xl257M4VNI",
ExportName = "pthread_equal",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int PosixPthreadEqual(CpuContext ctx) => PthreadEqual(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "T72hz6ffq08", Nid = "T72hz6ffq08",
ExportName = "scePthreadYield", ExportName = "scePthreadYield",
@@ -717,6 +717,35 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "FXPWHNk8Of0",
ExportName = "scePthreadAttrGetschedparam",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrGetschedparam(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var schedParamAddress = ctx[CpuRegister.Rsi];
if (attrAddress == 0 || schedParamAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
PthreadAttrState state;
lock (_stateGate)
{
state = GetOrCreateAttrStateLocked(attrAddress);
}
if (!TryWriteInt32(ctx, schedParamAddress, state.SchedPriority))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport( [SysAbiExport(
Nid = "DzES9hQF4f4", Nid = "DzES9hQF4f4",
ExportName = "scePthreadAttrSetschedparam", ExportName = "scePthreadAttrSetschedparam",
@@ -7,7 +7,6 @@ using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
using System.Security.Cryptography; using System.Security.Cryptography;
@@ -87,11 +86,11 @@ public static class KernelRuntimeCompatExports
if (micros < 1000) if (micros < 1000)
{ {
// Guest worker pools use usleep(1) as a polling backoff. Periodically // Guest worker pools use usleep(1) as a polling backoff. Do not turn
// relinquish a full host time slice so spin workers cannot starve producers. // PS microsecond waits into Windows millisecond sleeps on hot paths.
if ((++_shortUsleepCount & 31) == 0) if ((++_shortUsleepCount & 255) == 0)
{ {
Thread.Sleep(1); Thread.Sleep(0);
} }
else else
{ {
@@ -739,8 +738,11 @@ public static class KernelRuntimeCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
if (ShouldTraceVirtualMemory())
{
Console.Error.WriteLine( Console.Error.WriteLine(
$"[LOADER][TRACE] reserve_virtual_range: req=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} mapped=0x{mappedAddress:X16} len=0x{length:X16} flags=0x{flags:X8} align=0x{effectiveAlignment:X16}"); $"[LOADER][TRACE] reserve_virtual_range: req=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} mapped=0x{mappedAddress:X16} len=0x{length:X16} flags=0x{flags:X8} align=0x{effectiveAlignment:X16}");
}
if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress)) if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress))
{ {
@@ -752,6 +754,7 @@ public static class KernelRuntimeCompatExports
_nextReservedVirtualBase = Math.Max(_nextReservedVirtualBase, mappedAddress + length); _nextReservedVirtualBase = Math.Max(_nextReservedVirtualBase, mappedAddress + length);
} }
KernelMemoryCompatExports.RegisterReservedVirtualRange(mappedAddress, length);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -1732,117 +1735,16 @@ public static class KernelRuntimeCompatExports
bool allowSearch, bool allowSearch,
out ulong mappedAddress) out ulong mappedAddress)
{ {
mappedAddress = 0; return KernelVirtualRangeAllocator.TryReserve(
if (length == 0) ctx,
{ desiredAddress,
return false; length,
} executable: false,
alignment,
try allowSearch,
{ allowAllocateAtAlternative: allowSearch,
object memoryObject = ctx.Memory; "reserve_virtual_range",
MethodInfo? allocateAt = null; out mappedAddress);
MethodInfo? allocateAtOrAbove = null;
var allocateAtHasAllowAlternativeArg = false;
for (var depth = 0; depth < 4; depth++)
{
foreach (var candidate in memoryObject.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
var parameters = candidate.GetParameters();
if (string.Equals(candidate.Name, "TryAllocateAtOrAbove", StringComparison.Ordinal) &&
parameters.Length == 5 &&
parameters[0].ParameterType == typeof(ulong) &&
parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool) &&
parameters[3].ParameterType == typeof(ulong) &&
parameters[4].ParameterType == typeof(ulong).MakeByRefType())
{
allocateAtOrAbove = candidate;
}
else if (string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal))
{
if (parameters.Length == 3 &&
parameters[0].ParameterType == typeof(ulong) &&
parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool))
{
allocateAt = candidate;
allocateAtHasAllowAlternativeArg = false;
}
else if (parameters.Length == 4 &&
parameters[0].ParameterType == typeof(ulong) &&
parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool) &&
parameters[3].ParameterType == typeof(bool))
{
allocateAt = candidate;
allocateAtHasAllowAlternativeArg = true;
}
}
if (allocateAtOrAbove is not null && allocateAt is not null)
{
break;
}
}
if (allocateAtOrAbove is not null || allocateAt is not null)
{
break;
}
var innerProperty = memoryObject.GetType().GetProperty("Inner", BindingFlags.Public | BindingFlags.Instance);
if (innerProperty is null)
{
break;
}
var innerValue = innerProperty.GetValue(memoryObject);
if (innerValue is null || ReferenceEquals(innerValue, memoryObject))
{
break;
}
memoryObject = innerValue;
}
if (allowSearch && allocateAtOrAbove is not null)
{
var searchArgs = new object[] { desiredAddress, length, false, alignment, 0UL };
var searchResult = allocateAtOrAbove.Invoke(memoryObject, searchArgs);
if (searchResult is bool trueValue && trueValue &&
searchArgs[4] is ulong searchedAddress && searchedAddress != 0)
{
mappedAddress = searchedAddress;
return true;
}
}
if (allocateAt is null)
{
Console.Error.WriteLine($"[LOADER][TRACE] reserve_virtual_range: AllocateAt missing on {ctx.Memory.GetType().FullName}");
return false;
}
var invokeArgs = allocateAtHasAllowAlternativeArg
? new object[] { desiredAddress, length, false, allowSearch }
: new object[] { desiredAddress, length, false };
var result = allocateAt.Invoke(memoryObject, invokeArgs);
if (result is not ulong allocated || allocated == 0)
{
var resultType = result?.GetType().FullName ?? "null";
Console.Error.WriteLine($"[LOADER][TRACE] reserve_virtual_range: AllocateAt returned {resultType} value={result ?? "null"}");
return false;
}
mappedAddress = allocated;
return true;
}
catch
{
Console.Error.WriteLine("[LOADER][TRACE] reserve_virtual_range: AllocateAt invocation threw");
return false;
}
} }
private static ulong AlignUp(ulong value, ulong alignment) private static ulong AlignUp(ulong value, ulong alignment)
@@ -1855,4 +1757,9 @@ public static class KernelRuntimeCompatExports
var mask = alignment - 1; var mask = alignment - 1;
return (value + mask) & ~mask; return (value + mask) & ~mask;
} }
private static bool ShouldTraceVirtualMemory()
{
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_VIRTUAL_MEMORY"), "1", StringComparison.Ordinal);
}
} }
@@ -0,0 +1,159 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System;
using System.Collections.Concurrent;
using System.Reflection;
namespace SharpEmu.Libs.Kernel;
internal static class KernelVirtualRangeAllocator
{
private static readonly ConcurrentDictionary<Type, Accessor> _accessors = new();
public static bool TryReserve(
CpuContext ctx,
ulong desiredAddress,
ulong length,
bool executable,
ulong alignment,
bool allowSearch,
bool allowAllocateAtAlternative,
string traceName,
out ulong mappedAddress)
{
mappedAddress = 0;
if (length == 0)
{
return false;
}
try
{
if (!TryResolveAccessor(ctx.Memory, out var target, out var accessor))
{
Console.Error.WriteLine($"[LOADER][TRACE] {traceName}: AllocateAt missing on {ctx.Memory.GetType().FullName}");
return false;
}
if (allowSearch && accessor.AllocateAtOrAbove is not null)
{
var searchArgs = new object[] { desiredAddress, length, executable, alignment, 0UL };
var searchResult = accessor.AllocateAtOrAbove.Invoke(target, searchArgs);
if (searchResult is bool trueValue && trueValue &&
searchArgs[4] is ulong searchedAddress && searchedAddress != 0)
{
mappedAddress = searchedAddress;
return true;
}
}
if (accessor.AllocateAt is null)
{
Console.Error.WriteLine($"[LOADER][TRACE] {traceName}: AllocateAt missing on {target.GetType().FullName}");
return false;
}
var invokeArgs = accessor.AllocateAtHasAllowAlternativeArg
? new object[] { desiredAddress, length, executable, allowAllocateAtAlternative }
: new object[] { desiredAddress, length, executable };
var result = accessor.AllocateAt.Invoke(target, invokeArgs);
if (result is not ulong allocated || allocated == 0)
{
var resultType = result?.GetType().FullName ?? "null";
Console.Error.WriteLine($"[LOADER][TRACE] {traceName}: AllocateAt returned {resultType} value={result ?? "null"}");
return false;
}
mappedAddress = allocated;
return true;
}
catch
{
Console.Error.WriteLine($"[LOADER][TRACE] {traceName}: AllocateAt invocation threw");
return false;
}
}
private static bool TryResolveAccessor(object rootMemory, out object target, out Accessor accessor)
{
target = rootMemory;
accessor = default;
for (var depth = 0; depth < 4; depth++)
{
accessor = _accessors.GetOrAdd(target.GetType(), DiscoverAccessor);
if (accessor.AllocateAt is not null || accessor.AllocateAtOrAbove is not null)
{
return true;
}
if (accessor.InnerProperty is null)
{
break;
}
var innerValue = accessor.InnerProperty.GetValue(target);
if (innerValue is null || ReferenceEquals(innerValue, target))
{
break;
}
target = innerValue;
}
return false;
}
private static Accessor DiscoverAccessor(Type type)
{
MethodInfo? allocateAt = null;
MethodInfo? allocateAtOrAbove = null;
var allocateAtHasAllowAlternativeArg = false;
foreach (var candidate in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
var parameters = candidate.GetParameters();
if (string.Equals(candidate.Name, "TryAllocateAtOrAbove", StringComparison.Ordinal) &&
parameters.Length == 5 &&
parameters[0].ParameterType == typeof(ulong) &&
parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool) &&
parameters[3].ParameterType == typeof(ulong) &&
parameters[4].ParameterType == typeof(ulong).MakeByRefType())
{
allocateAtOrAbove = candidate;
}
else if (string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal))
{
if (parameters.Length == 3 &&
parameters[0].ParameterType == typeof(ulong) &&
parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool))
{
allocateAt = candidate;
allocateAtHasAllowAlternativeArg = false;
}
else if (parameters.Length == 4 &&
parameters[0].ParameterType == typeof(ulong) &&
parameters[1].ParameterType == typeof(ulong) &&
parameters[2].ParameterType == typeof(bool) &&
parameters[3].ParameterType == typeof(bool))
{
allocateAt = candidate;
allocateAtHasAllowAlternativeArg = true;
}
}
}
var innerProperty = type.GetProperty("Inner", BindingFlags.Public | BindingFlags.Instance);
return new Accessor(allocateAt, allocateAtOrAbove, allocateAtHasAllowAlternativeArg, innerProperty);
}
private readonly record struct Accessor(
MethodInfo? AllocateAt,
MethodInfo? AllocateAtOrAbove,
bool AllocateAtHasAllowAlternativeArg,
PropertyInfo? InnerProperty);
}
+322
View File
@@ -0,0 +1,322 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
using System.Buffers.Binary;
using System.Threading;
namespace SharpEmu.Libs.Ngs2;
public static class Ngs2Exports
{
private const int OrbisNgs2ErrorInvalidOutAddress = unchecked((int)0x804A0053);
private const int OrbisNgs2ErrorInvalidSystemHandle = unchecked((int)0x804A0230);
private const int OrbisNgs2ErrorInvalidRackHandle = unchecked((int)0x804A0261);
private const int OrbisNgs2ErrorInvalidVoiceHandle = unchecked((int)0x804A0300);
private const ulong HandleStorageSize = 0x20;
private const int RenderBufferInfoSize = 0x18;
private const ulong MaximumRenderBufferSize = 16 * 1024 * 1024;
private static readonly object StateGate = new();
private static readonly Dictionary<ulong, SystemState> Systems = new();
private static readonly Dictionary<ulong, RackState> Racks = new();
private static readonly Dictionary<ulong, VoiceState> Voices = new();
private static long _nextUid;
private static long _renderCount;
private sealed record SystemState(uint Uid);
private sealed record RackState(ulong SystemHandle, uint RackId);
private sealed record VoiceState(ulong RackHandle, uint VoiceIndex);
[SysAbiExport(
Nid = "mPYgU4oYpuY",
ExportName = "sceNgs2SystemCreateWithAllocator",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2SystemCreateWithAllocator(CpuContext ctx)
{
var outHandleAddress = ctx[CpuRegister.Rdx];
if (outHandleAddress == 0)
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidOutAddress);
}
if (!TryCreateHandle(ctx, type: 1, ownerHandle: 0, out var handle) ||
!ctx.TryWriteUInt64(outHandleAddress, handle))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
lock (StateGate)
{
Systems[handle] = new SystemState(unchecked((uint)Interlocked.Increment(ref _nextUid)));
}
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "u-WrYDaJA3k",
ExportName = "sceNgs2SystemDestroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2SystemDestroy(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
lock (StateGate)
{
if (!Systems.Remove(handle))
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidSystemHandle);
}
var rackHandles = Racks
.Where(pair => pair.Value.SystemHandle == handle)
.Select(pair => pair.Key)
.ToArray();
foreach (var rackHandle in rackHandles)
{
RemoveRackLocked(rackHandle);
}
}
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "U546k6orxQo",
ExportName = "sceNgs2RackCreateWithAllocator",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2RackCreateWithAllocator(CpuContext ctx)
{
var systemHandle = ctx[CpuRegister.Rdi];
var rackId = unchecked((uint)ctx[CpuRegister.Rsi]);
var outHandleAddress = ctx[CpuRegister.R8];
lock (StateGate)
{
if (!Systems.ContainsKey(systemHandle))
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidSystemHandle);
}
}
if (outHandleAddress == 0)
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidOutAddress);
}
if (!TryCreateHandle(ctx, type: 2, systemHandle, out var handle) ||
!ctx.TryWriteUInt64(outHandleAddress, handle))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
lock (StateGate)
{
Racks[handle] = new RackState(systemHandle, rackId);
}
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "lCqD7oycmIM",
ExportName = "sceNgs2RackDestroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2RackDestroy(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
lock (StateGate)
{
if (!Racks.ContainsKey(handle))
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidRackHandle);
}
RemoveRackLocked(handle);
}
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "MwmHz8pAdAo",
ExportName = "sceNgs2RackGetVoiceHandle",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2RackGetVoiceHandle(CpuContext ctx)
{
var rackHandle = ctx[CpuRegister.Rdi];
var voiceIndex = unchecked((uint)ctx[CpuRegister.Rsi]);
var outHandleAddress = ctx[CpuRegister.Rdx];
lock (StateGate)
{
if (!Racks.ContainsKey(rackHandle))
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidRackHandle);
}
var existing = Voices.FirstOrDefault(
pair => pair.Value.RackHandle == rackHandle && pair.Value.VoiceIndex == voiceIndex);
if (existing.Key != 0)
{
return ctx.TryWriteUInt64(outHandleAddress, existing.Key)
? SetReturn(ctx, 0)
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
if (outHandleAddress == 0)
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidOutAddress);
}
if (!TryCreateHandle(ctx, type: 4, rackHandle, out var handle) ||
!ctx.TryWriteUInt64(outHandleAddress, handle))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
lock (StateGate)
{
Voices[handle] = new VoiceState(rackHandle, voiceIndex);
}
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "uu94irFOGpA",
ExportName = "sceNgs2VoiceControl",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2VoiceControl(CpuContext ctx)
{
lock (StateGate)
{
return SetReturn(
ctx,
Voices.ContainsKey(ctx[CpuRegister.Rdi]) ? 0 : OrbisNgs2ErrorInvalidVoiceHandle);
}
}
[SysAbiExport(
Nid = "AbYvTOZ8Pts",
ExportName = "sceNgs2VoiceRunCommands",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2VoiceRunCommands(CpuContext ctx) => Ngs2VoiceControl(ctx);
[SysAbiExport(
Nid = "i0VnXM-C9fc",
ExportName = "sceNgs2SystemRender",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2SystemRender(CpuContext ctx)
{
var systemHandle = ctx[CpuRegister.Rdi];
var bufferInfoAddress = ctx[CpuRegister.Rsi];
var bufferInfoCount = unchecked((uint)ctx[CpuRegister.Rdx]);
lock (StateGate)
{
if (!Systems.ContainsKey(systemHandle))
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidSystemHandle);
}
}
if (bufferInfoCount != 0 && bufferInfoAddress == 0)
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
for (uint i = 0; i < bufferInfoCount; i++)
{
var entryAddress = bufferInfoAddress + (i * RenderBufferInfoSize);
if (!ctx.TryReadUInt64(entryAddress, out var bufferAddress) ||
!ctx.TryReadUInt64(entryAddress + 8, out var bufferSize))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (bufferAddress != 0 && bufferSize != 0)
{
if (bufferSize > MaximumRenderBufferSize || !TryClearGuestBuffer(ctx, bufferAddress, bufferSize))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
}
var count = Interlocked.Increment(ref _renderCount);
if (ShouldTrace() && (count <= 4 || count % 10_000 == 0))
{
Console.Error.WriteLine(
$"[LOADER][TRACE] ngs2.render#{count} system=0x{systemHandle:X16} buffers={bufferInfoCount}");
}
return SetReturn(ctx, 0);
}
private static bool TryCreateHandle(CpuContext ctx, uint type, ulong ownerHandle, out ulong handle)
{
handle = 0;
if (!KernelMemoryCompatExports.TryAllocateHleData(ctx, HandleStorageSize, 16, out handle))
{
return false;
}
Span<byte> data = stackalloc byte[(int)HandleStorageSize];
data.Clear();
BinaryPrimitives.WriteUInt64LittleEndian(data[0..8], handle);
BinaryPrimitives.WriteUInt64LittleEndian(data[8..16], ownerHandle);
BinaryPrimitives.WriteUInt32LittleEndian(data[16..20], 1);
BinaryPrimitives.WriteUInt32LittleEndian(data[24..28], type);
return ctx.Memory.TryWrite(handle, data);
}
private static bool TryClearGuestBuffer(CpuContext ctx, ulong address, ulong length)
{
Span<byte> zeroes = stackalloc byte[4096];
zeroes.Clear();
for (ulong offset = 0; offset < length;)
{
var chunkSize = (int)Math.Min((ulong)zeroes.Length, length - offset);
if (!ctx.Memory.TryWrite(address + offset, zeroes[..chunkSize]))
{
return false;
}
offset += unchecked((uint)chunkSize);
}
return true;
}
private static void RemoveRackLocked(ulong rackHandle)
{
Racks.Remove(rackHandle);
foreach (var voiceHandle in Voices
.Where(pair => pair.Value.RackHandle == rackHandle)
.Select(pair => pair.Key)
.ToArray())
{
Voices.Remove(voiceHandle);
}
}
private static bool ShouldTrace() =>
string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_NGS2"),
"1",
StringComparison.Ordinal);
private static int SetReturn(CpuContext ctx, int result)
{
ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
}
}
+79 -4
View File
@@ -4,6 +4,7 @@
using SharpEmu.HLE; using SharpEmu.HLE;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices;
namespace SharpEmu.Libs.Pad; namespace SharpEmu.Libs.Pad;
@@ -58,6 +59,7 @@ public static class PadExports
return SetReturn(ctx, OrbisPadErrorDeviceNotConnected); return SetReturn(ctx, OrbisPadErrorDeviceNotConnected);
} }
Console.Error.WriteLine("[LOADER][INFO] Keyboard controls: Arrow keys = D-pad, WASD = left stick, IJKL = right stick, Z/Enter = Cross, X/Esc = Circle, C = Square, V = Triangle, Q = L1, E = R1, R = L2, F = R2, Tab/Backspace = Options");
return SetReturn(ctx, PrimaryPadHandle); return SetReturn(ctx, PrimaryPadHandle);
} }
@@ -158,14 +160,33 @@ public static class PadExports
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); : SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
} }
[SysAbiExport(
Nid = "W2G-yoyMF5U",
ExportName = "scePadSetVibrationMode",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libScePad")]
public static int PadSetVibrationMode(CpuContext ctx)
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_OK);
}
private static bool WriteNeutralPadData(CpuContext ctx, ulong dataAddress) private static bool WriteNeutralPadData(CpuContext ctx, ulong dataAddress)
{ {
Span<byte> data = stackalloc byte[PadDataSize]; Span<byte> data = stackalloc byte[PadDataSize];
data.Clear(); data.Clear();
data[0x04] = 128; var acceptsKeyboardInput = IsEmulatorWindowFocused();
data[0x05] = 128; var buttons = acceptsKeyboardInput ? ReadKeyboardButtons() : 0;
data[0x06] = 128; BinaryPrimitives.WriteUInt32LittleEndian(data[0x00..], buttons);
data[0x07] = 128; var leftX = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x41), IsKeyDown(0x44)) : (byte)128;
var leftY = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x57), IsKeyDown(0x53)) : (byte)128;
var rightX = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x4A), IsKeyDown(0x4C)) : (byte)128;
var rightY = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x49), IsKeyDown(0x4B)) : (byte)128;
data[0x04] = leftX;
data[0x05] = leftY;
data[0x06] = rightX;
data[0x07] = rightY;
data[0x08] = acceptsKeyboardInput && IsKeyDown(0x52) ? (byte)255 : (byte)0;
data[0x09] = acceptsKeyboardInput && IsKeyDown(0x46) ? (byte)255 : (byte)0;
BinaryPrimitives.WriteSingleLittleEndian(data[0x18..], 1.0f); BinaryPrimitives.WriteSingleLittleEndian(data[0x18..], 1.0f);
data[0x4C] = 1; data[0x4C] = 1;
var timestampTicks = Stopwatch.GetTimestamp(); var timestampTicks = Stopwatch.GetTimestamp();
@@ -185,4 +206,58 @@ public static class PadExports
ctx[CpuRegister.Rax] = unchecked((ulong)result); ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result; return result;
} }
[DllImport("user32.dll")]
private static extern short GetAsyncKeyState(int vKey);
[DllImport("user32.dll")]
private static extern nint GetForegroundWindow();
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(nint hWnd, out uint processId);
private static bool IsKeyDown(int vk) =>
(GetAsyncKeyState(vk) & 0x8000) != 0;
private static bool IsEmulatorWindowFocused()
{
var foregroundWindow = GetForegroundWindow();
if (foregroundWindow == 0)
{
return false;
}
GetWindowThreadProcessId(foregroundWindow, out var processId);
return processId == (uint)Environment.ProcessId;
}
private static uint ReadKeyboardButtons()
{
uint buttons = 0;
// D-pad
if (IsKeyDown(0x25)) buttons |= 0x0080; // Left
if (IsKeyDown(0x27)) buttons |= 0x0020; // Right
if (IsKeyDown(0x26)) buttons |= 0x0010; // Up
if (IsKeyDown(0x28)) buttons |= 0x0040; // Down
// Face buttons
if (IsKeyDown(0x5A) || IsKeyDown(0x0D)) buttons |= 0x4000; // Z / Enter = Cross
if (IsKeyDown(0x58) || IsKeyDown(0x1B)) buttons |= 0x2000; // X / Escape = Circle
if (IsKeyDown(0x43)) buttons |= 0x8000; // C = Square
if (IsKeyDown(0x56)) buttons |= 0x1000; // V = Triangle
// Shoulder buttons
if (IsKeyDown(0x51)) buttons |= 0x0400; // Q = L1
if (IsKeyDown(0x45)) buttons |= 0x0800; // E = R1
if (IsKeyDown(0x52)) buttons |= 0x0100; // R = L2 (digital)
if (IsKeyDown(0x46)) buttons |= 0x0200; // F = R2 (digital)
// Options (Start)
if (IsKeyDown(0x09) || IsKeyDown(0x08)) buttons |= 0x0008; // Tab / Backspace = Options
return buttons;
}
private static byte ReadAnalogStick(bool negative, bool positive)
{
if (negative && !positive) return 0;
if (positive && !negative) return 255;
return 128;
}
} }
+31 -1
View File
@@ -244,6 +244,36 @@ public static class SaveDataExports
} }
} }
private static int _nextTransactionResource;
[SysAbiExport(
Nid = "gjRZNnw0JPE",
ExportName = "sceSaveDataCreateTransactionResource",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceSaveData")]
public static int SaveDataCreateTransactionResource(CpuContext ctx)
{
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
var reserved = ctx[CpuRegister.Rsi];
var resourceAddress = ctx[CpuRegister.Rdx];
if (resourceAddress == 0)
{
return SetReturn(ctx, OrbisSaveDataErrorParameter);
}
var id = (uint)Interlocked.Increment(ref _nextTransactionResource);
if (!TryWriteUInt32(ctx, resourceAddress, id))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceSaveData(
$"create_transaction_resource user={userId} reserved=0x{reserved:X} resource_addr=0x{resourceAddress:X} id={id}");
return SetReturn(ctx, 0);
}
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;
@@ -400,7 +430,7 @@ public static class SaveDataExports
{ {
var configured = Environment.GetEnvironmentVariable("SHARPEMU_SAVEDATA_DIR"); var configured = Environment.GetEnvironmentVariable("SHARPEMU_SAVEDATA_DIR");
var root = string.IsNullOrWhiteSpace(configured) var root = string.IsNullOrWhiteSpace(configured)
? Path.Combine(Environment.CurrentDirectory, "user", "savedata") ? Path.Combine(AppContext.BaseDirectory, "user", "savedata")
: configured; : configured;
return Path.GetFullPath(root); return Path.GetFullPath(root);
} }
+1
View File
@@ -10,6 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<ItemGroup> <ItemGroup>
<PackageReference Include="Silk.NET.Vulkan" /> <PackageReference Include="Silk.NET.Vulkan" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="Silk.NET.Windowing" /> <PackageReference Include="Silk.NET.Windowing" />
</ItemGroup> </ItemGroup>
@@ -4,6 +4,7 @@
using SharpEmu.HLE; using SharpEmu.HLE;
using SharpEmu.Libs.Kernel; using SharpEmu.Libs.Kernel;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Threading; using System.Threading;
@@ -48,6 +49,13 @@ public static class VideoOutExports
private static int _frameDumpCount; private static int _frameDumpCount;
private static long _nextFrameDumpIndex; private static long _nextFrameDumpIndex;
private static string _windowTitle = "SharpEmu VideoOut"; private static string _windowTitle = "SharpEmu VideoOut";
private static readonly bool _logFrameRate = string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_VIDEOOUT_FPS"),
"1",
StringComparison.Ordinal);
private static long _frameRateWindowStart = Stopwatch.GetTimestamp();
private static long _submittedFrameCount;
private static long _presentedFrameCount;
public static void ConfigureApplicationInfo(string? title, string? titleId, string? version) public static void ConfigureApplicationInfo(string? title, string? titleId, string? version)
{ {
@@ -812,9 +820,46 @@ public static class VideoOutExports
} }
TraceVideoOut($"videoout.submit_flip handle={handle} index={bufferIndex} mode={flipMode} arg={flipArg} events={flipEvents.Count}"); TraceVideoOut($"videoout.submit_flip handle={handle} index={bufferIndex} mode={flipMode} arg={flipArg} events={flipEvents.Count}");
ReportFrameRate(presented: false);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
internal static void ReportPresentedFrame() =>
ReportFrameRate(presented: true);
private static void ReportFrameRate(bool presented)
{
if (!_logFrameRate)
{
return;
}
if (presented)
{
Interlocked.Increment(ref _presentedFrameCount);
}
else
{
Interlocked.Increment(ref _submittedFrameCount);
}
var started = Volatile.Read(ref _frameRateWindowStart);
var now = Stopwatch.GetTimestamp();
var elapsedTicks = now - started;
if (elapsedTicks < Stopwatch.Frequency ||
Interlocked.CompareExchange(ref _frameRateWindowStart, now, started) != started)
{
return;
}
var elapsedSeconds = (double)elapsedTicks / Stopwatch.Frequency;
var submitted = Interlocked.Exchange(ref _submittedFrameCount, 0);
var presentedCount = Interlocked.Exchange(ref _presentedFrameCount, 0);
Console.Error.WriteLine(
$"[LOADER][PERF] videoout submitted_fps={submitted / elapsedSeconds:F1} " +
$"presented_fps={presentedCount / elapsedSeconds:F1}");
}
private static int RegisterBufferRange(VideoOutPortState port, int startIndex, ReadOnlySpan<ulong> addresses, BufferAttribute attribute, int requestedGroupIndex = -1) private static int RegisterBufferRange(VideoOutPortState port, int startIndex, ReadOnlySpan<ulong> addresses, BufferAttribute attribute, int requestedGroupIndex = -1)
{ {
lock (_stateGate) lock (_stateGate)
File diff suppressed because it is too large Load Diff
+10
View File
@@ -11,6 +11,16 @@
"Silk.NET.Core": "2.23.0" "Silk.NET.Core": "2.23.0"
} }
}, },
"Silk.NET.Vulkan.Extensions.EXT": {
"type": "Direct",
"requested": "[2.23.0, )",
"resolved": "2.23.0",
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
"dependencies": {
"Silk.NET.Core": "2.23.0",
"Silk.NET.Vulkan": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.KHR": { "Silk.NET.Vulkan.Extensions.KHR": {
"type": "Direct", "type": "Direct",
"requested": "[2.23.0, )", "requested": "[2.23.0, )",