mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-28 05:30:57 +08:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a828f8ed3 | |||
| a63a7de35a | |||
| fe1a592496 | |||
| 9d4dad2c24 | |||
| 38d94d8e54 | |||
| 61a1abdb13 | |||
| 88452218cb | |||
| 427a511e48 | |||
| e103795768 | |||
| 24c6e44800 | |||
| 3a30d6419f | |||
| 2d8a795f23 | |||
| adb2acdb39 | |||
| 5e5bead02c | |||
| e98f02966a | |||
| 9ab1e2f21a | |||
| 340eb9e0ab | |||
| 03e3d25e85 | |||
| 2bc466b10e | |||
| 45d0be474b | |||
| 5bb91eff9b | |||
| f4df0ed4bd | |||
| 23691a2bdc | |||
| 5c5ed8c064 | |||
| a4748ce266 | |||
| 5617646c8a | |||
| a4f3d3cd7f | |||
| 92526ecacf |
+1
-1
@@ -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 |
@@ -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
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ arm64/
|
|||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
|
||||||
|
.packages
|
||||||
packages/
|
packages/
|
||||||
*.nupkg
|
*.nupkg
|
||||||
.nuget/
|
.nuget/
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
+3
-1
@@ -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 |
@@ -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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -723,6 +765,7 @@ public sealed partial class DirectExecutionBackend
|
|||||||
"Vo5V8KAwCmk" or // sceSystemServiceHideSplashScreen
|
"Vo5V8KAwCmk" or // sceSystemServiceHideSplashScreen
|
||||||
"TywrFKCoLGY" or // sceSaveDataInitialize3
|
"TywrFKCoLGY" or // sceSaveDataInitialize3
|
||||||
"dyIhnXq-0SM" or // sceSaveDataDirNameSearch
|
"dyIhnXq-0SM" or // sceSaveDataDirNameSearch
|
||||||
|
"ZP4e7rlzOUk" or // sceSaveDataMount3
|
||||||
"ERKzksauAJA" or // sceSaveDataDialogGetStatus
|
"ERKzksauAJA" or // sceSaveDataDialogGetStatus
|
||||||
"KK3Bdg1RWK0" or // sceSaveDataDialogUpdateStatus
|
"KK3Bdg1RWK0" or // sceSaveDataDialogUpdateStatus
|
||||||
"en7gNVnh878" or // sceSaveDataDialogIsReadyToDisplay
|
"en7gNVnh878" or // sceSaveDataDialogIsReadyToDisplay
|
||||||
@@ -1179,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;
|
||||||
}
|
}
|
||||||
@@ -1193,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;
|
||||||
|
|||||||
@@ -355,6 +355,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
public GuestThreadRunState State { get; set; }
|
public GuestThreadRunState State { get; set; }
|
||||||
|
|
||||||
|
public ulong ExitValue { get; set; }
|
||||||
|
|
||||||
public string? BlockReason { get; set; }
|
public string? BlockReason { get; set; }
|
||||||
|
|
||||||
public bool HasBlockedContinuation { get; set; }
|
public bool HasBlockedContinuation { get; set; }
|
||||||
@@ -1266,7 +1268,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
libraryName.IndexOf("Kernel", StringComparison.OrdinalIgnoreCase) >= 0;
|
libraryName.IndexOf("Kernel", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool PreferLleForLibcExport(string exportName)
|
private bool PreferLleForLibcExport(string exportName)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(exportName))
|
if (string.IsNullOrWhiteSpace(exportName))
|
||||||
{
|
{
|
||||||
@@ -1287,6 +1289,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (IsLibcAllocatorExport(exportName))
|
||||||
|
{
|
||||||
|
return CanUseLleLibcAllocatorFamily();
|
||||||
|
}
|
||||||
if (string.Equals(value, "0", StringComparison.Ordinal))
|
if (string.Equals(value, "0", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
@@ -1298,6 +1304,51 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
return IsSafeLleLibcExport(exportName);
|
return IsSafeLleLibcExport(exportName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool CanUseLleLibcAllocatorFamily()
|
||||||
|
{
|
||||||
|
return HasUsableLleLibcExport("gQX+4GDQjpM", "malloc") &&
|
||||||
|
HasUsableLleLibcExport("tIhsqj0qsFE", "free") &&
|
||||||
|
HasUsableLleLibcExport("2X5agFjKxMc", "calloc") &&
|
||||||
|
HasUsableLleLibcExport("Y7aJ1uydPMo", "realloc") &&
|
||||||
|
HasUsableLleLibcExport("Ujf3KzMvRmI", "memalign") &&
|
||||||
|
HasUsableLleLibcExport("2Btkg8k24Zg", "aligned_alloc") &&
|
||||||
|
HasUsableLleLibcExport("cVSk9y8URbc", "posix_memalign");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasUsableLleLibcExport(string nid, string exportName)
|
||||||
|
{
|
||||||
|
if (TryResolveRuntimeSymbolAddress(nid, out var address) && IsDirectImportTargetUsable(address))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var candidate in EnumerateRuntimeSymbolCandidates(exportName))
|
||||||
|
{
|
||||||
|
if (TryResolveRuntimeSymbolAddress(candidate, out address) && IsDirectImportTargetUsable(address))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsLibcAllocatorExport(string exportName)
|
||||||
|
{
|
||||||
|
return exportName switch
|
||||||
|
{
|
||||||
|
"malloc" or
|
||||||
|
"free" or
|
||||||
|
"calloc" or
|
||||||
|
"realloc" or
|
||||||
|
"memalign" or
|
||||||
|
"aligned_alloc" or
|
||||||
|
"posix_memalign" or
|
||||||
|
"malloc_usable_size" => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsSafeLleLibcExport(string exportName)
|
private static bool IsSafeLleLibcExport(string exportName)
|
||||||
{
|
{
|
||||||
return exportName switch
|
return exportName switch
|
||||||
@@ -2406,6 +2457,69 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
public bool SupportsGuestContextTransfer => true;
|
public bool SupportsGuestContextTransfer => true;
|
||||||
|
|
||||||
|
public bool TryJoinThread(
|
||||||
|
CpuContext callerContext,
|
||||||
|
ulong threadHandle,
|
||||||
|
out ulong returnValue,
|
||||||
|
out string? error)
|
||||||
|
{
|
||||||
|
returnValue = 0;
|
||||||
|
error = null;
|
||||||
|
if (threadHandle == 0)
|
||||||
|
{
|
||||||
|
error = "thread handle is zero";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (threadHandle == GuestThreadExecution.CurrentGuestThreadHandle)
|
||||||
|
{
|
||||||
|
error = "thread cannot join itself";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!ActiveForcedGuestExit)
|
||||||
|
{
|
||||||
|
Thread? hostThread;
|
||||||
|
lock (_guestThreadGate)
|
||||||
|
{
|
||||||
|
if (!_guestThreads.TryGetValue(threadHandle, out var thread))
|
||||||
|
{
|
||||||
|
error = $"unknown guest thread 0x{threadHandle:X16}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thread.State == GuestThreadRunState.Exited)
|
||||||
|
{
|
||||||
|
returnValue = thread.ExitValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thread.State == GuestThreadRunState.Faulted)
|
||||||
|
{
|
||||||
|
error =
|
||||||
|
$"guest thread 0x{threadHandle:X16} faulted: " +
|
||||||
|
(thread.BlockReason ?? "unknown error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hostThread = thread.HostThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hostThread is not null &&
|
||||||
|
!ReferenceEquals(hostThread, Thread.CurrentThread))
|
||||||
|
{
|
||||||
|
hostThread.Join(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Thread.Sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error = "guest execution stopped while joining thread";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void Pump(CpuContext callerContext, string reason)
|
public void Pump(CpuContext callerContext, string reason)
|
||||||
{
|
{
|
||||||
_ = callerContext;
|
_ = callerContext;
|
||||||
@@ -3254,6 +3368,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
switch (exitReason)
|
switch (exitReason)
|
||||||
{
|
{
|
||||||
case GuestNativeCallExitReason.Returned:
|
case GuestNativeCallExitReason.Returned:
|
||||||
|
thread.ExitValue = thread.Context[CpuRegister.Rax];
|
||||||
thread.State = GuestThreadRunState.Exited;
|
thread.State = GuestThreadRunState.Exited;
|
||||||
break;
|
break;
|
||||||
case GuestNativeCallExitReason.Blocked:
|
case GuestNativeCallExitReason.Blocked:
|
||||||
|
|||||||
@@ -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,30 +485,34 @@ 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 &&
|
||||||
|
TryResolveRegionOffset(
|
||||||
|
virtualAddress,
|
||||||
|
(ulong)destination.Length,
|
||||||
|
region,
|
||||||
|
out var offset))
|
||||||
{
|
{
|
||||||
if (TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset))
|
var srcPtr = (void*)(region.VirtualAddress + offset);
|
||||||
|
if (destination.IsEmpty)
|
||||||
{
|
{
|
||||||
var srcPtr = (void*)(region.VirtualAddress + offset);
|
return true;
|
||||||
if (destination.IsEmpty)
|
}
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (region.IsReservedOnly)
|
if (region.IsReservedOnly)
|
||||||
|
{
|
||||||
|
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
|
||||||
{
|
{
|
||||||
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
|
return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region))
|
|
||||||
{
|
|
||||||
requiresExclusiveAccess = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region))
|
||||||
|
{
|
||||||
|
requiresExclusiveAccess = true;
|
||||||
|
}
|
||||||
|
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,30 +549,34 @@ 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 &&
|
||||||
|
TryResolveRegionOffset(
|
||||||
|
virtualAddress,
|
||||||
|
(ulong)source.Length,
|
||||||
|
region,
|
||||||
|
out var offset))
|
||||||
{
|
{
|
||||||
if (TryResolveRegionOffset(virtualAddress, (ulong)source.Length, region, out var offset))
|
var destPtr = (void*)(region.VirtualAddress + offset);
|
||||||
|
if (source.IsEmpty)
|
||||||
{
|
{
|
||||||
var destPtr = (void*)(region.VirtualAddress + offset);
|
return true;
|
||||||
if (source.IsEmpty)
|
}
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (region.IsReservedOnly)
|
if (region.IsReservedOnly)
|
||||||
|
{
|
||||||
|
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
|
||||||
{
|
{
|
||||||
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
|
return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region))
|
|
||||||
{
|
|
||||||
requiresExclusiveAccess = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region))
|
||||||
|
{
|
||||||
|
requiresExclusiveAccess = true;
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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, )",
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ public interface IGuestThreadScheduler
|
|||||||
|
|
||||||
bool TryStartThread(CpuContext creatorContext, GuestThreadStartRequest request, out string? error);
|
bool TryStartThread(CpuContext creatorContext, GuestThreadStartRequest request, out string? error);
|
||||||
|
|
||||||
|
bool TryJoinThread(
|
||||||
|
CpuContext callerContext,
|
||||||
|
ulong threadHandle,
|
||||||
|
out ulong returnValue,
|
||||||
|
out string? error);
|
||||||
|
|
||||||
void Pump(CpuContext callerContext, string reason);
|
void Pump(CpuContext callerContext, string reason);
|
||||||
|
|
||||||
int WakeBlockedThreads(string wakeKey, int maxCount = int.MaxValue);
|
int WakeBlockedThreads(string wakeKey, int maxCount = int.MaxValue);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -60,7 +60,8 @@ internal sealed record Gen5SpirvShader(
|
|||||||
byte[] Spirv,
|
byte[] Spirv,
|
||||||
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
|
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
|
||||||
IReadOnlyList<Gen5ImageBinding> ImageBindings,
|
IReadOnlyList<Gen5ImageBinding> ImageBindings,
|
||||||
uint AttributeCount);
|
uint AttributeCount,
|
||||||
|
IReadOnlyList<Gen5VertexInputBinding> VertexInputs);
|
||||||
|
|
||||||
internal readonly record struct Gen5ShaderResourceMapping(
|
internal readonly record struct Gen5ShaderResourceMapping(
|
||||||
Gen5ShaderResourceKind Kind,
|
Gen5ShaderResourceKind Kind,
|
||||||
@@ -131,7 +132,8 @@ internal sealed record Gen5ShaderState(
|
|||||||
Gen5ShaderProgram Program,
|
Gen5ShaderProgram Program,
|
||||||
IReadOnlyList<uint> UserData,
|
IReadOnlyList<uint> UserData,
|
||||||
Gen5ShaderMetadata? Metadata,
|
Gen5ShaderMetadata? Metadata,
|
||||||
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null);
|
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null,
|
||||||
|
uint UserDataScalarRegisterBase = 0);
|
||||||
|
|
||||||
internal readonly record struct Gen5Operand(Gen5OperandKind Kind, uint Value)
|
internal readonly record struct Gen5Operand(Gen5OperandKind Kind, uint Value)
|
||||||
{
|
{
|
||||||
@@ -270,6 +272,17 @@ internal sealed record Gen5GlobalMemoryBinding(
|
|||||||
IReadOnlyList<uint> InstructionPcs,
|
IReadOnlyList<uint> InstructionPcs,
|
||||||
byte[] Data);
|
byte[] Data);
|
||||||
|
|
||||||
|
internal sealed record Gen5VertexInputBinding(
|
||||||
|
uint Pc,
|
||||||
|
uint Location,
|
||||||
|
uint ComponentCount,
|
||||||
|
uint DataFormat,
|
||||||
|
uint NumberFormat,
|
||||||
|
ulong BaseAddress,
|
||||||
|
uint Stride,
|
||||||
|
uint OffsetBytes,
|
||||||
|
byte[] Data);
|
||||||
|
|
||||||
internal sealed record Gen5ShaderEvaluation(
|
internal sealed record Gen5ShaderEvaluation(
|
||||||
IReadOnlyList<uint> InitialScalarRegisters,
|
IReadOnlyList<uint> InitialScalarRegisters,
|
||||||
IReadOnlyList<uint> ScalarRegisters,
|
IReadOnlyList<uint> ScalarRegisters,
|
||||||
@@ -277,7 +290,8 @@ internal sealed record Gen5ShaderEvaluation(
|
|||||||
IReadOnlyList<Gen5ImageBinding> ImageBindings,
|
IReadOnlyList<Gen5ImageBinding> ImageBindings,
|
||||||
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
|
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
|
||||||
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null,
|
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null,
|
||||||
IReadOnlySet<uint>? RuntimeScalarRegisters = null);
|
IReadOnlySet<uint>? RuntimeScalarRegisters = null,
|
||||||
|
IReadOnlyList<Gen5VertexInputBinding>? VertexInputs = null);
|
||||||
|
|
||||||
internal sealed record Gen5ShaderInstruction(
|
internal sealed record Gen5ShaderInstruction(
|
||||||
uint Pc,
|
uint Pc,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
using SharpEmu.HLE;
|
using SharpEmu.HLE;
|
||||||
|
using SharpEmu.Libs.Kernel;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@@ -13,12 +14,15 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
private const int ImageDescriptorDwords = 8;
|
private const int ImageDescriptorDwords = 8;
|
||||||
private const int SamplerDescriptorDwords = 4;
|
private const int SamplerDescriptorDwords = 4;
|
||||||
private const int MaxGlobalMemoryBindingBytes = 16 * 1024 * 1024;
|
private const int MaxGlobalMemoryBindingBytes = 16 * 1024 * 1024;
|
||||||
|
private const ulong RdnaWaveMask = 0xFFFF_FFFFUL;
|
||||||
|
|
||||||
private readonly record struct BufferDescriptor(
|
private readonly record struct BufferDescriptor(
|
||||||
ulong BaseAddress,
|
ulong BaseAddress,
|
||||||
uint Stride,
|
uint Stride,
|
||||||
uint NumRecords,
|
uint NumRecords,
|
||||||
ulong SizeBytes);
|
ulong SizeBytes,
|
||||||
|
uint NumberFormat,
|
||||||
|
uint DataFormat);
|
||||||
|
|
||||||
public static bool TryResolveImageBindings(
|
public static bool TryResolveImageBindings(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
@@ -40,14 +44,19 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
Gen5ShaderState state,
|
Gen5ShaderState state,
|
||||||
out Gen5ShaderEvaluation evaluation,
|
out Gen5ShaderEvaluation evaluation,
|
||||||
out string error)
|
out string error,
|
||||||
|
bool resolveVertexInputs = false)
|
||||||
{
|
{
|
||||||
evaluation = default!;
|
evaluation = default!;
|
||||||
error = string.Empty;
|
error = string.Empty;
|
||||||
var scalarRegisters = new uint[ScalarRegisterCount];
|
var scalarRegisters = new uint[ScalarRegisterCount];
|
||||||
for (var index = 0; index < state.UserData.Count && index < scalarRegisters.Length; index++)
|
for (var index = 0;
|
||||||
|
index < state.UserData.Count &&
|
||||||
|
state.UserDataScalarRegisterBase + (uint)index < scalarRegisters.Length;
|
||||||
|
index++)
|
||||||
{
|
{
|
||||||
scalarRegisters[index] = state.UserData[index];
|
scalarRegisters[state.UserDataScalarRegisterBase + (uint)index] =
|
||||||
|
state.UserData[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.ComputeSystemRegisters is { } computeSystemRegisters)
|
if (state.ComputeSystemRegisters is { } computeSystemRegisters)
|
||||||
@@ -55,14 +64,15 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
computeSystemRegisters.ClearStaticValues(scalarRegisters);
|
computeSystemRegisters.ClearStaticValues(scalarRegisters);
|
||||||
}
|
}
|
||||||
|
|
||||||
var execMask = ulong.MaxValue;
|
var execMask = RdnaWaveMask;
|
||||||
WriteScalarPair(scalarRegisters, 106, ulong.MaxValue, ref execMask);
|
WriteScalarPair(scalarRegisters, 106, 0, ref execMask);
|
||||||
WriteScalarPair(scalarRegisters, 126, execMask, ref execMask);
|
WriteScalarPair(scalarRegisters, 126, execMask, ref execMask);
|
||||||
var initialScalarRegisters = (uint[])scalarRegisters.Clone();
|
var initialScalarRegisters = (uint[])scalarRegisters.Clone();
|
||||||
|
|
||||||
var resolved = new List<Gen5ImageBinding>();
|
var resolved = new List<Gen5ImageBinding>();
|
||||||
var globalMemoryBindings = new List<Gen5GlobalMemoryBinding>();
|
var globalMemoryBindings = new List<Gen5GlobalMemoryBinding>();
|
||||||
var globalMemoryByAddress = new Dictionary<(uint ScalarAddress, ulong BaseAddress), Gen5GlobalMemoryBinding>();
|
var globalMemoryByAddress = new Dictionary<(uint ScalarAddress, ulong BaseAddress), Gen5GlobalMemoryBinding>();
|
||||||
|
var vertexInputBindings = new List<Gen5VertexInputBinding>();
|
||||||
var runtimeScalarRegisters = CollectRuntimeScalarRegisters(state.Program);
|
var runtimeScalarRegisters = CollectRuntimeScalarRegisters(state.Program);
|
||||||
var scalarRegisterSnapshots = new Dictionary<uint, IReadOnlyList<uint>>();
|
var scalarRegisterSnapshots = new Dictionary<uint, IReadOnlyList<uint>>();
|
||||||
var scalarConditionCode = false;
|
var scalarConditionCode = false;
|
||||||
@@ -243,6 +253,42 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resolveVertexInputs &&
|
||||||
|
IsVertexFetchCandidate(instruction, bufferMemory, bufferDescriptor))
|
||||||
|
{
|
||||||
|
if (!TryReadGlobalMemory(
|
||||||
|
ctx,
|
||||||
|
bufferDescriptor.BaseAddress,
|
||||||
|
bufferDescriptor.SizeBytes,
|
||||||
|
out var vertexData))
|
||||||
|
{
|
||||||
|
error =
|
||||||
|
$"vertex-buffer-read-failed pc=0x{instruction.Pc:X} " +
|
||||||
|
$"address=0x{bufferDescriptor.BaseAddress:X16} " +
|
||||||
|
$"bytes={bufferDescriptor.SizeBytes} " +
|
||||||
|
$"stride={bufferDescriptor.Stride} records={bufferDescriptor.NumRecords}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryCreateVertexInputBinding(
|
||||||
|
instruction,
|
||||||
|
bufferMemory,
|
||||||
|
bufferDescriptor,
|
||||||
|
vertexData,
|
||||||
|
(uint)vertexInputBindings.Count,
|
||||||
|
scalarRegisters,
|
||||||
|
out var vertexInputBinding))
|
||||||
|
{
|
||||||
|
error =
|
||||||
|
$"vertex-input-binding-failed pc=0x{instruction.Pc:X} " +
|
||||||
|
$"s{bufferMemory.ScalarResource}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vertexInputBindings.Add(vertexInputBinding);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var key = (bufferMemory.ScalarResource, bufferDescriptor.BaseAddress);
|
var key = (bufferMemory.ScalarResource, bufferDescriptor.BaseAddress);
|
||||||
if (globalMemoryByAddress.TryGetValue(key, out var existingBinding))
|
if (globalMemoryByAddress.TryGetValue(key, out var existingBinding))
|
||||||
{
|
{
|
||||||
@@ -259,10 +305,16 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
bufferDescriptor.SizeBytes,
|
bufferDescriptor.SizeBytes,
|
||||||
out var data))
|
out var data))
|
||||||
{
|
{
|
||||||
|
var descriptorWords = string.Join(
|
||||||
|
':',
|
||||||
|
Enumerable.Range(0, 4).Select(index =>
|
||||||
|
$"{scalarRegisters[bufferMemory.ScalarResource + (uint)index]:X8}"));
|
||||||
error =
|
error =
|
||||||
$"buffer-memory-read-failed pc=0x{instruction.Pc:X} " +
|
$"buffer-memory-read-failed pc=0x{instruction.Pc:X} " +
|
||||||
$"address=0x{bufferDescriptor.BaseAddress:X16} " +
|
$"address=0x{bufferDescriptor.BaseAddress:X16} " +
|
||||||
$"bytes={bufferDescriptor.SizeBytes}";
|
$"bytes={bufferDescriptor.SizeBytes} " +
|
||||||
|
$"stride={bufferDescriptor.Stride} records={bufferDescriptor.NumRecords} " +
|
||||||
|
$"s{bufferMemory.ScalarResource}=[{descriptorWords}]";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,10 +380,58 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
resolved,
|
resolved,
|
||||||
globalMemoryBindings,
|
globalMemoryBindings,
|
||||||
state.ComputeSystemRegisters,
|
state.ComputeSystemRegisters,
|
||||||
runtimeScalarRegisters);
|
runtimeScalarRegisters,
|
||||||
|
vertexInputBindings);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryCreateVertexInputBinding(
|
||||||
|
Gen5ShaderInstruction instruction,
|
||||||
|
Gen5BufferMemoryControl control,
|
||||||
|
BufferDescriptor descriptor,
|
||||||
|
byte[] data,
|
||||||
|
uint location,
|
||||||
|
uint[] scalarRegisters,
|
||||||
|
out Gen5VertexInputBinding binding)
|
||||||
|
{
|
||||||
|
binding = default!;
|
||||||
|
if (!IsVertexFetchCandidate(instruction, control, descriptor) ||
|
||||||
|
instruction.Sources.Count <= 2 ||
|
||||||
|
!TryEvaluateScalarOperand(instruction.Sources[2], scalarRegisters, out var scalarOffset))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bindingData = data;
|
||||||
|
var bindingStride = descriptor.Stride;
|
||||||
|
var bindingOffset = unchecked((uint)control.OffsetBytes + scalarOffset);
|
||||||
|
var bindingDataFormat = descriptor.DataFormat;
|
||||||
|
var bindingNumberFormat = descriptor.NumberFormat;
|
||||||
|
binding = new Gen5VertexInputBinding(
|
||||||
|
instruction.Pc,
|
||||||
|
location,
|
||||||
|
control.DwordCount,
|
||||||
|
bindingDataFormat,
|
||||||
|
bindingNumberFormat,
|
||||||
|
descriptor.BaseAddress,
|
||||||
|
bindingStride,
|
||||||
|
bindingOffset,
|
||||||
|
bindingData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsVertexFetchCandidate(
|
||||||
|
Gen5ShaderInstruction instruction,
|
||||||
|
Gen5BufferMemoryControl control,
|
||||||
|
BufferDescriptor descriptor) =>
|
||||||
|
control.IndexEnabled &&
|
||||||
|
!control.OffsetEnabled &&
|
||||||
|
control.DwordCount is >= 1 and <= 4 &&
|
||||||
|
descriptor.BaseAddress != 0 &&
|
||||||
|
descriptor.Stride != 0 &&
|
||||||
|
(instruction.Opcode.StartsWith("BufferLoadFormat", StringComparison.Ordinal) ||
|
||||||
|
instruction.Opcode.StartsWith("TBufferLoadFormat", StringComparison.Ordinal));
|
||||||
|
|
||||||
private static HashSet<uint> CollectRuntimeScalarRegisters(Gen5ShaderProgram program)
|
private static HashSet<uint> CollectRuntimeScalarRegisters(Gen5ShaderProgram program)
|
||||||
{
|
{
|
||||||
var registers = new HashSet<uint>();
|
var registers = new HashSet<uint>();
|
||||||
@@ -471,10 +571,22 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = GC.AllocateUninitializedArray<byte>((int)cappedSize);
|
var candidateSize = (int)cappedSize;
|
||||||
if (ctx.Memory.TryRead(baseAddress, data))
|
while (candidateSize >= sizeof(uint))
|
||||||
{
|
{
|
||||||
return true;
|
data = GC.AllocateUninitializedArray<byte>(candidateSize);
|
||||||
|
if (ctx.Memory.TryRead(baseAddress, data) ||
|
||||||
|
KernelMemoryCompatExports.TryReadTrackedLibcHeap(baseAddress, data))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidateSize == sizeof(uint))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidateSize = Math.Max(candidateSize / 2, sizeof(uint));
|
||||||
}
|
}
|
||||||
|
|
||||||
data = [];
|
data = [];
|
||||||
@@ -582,6 +694,49 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (instruction.Opcode is "SBfeU64" or "SBfeI64")
|
||||||
|
{
|
||||||
|
if (instruction.Sources.Count < 2 ||
|
||||||
|
destination.Value >= ScalarRegisterCount - 1 ||
|
||||||
|
!TryEvaluateScalarOperand64(
|
||||||
|
instruction.Sources[0],
|
||||||
|
registers,
|
||||||
|
execMask,
|
||||||
|
out var source) ||
|
||||||
|
!TryEvaluateScalarOperand(
|
||||||
|
instruction.Sources[1],
|
||||||
|
registers,
|
||||||
|
out var control))
|
||||||
|
{
|
||||||
|
error = $"scalar-source64 pc=0x{instruction.Pc:X} op={instruction.Opcode}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = (int)control & 63;
|
||||||
|
var width = Math.Min(((int)control >> 16) & 0x7F, 64 - offset);
|
||||||
|
ulong value;
|
||||||
|
if (width == 0)
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = source >> offset;
|
||||||
|
if (width < 64)
|
||||||
|
{
|
||||||
|
value &= ulong.MaxValue >> (64 - width);
|
||||||
|
if (instruction.Opcode == "SBfeI64")
|
||||||
|
{
|
||||||
|
value = unchecked((ulong)((long)(value << (64 - width)) >> (64 - width)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteScalarPair(registers, destination.Value, value, ref execMask);
|
||||||
|
scalarConditionCode = value != 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (instruction.Opcode is
|
if (instruction.Opcode is
|
||||||
"SCselectB64" or
|
"SCselectB64" or
|
||||||
"SAndB64" or
|
"SAndB64" or
|
||||||
@@ -657,7 +812,11 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
"SFF1I32B32" => left == 0 ? uint.MaxValue : (uint)BitOperations.TrailingZeroCount(left),
|
"SFF1I32B32" => left == 0 ? uint.MaxValue : (uint)BitOperations.TrailingZeroCount(left),
|
||||||
_ => registers[destination.Value] | (1u << ((int)left & 31)),
|
_ => registers[destination.Value] | (1u << ((int)left & 31)),
|
||||||
};
|
};
|
||||||
scalarConditionCode = registers[destination.Value] != 0;
|
if (instruction.Opcode != "SBitset1B32")
|
||||||
|
{
|
||||||
|
scalarConditionCode = registers[destination.Value] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,13 +842,15 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
}
|
}
|
||||||
case "SSubU32":
|
case "SSubU32":
|
||||||
result = left - right;
|
result = left - right;
|
||||||
scalarConditionCode = left >= right;
|
scalarConditionCode = right > left;
|
||||||
break;
|
break;
|
||||||
case "SAddI32":
|
case "SAddI32":
|
||||||
result = unchecked((uint)((int)left + (int)right));
|
result = unchecked((uint)((int)left + (int)right));
|
||||||
|
scalarConditionCode = SignedAddOverflow(left, right, result);
|
||||||
break;
|
break;
|
||||||
case "SSubI32":
|
case "SSubI32":
|
||||||
result = unchecked((uint)((int)left - (int)right));
|
result = unchecked((uint)((int)left - (int)right));
|
||||||
|
scalarConditionCode = SignedSubOverflow(left, right, result);
|
||||||
break;
|
break;
|
||||||
case "SAddcU32":
|
case "SAddcU32":
|
||||||
{
|
{
|
||||||
@@ -700,23 +861,27 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
}
|
}
|
||||||
case "SSubbU32":
|
case "SSubbU32":
|
||||||
{
|
{
|
||||||
var borrow = scalarConditionCode ? 0UL : 1UL;
|
var borrow = scalarConditionCode ? 1UL : 0UL;
|
||||||
var subtrahend = (ulong)right + borrow;
|
var subtrahend = (ulong)right + borrow;
|
||||||
result = unchecked(left - (uint)subtrahend);
|
result = unchecked(left - (uint)subtrahend);
|
||||||
scalarConditionCode = left >= subtrahend;
|
scalarConditionCode = subtrahend > left;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "SMinI32":
|
case "SMinI32":
|
||||||
result = unchecked((uint)Math.Min((int)left, (int)right));
|
result = unchecked((uint)Math.Min((int)left, (int)right));
|
||||||
|
scalarConditionCode = (int)left < (int)right;
|
||||||
break;
|
break;
|
||||||
case "SMinU32":
|
case "SMinU32":
|
||||||
result = Math.Min(left, right);
|
result = Math.Min(left, right);
|
||||||
|
scalarConditionCode = left < right;
|
||||||
break;
|
break;
|
||||||
case "SMaxI32":
|
case "SMaxI32":
|
||||||
result = unchecked((uint)Math.Max((int)left, (int)right));
|
result = unchecked((uint)Math.Max((int)left, (int)right));
|
||||||
|
scalarConditionCode = (int)left > (int)right;
|
||||||
break;
|
break;
|
||||||
case "SMaxU32":
|
case "SMaxU32":
|
||||||
result = Math.Max(left, right);
|
result = Math.Max(left, right);
|
||||||
|
scalarConditionCode = left > right;
|
||||||
break;
|
break;
|
||||||
case "SCselectB32":
|
case "SCselectB32":
|
||||||
result = scalarConditionCode ? left : right;
|
result = scalarConditionCode ? left : right;
|
||||||
@@ -780,6 +945,7 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
var offset = (int)right & 31;
|
var offset = (int)right & 31;
|
||||||
var width = Math.Min(((int)right >> 16) & 0x7F, 32 - offset);
|
var width = Math.Min(((int)right >> 16) & 0x7F, 32 - offset);
|
||||||
result = width == 0 ? 0 : left >> offset & (uint.MaxValue >> (32 - width));
|
result = width == 0 ? 0 : left >> offset & (uint.MaxValue >> (32 - width));
|
||||||
|
scalarConditionCode = result != 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "SBfeI32":
|
case "SBfeI32":
|
||||||
@@ -789,23 +955,41 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
result = width == 0
|
result = width == 0
|
||||||
? 0
|
? 0
|
||||||
: unchecked((uint)(((int)(left << (32 - width - offset))) >> (32 - width)));
|
: unchecked((uint)(((int)(left << (32 - width - offset))) >> (32 - width)));
|
||||||
|
scalarConditionCode = result != 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "SAbsdiffI32":
|
case "SAbsdiffI32":
|
||||||
result = unchecked((uint)Math.Abs((long)(int)left - (int)right));
|
result = unchecked((uint)Math.Abs((long)(int)left - (int)right));
|
||||||
|
scalarConditionCode = result != 0;
|
||||||
break;
|
break;
|
||||||
case "SLshl1AddU32":
|
case "SLshl1AddU32":
|
||||||
result = (left << 1) + right;
|
{
|
||||||
break;
|
var wide = ((ulong)left << 1) + right;
|
||||||
|
result = (uint)wide;
|
||||||
|
scalarConditionCode = wide > uint.MaxValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "SLshl2AddU32":
|
case "SLshl2AddU32":
|
||||||
result = (left << 2) + right;
|
{
|
||||||
break;
|
var wide = ((ulong)left << 2) + right;
|
||||||
|
result = (uint)wide;
|
||||||
|
scalarConditionCode = wide > uint.MaxValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "SLshl3AddU32":
|
case "SLshl3AddU32":
|
||||||
result = (left << 3) + right;
|
{
|
||||||
break;
|
var wide = ((ulong)left << 3) + right;
|
||||||
|
result = (uint)wide;
|
||||||
|
scalarConditionCode = wide > uint.MaxValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "SLshl4AddU32":
|
case "SLshl4AddU32":
|
||||||
result = (left << 4) + right;
|
{
|
||||||
break;
|
var wide = ((ulong)left << 4) + right;
|
||||||
|
result = (uint)wide;
|
||||||
|
scalarConditionCode = wide > uint.MaxValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "SPackLlB32B16":
|
case "SPackLlB32B16":
|
||||||
result = (left & 0xFFFFu) | (right << 16);
|
result = (left & 0xFFFFu) | (right << 16);
|
||||||
break;
|
break;
|
||||||
@@ -847,7 +1031,8 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
"SNandSaveexecB64" or
|
"SNandSaveexecB64" or
|
||||||
"SNorSaveexecB64" or
|
"SNorSaveexecB64" or
|
||||||
"SXnorSaveexecB64" or
|
"SXnorSaveexecB64" or
|
||||||
"SAndn1SaveexecB64"))
|
"SAndn1SaveexecB64" or
|
||||||
|
"SOrn1SaveexecB64"))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -875,18 +1060,19 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
"SAndSaveexecB64" => oldExec & source,
|
"SAndSaveexecB64" => oldExec & source,
|
||||||
"SOrSaveexecB64" => oldExec | source,
|
"SOrSaveexecB64" => oldExec | source,
|
||||||
"SXorSaveexecB64" => oldExec ^ source,
|
"SXorSaveexecB64" => oldExec ^ source,
|
||||||
"SAndn1SaveexecB64" => ~oldExec & source,
|
"SAndn1SaveexecB64" => ~source & oldExec,
|
||||||
"SAndn2SaveexecB64" => oldExec & ~source,
|
"SAndn2SaveexecB64" => source & ~oldExec,
|
||||||
"SOrn2SaveexecB64" => oldExec | ~source,
|
"SOrn1SaveexecB64" => ~source | oldExec,
|
||||||
"SNandSaveexecB64" => ~(oldExec & source),
|
"SOrn2SaveexecB64" => source | ~oldExec,
|
||||||
"SNorSaveexecB64" => ~(oldExec | source),
|
"SNandSaveexecB64" => ~(source & oldExec),
|
||||||
|
"SNorSaveexecB64" => ~(source | oldExec),
|
||||||
_ => ~(oldExec ^ source),
|
_ => ~(oldExec ^ source),
|
||||||
};
|
};
|
||||||
|
|
||||||
WriteScalarPair(registers, destination.Value, oldExec, ref execMask);
|
WriteScalarPair(registers, destination.Value, oldExec, ref execMask);
|
||||||
execMask = newExec;
|
execMask = MaskWaveValue(newExec);
|
||||||
WriteScalarPair(registers, 126, execMask, ref execMask);
|
WriteScalarPair(registers, 126, execMask, ref execMask);
|
||||||
scalarConditionCode = newExec != 0;
|
scalarConditionCode = execMask != 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -934,6 +1120,11 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (destination == 126)
|
||||||
|
{
|
||||||
|
value = MaskWaveValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
registers[destination] = (uint)value;
|
registers[destination] = (uint)value;
|
||||||
registers[destination + 1] = (uint)(value >> 32);
|
registers[destination + 1] = (uint)(value >> 32);
|
||||||
if (destination == 126)
|
if (destination == 126)
|
||||||
@@ -942,6 +1133,14 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ulong MaskWaveValue(ulong value) => value & RdnaWaveMask;
|
||||||
|
|
||||||
|
private static bool SignedAddOverflow(uint left, uint right, uint result) =>
|
||||||
|
((left ^ result) & (right ^ result) & 0x80000000u) != 0;
|
||||||
|
|
||||||
|
private static bool SignedSubOverflow(uint left, uint right, uint result) =>
|
||||||
|
((left ^ right) & (left ^ result) & 0x80000000u) != 0;
|
||||||
|
|
||||||
private static bool TryExecuteScalarCompare(
|
private static bool TryExecuteScalarCompare(
|
||||||
Gen5ShaderInstruction instruction,
|
Gen5ShaderInstruction instruction,
|
||||||
uint[] registers,
|
uint[] registers,
|
||||||
@@ -1263,7 +1462,7 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
word2 == 0 &&
|
word2 == 0 &&
|
||||||
word3 == 0)
|
word3 == 0)
|
||||||
{
|
{
|
||||||
descriptor = new BufferDescriptor(0, 0, 0, 0);
|
descriptor = new BufferDescriptor(0, 0, 0, 0, 0, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1275,19 +1474,64 @@ internal static class Gen5ShaderScalarEvaluator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
descriptor = new BufferDescriptor(0, 0, 0, 0);
|
descriptor = new BufferDescriptor(0, 0, 0, 0, 0, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseAddress = word0 | ((ulong)(word1 & 0x0FFFu) << 32);
|
var baseAddress = word0 | ((ulong)(word1 & 0xFFFFu) << 32);
|
||||||
var stride = (word1 >> 16) & 0x3FFFu;
|
var stride = (word1 >> 16) & 0x3FFFu;
|
||||||
|
var unifiedFormat = (word3 >> 12) & 0x7Fu;
|
||||||
|
var (dataFormat, numberFormat) =
|
||||||
|
DecodeGfx10BufferFormat(unifiedFormat);
|
||||||
var sizeBytes = stride == 0
|
var sizeBytes = stride == 0
|
||||||
? word2
|
? word2
|
||||||
: (ulong)stride * word2;
|
: (ulong)stride * word2;
|
||||||
descriptor = new BufferDescriptor(baseAddress, stride, word2, sizeBytes);
|
descriptor = new BufferDescriptor(baseAddress, stride, word2, sizeBytes, numberFormat, dataFormat);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static (uint DataFormat, uint NumberFormat)
|
||||||
|
DecodeGfx10BufferFormat(uint format) =>
|
||||||
|
format switch
|
||||||
|
{
|
||||||
|
0 => (0, 0),
|
||||||
|
>= 1 and <= 6 => (1, format - 1),
|
||||||
|
>= 7 and <= 13 => (2, DecodeUnifiedNumber(format - 7, 7)),
|
||||||
|
>= 14 and <= 19 => (3, format - 14),
|
||||||
|
>= 20 and <= 22 => (4, DecodeIntegerOrFloatNumber(format - 20)),
|
||||||
|
>= 23 and <= 29 => (5, DecodeUnifiedNumber(format - 23, 7)),
|
||||||
|
>= 30 and <= 36 => (6, DecodeUnifiedNumber(format - 30, 7)),
|
||||||
|
>= 37 and <= 43 => (7, DecodeUnifiedNumber(format - 37, 7)),
|
||||||
|
>= 44 and <= 49 => (8, format - 44),
|
||||||
|
>= 50 and <= 55 => (9, format - 50),
|
||||||
|
>= 56 and <= 61 => (10, format - 56),
|
||||||
|
>= 62 and <= 64 => (11, DecodeIntegerOrFloatNumber(format - 62)),
|
||||||
|
>= 65 and <= 71 => (12, DecodeUnifiedNumber(format - 65, 7)),
|
||||||
|
>= 72 and <= 74 => (13, DecodeIntegerOrFloatNumber(format - 72)),
|
||||||
|
>= 75 and <= 77 => (14, DecodeIntegerOrFloatNumber(format - 75)),
|
||||||
|
128 => (1, 9),
|
||||||
|
129 => (3, 9),
|
||||||
|
130 => (10, 9),
|
||||||
|
132 => (34, 7),
|
||||||
|
133 => (16, 0),
|
||||||
|
134 => (17, 0),
|
||||||
|
135 => (18, 0),
|
||||||
|
136 => (19, 0),
|
||||||
|
140 => (4, 7),
|
||||||
|
_ => (0, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static uint DecodeUnifiedNumber(uint offset, uint formatCount) =>
|
||||||
|
offset == formatCount - 1 ? 7u : offset;
|
||||||
|
|
||||||
|
private static uint DecodeIntegerOrFloatNumber(uint offset) =>
|
||||||
|
offset switch
|
||||||
|
{
|
||||||
|
0 => 4,
|
||||||
|
1 => 5,
|
||||||
|
_ => 7,
|
||||||
|
};
|
||||||
|
|
||||||
private static bool TryReadUserDataScalarLoad(
|
private static bool TryReadUserDataScalarLoad(
|
||||||
Gen5ShaderState state,
|
Gen5ShaderState state,
|
||||||
Gen5ShaderInstruction instruction,
|
Gen5ShaderInstruction instruction,
|
||||||
|
|||||||
@@ -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 =
|
||||||
[
|
[
|
||||||
@@ -113,19 +122,55 @@ internal static class Gen5ShaderTranslator
|
|||||||
uint userDataBaseRegister,
|
uint userDataBaseRegister,
|
||||||
out Gen5ShaderState state,
|
out Gen5ShaderState state,
|
||||||
out string error,
|
out string error,
|
||||||
Gen5ComputeSystemRegisters? computeSystemRegisters = null)
|
Gen5ComputeSystemRegisters? computeSystemRegisters = null,
|
||||||
|
uint userDataScalarRegisterBase = 0)
|
||||||
{
|
{
|
||||||
state = default!;
|
state = default!;
|
||||||
if (!TryDecodeProgram(ctx, shaderAddress, out var program, out error))
|
error = string.Empty;
|
||||||
|
var cache = _decodeCaches.GetValue(ctx.Memory, static _ => new ShaderDecodeCache());
|
||||||
|
Gen5ShaderProgram? program;
|
||||||
|
lock (cache.Gate)
|
||||||
{
|
{
|
||||||
return false;
|
cache.Programs.TryGetValue(shaderAddress, out program);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (program is null)
|
||||||
|
{
|
||||||
|
if (!TryDecodeProgram(ctx, shaderAddress, out program, out error))
|
||||||
|
{
|
||||||
|
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))
|
|
||||||
{
|
{
|
||||||
metadata = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (cache.Gate)
|
||||||
|
{
|
||||||
|
cache.Metadata.TryAdd(shaderHeaderAddress, metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var userData = new uint[GetUserDataDwordCount(metadata)];
|
var userData = new uint[GetUserDataDwordCount(metadata)];
|
||||||
@@ -134,7 +179,12 @@ internal static class Gen5ShaderTranslator
|
|||||||
shaderRegisters.TryGetValue(userDataBaseRegister + index, out userData[index]);
|
shaderRegisters.TryGetValue(userDataBaseRegister + index, out userData[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
state = new Gen5ShaderState(program, userData, metadata, computeSystemRegisters);
|
state = new Gen5ShaderState(
|
||||||
|
program,
|
||||||
|
userData,
|
||||||
|
metadata,
|
||||||
|
computeSystemRegisters,
|
||||||
|
userDataScalarRegisterBase);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +229,9 @@ internal static class Gen5ShaderTranslator
|
|||||||
: string.Empty;
|
: string.Empty;
|
||||||
if (state.Metadata is not { } metadata)
|
if (state.Metadata is not { } metadata)
|
||||||
{
|
{
|
||||||
return $"ud[{userData}]{systemRegisters} metadata=missing";
|
return
|
||||||
|
$"ud_base=s{state.UserDataScalarRegisterBase} ud[{userData}]" +
|
||||||
|
$"{systemRegisters} metadata=missing";
|
||||||
}
|
}
|
||||||
|
|
||||||
var direct = string.Join(
|
var direct = string.Join(
|
||||||
@@ -191,7 +243,8 @@ internal static class Gen5ShaderTranslator
|
|||||||
$"{resource.Kind}[{resource.Slot}]@{resource.OffsetDwords}" +
|
$"{resource.Kind}[{resource.Slot}]@{resource.OffsetDwords}" +
|
||||||
(resource.SizeFlag ? "+" : string.Empty)));
|
(resource.SizeFlag ? "+" : string.Empty)));
|
||||||
return
|
return
|
||||||
$"ud[{userData}]{systemRegisters} metadata[eud={metadata.ExtendedUserDataSizeDwords}," +
|
$"ud_base=s{state.UserDataScalarRegisterBase} ud[{userData}]" +
|
||||||
|
$"{systemRegisters} metadata[eud={metadata.ExtendedUserDataSizeDwords}," +
|
||||||
$"srt={metadata.ShaderResourceTableSizeDwords},direct={direct},resources={resources}]";
|
$"srt={metadata.ShaderResourceTableSizeDwords},direct={direct},resources={resources}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,6 +496,7 @@ internal static class Gen5ShaderTranslator
|
|||||||
0x2A => "SNorSaveexecB64",
|
0x2A => "SNorSaveexecB64",
|
||||||
0x2B => "SXnorSaveexecB64",
|
0x2B => "SXnorSaveexecB64",
|
||||||
0x37 => "SAndn1SaveexecB64",
|
0x37 => "SAndn1SaveexecB64",
|
||||||
|
0x38 => "SOrn1SaveexecB64",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -668,9 +722,8 @@ internal static class Gen5ShaderTranslator
|
|||||||
error = string.Empty;
|
error = string.Empty;
|
||||||
name = opcode switch
|
name = opcode switch
|
||||||
{
|
{
|
||||||
0x00 => "VCndmaskB32",
|
0x01 => "VCndmaskB32",
|
||||||
0x01 => "VReadlaneB32",
|
0x02 => "VDot2cF32F16",
|
||||||
0x02 => "VWritelaneB32",
|
|
||||||
0x03 => "VAddF32",
|
0x03 => "VAddF32",
|
||||||
0x04 => "VSubF32",
|
0x04 => "VSubF32",
|
||||||
0x05 => "VSubrevF32",
|
0x05 => "VSubrevF32",
|
||||||
@@ -810,7 +863,7 @@ internal static class Gen5ShaderTranslator
|
|||||||
}
|
}
|
||||||
: opcode switch
|
: opcode switch
|
||||||
{
|
{
|
||||||
0x101 => "VReadlaneB32",
|
0x101 => "VCndmaskB32",
|
||||||
0x103 => "VAddF32",
|
0x103 => "VAddF32",
|
||||||
0x104 => "VSubF32",
|
0x104 => "VSubF32",
|
||||||
0x108 => "VMulF32",
|
0x108 => "VMulF32",
|
||||||
@@ -1246,17 +1299,16 @@ internal static class Gen5ShaderTranslator
|
|||||||
var scalarOffset = (extra >> 25) & 0x7F;
|
var scalarOffset = (extra >> 25) & 0x7F;
|
||||||
var offset = SignExtend(extra & 0x1FFFFF, 21);
|
var offset = SignExtend(extra & 0x1FFFFF, 21);
|
||||||
var count = ScalarLoadDwordCount(opcode);
|
var count = ScalarLoadDwordCount(opcode);
|
||||||
var dynamicOffsetRegister = scalarOffset <= 105 || scalarOffset == 124
|
sources =
|
||||||
? scalarOffset
|
[
|
||||||
: (uint?)null;
|
Gen5Operand.Scalar(scalarBase),
|
||||||
sources = dynamicOffsetRegister.HasValue
|
Gen5Operand.Scalar(scalarOffset),
|
||||||
? [Gen5Operand.Scalar(scalarBase), Gen5Operand.Scalar(dynamicOffsetRegister.Value)]
|
];
|
||||||
: [Gen5Operand.Scalar(scalarBase)];
|
|
||||||
destinations = Enumerable
|
destinations = Enumerable
|
||||||
.Range((int)scalarDestination, checked((int)count))
|
.Range((int)scalarDestination, checked((int)count))
|
||||||
.Select(index => Gen5Operand.Scalar((uint)index))
|
.Select(index => Gen5Operand.Scalar((uint)index))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
control = new Gen5ScalarMemoryControl(count, offset, dynamicOffsetRegister);
|
control = new Gen5ScalarMemoryControl(count, offset, scalarOffset);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Gen5ShaderEncoding.Vop1:
|
case Gen5ShaderEncoding.Vop1:
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
break;
|
break;
|
||||||
case "VCndmaskB32":
|
case "VCndmaskB32":
|
||||||
{
|
{
|
||||||
var condition = Load(_boolType, _vcc);
|
var condition = instruction.Sources.Count > 2
|
||||||
|
? IsCurrentLaneSet(GetRawSource64(instruction, 2))
|
||||||
|
: Load(_boolType, _vcc);
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
SpirvOp.Select,
|
SpirvOp.Select,
|
||||||
_uintType,
|
_uintType,
|
||||||
@@ -131,9 +133,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
SpirvOp.ConvertFToU,
|
SpirvOp.ConvertFToU,
|
||||||
_uintType,
|
_uintType,
|
||||||
GetFloatSource(instruction, 0));
|
GetFloatSource(instruction, 0));
|
||||||
var offset = _module.AddInstruction(
|
var offset = ShiftLeftLogical(
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
BitwiseAnd(GetRawSource(instruction, 1), UInt(3)),
|
BitwiseAnd(GetRawSource(instruction, 1), UInt(3)),
|
||||||
UInt(3));
|
UInt(3));
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
@@ -212,12 +212,26 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
case "VSinF32":
|
case "VSinF32":
|
||||||
result = EmitFloatResult(
|
result = EmitFloatResult(
|
||||||
instruction,
|
instruction,
|
||||||
Ext(13, _floatType, GetFloatSource(instruction, 0)));
|
Ext(
|
||||||
|
13,
|
||||||
|
_floatType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.FMul,
|
||||||
|
_floatType,
|
||||||
|
GetFloatSource(instruction, 0),
|
||||||
|
Float(MathF.Tau))));
|
||||||
break;
|
break;
|
||||||
case "VCosF32":
|
case "VCosF32":
|
||||||
result = EmitFloatResult(
|
result = EmitFloatResult(
|
||||||
instruction,
|
instruction,
|
||||||
Ext(14, _floatType, GetFloatSource(instruction, 0)));
|
Ext(
|
||||||
|
14,
|
||||||
|
_floatType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.FMul,
|
||||||
|
_floatType,
|
||||||
|
GetFloatSource(instruction, 0),
|
||||||
|
Float(MathF.Tau))));
|
||||||
break;
|
break;
|
||||||
case "VAddF32":
|
case "VAddF32":
|
||||||
result = EmitFloatBinary(instruction, SpirvOp.FAdd);
|
result = EmitFloatBinary(instruction, SpirvOp.FAdd);
|
||||||
@@ -350,9 +364,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
SpirvOp.UConvert,
|
SpirvOp.UConvert,
|
||||||
_uintType,
|
_uintType,
|
||||||
_module.AddInstruction(
|
ShiftRightLogical64(
|
||||||
SpirvOp.ShiftRightLogical,
|
|
||||||
_ulongType,
|
|
||||||
product,
|
product,
|
||||||
_module.Constant64(_ulongType, 32)));
|
_module.Constant64(_ulongType, 32)));
|
||||||
break;
|
break;
|
||||||
@@ -415,21 +427,12 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var reverse = instruction.Opcode == "VAshrrevI32";
|
var reverse = instruction.Opcode == "VAshrrevI32";
|
||||||
var left = GetRawSource(instruction, reverse ? 1 : 0);
|
var left = GetRawSource(instruction, reverse ? 1 : 0);
|
||||||
var right = GetRawSource(instruction, reverse ? 0 : 1);
|
var right = GetRawSource(instruction, reverse ? 0 : 1);
|
||||||
right = BitwiseAnd(right, UInt(31));
|
result = ShiftRightArithmetic(left, right);
|
||||||
result = Bitcast(
|
|
||||||
_uintType,
|
|
||||||
_module.AddInstruction(
|
|
||||||
SpirvOp.ShiftRightArithmetic,
|
|
||||||
_intType,
|
|
||||||
Bitcast(_intType, left),
|
|
||||||
right));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "VLshlAddU32":
|
case "VLshlAddU32":
|
||||||
{
|
{
|
||||||
var shifted = _module.AddInstruction(
|
var shifted = ShiftLeftLogical(
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
GetRawSource(instruction, 0),
|
GetRawSource(instruction, 0),
|
||||||
BitwiseAnd(GetRawSource(instruction, 1), UInt(31)));
|
BitwiseAnd(GetRawSource(instruction, 1), UInt(31)));
|
||||||
result = IAdd(shifted, GetRawSource(instruction, 2));
|
result = IAdd(shifted, GetRawSource(instruction, 2));
|
||||||
@@ -437,9 +440,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
case "VLshlOrU32":
|
case "VLshlOrU32":
|
||||||
{
|
{
|
||||||
var shifted = _module.AddInstruction(
|
var shifted = ShiftLeftLogical(
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
GetRawSource(instruction, 0),
|
GetRawSource(instruction, 0),
|
||||||
BitwiseAnd(GetRawSource(instruction, 1), UInt(31)));
|
BitwiseAnd(GetRawSource(instruction, 1), UInt(31)));
|
||||||
result = BitwiseOr(
|
result = BitwiseOr(
|
||||||
@@ -472,11 +473,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var added = IAdd(
|
var added = IAdd(
|
||||||
GetRawSource(instruction, 0),
|
GetRawSource(instruction, 0),
|
||||||
GetRawSource(instruction, 1));
|
GetRawSource(instruction, 1));
|
||||||
result = _module.AddInstruction(
|
result = ShiftLeftLogical(added, GetRawSource(instruction, 2));
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
added,
|
|
||||||
BitwiseAnd(GetRawSource(instruction, 2), UInt(31)));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "VAdd3U32":
|
case "VAdd3U32":
|
||||||
@@ -652,13 +649,29 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
width);
|
width);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "VBfiB32":
|
||||||
|
{
|
||||||
|
var mask = GetRawSource(instruction, 0);
|
||||||
|
var insert = GetRawSource(instruction, 1);
|
||||||
|
var source = GetRawSource(instruction, 2);
|
||||||
|
result = _module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseOr,
|
||||||
|
_uintType,
|
||||||
|
BitwiseAnd(mask, insert),
|
||||||
|
BitwiseAnd(
|
||||||
|
_module.AddInstruction(SpirvOp.Not, _uintType, mask),
|
||||||
|
source));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "VCvtPkrtzF16F32":
|
case "VCvtPkrtzF16F32":
|
||||||
{
|
{
|
||||||
|
var first = TruncateFloat32ForPack(GetFloatSource(instruction, 0));
|
||||||
|
var second = TruncateFloat32ForPack(GetFloatSource(instruction, 1));
|
||||||
var vector = _module.AddInstruction(
|
var vector = _module.AddInstruction(
|
||||||
SpirvOp.CompositeConstruct,
|
SpirvOp.CompositeConstruct,
|
||||||
_vec2Type,
|
_vec2Type,
|
||||||
GetFloatSource(instruction, 0),
|
first,
|
||||||
GetFloatSource(instruction, 1));
|
second);
|
||||||
result = Ext(58, _uintType, vector);
|
result = Ext(58, _uintType, vector);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -857,7 +870,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
condition = _module.AddInstruction(operation, _boolType, left, right);
|
condition = _module.AddInstruction(operation, _boolType, left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
Store(_vcc, condition);
|
StoreWaveMask(106, condition);
|
||||||
if (opcode.StartsWith("VCmpx", StringComparison.Ordinal))
|
if (opcode.StartsWith("VCmpx", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
var active = _module.AddInstruction(
|
var active = _module.AddInstruction(
|
||||||
@@ -865,7 +878,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_boolType,
|
_boolType,
|
||||||
Load(_boolType, _exec),
|
Load(_boolType, _exec),
|
||||||
condition);
|
condition);
|
||||||
Store(_exec, active);
|
StoreWaveMask(126, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -930,7 +943,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (instruction.Opcode.EndsWith("B64", StringComparison.Ordinal) ||
|
if (instruction.Opcode.EndsWith("B64", StringComparison.Ordinal) ||
|
||||||
instruction.Opcode == "SWqmB64")
|
instruction.Opcode is "SWqmB64" or "SBfeU64" or "SBfeI64")
|
||||||
{
|
{
|
||||||
return TryEmitScalar64(instruction, destination, out error);
|
return TryEmitScalar64(instruction, destination, out error);
|
||||||
}
|
}
|
||||||
@@ -962,6 +975,16 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
StoreS(destination, result);
|
StoreS(destination, result);
|
||||||
Store(_scc, IsNotZero(result));
|
Store(_scc, IsNotZero(result));
|
||||||
return true;
|
return true;
|
||||||
|
case "SBitset1B32":
|
||||||
|
result = _module.AddInstruction(
|
||||||
|
SpirvOp.BitFieldInsert,
|
||||||
|
_uintType,
|
||||||
|
LoadS(destination),
|
||||||
|
UInt(1),
|
||||||
|
BitwiseAnd(left, UInt(31)),
|
||||||
|
UInt(1));
|
||||||
|
StoreS(destination, result);
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
if (instruction.Sources.Count < 2)
|
if (instruction.Sources.Count < 2)
|
||||||
@@ -988,13 +1011,14 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
left,
|
left,
|
||||||
right);
|
right);
|
||||||
Store(_scc, _module.AddInstruction(
|
Store(_scc, _module.AddInstruction(
|
||||||
SpirvOp.UGreaterThanEqual,
|
SpirvOp.UGreaterThan,
|
||||||
_boolType,
|
_boolType,
|
||||||
left,
|
right,
|
||||||
right));
|
left));
|
||||||
break;
|
break;
|
||||||
case "SAddI32":
|
case "SAddI32":
|
||||||
result = IAdd(left, right);
|
result = IAdd(left, right);
|
||||||
|
Store(_scc, SignedAddOverflow(left, right, result));
|
||||||
break;
|
break;
|
||||||
case "SSubI32":
|
case "SSubI32":
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
@@ -1002,6 +1026,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_uintType,
|
_uintType,
|
||||||
left,
|
left,
|
||||||
right);
|
right);
|
||||||
|
Store(_scc, SignedSubOverflow(left, right, result));
|
||||||
break;
|
break;
|
||||||
case "SAddcU32":
|
case "SAddcU32":
|
||||||
{
|
{
|
||||||
@@ -1038,8 +1063,8 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
SpirvOp.Select,
|
SpirvOp.Select,
|
||||||
_uintType,
|
_uintType,
|
||||||
Load(_boolType, _scc),
|
Load(_boolType, _scc),
|
||||||
UInt(0),
|
UInt(1),
|
||||||
UInt(1));
|
UInt(0));
|
||||||
var partial = _module.AddInstruction(
|
var partial = _module.AddInstruction(
|
||||||
SpirvOp.ISub,
|
SpirvOp.ISub,
|
||||||
_uintType,
|
_uintType,
|
||||||
@@ -1050,23 +1075,31 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_uintType,
|
_uintType,
|
||||||
partial,
|
partial,
|
||||||
borrow);
|
borrow);
|
||||||
var firstNoBorrow = _module.AddInstruction(
|
var firstBorrow = _module.AddInstruction(
|
||||||
SpirvOp.UGreaterThanEqual,
|
SpirvOp.UGreaterThan,
|
||||||
_boolType,
|
_boolType,
|
||||||
left,
|
right,
|
||||||
right);
|
left);
|
||||||
var secondNoBorrow = _module.AddInstruction(
|
var secondBorrow = _module.AddInstruction(
|
||||||
SpirvOp.UGreaterThanEqual,
|
SpirvOp.LogicalAnd,
|
||||||
_boolType,
|
_boolType,
|
||||||
partial,
|
_module.AddInstruction(
|
||||||
borrow);
|
SpirvOp.IEqual,
|
||||||
|
_boolType,
|
||||||
|
borrow,
|
||||||
|
UInt(1)),
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.IEqual,
|
||||||
|
_boolType,
|
||||||
|
right,
|
||||||
|
left));
|
||||||
Store(
|
Store(
|
||||||
_scc,
|
_scc,
|
||||||
_module.AddInstruction(
|
_module.AddInstruction(
|
||||||
SpirvOp.LogicalAnd,
|
SpirvOp.LogicalOr,
|
||||||
_boolType,
|
_boolType,
|
||||||
firstNoBorrow,
|
firstBorrow,
|
||||||
secondNoBorrow));
|
secondBorrow));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "SMulI32":
|
case "SMulI32":
|
||||||
@@ -1078,6 +1111,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
break;
|
break;
|
||||||
case "SAndB32":
|
case "SAndB32":
|
||||||
result = BitwiseAnd(left, right);
|
result = BitwiseAnd(left, right);
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
case "SOrB32":
|
case "SOrB32":
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
@@ -1085,6 +1119,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_uintType,
|
_uintType,
|
||||||
left,
|
left,
|
||||||
right);
|
right);
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
case "SXorB32":
|
case "SXorB32":
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
@@ -1092,32 +1127,64 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_uintType,
|
_uintType,
|
||||||
left,
|
left,
|
||||||
right);
|
right);
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
case "SAndn2B32":
|
case "SAndn2B32":
|
||||||
result = BitwiseAnd(
|
result = BitwiseAnd(
|
||||||
left,
|
left,
|
||||||
_module.AddInstruction(SpirvOp.Not, _uintType, right));
|
_module.AddInstruction(SpirvOp.Not, _uintType, right));
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
case "SLshlB32":
|
case "SOrn2B32":
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
SpirvOp.ShiftLeftLogical,
|
SpirvOp.BitwiseOr,
|
||||||
_uintType,
|
_uintType,
|
||||||
left,
|
left,
|
||||||
BitwiseAnd(right, UInt(31)));
|
_module.AddInstruction(SpirvOp.Not, _uintType, right));
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
|
break;
|
||||||
|
case "SNandB32":
|
||||||
|
result = _module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_uintType,
|
||||||
|
BitwiseAnd(left, right));
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
|
break;
|
||||||
|
case "SNorB32":
|
||||||
|
result = _module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_uintType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseOr,
|
||||||
|
_uintType,
|
||||||
|
left,
|
||||||
|
right));
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
|
break;
|
||||||
|
case "SXnorB32":
|
||||||
|
result = _module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_uintType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseXor,
|
||||||
|
_uintType,
|
||||||
|
left,
|
||||||
|
right));
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
|
break;
|
||||||
|
case "SLshlB32":
|
||||||
|
result = ShiftLeftLogical(left, right);
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
case "SLshrB32":
|
case "SLshrB32":
|
||||||
result = ShiftRightLogical(
|
result = ShiftRightLogical(
|
||||||
left,
|
left,
|
||||||
BitwiseAnd(right, UInt(31)));
|
BitwiseAnd(right, UInt(31)));
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
case "SAshrI32":
|
case "SAshrI32":
|
||||||
result = Bitcast(
|
result = ShiftRightArithmetic(left, right);
|
||||||
_uintType,
|
Store(_scc, IsNotZero(result));
|
||||||
_module.AddInstruction(
|
|
||||||
SpirvOp.ShiftRightArithmetic,
|
|
||||||
_intType,
|
|
||||||
Bitcast(_intType, left),
|
|
||||||
BitwiseAnd(right, UInt(31))));
|
|
||||||
break;
|
break;
|
||||||
case "SBfmB32":
|
case "SBfmB32":
|
||||||
result = _module.AddInstruction(
|
result = _module.AddInstruction(
|
||||||
@@ -1160,6 +1227,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
left,
|
left,
|
||||||
offset,
|
offset,
|
||||||
width);
|
width);
|
||||||
|
Store(_scc, IsNotZero(result));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "SCselectB32":
|
case "SCselectB32":
|
||||||
@@ -1172,9 +1240,47 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
break;
|
break;
|
||||||
case "SMinU32":
|
case "SMinU32":
|
||||||
result = Ext(38, _uintType, left, right);
|
result = Ext(38, _uintType, left, right);
|
||||||
|
Store(
|
||||||
|
_scc,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.ULessThan,
|
||||||
|
_boolType,
|
||||||
|
left,
|
||||||
|
right));
|
||||||
|
break;
|
||||||
|
case "SMinI32":
|
||||||
|
result = Bitcast(
|
||||||
|
_uintType,
|
||||||
|
Ext(39, _intType, Bitcast(_intType, left), Bitcast(_intType, right)));
|
||||||
|
Store(
|
||||||
|
_scc,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.SLessThan,
|
||||||
|
_boolType,
|
||||||
|
Bitcast(_intType, left),
|
||||||
|
Bitcast(_intType, right)));
|
||||||
break;
|
break;
|
||||||
case "SMaxU32":
|
case "SMaxU32":
|
||||||
result = Ext(41, _uintType, left, right);
|
result = Ext(41, _uintType, left, right);
|
||||||
|
Store(
|
||||||
|
_scc,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.UGreaterThan,
|
||||||
|
_boolType,
|
||||||
|
left,
|
||||||
|
right));
|
||||||
|
break;
|
||||||
|
case "SMaxI32":
|
||||||
|
result = Bitcast(
|
||||||
|
_uintType,
|
||||||
|
Ext(42, _intType, Bitcast(_intType, left), Bitcast(_intType, right)));
|
||||||
|
Store(
|
||||||
|
_scc,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.SGreaterThan,
|
||||||
|
_boolType,
|
||||||
|
Bitcast(_intType, left),
|
||||||
|
Bitcast(_intType, right)));
|
||||||
break;
|
break;
|
||||||
case "SLshl1AddU32":
|
case "SLshl1AddU32":
|
||||||
case "SLshl2AddU32":
|
case "SLshl2AddU32":
|
||||||
@@ -1183,11 +1289,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
{
|
{
|
||||||
var shift = (uint)(instruction.Opcode[5] - '0');
|
var shift = (uint)(instruction.Opcode[5] - '0');
|
||||||
result = IAdd(
|
result = IAdd(
|
||||||
_module.AddInstruction(
|
ShiftLeftLogical(left, UInt(shift)),
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
left,
|
|
||||||
UInt(shift)),
|
|
||||||
right);
|
right);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1312,7 +1414,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var left = GetRawSource64(instruction, 0);
|
var left = GetRawSource64(instruction, 0);
|
||||||
if (instruction.Opcode.EndsWith("SaveexecB64", StringComparison.Ordinal))
|
if (instruction.Opcode.EndsWith("SaveexecB64", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
var oldExec = LoadS64(126);
|
var oldExec = BooleanToLaneMask(Load(_boolType, _exec));
|
||||||
var notLeft = _module.AddInstruction(SpirvOp.Not, _ulongType, left);
|
var notLeft = _module.AddInstruction(SpirvOp.Not, _ulongType, left);
|
||||||
var newExec = instruction.Opcode switch
|
var newExec = instruction.Opcode switch
|
||||||
{
|
{
|
||||||
@@ -1323,17 +1425,55 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
"SXorSaveexecB64" => _module.AddInstruction(
|
"SXorSaveexecB64" => _module.AddInstruction(
|
||||||
SpirvOp.BitwiseXor, _ulongType, oldExec, left),
|
SpirvOp.BitwiseXor, _ulongType, oldExec, left),
|
||||||
"SAndn2SaveexecB64" => _module.AddInstruction(
|
"SAndn2SaveexecB64" => _module.AddInstruction(
|
||||||
SpirvOp.BitwiseAnd, _ulongType, oldExec, notLeft),
|
|
||||||
"SAndn1SaveexecB64" => _module.AddInstruction(
|
|
||||||
SpirvOp.BitwiseAnd,
|
SpirvOp.BitwiseAnd,
|
||||||
_ulongType,
|
_ulongType,
|
||||||
|
left,
|
||||||
_module.AddInstruction(
|
_module.AddInstruction(
|
||||||
SpirvOp.Not,
|
SpirvOp.Not,
|
||||||
_ulongType,
|
_ulongType,
|
||||||
oldExec),
|
oldExec)),
|
||||||
left),
|
"SAndn1SaveexecB64" => _module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseAnd,
|
||||||
|
_ulongType,
|
||||||
|
notLeft,
|
||||||
|
oldExec),
|
||||||
|
"SOrn1SaveexecB64" => _module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseOr,
|
||||||
|
_ulongType,
|
||||||
|
notLeft,
|
||||||
|
oldExec),
|
||||||
"SOrn2SaveexecB64" => _module.AddInstruction(
|
"SOrn2SaveexecB64" => _module.AddInstruction(
|
||||||
SpirvOp.BitwiseOr, _ulongType, oldExec, notLeft),
|
SpirvOp.BitwiseOr,
|
||||||
|
_ulongType,
|
||||||
|
left,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_ulongType,
|
||||||
|
oldExec)),
|
||||||
|
"SNandSaveexecB64" => _module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_ulongType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseAnd,
|
||||||
|
_ulongType,
|
||||||
|
left,
|
||||||
|
oldExec)),
|
||||||
|
"SNorSaveexecB64" => _module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_ulongType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseOr,
|
||||||
|
_ulongType,
|
||||||
|
left,
|
||||||
|
oldExec)),
|
||||||
|
"SXnorSaveexecB64" => _module.AddInstruction(
|
||||||
|
SpirvOp.Not,
|
||||||
|
_ulongType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseXor,
|
||||||
|
_ulongType,
|
||||||
|
left,
|
||||||
|
oldExec)),
|
||||||
_ => 0u,
|
_ => 0u,
|
||||||
};
|
};
|
||||||
if (newExec == 0)
|
if (newExec == 0)
|
||||||
@@ -1345,7 +1485,6 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
|
|
||||||
StoreS64(destination, oldExec);
|
StoreS64(destination, oldExec);
|
||||||
StoreS64(126, newExec);
|
StoreS64(126, newExec);
|
||||||
Store(_exec, IsNotZero64(newExec));
|
|
||||||
Store(_scc, IsNotZero64(newExec));
|
Store(_scc, IsNotZero64(newExec));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1361,19 +1500,108 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var shift = _module.AddInstruction(
|
var shift = _module.AddInstruction(
|
||||||
SpirvOp.UConvert,
|
SpirvOp.UConvert,
|
||||||
_ulongType,
|
_ulongType,
|
||||||
BitwiseAnd(GetRawSource(instruction, 1), UInt(63)));
|
GetRawSource(instruction, 1));
|
||||||
var shiftedValue = _module.AddInstruction(
|
var shiftedValue = instruction.Opcode == "SLshlB64"
|
||||||
instruction.Opcode == "SLshlB64"
|
? ShiftLeftLogical64(left, shift)
|
||||||
? SpirvOp.ShiftLeftLogical
|
: ShiftRightLogical64(left, shift);
|
||||||
: SpirvOp.ShiftRightLogical,
|
|
||||||
_ulongType,
|
|
||||||
left,
|
|
||||||
shift);
|
|
||||||
StoreS64(destination, shiftedValue);
|
StoreS64(destination, shiftedValue);
|
||||||
Store(_scc, IsNotZero64(shiftedValue));
|
Store(_scc, IsNotZero64(shiftedValue));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (instruction.Opcode is "SBfeU64" or "SBfeI64")
|
||||||
|
{
|
||||||
|
if (instruction.Sources.Count < 2)
|
||||||
|
{
|
||||||
|
error = "missing scalar 64-bit bitfield source";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var control = GetRawSource(instruction, 1);
|
||||||
|
var offset = BitwiseAnd(control, UInt(63));
|
||||||
|
var requestedWidth = BitwiseAnd(
|
||||||
|
ShiftRightLogical(control, UInt(16)),
|
||||||
|
UInt(0x7F));
|
||||||
|
var remaining = _module.AddInstruction(
|
||||||
|
SpirvOp.ISub,
|
||||||
|
_uintType,
|
||||||
|
UInt(64),
|
||||||
|
offset);
|
||||||
|
var width = Ext(
|
||||||
|
38,
|
||||||
|
_uintType,
|
||||||
|
requestedWidth,
|
||||||
|
remaining);
|
||||||
|
var offset64 = _module.AddInstruction(
|
||||||
|
SpirvOp.UConvert,
|
||||||
|
_ulongType,
|
||||||
|
offset);
|
||||||
|
var width64 = _module.AddInstruction(
|
||||||
|
SpirvOp.UConvert,
|
||||||
|
_ulongType,
|
||||||
|
width);
|
||||||
|
var one64 = _module.Constant64(_ulongType, 1);
|
||||||
|
var shifted = ShiftRightLogical64(left, offset64);
|
||||||
|
var partialMask = _module.AddInstruction(
|
||||||
|
SpirvOp.ISub,
|
||||||
|
_ulongType,
|
||||||
|
ShiftLeftLogical64(one64, width64),
|
||||||
|
one64);
|
||||||
|
var fullWidth = _module.AddInstruction(
|
||||||
|
SpirvOp.IEqual,
|
||||||
|
_boolType,
|
||||||
|
width,
|
||||||
|
UInt(64));
|
||||||
|
var mask = _module.AddInstruction(
|
||||||
|
SpirvOp.Select,
|
||||||
|
_ulongType,
|
||||||
|
fullWidth,
|
||||||
|
_module.Constant64(_ulongType, ulong.MaxValue),
|
||||||
|
partialMask);
|
||||||
|
var extracted = _module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseAnd,
|
||||||
|
_ulongType,
|
||||||
|
shifted,
|
||||||
|
mask);
|
||||||
|
if (instruction.Opcode == "SBfeI64")
|
||||||
|
{
|
||||||
|
var signShift = _module.AddInstruction(
|
||||||
|
SpirvOp.ISub,
|
||||||
|
_uintType,
|
||||||
|
width,
|
||||||
|
UInt(1));
|
||||||
|
var signBit = ShiftLeftLogical64(
|
||||||
|
one64,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.UConvert,
|
||||||
|
_ulongType,
|
||||||
|
signShift));
|
||||||
|
var signExtended = _module.AddInstruction(
|
||||||
|
SpirvOp.ISub,
|
||||||
|
_ulongType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseXor,
|
||||||
|
_ulongType,
|
||||||
|
extracted,
|
||||||
|
signBit),
|
||||||
|
signBit);
|
||||||
|
extracted = _module.AddInstruction(
|
||||||
|
SpirvOp.Select,
|
||||||
|
_ulongType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.IEqual,
|
||||||
|
_boolType,
|
||||||
|
width,
|
||||||
|
UInt(0)),
|
||||||
|
_module.Constant64(_ulongType, 0),
|
||||||
|
signExtended);
|
||||||
|
}
|
||||||
|
|
||||||
|
StoreS64(destination, extracted);
|
||||||
|
Store(_scc, IsNotZero64(extracted));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
uint value;
|
uint value;
|
||||||
if (instruction.Opcode is "SMovB64" or "SWqmB64")
|
if (instruction.Opcode is "SMovB64" or "SWqmB64")
|
||||||
{
|
{
|
||||||
@@ -1451,9 +1679,20 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
StoreS64(destination, value);
|
StoreS64(destination, value);
|
||||||
if (destination == 126)
|
if (instruction.Opcode is
|
||||||
|
"SNotB64" or
|
||||||
|
"SAndB64" or
|
||||||
|
"SOrB64" or
|
||||||
|
"SXorB64" or
|
||||||
|
"SAndn1B64" or
|
||||||
|
"SAndn2B64" or
|
||||||
|
"SOrn1B64" or
|
||||||
|
"SOrn2B64" or
|
||||||
|
"SNandB64" or
|
||||||
|
"SNorB64" or
|
||||||
|
"SXnorB64")
|
||||||
{
|
{
|
||||||
Store(_exec, IsNotZero64(value));
|
Store(_scc, IsNotZero64(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -1472,14 +1711,6 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
uint value = operand.Kind switch
|
uint value = operand.Kind switch
|
||||||
{
|
{
|
||||||
Gen5OperandKind.VectorRegister => LoadV(operand.Value),
|
Gen5OperandKind.VectorRegister => LoadV(operand.Value),
|
||||||
Gen5OperandKind.ScalarRegister when operand.Value == 106 =>
|
|
||||||
_module.AddInstruction(
|
|
||||||
SpirvOp.Select,
|
|
||||||
_uintType,
|
|
||||||
Load(_boolType, _vcc),
|
|
||||||
UInt(1),
|
|
||||||
UInt(0)),
|
|
||||||
Gen5OperandKind.ScalarRegister when operand.Value == 107 => UInt(0),
|
|
||||||
Gen5OperandKind.ScalarRegister => LoadS(operand.Value),
|
Gen5OperandKind.ScalarRegister => LoadS(operand.Value),
|
||||||
Gen5OperandKind.LiteralConstant => UInt(operand.Value),
|
Gen5OperandKind.LiteralConstant => UInt(operand.Value),
|
||||||
Gen5OperandKind.EncodedConstant when TryDecodeInlineConstant(
|
Gen5OperandKind.EncodedConstant when TryDecodeInlineConstant(
|
||||||
@@ -1584,11 +1815,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
SpirvOp.UConvert,
|
SpirvOp.UConvert,
|
||||||
_ulongType,
|
_ulongType,
|
||||||
LoadS(register + 1));
|
LoadS(register + 1));
|
||||||
high = _module.AddInstruction(
|
high = ShiftLeftLogical64(high, _module.Constant64(_ulongType, 32));
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_ulongType,
|
|
||||||
high,
|
|
||||||
_module.Constant64(_ulongType, 32));
|
|
||||||
return _module.AddInstruction(SpirvOp.BitwiseOr, _ulongType, low, high);
|
return _module.AddInstruction(SpirvOp.BitwiseOr, _ulongType, low, high);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1597,9 +1824,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
StoreS(
|
StoreS(
|
||||||
register,
|
register,
|
||||||
_module.AddInstruction(SpirvOp.UConvert, _uintType, value));
|
_module.AddInstruction(SpirvOp.UConvert, _uintType, value));
|
||||||
var high = _module.AddInstruction(
|
var high = ShiftRightLogical64(
|
||||||
SpirvOp.ShiftRightLogical,
|
|
||||||
_ulongType,
|
|
||||||
value,
|
value,
|
||||||
_module.Constant64(_ulongType, 32));
|
_module.Constant64(_ulongType, 32));
|
||||||
StoreS(
|
StoreS(
|
||||||
@@ -1651,12 +1876,19 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
{
|
{
|
||||||
var left = GetRawSource(instruction, reverse ? 1 : 0);
|
var left = GetRawSource(instruction, reverse ? 1 : 0);
|
||||||
var right = GetRawSource(instruction, reverse ? 0 : 1);
|
var right = GetRawSource(instruction, reverse ? 0 : 1);
|
||||||
if (operation is
|
if (operation == SpirvOp.ShiftLeftLogical)
|
||||||
SpirvOp.ShiftLeftLogical or
|
|
||||||
SpirvOp.ShiftRightLogical or
|
|
||||||
SpirvOp.ShiftRightArithmetic)
|
|
||||||
{
|
{
|
||||||
right = BitwiseAnd(right, UInt(31));
|
return ShiftLeftLogical(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation == SpirvOp.ShiftRightLogical)
|
||||||
|
{
|
||||||
|
return ShiftRightLogical(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation == SpirvOp.ShiftRightArithmetic)
|
||||||
|
{
|
||||||
|
return ShiftRightArithmetic(left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _module.AddInstruction(operation, _uintType, left, right);
|
return _module.AddInstruction(operation, _uintType, left, right);
|
||||||
@@ -1881,7 +2113,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_boolType,
|
_boolType,
|
||||||
_module.AddInstruction(SpirvOp.ULessThan, _boolType, partial, left),
|
_module.AddInstruction(SpirvOp.ULessThan, _boolType, partial, left),
|
||||||
_module.AddInstruction(SpirvOp.ULessThan, _boolType, result, partial));
|
_module.AddInstruction(SpirvOp.ULessThan, _boolType, result, partial));
|
||||||
Store(_vcc, carry);
|
StoreWaveMask(106, carry);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1912,7 +2144,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_boolType,
|
_boolType,
|
||||||
partial,
|
partial,
|
||||||
borrowIn));
|
borrowIn));
|
||||||
Store(_vcc, borrow);
|
StoreWaveMask(106, borrow);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1932,13 +2164,13 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
UInt(0)));
|
UInt(0)));
|
||||||
if (register == 106)
|
if (register == 106)
|
||||||
{
|
{
|
||||||
Store(_vcc, carry);
|
StoreWaveMask(106, carry);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Store(_vcc, carry);
|
StoreWaveMask(106, carry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint EmitPermlane16(
|
private uint EmitPermlane16(
|
||||||
@@ -1955,21 +2187,13 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_boolType,
|
_boolType,
|
||||||
localLane,
|
localLane,
|
||||||
UInt(8));
|
UInt(8));
|
||||||
var lowShift = _module.AddInstruction(
|
var lowShift = ShiftLeftLogical(localLane, UInt(2));
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
localLane,
|
|
||||||
UInt(2));
|
|
||||||
var highLane = _module.AddInstruction(
|
var highLane = _module.AddInstruction(
|
||||||
SpirvOp.ISub,
|
SpirvOp.ISub,
|
||||||
_uintType,
|
_uintType,
|
||||||
localLane,
|
localLane,
|
||||||
UInt(8));
|
UInt(8));
|
||||||
var highShift = _module.AddInstruction(
|
var highShift = ShiftLeftLogical(highLane, UInt(2));
|
||||||
SpirvOp.ShiftLeftLogical,
|
|
||||||
_uintType,
|
|
||||||
highLane,
|
|
||||||
UInt(2));
|
|
||||||
var lowSelector = BitwiseAnd(
|
var lowSelector = BitwiseAnd(
|
||||||
ShiftRightLogical(selectorLow, lowShift),
|
ShiftRightLogical(selectorLow, lowShift),
|
||||||
UInt(15));
|
UInt(15));
|
||||||
@@ -2030,6 +2254,14 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
return Bitcast(_uintType, value);
|
return Bitcast(_uintType, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private uint TruncateFloat32ForPack(uint value)
|
||||||
|
{
|
||||||
|
var raw = BitwiseAnd(
|
||||||
|
Bitcast(_uintType, value),
|
||||||
|
UInt(0xFFFF_E000));
|
||||||
|
return Bitcast(_floatType, raw);
|
||||||
|
}
|
||||||
|
|
||||||
private uint Ext(uint operation, uint resultType, params uint[] operands)
|
private uint Ext(uint operation, uint resultType, params uint[] operands)
|
||||||
{
|
{
|
||||||
var values = new uint[2 + operands.Length];
|
var values = new uint[2 + operands.Length];
|
||||||
@@ -2049,6 +2281,53 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
value,
|
value,
|
||||||
_module.Constant64(_ulongType, 0));
|
_module.Constant64(_ulongType, 0));
|
||||||
|
|
||||||
|
private uint SignBit(uint value) =>
|
||||||
|
ShiftRightLogical(value, UInt(31));
|
||||||
|
|
||||||
|
private uint SignedAddOverflow(uint left, uint right, uint result)
|
||||||
|
{
|
||||||
|
var leftSign = SignBit(left);
|
||||||
|
var rightSign = SignBit(right);
|
||||||
|
var resultSign = SignBit(result);
|
||||||
|
var sameSourceSign = _module.AddInstruction(
|
||||||
|
SpirvOp.IEqual,
|
||||||
|
_boolType,
|
||||||
|
leftSign,
|
||||||
|
rightSign);
|
||||||
|
var resultSignChanged = _module.AddInstruction(
|
||||||
|
SpirvOp.INotEqual,
|
||||||
|
_boolType,
|
||||||
|
leftSign,
|
||||||
|
resultSign);
|
||||||
|
return _module.AddInstruction(
|
||||||
|
SpirvOp.LogicalAnd,
|
||||||
|
_boolType,
|
||||||
|
sameSourceSign,
|
||||||
|
resultSignChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint SignedSubOverflow(uint left, uint right, uint result)
|
||||||
|
{
|
||||||
|
var leftSign = SignBit(left);
|
||||||
|
var rightSign = SignBit(right);
|
||||||
|
var resultSign = SignBit(result);
|
||||||
|
var differentSourceSign = _module.AddInstruction(
|
||||||
|
SpirvOp.INotEqual,
|
||||||
|
_boolType,
|
||||||
|
leftSign,
|
||||||
|
rightSign);
|
||||||
|
var resultSignChanged = _module.AddInstruction(
|
||||||
|
SpirvOp.INotEqual,
|
||||||
|
_boolType,
|
||||||
|
leftSign,
|
||||||
|
resultSign);
|
||||||
|
return _module.AddInstruction(
|
||||||
|
SpirvOp.LogicalAnd,
|
||||||
|
_boolType,
|
||||||
|
differentSourceSign,
|
||||||
|
resultSignChanged);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryDecodeInlineConstant(uint encoded, out uint value)
|
private static bool TryDecodeInlineConstant(uint encoded, out uint value)
|
||||||
{
|
{
|
||||||
if (encoded == 125)
|
if (encoded == 125)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
private const uint ScalarRegisterCount = 256;
|
private const uint ScalarRegisterCount = 256;
|
||||||
private const uint VectorRegisterCount = 512;
|
private const uint VectorRegisterCount = 512;
|
||||||
private const uint LdsDwordCount = 8192;
|
private const uint LdsDwordCount = 8192;
|
||||||
|
private const uint RdnaWaveLaneCount = 32;
|
||||||
|
|
||||||
public static bool TryCompilePixelShader(
|
public static bool TryCompilePixelShader(
|
||||||
Gen5ShaderState state,
|
Gen5ShaderState state,
|
||||||
@@ -95,6 +96,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
private readonly List<uint> _interfaces = [];
|
private readonly List<uint> _interfaces = [];
|
||||||
private readonly Dictionary<uint, uint> _pixelInputs = [];
|
private readonly Dictionary<uint, uint> _pixelInputs = [];
|
||||||
private readonly Dictionary<uint, uint> _vertexOutputs = [];
|
private readonly Dictionary<uint, uint> _vertexOutputs = [];
|
||||||
|
private readonly Dictionary<uint, SpirvVertexInput> _vertexInputsByPc = [];
|
||||||
private readonly List<SpirvImageResource> _imageResources = [];
|
private readonly List<SpirvImageResource> _imageResources = [];
|
||||||
private readonly Dictionary<uint, int> _imageBindingByPc = [];
|
private readonly Dictionary<uint, int> _imageBindingByPc = [];
|
||||||
private readonly Dictionary<uint, int> _bufferBindingByPc = [];
|
private readonly Dictionary<uint, int> _bufferBindingByPc = [];
|
||||||
@@ -102,6 +104,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
private uint _boolType;
|
private uint _boolType;
|
||||||
private uint _uintType;
|
private uint _uintType;
|
||||||
private uint _intType;
|
private uint _intType;
|
||||||
|
private uint _longType;
|
||||||
private uint _ulongType;
|
private uint _ulongType;
|
||||||
private uint _floatType;
|
private uint _floatType;
|
||||||
private uint _vec2Type;
|
private uint _vec2Type;
|
||||||
@@ -149,6 +152,11 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
ImageComponentKind ComponentKind,
|
ImageComponentKind ComponentKind,
|
||||||
bool IsStorage);
|
bool IsStorage);
|
||||||
|
|
||||||
|
private readonly record struct SpirvVertexInput(
|
||||||
|
uint Variable,
|
||||||
|
uint Type,
|
||||||
|
uint ComponentCount);
|
||||||
|
|
||||||
public CompilationContext(
|
public CompilationContext(
|
||||||
Gen5SpirvStage stage,
|
Gen5SpirvStage stage,
|
||||||
Gen5ShaderState state,
|
Gen5ShaderState state,
|
||||||
@@ -282,7 +290,10 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_module.Build(),
|
_module.Build(),
|
||||||
_evaluation.GlobalMemoryBindings,
|
_evaluation.GlobalMemoryBindings,
|
||||||
_evaluation.ImageBindings,
|
_evaluation.ImageBindings,
|
||||||
attributeCount);
|
attributeCount,
|
||||||
|
_stage == Gen5SpirvStage.Vertex
|
||||||
|
? _evaluation.VertexInputs ?? []
|
||||||
|
: []);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
@@ -297,10 +308,31 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_module.AddCapability(SpirvCapability.Shader);
|
_module.AddCapability(SpirvCapability.Shader);
|
||||||
_module.AddCapability(SpirvCapability.Int64);
|
_module.AddCapability(SpirvCapability.Int64);
|
||||||
_module.AddCapability(SpirvCapability.ImageQuery);
|
_module.AddCapability(SpirvCapability.ImageQuery);
|
||||||
if (UsesSubgroupShuffle())
|
if (_evaluation.ImageBindings.Any(
|
||||||
|
static binding =>
|
||||||
|
(binding.Opcode.StartsWith(
|
||||||
|
"ImageSample",
|
||||||
|
StringComparison.Ordinal) ||
|
||||||
|
binding.Opcode.StartsWith(
|
||||||
|
"ImageGather4",
|
||||||
|
StringComparison.Ordinal)) &&
|
||||||
|
binding.Opcode.EndsWith("O", StringComparison.Ordinal)))
|
||||||
|
{
|
||||||
|
_module.AddCapability(SpirvCapability.ImageGatherExtended);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UsesSubgroupOperations())
|
||||||
{
|
{
|
||||||
_module.AddCapability(SpirvCapability.GroupNonUniform);
|
_module.AddCapability(SpirvCapability.GroupNonUniform);
|
||||||
_module.AddCapability(SpirvCapability.GroupNonUniformShuffle);
|
if (UsesSubgroupShuffle())
|
||||||
|
{
|
||||||
|
_module.AddCapability(SpirvCapability.GroupNonUniformShuffle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UsesWaveControl())
|
||||||
|
{
|
||||||
|
_module.AddCapability(SpirvCapability.GroupNonUniformVote);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_glsl = _module.ImportExtInst("GLSL.std.450");
|
_glsl = _module.ImportExtInst("GLSL.std.450");
|
||||||
@@ -308,6 +340,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_boolType = _module.TypeBool();
|
_boolType = _module.TypeBool();
|
||||||
_uintType = _module.TypeInt(32, signed: false);
|
_uintType = _module.TypeInt(32, signed: false);
|
||||||
_intType = _module.TypeInt(32, signed: true);
|
_intType = _module.TypeInt(32, signed: true);
|
||||||
|
_longType = _module.TypeInt(64, signed: true);
|
||||||
_ulongType = _module.TypeInt(64, signed: false);
|
_ulongType = _module.TypeInt(64, signed: false);
|
||||||
_floatType = _module.TypeFloat(32);
|
_floatType = _module.TypeFloat(32);
|
||||||
_vec2Type = _module.TypeVector(_floatType, 2);
|
_vec2Type = _module.TypeVector(_floatType, 2);
|
||||||
@@ -379,10 +412,6 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ldsArrayType = _module.TypeArray(_uintType, LdsDwordCount);
|
var ldsArrayType = _module.TypeArray(_uintType, LdsDwordCount);
|
||||||
_module.AddDecoration(
|
|
||||||
ldsArrayType,
|
|
||||||
SpirvDecoration.ArrayStride,
|
|
||||||
sizeof(uint));
|
|
||||||
var ldsPointer =
|
var ldsPointer =
|
||||||
_module.TypePointer(SpirvStorageClass.Workgroup, ldsArrayType);
|
_module.TypePointer(SpirvStorageClass.Workgroup, ldsArrayType);
|
||||||
_workgroupUintPointer =
|
_workgroupUintPointer =
|
||||||
@@ -438,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
|
||||||
@@ -456,11 +482,16 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_module.AddCapability(
|
_module.AddCapability(
|
||||||
SpirvCapability.StorageImageWriteWithoutFormat);
|
SpirvCapability.StorageImageWriteWithoutFormat);
|
||||||
}
|
}
|
||||||
|
else if (isStorage && RequiresExtendedStorageImageFormat(format))
|
||||||
|
{
|
||||||
|
_module.AddCapability(
|
||||||
|
SpirvCapability.StorageImageExtendedFormats);
|
||||||
|
}
|
||||||
|
|
||||||
var imageType = _module.TypeImage(
|
var imageType = _module.TypeImage(
|
||||||
componentType,
|
componentType,
|
||||||
SpirvImageDim.Dim2D,
|
SpirvImageDim.Dim2D,
|
||||||
depth: isDepth,
|
depth: false,
|
||||||
arrayed: false,
|
arrayed: false,
|
||||||
multisampled: false,
|
multisampled: false,
|
||||||
sampled: isStorage ? 2u : 1u,
|
sampled: isStorage ? 2u : 1u,
|
||||||
@@ -493,6 +524,13 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool RequiresExtendedStorageImageFormat(
|
||||||
|
SpirvImageFormat format) =>
|
||||||
|
format is not SpirvImageFormat.Unknown and
|
||||||
|
not SpirvImageFormat.Rgba32f and
|
||||||
|
not SpirvImageFormat.Rgba32i and
|
||||||
|
not SpirvImageFormat.Rgba32ui;
|
||||||
|
|
||||||
private static (SpirvImageFormat Format, ImageComponentKind Kind)
|
private static (SpirvImageFormat Format, ImageComponentKind Kind)
|
||||||
DecodeImageFormat(IReadOnlyList<uint> descriptor)
|
DecodeImageFormat(IReadOnlyList<uint> descriptor)
|
||||||
{
|
{
|
||||||
@@ -557,7 +595,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
|
|
||||||
private void DeclareStageInterface()
|
private void DeclareStageInterface()
|
||||||
{
|
{
|
||||||
if (UsesSubgroupShuffle())
|
if (UsesSubgroupOperations())
|
||||||
{
|
{
|
||||||
var subgroupPointer =
|
var subgroupPointer =
|
||||||
_module.TypePointer(SpirvStorageClass.Input, _uintType);
|
_module.TypePointer(SpirvStorageClass.Input, _uintType);
|
||||||
@@ -573,6 +611,8 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
|
|
||||||
if (_stage == Gen5SpirvStage.Vertex)
|
if (_stage == Gen5SpirvStage.Vertex)
|
||||||
{
|
{
|
||||||
|
DeclareVertexInputs();
|
||||||
|
|
||||||
var inputPointer =
|
var inputPointer =
|
||||||
_module.TypePointer(SpirvStorageClass.Input, _uintType);
|
_module.TypePointer(SpirvStorageClass.Input, _uintType);
|
||||||
_vertexIndexInput = _module.AddGlobalVariable(
|
_vertexIndexInput = _module.AddGlobalVariable(
|
||||||
@@ -618,7 +658,6 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
outputPointer,
|
outputPointer,
|
||||||
SpirvStorageClass.Output);
|
SpirvStorageClass.Output);
|
||||||
_module.AddDecoration(variable, SpirvDecoration.Location, parameter);
|
_module.AddDecoration(variable, SpirvDecoration.Location, parameter);
|
||||||
_module.AddDecoration(variable, SpirvDecoration.NoPerspective);
|
|
||||||
_vertexOutputs.Add(parameter, variable);
|
_vertexOutputs.Add(parameter, variable);
|
||||||
_interfaces.Add(variable);
|
_interfaces.Add(variable);
|
||||||
}
|
}
|
||||||
@@ -640,7 +679,6 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
inputVec4Pointer,
|
inputVec4Pointer,
|
||||||
SpirvStorageClass.Input);
|
SpirvStorageClass.Input);
|
||||||
_module.AddDecoration(variable, SpirvDecoration.Location, attribute);
|
_module.AddDecoration(variable, SpirvDecoration.Location, attribute);
|
||||||
_module.AddDecoration(variable, SpirvDecoration.NoPerspective);
|
|
||||||
_pixelInputs.Add(attribute, variable);
|
_pixelInputs.Add(attribute, variable);
|
||||||
_interfaces.Add(variable);
|
_interfaces.Add(variable);
|
||||||
}
|
}
|
||||||
@@ -691,6 +729,42 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DeclareVertexInputs()
|
||||||
|
{
|
||||||
|
foreach (var input in _evaluation.VertexInputs ?? [])
|
||||||
|
{
|
||||||
|
var type = input.ComponentCount switch
|
||||||
|
{
|
||||||
|
1u => _floatType,
|
||||||
|
2u => _vec2Type,
|
||||||
|
3u => _vec3Type,
|
||||||
|
4u => _vec4Type,
|
||||||
|
_ => 0u,
|
||||||
|
};
|
||||||
|
if (type == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pointer = _module.TypePointer(SpirvStorageClass.Input, type);
|
||||||
|
var variable = _module.AddGlobalVariable(
|
||||||
|
pointer,
|
||||||
|
SpirvStorageClass.Input);
|
||||||
|
_module.AddName(variable, $"attr{input.Location}");
|
||||||
|
_module.AddDecoration(
|
||||||
|
variable,
|
||||||
|
SpirvDecoration.Location,
|
||||||
|
input.Location);
|
||||||
|
_vertexInputsByPc.TryAdd(
|
||||||
|
input.Pc,
|
||||||
|
new SpirvVertexInput(
|
||||||
|
variable,
|
||||||
|
type,
|
||||||
|
input.ComponentCount));
|
||||||
|
_interfaces.Add(variable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void EmitInitialState()
|
private void EmitInitialState()
|
||||||
{
|
{
|
||||||
for (uint index = 0;
|
for (uint index = 0;
|
||||||
@@ -706,8 +780,16 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
Store(_scc, _module.ConstantBool(false));
|
Store(_scc, _module.ConstantBool(false));
|
||||||
Store(_vcc, _module.ConstantBool(false));
|
if (_subgroupInvocationIdInput != 0)
|
||||||
Store(_exec, _module.ConstantBool(true));
|
{
|
||||||
|
StoreWaveMask(106, _module.ConstantBool(false));
|
||||||
|
StoreWaveMask(126, _module.ConstantBool(true));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Store(_vcc, _module.ConstantBool(false));
|
||||||
|
Store(_exec, _module.ConstantBool(true));
|
||||||
|
}
|
||||||
Store(_programCounter, UInt(0));
|
Store(_programCounter, UInt(0));
|
||||||
Store(_programActive, _module.ConstantBool(true));
|
Store(_programActive, _module.ConstantBool(true));
|
||||||
|
|
||||||
@@ -923,10 +1005,10 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
{
|
{
|
||||||
"SCbranchScc0" => LogicalNot(Load(_boolType, _scc)),
|
"SCbranchScc0" => LogicalNot(Load(_boolType, _scc)),
|
||||||
"SCbranchScc1" => Load(_boolType, _scc),
|
"SCbranchScc1" => Load(_boolType, _scc),
|
||||||
"SCbranchVccz" => LogicalNot(Load(_boolType, _vcc)),
|
"SCbranchVccz" => LogicalNot(SubgroupAny(Load(_boolType, _vcc))),
|
||||||
"SCbranchVccnz" => Load(_boolType, _vcc),
|
"SCbranchVccnz" => SubgroupAny(Load(_boolType, _vcc)),
|
||||||
"SCbranchExecz" => LogicalNot(Load(_boolType, _exec)),
|
"SCbranchExecz" => LogicalNot(SubgroupAny(Load(_boolType, _exec))),
|
||||||
"SCbranchExecnz" => Load(_boolType, _exec),
|
"SCbranchExecnz" => SubgroupAny(Load(_boolType, _exec)),
|
||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
return condition != 0;
|
return condition != 0;
|
||||||
@@ -1269,6 +1351,19 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
out string error)
|
out string error)
|
||||||
{
|
{
|
||||||
error = string.Empty;
|
error = string.Empty;
|
||||||
|
if (_stage == Gen5SpirvStage.Vertex &&
|
||||||
|
_vertexInputsByPc.TryGetValue(instruction.Pc, out var vertexInput))
|
||||||
|
{
|
||||||
|
return TryEmitVertexInputFetch(control, vertexInput, out error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_stage == Gen5SpirvStage.Vertex &&
|
||||||
|
IsFormatBufferLoad(instruction.Opcode))
|
||||||
|
{
|
||||||
|
error = $"missing vertex input for {instruction.Opcode} pc=0x{instruction.Pc:X}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_bufferBindingByPc.TryGetValue(instruction.Pc, out var bindingIndex))
|
if (!_bufferBindingByPc.TryGetValue(instruction.Pc, out var bindingIndex))
|
||||||
{
|
{
|
||||||
error = "missing buffer-memory binding";
|
error = "missing buffer-memory binding";
|
||||||
@@ -1297,30 +1392,39 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
|
|
||||||
if (instruction.Opcode == "BufferAtomicAdd")
|
if (instruction.Opcode == "BufferAtomicAdd")
|
||||||
{
|
{
|
||||||
var original = _module.AddInstruction(
|
EmitExecConditional(() =>
|
||||||
SpirvOp.AtomicIAdd,
|
|
||||||
_uintType,
|
|
||||||
BufferWordPointer(bindingIndex, dwordAddress),
|
|
||||||
UInt(1),
|
|
||||||
UInt(0x48),
|
|
||||||
LoadV(control.VectorData));
|
|
||||||
if (control.Glc)
|
|
||||||
{
|
{
|
||||||
StoreV(control.VectorData, original);
|
var original = _module.AddInstruction(
|
||||||
}
|
SpirvOp.AtomicIAdd,
|
||||||
|
_uintType,
|
||||||
|
BufferWordPointer(bindingIndex, dwordAddress),
|
||||||
|
UInt(1),
|
||||||
|
UInt(0x48),
|
||||||
|
LoadV(control.VectorData));
|
||||||
|
if (control.Glc)
|
||||||
|
{
|
||||||
|
StoreV(control.VectorData, original);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instruction.Opcode.StartsWith("BufferStoreDword", StringComparison.Ordinal))
|
if (instruction.Opcode.StartsWith("BufferStoreDword", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
for (uint index = 0; index < control.DwordCount; index++)
|
EmitExecConditional(() =>
|
||||||
{
|
{
|
||||||
var address = index == 0
|
for (uint index = 0; index < control.DwordCount; index++)
|
||||||
? dwordAddress
|
{
|
||||||
: IAdd(dwordAddress, UInt(index));
|
var address = index == 0
|
||||||
StoreBufferWord(bindingIndex, address, LoadV(control.VectorData + index));
|
? dwordAddress
|
||||||
}
|
: IAdd(dwordAddress, UInt(index));
|
||||||
|
StoreBufferWord(
|
||||||
|
bindingIndex,
|
||||||
|
address,
|
||||||
|
LoadV(control.VectorData + index));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1345,6 +1449,41 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsFormatBufferLoad(string opcode) =>
|
||||||
|
opcode.StartsWith("BufferLoadFormat", StringComparison.Ordinal) ||
|
||||||
|
opcode.StartsWith("TBufferLoadFormat", StringComparison.Ordinal);
|
||||||
|
|
||||||
|
private bool TryEmitVertexInputFetch(
|
||||||
|
Gen5BufferMemoryControl control,
|
||||||
|
SpirvVertexInput input,
|
||||||
|
out string error)
|
||||||
|
{
|
||||||
|
error = string.Empty;
|
||||||
|
if (control.DwordCount == 0 ||
|
||||||
|
control.DwordCount > input.ComponentCount)
|
||||||
|
{
|
||||||
|
error =
|
||||||
|
$"invalid vertex input fetch components={control.DwordCount} " +
|
||||||
|
$"input={input.ComponentCount}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var loaded = Load(input.Type, input.Variable);
|
||||||
|
for (uint component = 0; component < control.DwordCount; component++)
|
||||||
|
{
|
||||||
|
var value = input.ComponentCount == 1
|
||||||
|
? loaded
|
||||||
|
: _module.AddInstruction(
|
||||||
|
SpirvOp.CompositeExtract,
|
||||||
|
_floatType,
|
||||||
|
loaded,
|
||||||
|
component);
|
||||||
|
StoreV(control.VectorData + component, Bitcast(_uintType, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private bool TryEmitImage(
|
private bool TryEmitImage(
|
||||||
Gen5ShaderInstruction instruction,
|
Gen5ShaderInstruction instruction,
|
||||||
Gen5ImageControl image,
|
Gen5ImageControl image,
|
||||||
@@ -1362,11 +1501,20 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var imageObject = Load(resource.ObjectType, resource.Variable);
|
var imageObject = Load(resource.ObjectType, resource.Variable);
|
||||||
if (instruction.Opcode == "ImageGetResinfo")
|
if (instruction.Opcode == "ImageGetResinfo")
|
||||||
{
|
{
|
||||||
|
var queryImage = resource.IsStorage
|
||||||
|
? imageObject
|
||||||
|
: _module.AddInstruction(
|
||||||
|
SpirvOp.Image,
|
||||||
|
resource.ImageType,
|
||||||
|
imageObject);
|
||||||
var size = _module.AddInstruction(
|
var size = _module.AddInstruction(
|
||||||
SpirvOp.ImageQuerySizeLod,
|
resource.IsStorage
|
||||||
|
? SpirvOp.ImageQuerySize
|
||||||
|
: SpirvOp.ImageQuerySizeLod,
|
||||||
_module.TypeVector(_intType, 2),
|
_module.TypeVector(_intType, 2),
|
||||||
imageObject,
|
resource.IsStorage
|
||||||
UInt(0));
|
? [queryImage]
|
||||||
|
: [queryImage, UInt(0)]);
|
||||||
uint outputIndex = 0;
|
uint outputIndex = 0;
|
||||||
for (uint component = 0; component < 4; component++)
|
for (uint component = 0; component < 4; component++)
|
||||||
{
|
{
|
||||||
@@ -1449,11 +1597,12 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_module.AddStatement(
|
EmitExecConditional(
|
||||||
SpirvOp.ImageWrite,
|
() => _module.AddStatement(
|
||||||
imageObject,
|
SpirvOp.ImageWrite,
|
||||||
coordinates,
|
imageObject,
|
||||||
texel);
|
coordinates,
|
||||||
|
texel));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -1476,10 +1625,14 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
? BuildClampedIntegerCoordinates(image, 0, width, height)
|
? BuildClampedIntegerCoordinates(image, 0, width, height)
|
||||||
: BuildIntegerCoordinates(image, 0);
|
: BuildIntegerCoordinates(image, 0);
|
||||||
var mipLevel = _evaluation.ImageBindings[bindingIndex].MipLevel ?? 0;
|
var mipLevel = _evaluation.ImageBindings[bindingIndex].MipLevel ?? 0;
|
||||||
|
var fetchedImage = _module.AddInstruction(
|
||||||
|
SpirvOp.Image,
|
||||||
|
resource.ImageType,
|
||||||
|
imageObject);
|
||||||
sampled = _module.AddInstruction(
|
sampled = _module.AddInstruction(
|
||||||
SpirvOp.ImageFetch,
|
SpirvOp.ImageFetch,
|
||||||
resource.VectorType,
|
resource.VectorType,
|
||||||
imageObject,
|
fetchedImage,
|
||||||
coordinates,
|
coordinates,
|
||||||
2,
|
2,
|
||||||
UInt(mipLevel));
|
UInt(mipLevel));
|
||||||
@@ -1505,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)
|
||||||
{
|
{
|
||||||
@@ -1533,29 +1682,14 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
sampled = _module.AddInstruction(
|
sampled = _module.AddInstruction(
|
||||||
hasCompare
|
explicitLod
|
||||||
? explicitLod
|
? SpirvOp.ImageSampleExplicitLod
|
||||||
? SpirvOp.ImageSampleDrefExplicitLod
|
: SpirvOp.ImageSampleImplicitLod,
|
||||||
: SpirvOp.ImageSampleDrefImplicitLod
|
resource.VectorType,
|
||||||
: explicitLod
|
|
||||||
? SpirvOp.ImageSampleExplicitLod
|
|
||||||
: SpirvOp.ImageSampleImplicitLod,
|
|
||||||
hasCompare ? resource.ComponentType : 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(
|
||||||
@@ -1569,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,
|
||||||
@@ -1576,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
|
||||||
{
|
{
|
||||||
@@ -1600,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
|
||||||
@@ -1638,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(
|
||||||
@@ -1774,6 +1986,11 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_boolType,
|
_boolType,
|
||||||
lowerInRange,
|
lowerInRange,
|
||||||
upperInRange);
|
upperInRange);
|
||||||
|
inRange = _module.AddInstruction(
|
||||||
|
SpirvOp.LogicalAnd,
|
||||||
|
_boolType,
|
||||||
|
Load(_boolType, _exec),
|
||||||
|
inRange);
|
||||||
var writeLabel = _module.AllocateId();
|
var writeLabel = _module.AllocateId();
|
||||||
var mergeLabel = _module.AllocateId();
|
var mergeLabel = _module.AllocateId();
|
||||||
_module.AddStatement(SpirvOp.SelectionMerge, mergeLabel, 0);
|
_module.AddStatement(SpirvOp.SelectionMerge, mergeLabel, 0);
|
||||||
@@ -1900,6 +2117,12 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
SpirvOp.CompositeConstruct,
|
SpirvOp.CompositeConstruct,
|
||||||
outputType,
|
outputType,
|
||||||
values);
|
values);
|
||||||
|
vector = _module.AddInstruction(
|
||||||
|
SpirvOp.Select,
|
||||||
|
outputType,
|
||||||
|
Load(_boolType, _exec),
|
||||||
|
vector,
|
||||||
|
Load(outputType, _pixelOutput));
|
||||||
Store(_pixelOutput, vector);
|
Store(_pixelOutput, vector);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1945,6 +2168,12 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
SpirvOp.CompositeConstruct,
|
SpirvOp.CompositeConstruct,
|
||||||
_vec4Type,
|
_vec4Type,
|
||||||
components);
|
components);
|
||||||
|
outputValue = _module.AddInstruction(
|
||||||
|
SpirvOp.Select,
|
||||||
|
_vec4Type,
|
||||||
|
Load(_boolType, _exec),
|
||||||
|
outputValue,
|
||||||
|
Load(_vec4Type, outputVariable));
|
||||||
Store(outputVariable, outputValue);
|
Store(outputVariable, outputValue);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2009,7 +2238,18 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
|
|
||||||
private uint LoadV(uint register) => Load(_uintType, VectorPointer(register));
|
private uint LoadV(uint register) => Load(_uintType, VectorPointer(register));
|
||||||
|
|
||||||
private void StoreS(uint register, uint value) => Store(ScalarPointer(register), value);
|
private void StoreS(uint register, uint value)
|
||||||
|
{
|
||||||
|
Store(ScalarPointer(register), value);
|
||||||
|
if (register is 106 or 107)
|
||||||
|
{
|
||||||
|
Store(_vcc, IsWaveMaskActive(LoadS64(106)));
|
||||||
|
}
|
||||||
|
else if (register is 126 or 127)
|
||||||
|
{
|
||||||
|
Store(_exec, IsWaveMaskActive(LoadS64(126)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void StoreV(uint register, uint value, bool guardWithExec = true)
|
private void StoreV(uint register, uint value, bool guardWithExec = true)
|
||||||
{
|
{
|
||||||
@@ -2044,12 +2284,49 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
private uint IAdd(uint left, uint right) =>
|
private uint IAdd(uint left, uint right) =>
|
||||||
_module.AddInstruction(SpirvOp.IAdd, _uintType, left, right);
|
_module.AddInstruction(SpirvOp.IAdd, _uintType, left, right);
|
||||||
|
|
||||||
|
private uint ShiftLeftLogical(uint left, uint right) =>
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.ShiftLeftLogical,
|
||||||
|
_uintType,
|
||||||
|
left,
|
||||||
|
BitwiseAnd(right, UInt(31)));
|
||||||
|
|
||||||
private uint ShiftRightLogical(uint left, uint right) =>
|
private uint ShiftRightLogical(uint left, uint right) =>
|
||||||
_module.AddInstruction(SpirvOp.ShiftRightLogical, _uintType, left, right);
|
_module.AddInstruction(
|
||||||
|
SpirvOp.ShiftRightLogical,
|
||||||
|
_uintType,
|
||||||
|
left,
|
||||||
|
BitwiseAnd(right, UInt(31)));
|
||||||
|
|
||||||
|
private uint ShiftRightArithmetic(uint left, uint right) =>
|
||||||
|
Bitcast(
|
||||||
|
_uintType,
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.ShiftRightArithmetic,
|
||||||
|
_intType,
|
||||||
|
Bitcast(_intType, left),
|
||||||
|
BitwiseAnd(right, UInt(31))));
|
||||||
|
|
||||||
|
private uint ShiftLeftLogical64(uint left, uint right) =>
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.ShiftLeftLogical,
|
||||||
|
_ulongType,
|
||||||
|
left,
|
||||||
|
BitwiseAnd64(right, _module.Constant64(_ulongType, 63)));
|
||||||
|
|
||||||
|
private uint ShiftRightLogical64(uint left, uint right) =>
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.ShiftRightLogical,
|
||||||
|
_ulongType,
|
||||||
|
left,
|
||||||
|
BitwiseAnd64(right, _module.Constant64(_ulongType, 63)));
|
||||||
|
|
||||||
private uint BitwiseAnd(uint left, uint right) =>
|
private uint BitwiseAnd(uint left, uint right) =>
|
||||||
_module.AddInstruction(SpirvOp.BitwiseAnd, _uintType, left, right);
|
_module.AddInstruction(SpirvOp.BitwiseAnd, _uintType, left, right);
|
||||||
|
|
||||||
|
private uint BitwiseAnd64(uint left, uint right) =>
|
||||||
|
_module.AddInstruction(SpirvOp.BitwiseAnd, _ulongType, left, right);
|
||||||
|
|
||||||
private uint BitwiseOr(uint left, uint right) =>
|
private uint BitwiseOr(uint left, uint right) =>
|
||||||
_module.AddInstruction(SpirvOp.BitwiseOr, _uintType, left, right);
|
_module.AddInstruction(SpirvOp.BitwiseOr, _uintType, left, right);
|
||||||
|
|
||||||
@@ -2059,6 +2336,86 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
private uint LogicalNot(uint value) =>
|
private uint LogicalNot(uint value) =>
|
||||||
_module.AddInstruction(SpirvOp.LogicalNot, _boolType, value);
|
_module.AddInstruction(SpirvOp.LogicalNot, _boolType, value);
|
||||||
|
|
||||||
|
private uint SubgroupAny(uint condition) =>
|
||||||
|
_subgroupInvocationIdInput == 0
|
||||||
|
? condition
|
||||||
|
: _module.AddInstruction(
|
||||||
|
SpirvOp.GroupNonUniformAny,
|
||||||
|
_boolType,
|
||||||
|
UInt(3),
|
||||||
|
condition);
|
||||||
|
|
||||||
|
private uint CurrentLaneBit()
|
||||||
|
{
|
||||||
|
if (_subgroupInvocationIdInput == 0)
|
||||||
|
{
|
||||||
|
return _module.Constant64(_ulongType, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var lane = Load(_uintType, _subgroupInvocationIdInput);
|
||||||
|
var maskedLane = BitwiseAnd(lane, UInt(RdnaWaveLaneCount - 1));
|
||||||
|
var shifted = ShiftLeftLogical64(
|
||||||
|
_module.Constant64(_ulongType, 1),
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.UConvert,
|
||||||
|
_ulongType,
|
||||||
|
maskedLane));
|
||||||
|
return _module.AddInstruction(
|
||||||
|
SpirvOp.Select,
|
||||||
|
_ulongType,
|
||||||
|
IsCurrentLaneInRdnaWave(),
|
||||||
|
shifted,
|
||||||
|
_module.Constant64(_ulongType, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint IsCurrentLaneInRdnaWave() =>
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.ULessThan,
|
||||||
|
_boolType,
|
||||||
|
Load(_uintType, _subgroupInvocationIdInput),
|
||||||
|
UInt(RdnaWaveLaneCount));
|
||||||
|
|
||||||
|
private uint BooleanToLaneMask(uint condition) =>
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.Select,
|
||||||
|
_ulongType,
|
||||||
|
condition,
|
||||||
|
CurrentLaneBit(),
|
||||||
|
_module.Constant64(_ulongType, 0));
|
||||||
|
|
||||||
|
private uint IsWaveMaskActive(uint mask) =>
|
||||||
|
_subgroupInvocationIdInput == 0
|
||||||
|
? IsNotZero64(mask)
|
||||||
|
: IsCurrentLaneSet(mask);
|
||||||
|
|
||||||
|
private uint IsCurrentLaneSet(uint mask) =>
|
||||||
|
IsNotZero64(
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.BitwiseAnd,
|
||||||
|
_ulongType,
|
||||||
|
mask,
|
||||||
|
CurrentLaneBit()));
|
||||||
|
|
||||||
|
private void StoreWaveMask(uint register, uint condition) =>
|
||||||
|
StoreS64(register, BooleanToLaneMask(condition));
|
||||||
|
|
||||||
|
private void EmitExecConditional(Action emit)
|
||||||
|
{
|
||||||
|
var activeLabel = _module.AllocateId();
|
||||||
|
var mergeLabel = _module.AllocateId();
|
||||||
|
var active = Load(_boolType, _exec);
|
||||||
|
_module.AddStatement(SpirvOp.SelectionMerge, mergeLabel, 0);
|
||||||
|
_module.AddStatement(
|
||||||
|
SpirvOp.BranchConditional,
|
||||||
|
active,
|
||||||
|
activeLabel,
|
||||||
|
mergeLabel);
|
||||||
|
_module.AddLabel(activeLabel);
|
||||||
|
emit();
|
||||||
|
_module.AddStatement(SpirvOp.Branch, mergeLabel);
|
||||||
|
_module.AddLabel(mergeLabel);
|
||||||
|
}
|
||||||
|
|
||||||
private bool UsesLds() =>
|
private bool UsesLds() =>
|
||||||
_state.Program.Instructions.Any(instruction =>
|
_state.Program.Instructions.Any(instruction =>
|
||||||
instruction.Control is Gen5DataShareControl);
|
instruction.Control is Gen5DataShareControl);
|
||||||
@@ -2067,6 +2424,23 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_state.Program.Instructions.Any(instruction =>
|
_state.Program.Instructions.Any(instruction =>
|
||||||
instruction.Opcode is "VPermlane16B32" or "VPermlanex16B32");
|
instruction.Opcode is "VPermlane16B32" or "VPermlanex16B32");
|
||||||
|
|
||||||
|
private bool UsesWaveControl() =>
|
||||||
|
_state.Program.Instructions.Any(instruction =>
|
||||||
|
instruction.Opcode.Contains("Saveexec", StringComparison.Ordinal) ||
|
||||||
|
instruction.Opcode.StartsWith("SCbranchExec", StringComparison.Ordinal) ||
|
||||||
|
instruction.Opcode.StartsWith("SCbranchVcc", StringComparison.Ordinal) ||
|
||||||
|
instruction.Opcode.StartsWith("VCmpx", StringComparison.Ordinal) ||
|
||||||
|
instruction.Sources.Any(IsWaveMaskOperand) ||
|
||||||
|
instruction.Destinations.Any(IsWaveMaskOperand));
|
||||||
|
|
||||||
|
private bool UsesSubgroupOperations() =>
|
||||||
|
_stage == Gen5SpirvStage.Compute &&
|
||||||
|
(UsesSubgroupShuffle() || UsesWaveControl());
|
||||||
|
|
||||||
|
private static bool IsWaveMaskOperand(Gen5Operand operand) =>
|
||||||
|
operand.Kind == Gen5OperandKind.ScalarRegister &&
|
||||||
|
operand.Value is 106 or 107 or 126 or 127;
|
||||||
|
|
||||||
private static bool TryGetVectorDestination(
|
private static bool TryGetVectorDestination(
|
||||||
Gen5ShaderInstruction instruction,
|
Gen5ShaderInstruction instruction,
|
||||||
out uint destination)
|
out uint destination)
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ internal enum SpirvOp : ushort
|
|||||||
ImageDrefGather = 97,
|
ImageDrefGather = 97,
|
||||||
ImageRead = 98,
|
ImageRead = 98,
|
||||||
ImageWrite = 99,
|
ImageWrite = 99,
|
||||||
|
Image = 100,
|
||||||
ImageQuerySizeLod = 103,
|
ImageQuerySizeLod = 103,
|
||||||
ImageQuerySize = 104,
|
ImageQuerySize = 104,
|
||||||
ImageQueryLod = 105,
|
ImageQueryLod = 105,
|
||||||
@@ -173,10 +174,13 @@ internal enum SpirvCapability : uint
|
|||||||
Float64 = 10,
|
Float64 = 10,
|
||||||
Int64 = 11,
|
Int64 = 11,
|
||||||
Int16 = 22,
|
Int16 = 22,
|
||||||
|
ImageGatherExtended = 25,
|
||||||
|
StorageImageExtendedFormats = 49,
|
||||||
ImageQuery = 50,
|
ImageQuery = 50,
|
||||||
StorageImageReadWithoutFormat = 55,
|
StorageImageReadWithoutFormat = 55,
|
||||||
StorageImageWriteWithoutFormat = 56,
|
StorageImageWriteWithoutFormat = 56,
|
||||||
GroupNonUniform = 61,
|
GroupNonUniform = 61,
|
||||||
|
GroupNonUniformVote = 62,
|
||||||
GroupNonUniformBallot = 64,
|
GroupNonUniformBallot = 64,
|
||||||
GroupNonUniformShuffle = 65,
|
GroupNonUniformShuffle = 65,
|
||||||
RuntimeDescriptorArray = 5302,
|
RuntimeDescriptorArray = 5302,
|
||||||
@@ -309,6 +313,18 @@ internal sealed class SpirvModuleBuilder
|
|||||||
private readonly Dictionary<(uint Width, bool Signed), uint> _integerTypes = [];
|
private readonly Dictionary<(uint Width, bool Signed), uint> _integerTypes = [];
|
||||||
private readonly Dictionary<uint, uint> _floatTypes = [];
|
private readonly Dictionary<uint, uint> _floatTypes = [];
|
||||||
private readonly Dictionary<(uint Component, uint Count), uint> _vectorTypes = [];
|
private readonly Dictionary<(uint Component, uint Count), uint> _vectorTypes = [];
|
||||||
|
private readonly Dictionary<
|
||||||
|
(
|
||||||
|
uint SampledType,
|
||||||
|
SpirvImageDim Dimension,
|
||||||
|
bool Depth,
|
||||||
|
bool Arrayed,
|
||||||
|
bool Multisampled,
|
||||||
|
uint Sampled,
|
||||||
|
SpirvImageFormat Format
|
||||||
|
),
|
||||||
|
uint> _imageTypes = [];
|
||||||
|
private readonly Dictionary<uint, uint> _sampledImageTypes = [];
|
||||||
private readonly Dictionary<(SpirvStorageClass Storage, uint Type), uint> _pointerTypes = [];
|
private readonly Dictionary<(SpirvStorageClass Storage, uint Type), uint> _pointerTypes = [];
|
||||||
private readonly Dictionary<(uint Element, uint Count), uint> _arrayTypes = [];
|
private readonly Dictionary<(uint Element, uint Count), uint> _arrayTypes = [];
|
||||||
private readonly Dictionary<uint, uint> _runtimeArrayTypes = [];
|
private readonly Dictionary<uint, uint> _runtimeArrayTypes = [];
|
||||||
@@ -477,6 +493,19 @@ internal sealed class SpirvModuleBuilder
|
|||||||
uint sampled,
|
uint sampled,
|
||||||
SpirvImageFormat format)
|
SpirvImageFormat format)
|
||||||
{
|
{
|
||||||
|
var key = (
|
||||||
|
sampledType,
|
||||||
|
dimension,
|
||||||
|
depth,
|
||||||
|
arrayed,
|
||||||
|
multisampled,
|
||||||
|
sampled,
|
||||||
|
format);
|
||||||
|
if (_imageTypes.TryGetValue(key, out var existing))
|
||||||
|
{
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
var id = AllocateId();
|
var id = AllocateId();
|
||||||
Emit(
|
Emit(
|
||||||
_typesConstantsGlobals,
|
_typesConstantsGlobals,
|
||||||
@@ -489,13 +518,20 @@ internal sealed class SpirvModuleBuilder
|
|||||||
multisampled ? 1u : 0u,
|
multisampled ? 1u : 0u,
|
||||||
sampled,
|
sampled,
|
||||||
(uint)format);
|
(uint)format);
|
||||||
|
_imageTypes.Add(key, id);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint TypeSampledImage(uint imageType)
|
public uint TypeSampledImage(uint imageType)
|
||||||
{
|
{
|
||||||
|
if (_sampledImageTypes.TryGetValue(imageType, out var existing))
|
||||||
|
{
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
var id = AllocateId();
|
var id = AllocateId();
|
||||||
Emit(_typesConstantsGlobals, SpirvOp.TypeSampledImage, id, imageType);
|
Emit(_typesConstantsGlobals, SpirvOp.TypeSampledImage, id, imageType);
|
||||||
|
_sampledImageTypes.Add(imageType, id);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,242 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
|
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 class PortState : IDisposable
|
||||||
|
{
|
||||||
|
private readonly object _paceGate = new();
|
||||||
|
private long _nextSilentOutput;
|
||||||
|
|
||||||
|
public PortState(
|
||||||
|
int userId,
|
||||||
|
int type,
|
||||||
|
uint bufferLength,
|
||||||
|
uint frequency,
|
||||||
|
int format,
|
||||||
|
int channels,
|
||||||
|
int bytesPerSample,
|
||||||
|
bool isFloat,
|
||||||
|
WinMmAudioPort? backend)
|
||||||
|
{
|
||||||
|
UserId = userId;
|
||||||
|
Type = type;
|
||||||
|
BufferLength = bufferLength;
|
||||||
|
Frequency = frequency;
|
||||||
|
Format = format;
|
||||||
|
Channels = channels;
|
||||||
|
BytesPerSample = bytesPerSample;
|
||||||
|
IsFloat = isFloat;
|
||||||
|
Backend = backend;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int UserId { get; }
|
||||||
|
public int Type { get; }
|
||||||
|
public uint BufferLength { get; }
|
||||||
|
public uint Frequency { get; }
|
||||||
|
public int Format { get; }
|
||||||
|
public int Channels { get; }
|
||||||
|
public int BytesPerSample { get; }
|
||||||
|
public bool IsFloat { get; }
|
||||||
|
public WinMmAudioPort? Backend { get; }
|
||||||
|
public int BufferByteLength =>
|
||||||
|
checked((int)BufferLength * Channels * BytesPerSample);
|
||||||
|
|
||||||
|
public void PaceSilence()
|
||||||
|
{
|
||||||
|
long delay;
|
||||||
|
lock (_paceGate)
|
||||||
|
{
|
||||||
|
var now = Stopwatch.GetTimestamp();
|
||||||
|
if (_nextSilentOutput < now)
|
||||||
|
{
|
||||||
|
_nextSilentOutput = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
delay = _nextSilentOutput - now;
|
||||||
|
_nextSilentOutput += checked(
|
||||||
|
(long)Math.Ceiling(
|
||||||
|
Stopwatch.Frequency * (double)BufferLength / Frequency));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delay > 0)
|
||||||
|
{
|
||||||
|
Thread.Sleep(TimeSpan.FromSeconds((double)delay / Stopwatch.Frequency));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() => Backend?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[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 ||
|
||||||
|
!TryGetFormat(format, out var channels, out var bytesPerSample, out var isFloat))
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
WinMmAudioPort? backend = null;
|
||||||
|
string backendName;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
backend = new WinMmAudioPort(frequency);
|
||||||
|
backendName = "winmm";
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
backendName = "silent";
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][WARN] AudioOut host backend unavailable: {exception.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var handle = Interlocked.Increment(ref _nextPortHandle);
|
||||||
|
Ports[handle] = new PortState(
|
||||||
|
userId,
|
||||||
|
type,
|
||||||
|
bufferLength,
|
||||||
|
frequency,
|
||||||
|
format,
|
||||||
|
channels,
|
||||||
|
bytesPerSample,
|
||||||
|
isFloat,
|
||||||
|
backend);
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][INFO] AudioOut port {handle}: {frequency} Hz, " +
|
||||||
|
$"{channels} ch, {(isFloat ? "float32" : "s16")}, " +
|
||||||
|
$"{bufferLength} frames, backend={backendName}");
|
||||||
|
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]);
|
||||||
|
if (!Ports.TryRemove(handle, out var port))
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
port.Dispose();
|
||||||
|
return SetReturn(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "QOQtbeDqsT4",
|
||||||
|
ExportName = "sceAudioOutOutput",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAudioOut")]
|
||||||
|
public static int AudioOutOutput(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||||
|
var sourceAddress = ctx[CpuRegister.Rsi];
|
||||||
|
if (!Ports.TryGetValue(handle, out var port))
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceAddress == 0)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = ArrayPool<byte>.Shared.Rent(port.BufferByteLength);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var source = buffer.AsSpan(0, port.BufferByteLength);
|
||||||
|
if (!ctx.Memory.TryRead(sourceAddress, source))
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port.Backend is null ||
|
||||||
|
!port.Backend.Submit(
|
||||||
|
source,
|
||||||
|
port.BufferLength,
|
||||||
|
port.Channels,
|
||||||
|
port.BytesPerSample,
|
||||||
|
port.IsFloat))
|
||||||
|
{
|
||||||
|
port.PaceSilence();
|
||||||
|
}
|
||||||
|
|
||||||
|
return SetReturn(ctx, 0);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetFormat(
|
||||||
|
int rawFormat,
|
||||||
|
out int channels,
|
||||||
|
out int bytesPerSample,
|
||||||
|
out bool isFloat)
|
||||||
|
{
|
||||||
|
var format = rawFormat & 0xFF;
|
||||||
|
channels = format switch
|
||||||
|
{
|
||||||
|
0 or 3 => 1,
|
||||||
|
1 or 4 => 2,
|
||||||
|
2 or 5 or 6 or 7 => 8,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
bytesPerSample = format is >= 3 and <= 5 or 7 ? 4 : 2;
|
||||||
|
isFloat = bytesPerSample == 4;
|
||||||
|
return channels != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace SharpEmu.Libs.Audio;
|
||||||
|
|
||||||
|
internal sealed class WinMmAudioPort : IDisposable
|
||||||
|
{
|
||||||
|
private const uint WaveMapper = uint.MaxValue;
|
||||||
|
private const uint CallbackEvent = 0x0005_0000;
|
||||||
|
private const ushort WaveFormatPcm = 1;
|
||||||
|
private const uint WaveHeaderDone = 0x0000_0001;
|
||||||
|
private const int MaximumQueuedPcmBytes = 32 * 1024;
|
||||||
|
|
||||||
|
private readonly object _gate = new();
|
||||||
|
private readonly AutoResetEvent _completion = new(false);
|
||||||
|
private readonly Queue<NativeBuffer> _buffers = new();
|
||||||
|
private IntPtr _device;
|
||||||
|
private int _queuedPcmBytes;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
public WinMmAudioPort(uint sampleRate)
|
||||||
|
{
|
||||||
|
if (!OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
throw new PlatformNotSupportedException("WinMM audio is only available on Windows.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var format = new WaveFormat
|
||||||
|
{
|
||||||
|
FormatTag = WaveFormatPcm,
|
||||||
|
Channels = 2,
|
||||||
|
SamplesPerSecond = sampleRate,
|
||||||
|
AverageBytesPerSecond = checked(sampleRate * 4),
|
||||||
|
BlockAlign = 4,
|
||||||
|
BitsPerSample = 16,
|
||||||
|
ExtraSize = 0,
|
||||||
|
};
|
||||||
|
var result = WaveOutOpen(
|
||||||
|
out _device,
|
||||||
|
WaveMapper,
|
||||||
|
ref format,
|
||||||
|
_completion.SafeWaitHandle.DangerousGetHandle(),
|
||||||
|
IntPtr.Zero,
|
||||||
|
CallbackEvent);
|
||||||
|
if (result != 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"waveOutOpen failed with MMRESULT {result}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Submit(
|
||||||
|
ReadOnlySpan<byte> source,
|
||||||
|
uint frames,
|
||||||
|
int channels,
|
||||||
|
int bytesPerSample,
|
||||||
|
bool isFloat)
|
||||||
|
{
|
||||||
|
lock (_gate)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputLength = checked((int)frames * 4);
|
||||||
|
ReapCompletedBuffers();
|
||||||
|
while (_queuedPcmBytes != 0 &&
|
||||||
|
_queuedPcmBytes + outputLength > MaximumQueuedPcmBytes)
|
||||||
|
{
|
||||||
|
if (!_completion.WaitOne(TimeSpan.FromSeconds(1)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReapCompletedBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
var output = ArrayPool<byte>.Shared.Rent(outputLength);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ConvertToStereoPcm16(
|
||||||
|
source,
|
||||||
|
output.AsSpan(0, outputLength),
|
||||||
|
checked((int)frames),
|
||||||
|
channels,
|
||||||
|
bytesPerSample,
|
||||||
|
isFloat);
|
||||||
|
return QueueBuffer(output.AsSpan(0, outputLength));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (_gate)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
if (_device != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
WaveOutReset(_device);
|
||||||
|
while (_buffers.TryDequeue(out var buffer))
|
||||||
|
{
|
||||||
|
ReleaseBuffer(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
WaveOutClose(_device);
|
||||||
|
_device = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
_completion.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool QueueBuffer(ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
var dataAddress = Marshal.AllocHGlobal(data.Length);
|
||||||
|
var headerAddress = IntPtr.Zero;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
data.CopyTo(new Span<byte>((void*)dataAddress, data.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
var header = new WaveHeader
|
||||||
|
{
|
||||||
|
Data = dataAddress,
|
||||||
|
BufferLength = checked((uint)data.Length),
|
||||||
|
};
|
||||||
|
headerAddress = Marshal.AllocHGlobal(Marshal.SizeOf<WaveHeader>());
|
||||||
|
Marshal.StructureToPtr(header, headerAddress, false);
|
||||||
|
|
||||||
|
var result = WaveOutPrepareHeader(
|
||||||
|
_device,
|
||||||
|
headerAddress,
|
||||||
|
checked((uint)Marshal.SizeOf<WaveHeader>()));
|
||||||
|
if (result != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = WaveOutWrite(
|
||||||
|
_device,
|
||||||
|
headerAddress,
|
||||||
|
checked((uint)Marshal.SizeOf<WaveHeader>()));
|
||||||
|
if (result != 0)
|
||||||
|
{
|
||||||
|
WaveOutUnprepareHeader(
|
||||||
|
_device,
|
||||||
|
headerAddress,
|
||||||
|
checked((uint)Marshal.SizeOf<WaveHeader>()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_buffers.Enqueue(new NativeBuffer(dataAddress, headerAddress, data.Length));
|
||||||
|
_queuedPcmBytes += data.Length;
|
||||||
|
dataAddress = IntPtr.Zero;
|
||||||
|
headerAddress = IntPtr.Zero;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (headerAddress != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(headerAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataAddress != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(dataAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReapCompletedBuffers()
|
||||||
|
{
|
||||||
|
while (_buffers.TryPeek(out var buffer))
|
||||||
|
{
|
||||||
|
var header = Marshal.PtrToStructure<WaveHeader>(buffer.Header);
|
||||||
|
if ((header.Flags & WaveHeaderDone) == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_buffers.Dequeue();
|
||||||
|
ReleaseBuffer(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReleaseBuffer(NativeBuffer buffer)
|
||||||
|
{
|
||||||
|
WaveOutUnprepareHeader(
|
||||||
|
_device,
|
||||||
|
buffer.Header,
|
||||||
|
checked((uint)Marshal.SizeOf<WaveHeader>()));
|
||||||
|
_queuedPcmBytes -= buffer.Length;
|
||||||
|
Marshal.FreeHGlobal(buffer.Header);
|
||||||
|
Marshal.FreeHGlobal(buffer.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConvertToStereoPcm16(
|
||||||
|
ReadOnlySpan<byte> source,
|
||||||
|
Span<byte> destination,
|
||||||
|
int frames,
|
||||||
|
int channels,
|
||||||
|
int bytesPerSample,
|
||||||
|
bool isFloat)
|
||||||
|
{
|
||||||
|
var sourceFrameSize = checked(channels * bytesPerSample);
|
||||||
|
for (var frame = 0; frame < frames; frame++)
|
||||||
|
{
|
||||||
|
var sourceFrame = source.Slice(frame * sourceFrameSize, sourceFrameSize);
|
||||||
|
var left = ReadSample(sourceFrame, 0, bytesPerSample, isFloat);
|
||||||
|
var right = channels == 1
|
||||||
|
? left
|
||||||
|
: ReadSample(sourceFrame, 1, bytesPerSample, isFloat);
|
||||||
|
BinaryPrimitives.WriteInt16LittleEndian(destination[(frame * 4)..], left);
|
||||||
|
BinaryPrimitives.WriteInt16LittleEndian(destination[((frame * 4) + 2)..], right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static short ReadSample(
|
||||||
|
ReadOnlySpan<byte> frame,
|
||||||
|
int channel,
|
||||||
|
int bytesPerSample,
|
||||||
|
bool isFloat)
|
||||||
|
{
|
||||||
|
var sample = frame.Slice(channel * bytesPerSample, bytesPerSample);
|
||||||
|
if (!isFloat)
|
||||||
|
{
|
||||||
|
return BinaryPrimitives.ReadInt16LittleEndian(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bits = BinaryPrimitives.ReadInt32LittleEndian(sample);
|
||||||
|
var value = Math.Clamp(BitConverter.Int32BitsToSingle(bits), -1.0f, 1.0f);
|
||||||
|
return checked((short)MathF.Round(value * short.MaxValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly record struct NativeBuffer(IntPtr Data, IntPtr Header, int Length);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 2)]
|
||||||
|
private struct WaveFormat
|
||||||
|
{
|
||||||
|
public ushort FormatTag;
|
||||||
|
public ushort Channels;
|
||||||
|
public uint SamplesPerSecond;
|
||||||
|
public uint AverageBytesPerSecond;
|
||||||
|
public ushort BlockAlign;
|
||||||
|
public ushort BitsPerSample;
|
||||||
|
public ushort ExtraSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct WaveHeader
|
||||||
|
{
|
||||||
|
public IntPtr Data;
|
||||||
|
public uint BufferLength;
|
||||||
|
public uint BytesRecorded;
|
||||||
|
public nuint User;
|
||||||
|
public uint Flags;
|
||||||
|
public uint Loops;
|
||||||
|
public IntPtr Next;
|
||||||
|
public nuint Reserved;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("winmm.dll", EntryPoint = "waveOutOpen")]
|
||||||
|
private static extern uint WaveOutOpen(
|
||||||
|
out IntPtr device,
|
||||||
|
uint deviceId,
|
||||||
|
ref WaveFormat format,
|
||||||
|
IntPtr callback,
|
||||||
|
IntPtr instance,
|
||||||
|
uint flags);
|
||||||
|
|
||||||
|
[DllImport("winmm.dll", EntryPoint = "waveOutPrepareHeader")]
|
||||||
|
private static extern uint WaveOutPrepareHeader(IntPtr device, IntPtr header, uint headerSize);
|
||||||
|
|
||||||
|
[DllImport("winmm.dll", EntryPoint = "waveOutWrite")]
|
||||||
|
private static extern uint WaveOutWrite(IntPtr device, IntPtr header, uint headerSize);
|
||||||
|
|
||||||
|
[DllImport("winmm.dll", EntryPoint = "waveOutUnprepareHeader")]
|
||||||
|
private static extern uint WaveOutUnprepareHeader(IntPtr device, IntPtr header, uint headerSize);
|
||||||
|
|
||||||
|
[DllImport("winmm.dll", EntryPoint = "waveOutReset")]
|
||||||
|
private static extern uint WaveOutReset(IntPtr device);
|
||||||
|
|
||||||
|
[DllImport("winmm.dll", EntryPoint = "waveOutClose")]
|
||||||
|
private static extern uint WaveOutClose(IntPtr device);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -306,10 +306,6 @@ public static class KernelExports
|
|||||||
{
|
{
|
||||||
var threadId = ctx[CpuRegister.Rdi];
|
var threadId = ctx[CpuRegister.Rdi];
|
||||||
var returnValueAddress = ctx[CpuRegister.Rsi];
|
var returnValueAddress = ctx[CpuRegister.Rsi];
|
||||||
if (returnValueAddress != 0 && !ctx.TryWriteUInt64(returnValueAddress, 0))
|
|
||||||
{
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ShouldTracePthread())
|
if (ShouldTracePthread())
|
||||||
{
|
{
|
||||||
@@ -317,6 +313,30 @@ public static class KernelExports
|
|||||||
$"[LOADER][TRACE] pthread_join: thread=0x{threadId:X16} retval_out=0x{returnValueAddress:X16}");
|
$"[LOADER][TRACE] pthread_join: thread=0x{threadId:X16} retval_out=0x{returnValueAddress:X16}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var returnValue = 0UL;
|
||||||
|
if (GuestThreadExecution.Scheduler is { } scheduler &&
|
||||||
|
!scheduler.TryJoinThread(ctx, threadId, out returnValue, out var error))
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][ERROR] pthread_join: thread=0x{threadId:X16}: {error}");
|
||||||
|
var result = string.Equals(
|
||||||
|
error,
|
||||||
|
"thread cannot join itself",
|
||||||
|
StringComparison.Ordinal)
|
||||||
|
? OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT
|
||||||
|
: OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||||
|
ctx[CpuRegister.Rax] = unchecked((ulong)(int)result);
|
||||||
|
return (int)result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnValueAddress != 0 &&
|
||||||
|
!ctx.TryWriteUInt64(returnValueAddress, returnValue))
|
||||||
|
{
|
||||||
|
ctx[CpuRegister.Rax] =
|
||||||
|
unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
|
}
|
||||||
|
|
||||||
ctx[CpuRegister.Rax] = 0;
|
ctx[CpuRegister.Rax] = 0;
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
@@ -345,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;
|
||||||
@@ -99,12 +99,16 @@ public static class KernelMemoryCompatExports
|
|||||||
private static readonly object _tlsGate = new();
|
private static readonly object _tlsGate = new();
|
||||||
private static readonly object _ioTraceGate = new();
|
private static readonly object _ioTraceGate = new();
|
||||||
private static readonly object _statCacheGate = new();
|
private static readonly object _statCacheGate = new();
|
||||||
|
private static readonly object _guestMountGate = new();
|
||||||
private static readonly Dictionary<ulong, DirectAllocation> _directAllocations = new();
|
private static readonly Dictionary<ulong, DirectAllocation> _directAllocations = new();
|
||||||
private static readonly Dictionary<ulong, LibcHeapAllocation> _libcAllocations = new();
|
private static readonly Dictionary<ulong, LibcHeapAllocation> _libcAllocations = new();
|
||||||
private static readonly Dictionary<ulong, MappedRegion> _mappedRegions = new();
|
private static readonly Dictionary<ulong, MappedRegion> _mappedRegions = new();
|
||||||
|
private static readonly Dictionary<ulong, 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 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;
|
||||||
@@ -153,6 +157,32 @@ public static class KernelMemoryCompatExports
|
|||||||
private readonly record struct MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, bool IsDirect, ulong DirectStart);
|
private readonly record struct MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, bool IsDirect, ulong DirectStart);
|
||||||
private readonly record struct BatchMapEntry(ulong Start, ulong Offset, ulong Length, byte Protection, byte Type, int Operation);
|
private readonly record struct BatchMapEntry(ulong Start, ulong Offset, ulong Length, byte Protection, byte Type, int Operation);
|
||||||
|
|
||||||
|
public static void RegisterGuestPathMount(string guestMountPoint, string hostRoot)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(guestMountPoint);
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(hostRoot);
|
||||||
|
|
||||||
|
var normalizedMountPoint = NormalizeGuestStatCachePath(guestMountPoint);
|
||||||
|
if (normalizedMountPoint is null || normalizedMountPoint == "/")
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Guest mount point must name a directory.", nameof(guestMountPoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedHostRoot = Path.GetFullPath(hostRoot);
|
||||||
|
Directory.CreateDirectory(normalizedHostRoot);
|
||||||
|
lock (_guestMountGate)
|
||||||
|
{
|
||||||
|
_guestMounts[normalizedMountPoint] = normalizedHostRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_statCacheGate)
|
||||||
|
{
|
||||||
|
_negativeStatCache.RemoveWhere(path =>
|
||||||
|
string.Equals(path, normalizedMountPoint, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
path.StartsWith(normalizedMountPoint + "/", StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal static bool TryAllocateHleData(
|
internal static bool TryAllocateHleData(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
ulong length,
|
ulong length,
|
||||||
@@ -203,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",
|
||||||
@@ -1294,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}");
|
||||||
@@ -1502,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;
|
||||||
@@ -2407,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",
|
||||||
@@ -2518,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];
|
||||||
Console.Error.WriteLine(
|
if (ShouldTraceDirectMemory())
|
||||||
$"[LOADER][TRACE] map_flexible: inout=0x{inOutAddressPointer:X16} len=0x{length:X16} prot=0x{protection:X8} flags=0x{flags:X16}");
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[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;
|
||||||
@@ -2549,8 +2633,11 @@ public static class KernelMemoryCompatExports
|
|||||||
: AllocateMappedGuestAddress(ctx, length, 0x1000UL);
|
: AllocateMappedGuestAddress(ctx, length, 0x1000UL);
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.Error.WriteLine(
|
if (ShouldTraceDirectMemory())
|
||||||
$"[LOADER][TRACE] map_flexible reserve: requested=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} mapped=0x{mappedAddress:X16}");
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][TRACE] map_flexible reserve: requested=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} mapped=0x{mappedAddress:X16}");
|
||||||
|
}
|
||||||
|
|
||||||
if (mappedAddress == 0)
|
if (mappedAddress == 0)
|
||||||
{
|
{
|
||||||
@@ -2586,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",
|
||||||
@@ -2639,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",
|
||||||
@@ -2696,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;
|
||||||
? "direct"
|
lock (_memoryGate)
|
||||||
: region.IsFlexible
|
{
|
||||||
? "flexible"
|
name = _mappedRegionNames.GetValueOrDefault(region.Address)
|
||||||
: string.Empty;
|
?? (region.IsDirect
|
||||||
|
? "direct"
|
||||||
|
: region.IsFlexible
|
||||||
|
? "flexible"
|
||||||
|
: string.Empty);
|
||||||
|
}
|
||||||
if (!string.IsNullOrEmpty(name))
|
if (!string.IsNullOrEmpty(name))
|
||||||
{
|
{
|
||||||
var nameBytes = Encoding.ASCII.GetBytes(name);
|
var nameBytes = Encoding.ASCII.GetBytes(name);
|
||||||
@@ -3842,118 +3982,17 @@ public static class KernelMemoryCompatExports
|
|||||||
ulong alignment,
|
ulong alignment,
|
||||||
out ulong mappedAddress)
|
out ulong mappedAddress)
|
||||||
{
|
{
|
||||||
mappedAddress = 0;
|
var executable = (protection & OrbisProtCpuExec) != 0;
|
||||||
if (length == 0)
|
return KernelVirtualRangeAllocator.TryReserve(
|
||||||
{
|
ctx,
|
||||||
return false;
|
desiredAddress,
|
||||||
}
|
length,
|
||||||
|
executable,
|
||||||
try
|
alignment,
|
||||||
{
|
allowSearch: true,
|
||||||
object memoryObject = ctx.Memory;
|
allowAllocateAtAlternative: false,
|
||||||
MethodInfo? allocateAt = null;
|
"reserve range",
|
||||||
MethodInfo? allocateAtOrAbove = null;
|
out mappedAddress);
|
||||||
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;
|
|
||||||
if (allocateAtOrAbove is not null)
|
|
||||||
{
|
|
||||||
var searchArgs = new object[] { desiredAddress, length, executable, 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 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(
|
||||||
@@ -4033,6 +4072,11 @@ public static class KernelMemoryCompatExports
|
|||||||
return guestPath;
|
return guestPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TryResolveRegisteredGuestMount(guestPath, out var mountedPath))
|
||||||
|
{
|
||||||
|
return mountedPath;
|
||||||
|
}
|
||||||
|
|
||||||
if (guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase))
|
if (guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var relative = NormalizeMountRelativePath(guestPath["/devlog/app/".Length..]);
|
var relative = NormalizeMountRelativePath(guestPath["/devlog/app/".Length..]);
|
||||||
@@ -4101,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))
|
||||||
{
|
{
|
||||||
@@ -4131,6 +4189,51 @@ public static class KernelMemoryCompatExports
|
|||||||
return guestPath;
|
return guestPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryResolveRegisteredGuestMount(string guestPath, out string hostPath)
|
||||||
|
{
|
||||||
|
hostPath = string.Empty;
|
||||||
|
var normalizedGuestPath = NormalizeGuestStatCachePath(guestPath);
|
||||||
|
if (normalizedGuestPath is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? matchedMountPoint = null;
|
||||||
|
string? matchedHostRoot = null;
|
||||||
|
lock (_guestMountGate)
|
||||||
|
{
|
||||||
|
foreach (var (mountPoint, hostRoot) in _guestMounts)
|
||||||
|
{
|
||||||
|
if ((string.Equals(normalizedGuestPath, mountPoint, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
normalizedGuestPath.StartsWith(mountPoint + "/", StringComparison.OrdinalIgnoreCase)) &&
|
||||||
|
(matchedMountPoint is null || mountPoint.Length > matchedMountPoint.Length))
|
||||||
|
{
|
||||||
|
matchedMountPoint = mountPoint;
|
||||||
|
matchedHostRoot = hostRoot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchedMountPoint is null || matchedHostRoot is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relativePath = normalizedGuestPath[matchedMountPoint.Length..].TrimStart('/');
|
||||||
|
var candidate = Path.GetFullPath(Path.Combine(
|
||||||
|
matchedHostRoot,
|
||||||
|
NormalizeMountRelativePath(relativePath)));
|
||||||
|
var rootWithSeparator = Path.TrimEndingDirectorySeparator(matchedHostRoot) + Path.DirectorySeparatorChar;
|
||||||
|
if (!string.Equals(candidate, matchedHostRoot, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!candidate.StartsWith(rootWithSeparator, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hostPath = candidate;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static string? ResolveApp0Root()
|
private static string? ResolveApp0Root()
|
||||||
{
|
{
|
||||||
var cached = Volatile.Read(ref _cachedApp0Root);
|
var cached = Volatile.Read(ref _cachedApp0Root);
|
||||||
@@ -5310,6 +5413,37 @@ public static class KernelMemoryCompatExports
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static bool TryReadTrackedLibcHeap(
|
||||||
|
ulong address,
|
||||||
|
Span<byte> destination)
|
||||||
|
{
|
||||||
|
if (destination.IsEmpty)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var length = (ulong)destination.Length;
|
||||||
|
lock (_libcAllocGate)
|
||||||
|
{
|
||||||
|
foreach (var (allocationAddress, allocation) in _libcAllocations)
|
||||||
|
{
|
||||||
|
var allocationSize = (ulong)allocation.Size;
|
||||||
|
var offset = address >= allocationAddress
|
||||||
|
? address - allocationAddress
|
||||||
|
: ulong.MaxValue;
|
||||||
|
if (offset > allocationSize ||
|
||||||
|
length > allocationSize - offset)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TryReadHostMemory(address, destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryAllocateLibcHeap(ulong requestedSize, nuint alignment, bool zeroFill, out ulong address)
|
private static bool TryAllocateLibcHeap(ulong requestedSize, nuint alignment, bool zeroFill, out ulong address)
|
||||||
{
|
{
|
||||||
address = 0;
|
address = 0;
|
||||||
@@ -5687,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
|
||||||
@@ -6001,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.Error.WriteLine(
|
if (ShouldTraceVirtualMemory())
|
||||||
$"[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}");
|
{
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
using SharpEmu.HLE;
|
using SharpEmu.HLE;
|
||||||
|
using SharpEmu.Libs.Kernel;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -10,6 +11,8 @@ namespace SharpEmu.Libs.SaveData;
|
|||||||
public static class SaveDataExports
|
public static class SaveDataExports
|
||||||
{
|
{
|
||||||
private const int OrbisSaveDataErrorParameter = unchecked((int)0x809F0000);
|
private const int OrbisSaveDataErrorParameter = unchecked((int)0x809F0000);
|
||||||
|
private const int OrbisSaveDataErrorExists = unchecked((int)0x809F0007);
|
||||||
|
private const int OrbisSaveDataErrorNotFound = unchecked((int)0x809F0008);
|
||||||
private const int OrbisSaveDataErrorInternal = unchecked((int)0x809F000B);
|
private const int OrbisSaveDataErrorInternal = unchecked((int)0x809F000B);
|
||||||
private const int SaveDataTitleIdSize = 10;
|
private const int SaveDataTitleIdSize = 10;
|
||||||
private const int SaveDataDirNameSize = 32;
|
private const int SaveDataDirNameSize = 32;
|
||||||
@@ -23,6 +26,9 @@ public static class SaveDataExports
|
|||||||
private const ulong ResultInfosOffset = 0x20;
|
private const ulong ResultInfosOffset = 0x20;
|
||||||
private const uint SortKeyFreeBlocks = 5;
|
private const uint SortKeyFreeBlocks = 5;
|
||||||
private const uint SortOrderDescent = 1;
|
private const uint SortOrderDescent = 1;
|
||||||
|
private const uint MountModeCreate = 1u << 2;
|
||||||
|
private const uint MountModeCreate2 = 1u << 5;
|
||||||
|
private const int MountResultSize = 0x40;
|
||||||
private static readonly object _stateGate = new();
|
private static readonly object _stateGate = new();
|
||||||
private static string? _titleId;
|
private static string? _titleId;
|
||||||
|
|
||||||
@@ -149,6 +155,125 @@ public static class SaveDataExports
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "ZP4e7rlzOUk",
|
||||||
|
ExportName = "sceSaveDataMount3",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceSaveData")]
|
||||||
|
public static int SaveDataMount3(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var mountAddress = ctx[CpuRegister.Rdi];
|
||||||
|
var resultAddress = ctx[CpuRegister.Rsi];
|
||||||
|
if (mountAddress == 0 || resultAddress == 0)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryReadInt32(ctx, mountAddress, out var userId) ||
|
||||||
|
!ctx.TryReadUInt64(mountAddress + 0x08, out var dirNameAddress) ||
|
||||||
|
!ctx.TryReadUInt64(mountAddress + 0x10, out var blocks) ||
|
||||||
|
!ctx.TryReadUInt64(mountAddress + 0x18, out var systemBlocks) ||
|
||||||
|
!TryReadUInt32(ctx, mountAddress + 0x20, out var mountMode) ||
|
||||||
|
!TryReadUInt32(ctx, mountAddress + 0x24, out var resource) ||
|
||||||
|
!TryReadUInt32(ctx, mountAddress + 0x28, out var mode) ||
|
||||||
|
dirNameAddress == 0 ||
|
||||||
|
!TryReadFixedAscii(ctx, dirNameAddress, SaveDataDirNameSize, out var dirName))
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId < 0 || string.IsNullOrWhiteSpace(dirName))
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var titleId = ResolveConfiguredTitleId();
|
||||||
|
var savePath = Path.Combine(
|
||||||
|
ResolveTitleSaveRoot(userId, titleId),
|
||||||
|
SanitizePathSegment(dirName));
|
||||||
|
var existed = Directory.Exists(savePath);
|
||||||
|
var create = (mountMode & MountModeCreate) != 0;
|
||||||
|
var createIfMissing = (mountMode & MountModeCreate2) != 0;
|
||||||
|
|
||||||
|
if (!existed && !create && !createIfMissing)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existed && create)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!existed)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(savePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const string mountPoint = "/savedata0";
|
||||||
|
KernelMemoryCompatExports.RegisterGuestPathMount(mountPoint, savePath);
|
||||||
|
|
||||||
|
Span<byte> result = stackalloc byte[MountResultSize];
|
||||||
|
result.Clear();
|
||||||
|
WriteAscii(result[..16], mountPoint);
|
||||||
|
BinaryPrimitives.WriteUInt32LittleEndian(result[0x1C..], createIfMissing && !existed ? 1u : 0u);
|
||||||
|
if (!ctx.Memory.TryWrite(resultAddress, result))
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceSaveData(
|
||||||
|
$"mount3 user={userId} title={titleId} dir={dirName} blocks={blocks} " +
|
||||||
|
$"system_blocks={systemBlocks} mount_mode=0x{mountMode:X} resource={resource} mode={mode} " +
|
||||||
|
$"mount_point={mountPoint} created={!existed} root='{savePath}'");
|
||||||
|
return SetReturn(ctx, 0);
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorInternal);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorInternal);
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorParameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static 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;
|
||||||
@@ -305,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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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, )",
|
||||||
|
|||||||
Reference in New Issue
Block a user