mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-25 20:28:48 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69d8490275 | |||
| 38d94d8e54 | |||
| 61a1abdb13 | |||
| 88452218cb | |||
| 427a511e48 | |||
| e103795768 | |||
| 24c6e44800 | |||
| 3a30d6419f | |||
| 2d8a795f23 | |||
| adb2acdb39 | |||
| 5e5bead02c | |||
| e98f02966a | |||
| 9ab1e2f21a | |||
| 340eb9e0ab |
+1
-1
@@ -14,7 +14,7 @@ indent_size = 4
|
||||
indent_style = space
|
||||
tab_width = 4
|
||||
|
||||
[*.{md,json,yml,props,csproj}]
|
||||
[*.{md,json,yml,xml,props,csproj}]
|
||||
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
|
||||
@@ -26,6 +26,7 @@ arm64/
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
.packages
|
||||
packages/
|
||||
*.nupkg
|
||||
.nuget/
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Copyright (C) 2026 SharpEmu Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
-->
|
||||
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
@@ -10,7 +9,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Iced" Version="1.21.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.Windowing" Version="2.23.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -3,6 +3,7 @@ version = 1
|
||||
[[annotations]]
|
||||
path = [
|
||||
"REUSE.toml",
|
||||
"nuget.config",
|
||||
"global.json",
|
||||
"**/packages.lock.json",
|
||||
"scripts/ps5_names.txt",
|
||||
|
||||
@@ -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>
|
||||
@@ -81,6 +81,7 @@
|
||||
"dependencies": {
|
||||
"SharpEmu.HLE": "[1.0.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.Windowing": "[2.23.0, )"
|
||||
}
|
||||
@@ -103,6 +104,16 @@
|
||||
"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": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
|
||||
@@ -338,10 +338,15 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
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();
|
||||
}
|
||||
else if (string.Equals(importStubEntry.Nid, "r8mvOaWdi28", StringComparison.Ordinal))
|
||||
{
|
||||
orbisGen2Result = DispatchIl2CppApiLookupSymbol();
|
||||
}
|
||||
else if (importStubEntry.Export is { } cachedExport &&
|
||||
(cachedExport.Target & cpuContext.TargetGeneration) != 0)
|
||||
{
|
||||
@@ -1180,8 +1185,11 @@ public sealed partial class DirectExecutionBackend
|
||||
cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
|
||||
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;
|
||||
return OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
@@ -1194,6 +1202,62 @@ public sealed partial class DirectExecutionBackend
|
||||
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()
|
||||
{
|
||||
var cpuContext = ActiveCpuContext;
|
||||
|
||||
@@ -543,6 +543,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
{
|
||||
Path.Combine(ebootDirectory, "sce_module"),
|
||||
Path.Combine(ebootDirectory, "sce_modules"),
|
||||
Path.Combine(ebootDirectory, "Media", "Modules"),
|
||||
}
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Where(Directory.Exists)
|
||||
@@ -554,7 +555,9 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
}
|
||||
|
||||
var allModulePaths = moduleDirectories
|
||||
.SelectMany(Directory.EnumerateFiles)
|
||||
.SelectMany(directory => Directory
|
||||
.EnumerateFiles(directory)
|
||||
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase))
|
||||
.Where(path =>
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
@@ -562,7 +565,6 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
||||
string.Equals(extension, ".sprx", StringComparison.OrdinalIgnoreCase);
|
||||
})
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
var modulePaths = allModulePaths
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"dependencies": {
|
||||
"SharpEmu.HLE": "[1.0.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.Windowing": "[2.23.0, )"
|
||||
}
|
||||
@@ -88,6 +89,16 @@
|
||||
"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": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.23.0, )",
|
||||
|
||||
@@ -1204,6 +1204,51 @@ public static class AgcExports
|
||||
return ReturnPointer(ctx, commandAddress);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "q88lQ+GP5Yk",
|
||||
ExportName = "sceAgcDcbDrawIndex",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DcbDrawIndex(CpuContext ctx)
|
||||
{
|
||||
var commandBufferAddress = ctx[CpuRegister.Rdi];
|
||||
var indexCount = (uint)ctx[CpuRegister.Rsi];
|
||||
var indexAddress = ctx[CpuRegister.Rdx];
|
||||
var modifier = (uint)ctx[CpuRegister.Rcx];
|
||||
|
||||
if (commandBufferAddress == 0 || modifier != 0x4000_0000)
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var baseCommand) ||
|
||||
!TryWriteUInt32(ctx, baseCommand, Pm4(3, ItIndexBase, 0)) ||
|
||||
!TryWriteUInt32(ctx, baseCommand + 4, (uint)indexAddress) ||
|
||||
!TryWriteUInt32(ctx, baseCommand + 8, (uint)(indexAddress >> 32)) ||
|
||||
!TryWriteUInt32(ctx, baseCommand + 12, Pm4(2, ItIndexBufferSize, 0)) ||
|
||||
!TryWriteUInt32(ctx, baseCommand + 16, indexCount))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var drawCommand) ||
|
||||
!TryWriteUInt32(ctx, drawCommand, Pm4(5, ItDrawIndex2, 0)) ||
|
||||
!TryWriteUInt32(ctx, drawCommand + 4, indexCount) ||
|
||||
!TryWriteUInt32(ctx, drawCommand + 8, 0) ||
|
||||
!TryWriteUInt32(ctx, drawCommand + 12, 0) ||
|
||||
!TryWriteUInt32(ctx, drawCommand + 16, 0))
|
||||
{
|
||||
return ReturnPointer(ctx, 0);
|
||||
}
|
||||
|
||||
TraceAgc(
|
||||
$"agc.dcb_draw_index buf=0x{commandBufferAddress:X16} " +
|
||||
$"base=0x{baseCommand:X16} draw=0x{drawCommand:X16} " +
|
||||
$"count={indexCount} index=0x{indexAddress:X16}");
|
||||
|
||||
return ReturnPointer(ctx, drawCommand);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Yw0jKSqop+E",
|
||||
ExportName = "sceAgcDcbDrawIndexAuto",
|
||||
@@ -2006,6 +2051,88 @@ public static class AgcExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "uJziRsODk1c",
|
||||
ExportName = "sceAgcDriverGetResourceRegistrationMaxNameLength",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DriverGetResourceRegistrationMaxNameLength(CpuContext ctx)
|
||||
{
|
||||
var outAddress = ctx[CpuRegister.Rdi];
|
||||
|
||||
if (outAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
if (!TryWriteUInt32(ctx, outAddress, 256))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
TraceAgc($"agc.driver_get_resource_registration_max_name_length out=0x{outAddress:X16} value=256");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
private const uint DefaultAgcOwner = 1;
|
||||
[SysAbiExport(
|
||||
Nid = "F0ZXt5q0ZTA",
|
||||
ExportName = "sceAgcDriverGetDefaultOwner",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DriverGetDefaultOwner(CpuContext ctx)
|
||||
{
|
||||
var ownerAddress = ctx[CpuRegister.Rdi];
|
||||
|
||||
if (ownerAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
if (!TryWriteUInt32(ctx, ownerAddress, DefaultAgcOwner))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
TraceAgc($"agc.driver_get_default_owner out=0x{ownerAddress:X16} owner={DefaultAgcOwner}");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "W5z4eZrjEas",
|
||||
ExportName = "sceAgcDriverRegisterResource",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DriverRegisterResource(CpuContext ctx)
|
||||
{
|
||||
var resourceAddress = ctx[CpuRegister.Rdi];
|
||||
var owner = (uint)ctx[CpuRegister.Rsi];
|
||||
var nameAddress = ctx[CpuRegister.Rdx];
|
||||
var type = (uint)ctx[CpuRegister.R8];
|
||||
var flags = (uint)ctx[CpuRegister.R9];
|
||||
|
||||
TraceAgc(
|
||||
$"agc.driver_register_resource resource=0x{resourceAddress:X16} owner={owner} " +
|
||||
$"name=0x{nameAddress:X16} type={type} flags={flags}");
|
||||
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "-KRzWekV120",
|
||||
ExportName = "sceAgcDriverUnknown_KRzWekV120",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAgc")]
|
||||
public static int DriverUnknownKRzWekV120(CpuContext ctx)
|
||||
{
|
||||
TraceAgc(
|
||||
$"agc.driver_unknown_krz rdi=0x{ctx[CpuRegister.Rdi]:X16} " +
|
||||
$"rsi=0x{ctx[CpuRegister.Rsi]:X16} rdx=0x{ctx[CpuRegister.Rdx]:X16} " +
|
||||
$"rcx=0x{ctx[CpuRegister.Rcx]:X16} r8=0x{ctx[CpuRegister.R8]:X16} r9=0x{ctx[CpuRegister.R9]:X16}");
|
||||
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "h9z6+0hEydk",
|
||||
ExportName = "sceAgcSuspendPoint",
|
||||
@@ -3482,7 +3609,7 @@ public static class AgcExports
|
||||
',',
|
||||
draw.GlobalMemoryBindings.Select((binding, index) =>
|
||||
$"{index}:0x{binding.BaseAddress:X16}:{binding.Data.Length}:" +
|
||||
Convert.ToHexString(binding.Data.AsSpan(0, Math.Min(binding.Data.Length, 32)))));
|
||||
Convert.ToHexString(binding.Data.AsSpan(0, Math.Min(binding.Data.Length, 256)))));
|
||||
var indices = draw.IndexBuffer is { } indexBuffer
|
||||
? $"{(indexBuffer.Is32Bit ? 32 : 16)}:" +
|
||||
Convert.ToHexString(indexBuffer.Data.AsSpan(0, Math.Min(indexBuffer.Data.Length, 32)))
|
||||
@@ -3617,7 +3744,7 @@ public static class AgcExports
|
||||
descriptor.Width > 8192 ||
|
||||
descriptor.Height > 8192)
|
||||
{
|
||||
texture = CreateFallbackGuestDrawTexture(isStorage);
|
||||
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3635,7 +3762,7 @@ public static class AgcExports
|
||||
sourceByteCount > MaxPresentedTextureBytes ||
|
||||
sourceByteCount > int.MaxValue)
|
||||
{
|
||||
texture = CreateFallbackGuestDrawTexture(isStorage);
|
||||
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3698,7 +3825,7 @@ public static class AgcExports
|
||||
var source = new byte[(int)sourceByteCount];
|
||||
if (!ctx.Memory.TryRead(descriptor.Address, source))
|
||||
{
|
||||
texture = CreateFallbackGuestDrawTexture(isStorage);
|
||||
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3741,18 +3868,25 @@ public static class AgcExports
|
||||
return true;
|
||||
}
|
||||
|
||||
private static VulkanGuestDrawTexture CreateFallbackGuestDrawTexture(bool isStorage = false) =>
|
||||
new(
|
||||
private static VulkanGuestDrawTexture CreateFallbackGuestDrawTexture(
|
||||
bool isStorage,
|
||||
uint format,
|
||||
uint numberType)
|
||||
{
|
||||
var fallbackFormat = format == 0 ? 10u : format;
|
||||
var fallbackNumberType = numberType;
|
||||
return new(
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
56,
|
||||
NumberType: 0,
|
||||
fallbackFormat,
|
||||
fallbackNumberType,
|
||||
[0, 0, 0, 255],
|
||||
IsFallback: true,
|
||||
IsStorage: isStorage,
|
||||
MipLevels: 1,
|
||||
MipLevel: 0);
|
||||
}
|
||||
|
||||
private static VulkanGuestSampler ToVulkanSampler(IReadOnlyList<uint> descriptor) =>
|
||||
descriptor.Count >= 4
|
||||
|
||||
@@ -467,9 +467,6 @@ internal static partial class Gen5SpirvTranslator
|
||||
_imageBindingByPc.TryAdd(binding.Pc, index);
|
||||
var isStorage =
|
||||
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode);
|
||||
var isDepth =
|
||||
binding.Opcode.Contains("SampleC", StringComparison.Ordinal) ||
|
||||
binding.Opcode.Contains("Gather4C", StringComparison.Ordinal);
|
||||
var (format, componentKind) =
|
||||
DecodeImageFormat(binding.ResourceDescriptor);
|
||||
var componentType = componentKind switch
|
||||
@@ -494,7 +491,7 @@ internal static partial class Gen5SpirvTranslator
|
||||
var imageType = _module.TypeImage(
|
||||
componentType,
|
||||
SpirvImageDim.Dim2D,
|
||||
depth: isDepth,
|
||||
depth: false,
|
||||
arrayed: false,
|
||||
multisampled: false,
|
||||
sampled: isStorage ? 2u : 1u,
|
||||
@@ -1661,18 +1658,14 @@ internal static partial class Gen5SpirvTranslator
|
||||
var offset = hasOffset ? BuildImageOffset(image, 0) : 0u;
|
||||
var imageOperands =
|
||||
(explicitLod ? 2u : 0u) | (hasOffset ? 0x10u : 0u);
|
||||
var reference = hasCompare
|
||||
? Bitcast(_floatType, LoadV(image.GetAddressRegister(hasOffset ? 1 : 0)))
|
||||
: 0u;
|
||||
var operands = new List<uint>
|
||||
{
|
||||
imageObject,
|
||||
coordinates,
|
||||
};
|
||||
if (hasCompare)
|
||||
{
|
||||
operands.Add(
|
||||
Bitcast(
|
||||
_floatType,
|
||||
LoadV(image.GetAddressRegister(hasOffset ? 1 : 0))));
|
||||
}
|
||||
|
||||
if (imageOperands != 0)
|
||||
{
|
||||
@@ -1689,29 +1682,14 @@ internal static partial class Gen5SpirvTranslator
|
||||
}
|
||||
|
||||
sampled = _module.AddInstruction(
|
||||
hasCompare
|
||||
? explicitLod
|
||||
? SpirvOp.ImageSampleDrefExplicitLod
|
||||
: SpirvOp.ImageSampleDrefImplicitLod
|
||||
: explicitLod
|
||||
? SpirvOp.ImageSampleExplicitLod
|
||||
: SpirvOp.ImageSampleImplicitLod,
|
||||
hasCompare ? resource.ComponentType : resource.VectorType,
|
||||
explicitLod
|
||||
? SpirvOp.ImageSampleExplicitLod
|
||||
: SpirvOp.ImageSampleImplicitLod,
|
||||
resource.VectorType,
|
||||
[.. operands]);
|
||||
if (hasCompare)
|
||||
{
|
||||
var scalar = sampled;
|
||||
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));
|
||||
sampled = EmitManualDepthCompare(resource, sampled, reference);
|
||||
}
|
||||
}
|
||||
else if (instruction.Opcode.StartsWith(
|
||||
@@ -1725,6 +1703,9 @@ internal static partial class Gen5SpirvTranslator
|
||||
var start = (hasOffset ? 1 : 0) + (hasCompare ? 1 : 0);
|
||||
var coordinates = BuildFloatCoordinates(image, start);
|
||||
var offset = hasOffset ? BuildImageOffset(image, 0) : 0u;
|
||||
var reference = hasCompare
|
||||
? Bitcast(_floatType, LoadV(image.GetAddressRegister(hasOffset ? 1 : 0)))
|
||||
: 0u;
|
||||
var operands = new List<uint>
|
||||
{
|
||||
imageObject,
|
||||
@@ -1732,10 +1713,7 @@ internal static partial class Gen5SpirvTranslator
|
||||
};
|
||||
if (hasCompare)
|
||||
{
|
||||
operands.Add(
|
||||
Bitcast(
|
||||
_floatType,
|
||||
LoadV(image.GetAddressRegister(hasOffset ? 1 : 0))));
|
||||
operands.Add(UInt(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1756,11 +1734,28 @@ internal static partial class Gen5SpirvTranslator
|
||||
}
|
||||
|
||||
sampled = _module.AddInstruction(
|
||||
hasCompare
|
||||
? SpirvOp.ImageDrefGather
|
||||
: SpirvOp.ImageGather,
|
||||
SpirvOp.ImageGather,
|
||||
resource.VectorType,
|
||||
[.. 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;
|
||||
}
|
||||
else
|
||||
@@ -1794,6 +1789,67 @@ internal static partial class Gen5SpirvTranslator
|
||||
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)
|
||||
{
|
||||
var x = Bitcast(
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace SharpEmu.Libs.Audio;
|
||||
|
||||
public static class AudioOutExports
|
||||
{
|
||||
private static readonly ConcurrentDictionary<int, PortState> Ports = new();
|
||||
private static int _nextPortHandle;
|
||||
|
||||
private sealed record PortState(int UserId, int Type, uint BufferLength, uint Frequency, int Format);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "JfEPXVxhFqA",
|
||||
ExportName = "sceAudioOutInit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceAudioOut")]
|
||||
public static int AudioOutInit(CpuContext ctx) => SetReturn(ctx, 0);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "ekNvsT22rsY",
|
||||
ExportName = "sceAudioOutOpen",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceAudioOut")]
|
||||
public static int AudioOutOpen(CpuContext ctx)
|
||||
{
|
||||
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var type = unchecked((int)ctx[CpuRegister.Rsi]);
|
||||
var bufferLength = unchecked((uint)ctx[CpuRegister.Rcx]);
|
||||
var frequency = unchecked((uint)ctx[CpuRegister.R8]);
|
||||
var format = unchecked((int)ctx[CpuRegister.R9]);
|
||||
if (bufferLength == 0 || frequency == 0)
|
||||
{
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
var handle = Interlocked.Increment(ref _nextPortHandle);
|
||||
Ports[handle] = new PortState(userId, type, bufferLength, frequency, format);
|
||||
return SetReturn(ctx, handle);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "s1--uE9mBFw",
|
||||
ExportName = "sceAudioOutClose",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceAudioOut")]
|
||||
public static int AudioOutClose(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
return SetReturn(
|
||||
ctx,
|
||||
Ports.TryRemove(handle, out _)
|
||||
? 0
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "QOQtbeDqsT4",
|
||||
ExportName = "sceAudioOutOutput",
|
||||
Target = Generation.Gen5,
|
||||
LibraryName = "libSceAudioOut")]
|
||||
public static int AudioOutOutput(CpuContext ctx)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "b+uAV89IlxE",
|
||||
ExportName = "sceAudioOutSetVolume",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceAudioOut")]
|
||||
public static int AudioOutSetVolume(CpuContext ctx)
|
||||
{
|
||||
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
return SetReturn(
|
||||
ctx,
|
||||
Ports.ContainsKey(handle)
|
||||
? 0
|
||||
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.Kernel;
|
||||
|
||||
namespace SharpEmu.Libs.AvPlayer;
|
||||
|
||||
public static class AvPlayerExports
|
||||
{
|
||||
private const int InvalidParameters = unchecked((int)0x806A0001);
|
||||
private static readonly object StateGate = new();
|
||||
private static readonly HashSet<ulong> Players = new();
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "aS66RI0gGgo",
|
||||
ExportName = "sceAvPlayerInit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceAvPlayer")]
|
||||
public static int AvPlayerInit(CpuContext ctx)
|
||||
{
|
||||
if (ctx[CpuRegister.Rdi] == 0 ||
|
||||
!KernelMemoryCompatExports.TryAllocateHleData(ctx, 0x40, 16, out var handle))
|
||||
{
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
lock (StateGate)
|
||||
{
|
||||
Players.Add(handle);
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = handle;
|
||||
return unchecked((int)handle);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "HD1YKVU26-M",
|
||||
ExportName = "sceAvPlayerPostInit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceAvPlayer")]
|
||||
public static int AvPlayerPostInit(CpuContext ctx)
|
||||
{
|
||||
var handle = ctx[CpuRegister.Rdi];
|
||||
var dataAddress = ctx[CpuRegister.Rsi];
|
||||
lock (StateGate)
|
||||
{
|
||||
return SetReturn(
|
||||
ctx,
|
||||
handle != 0 && dataAddress != 0 && Players.Contains(handle)
|
||||
? 0
|
||||
: InvalidParameters);
|
||||
}
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "NkJwDzKmIlw",
|
||||
ExportName = "sceAvPlayerClose",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceAvPlayer")]
|
||||
public static int AvPlayerClose(CpuContext ctx)
|
||||
{
|
||||
lock (StateGate)
|
||||
{
|
||||
return SetReturn(
|
||||
ctx,
|
||||
Players.Remove(ctx[CpuRegister.Rdi]) ? 0 : InvalidParameters);
|
||||
}
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Threading;
|
||||
|
||||
namespace SharpEmu.Libs.CommonDialog;
|
||||
|
||||
public static class MsgDialogExports
|
||||
{
|
||||
private const int AlreadyInitialized = unchecked((int)0x80B80002);
|
||||
private static int _initialized;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "lDqxaY1UbEo",
|
||||
ExportName = "sceMsgDialogInitialize",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceMsgDialog")]
|
||||
public static int MsgDialogInitialize(CpuContext ctx)
|
||||
{
|
||||
var result = Interlocked.Exchange(ref _initialized, 1) == 0
|
||||
? 0
|
||||
: AlreadyInitialized;
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,7 @@ public static class KernelMemoryCompatExports
|
||||
private static readonly Dictionary<ulong, DirectAllocation> _directAllocations = new();
|
||||
private static readonly Dictionary<ulong, LibcHeapAllocation> _libcAllocations = 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<string, string> _guestMounts = new(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly HashSet<string> _tracedStatResults = new(StringComparer.Ordinal);
|
||||
@@ -231,6 +232,25 @@ public static class KernelMemoryCompatExports
|
||||
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(
|
||||
Nid = "8zTFvBIAIN8",
|
||||
ExportName = "memset",
|
||||
@@ -2435,6 +2455,36 @@ public static class KernelMemoryCompatExports
|
||||
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(
|
||||
Nid = "L-Q3LEjIbgA",
|
||||
ExportName = "sceKernelMapDirectMemory",
|
||||
@@ -2614,6 +2664,16 @@ public static class KernelMemoryCompatExports
|
||||
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(
|
||||
Nid = "2SKEx6bSq-4",
|
||||
ExportName = "sceKernelBatchMap",
|
||||
@@ -2667,6 +2727,44 @@ public static class KernelMemoryCompatExports
|
||||
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(
|
||||
Nid = "rVjRvHJ0X6c",
|
||||
ExportName = "sceKernelVirtualQuery",
|
||||
@@ -2724,11 +2822,16 @@ public static class KernelMemoryCompatExports
|
||||
BinaryPrimitives.WriteInt32LittleEndian(payload[28..32], memoryType);
|
||||
payload[32] = unchecked((byte)stateFlags);
|
||||
|
||||
var name = region.IsDirect
|
||||
? "direct"
|
||||
: region.IsFlexible
|
||||
? "flexible"
|
||||
: string.Empty;
|
||||
string name;
|
||||
lock (_memoryGate)
|
||||
{
|
||||
name = _mappedRegionNames.GetValueOrDefault(region.Address)
|
||||
?? (region.IsDirect
|
||||
? "direct"
|
||||
: region.IsFlexible
|
||||
? "flexible"
|
||||
: string.Empty);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
var nameBytes = Encoding.ASCII.GetBytes(name);
|
||||
|
||||
@@ -717,6 +717,35 @@ public static class KernelPthreadExtendedCompatExports
|
||||
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(
|
||||
Nid = "DzES9hQF4f4",
|
||||
ExportName = "scePthreadAttrSetschedparam",
|
||||
|
||||
@@ -752,6 +752,7 @@ public static class KernelRuntimeCompatExports
|
||||
_nextReservedVirtualBase = Math.Max(_nextReservedVirtualBase, mappedAddress + length);
|
||||
}
|
||||
|
||||
KernelMemoryCompatExports.RegisterReservedVirtualRange(mappedAddress, length);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -160,6 +160,16 @@ public static class PadExports
|
||||
: 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)
|
||||
{
|
||||
Span<byte> data = stackalloc byte[PadDataSize];
|
||||
|
||||
@@ -244,6 +244,36 @@ public static class SaveDataExports
|
||||
}
|
||||
}
|
||||
|
||||
private static int _nextTransactionResource;
|
||||
[SysAbiExport(
|
||||
Nid = "gjRZNnw0JPE",
|
||||
ExportName = "sceSaveDataCreateTransactionResource",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveData")]
|
||||
public static int SaveDataCreateTransactionResource(CpuContext ctx)
|
||||
{
|
||||
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
|
||||
var reserved = ctx[CpuRegister.Rsi];
|
||||
var resourceAddress = ctx[CpuRegister.Rdx];
|
||||
|
||||
if (resourceAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, OrbisSaveDataErrorParameter);
|
||||
}
|
||||
|
||||
var id = (uint)Interlocked.Increment(ref _nextTransactionResource);
|
||||
|
||||
if (!TryWriteUInt32(ctx, resourceAddress, id))
|
||||
{
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
TraceSaveData(
|
||||
$"create_transaction_resource user={userId} reserved=0x{reserved:X} resource_addr=0x{resourceAddress:X} id={id}");
|
||||
|
||||
return SetReturn(ctx, 0);
|
||||
}
|
||||
|
||||
private static bool TryReadSearchCond(CpuContext ctx, ulong address, out SearchCond cond)
|
||||
{
|
||||
cond = default;
|
||||
|
||||
@@ -10,6 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Silk.NET.Vulkan" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||
<PackageReference Include="Silk.NET.Windowing" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -7,6 +7,7 @@ using Silk.NET.Maths;
|
||||
using SharpEmu.Libs.Agc;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.KHR;
|
||||
using Silk.NET.Vulkan.Extensions.EXT;
|
||||
using Silk.NET.Windowing;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -846,6 +847,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
private delegate* unmanaged<CommandBuffer, void> _cmdEndDebugUtilsLabel;
|
||||
private Instance _instance;
|
||||
private SurfaceKHR _surface;
|
||||
private DebugUtilsMessengerEXT _debugMessenger;
|
||||
private ExtDebugUtils? _debugUtils;
|
||||
private PhysicalDevice _physicalDevice;
|
||||
private Device _device;
|
||||
private Queue _queue;
|
||||
@@ -1241,6 +1244,9 @@ internal static unsafe class VulkanVideoPresenter
|
||||
private void CreateInstance()
|
||||
{
|
||||
var applicationName = (byte*)SilkMarshal.StringToPtr("SharpEmu");
|
||||
var enableValidation = Environment.GetEnvironmentVariable("SHARPEMU_VK_VALIDATION") == "1";
|
||||
byte* validationLayerName = null;
|
||||
|
||||
try
|
||||
{
|
||||
var applicationInfo = new ApplicationInfo
|
||||
@@ -1268,12 +1274,29 @@ internal static unsafe class VulkanVideoPresenter
|
||||
enabledExtensions[enabledExtensionCount++] = debugUtilsExtension;
|
||||
}
|
||||
|
||||
if (enableValidation && IsInstanceLayerAvailable("VK_LAYER_KHRONOS_validation"))
|
||||
{
|
||||
validationLayerName = (byte*)SilkMarshal.StringToPtr("VK_LAYER_KHRONOS_validation");
|
||||
}
|
||||
else if (enableValidation)
|
||||
{
|
||||
Console.Error.WriteLine("[LOADER][WARN] SHARPEMU_VK_VALIDATION=1 but VK_LAYER_KHRONOS_validation not found (Vulkan SDK installed?).");
|
||||
}
|
||||
|
||||
var layers = stackalloc byte*[1];
|
||||
if (validationLayerName is not null)
|
||||
{
|
||||
layers[0] = validationLayerName;
|
||||
}
|
||||
|
||||
var createInfo = new InstanceCreateInfo
|
||||
{
|
||||
SType = StructureType.InstanceCreateInfo,
|
||||
PApplicationInfo = &applicationInfo,
|
||||
EnabledExtensionCount = (uint)enabledExtensionCount,
|
||||
PpEnabledExtensionNames = enabledExtensions,
|
||||
EnabledLayerCount = validationLayerName is not null ? 1u : 0u,
|
||||
PpEnabledLayerNames = validationLayerName is not null ? layers : null,
|
||||
};
|
||||
|
||||
try
|
||||
@@ -1283,6 +1306,13 @@ internal static unsafe class VulkanVideoPresenter
|
||||
{
|
||||
throw new InvalidOperationException("VK_KHR_surface is unavailable.");
|
||||
}
|
||||
|
||||
if (validationLayerName is not null && _vk.TryGetInstanceExtension(_instance, out ExtDebugUtils debugUtils))
|
||||
{
|
||||
_debugUtils = debugUtils;
|
||||
RegisterDebugMessenger(debugUtils);
|
||||
Console.Error.WriteLine("[LOADER][INFO] Vulkan Validation Layers active (SHARPEMU_VK_VALIDATION=1).");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -1295,9 +1325,74 @@ internal static unsafe class VulkanVideoPresenter
|
||||
finally
|
||||
{
|
||||
SilkMarshal.Free((nint)applicationName);
|
||||
if (validationLayerName is not null)
|
||||
{
|
||||
SilkMarshal.Free((nint)validationLayerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsInstanceLayerAvailable(string layerName)
|
||||
{
|
||||
uint layerCount = 0;
|
||||
if (_vk.EnumerateInstanceLayerProperties(&layerCount, null) != Result.Success || layerCount == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var properties = new LayerProperties[layerCount];
|
||||
fixed (LayerProperties* propertyPointer = properties)
|
||||
{
|
||||
if (_vk.EnumerateInstanceLayerProperties(&layerCount, propertyPointer) != Result.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var expected = Encoding.UTF8.GetBytes(layerName);
|
||||
for (var index = 0; index < layerCount; index++)
|
||||
{
|
||||
if (Utf8NullTerminatedEquals(propertyPointer[index].LayerName, expected))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
private void RegisterDebugMessenger(ExtDebugUtils debugUtils)
|
||||
{
|
||||
var messengerInfo = new DebugUtilsMessengerCreateInfoEXT
|
||||
{
|
||||
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
|
||||
MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt
|
||||
| DebugUtilsMessageSeverityFlagsEXT.WarningBitExt,
|
||||
MessageType = DebugUtilsMessageTypeFlagsEXT.ValidationBitExt
|
||||
| DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt
|
||||
| DebugUtilsMessageTypeFlagsEXT.GeneralBitExt,
|
||||
PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(DebugCallback),
|
||||
};
|
||||
|
||||
Check(debugUtils.CreateDebugUtilsMessenger(_instance, &messengerInfo, null, out _debugMessenger),
|
||||
"vkCreateDebugUtilsMessengerEXT");
|
||||
}
|
||||
|
||||
private static unsafe uint DebugCallback(
|
||||
DebugUtilsMessageSeverityFlagsEXT severity,
|
||||
DebugUtilsMessageTypeFlagsEXT type,
|
||||
DebugUtilsMessengerCallbackDataEXT* callbackData,
|
||||
void* userData)
|
||||
{
|
||||
var message = SilkMarshal.PtrToString((nint)callbackData->PMessage);
|
||||
var prefix = severity switch
|
||||
{
|
||||
DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt => "[VULKAN][ERROR]",
|
||||
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt => "[VULKAN][WARN]",
|
||||
_ => "[VULKAN][INFO]",
|
||||
};
|
||||
Console.Error.WriteLine($"{prefix} {message}");
|
||||
return Vk.False;
|
||||
}
|
||||
private void CreateSurface()
|
||||
{
|
||||
var instanceHandle = new VkHandle(_instance.Handle);
|
||||
@@ -1358,17 +1453,129 @@ internal static unsafe class VulkanVideoPresenter
|
||||
QueueCount = 1,
|
||||
PQueuePriorities = &priority,
|
||||
};
|
||||
_vk.GetPhysicalDeviceFeatures(_physicalDevice, out var supportedFeatures);
|
||||
var enabledFeatures = new PhysicalDeviceFeatures
|
||||
{
|
||||
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
|
||||
FragmentStoresAndAtomics = supportedFeatures.FragmentStoresAndAtomics,
|
||||
ShaderInt64 = supportedFeatures.ShaderInt64,
|
||||
ShaderImageGatherExtended = supportedFeatures.ShaderImageGatherExtended,
|
||||
ShaderStorageImageExtendedFormats = supportedFeatures.ShaderStorageImageExtendedFormats,
|
||||
ShaderStorageImageReadWithoutFormat = supportedFeatures.ShaderStorageImageReadWithoutFormat,
|
||||
ShaderStorageImageWriteWithoutFormat = supportedFeatures.ShaderStorageImageWriteWithoutFormat,
|
||||
RobustBufferAccess = supportedFeatures.RobustBufferAccess,
|
||||
};
|
||||
|
||||
if (!supportedFeatures.RobustBufferAccess)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] GPU does not support robustBufferAccess " +
|
||||
"translated shaders performing out-of-bounds buffer access may cause device loss.");
|
||||
}
|
||||
|
||||
if (!supportedFeatures.ShaderInt64)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] GPU does not support shaderInt64 " +
|
||||
"translated shaders using 64-bit integers will fail.");
|
||||
}
|
||||
|
||||
if (!supportedFeatures.VertexPipelineStoresAndAtomics || !supportedFeatures.FragmentStoresAndAtomics)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] GPU does not support vertexPipelineStoresAndAtomics/fragmentStoresAndAtomics " +
|
||||
"translated shaders using storage buffers in vertex/fragment stages may fail.");
|
||||
}
|
||||
|
||||
if (!supportedFeatures.ShaderImageGatherExtended)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] GPU does not support shaderImageGatherExtended " +
|
||||
"translated shaders using image gather with offsets/LOD/bias will fail.");
|
||||
}
|
||||
|
||||
if (!supportedFeatures.ShaderStorageImageReadWithoutFormat ||
|
||||
!supportedFeatures.ShaderStorageImageWriteWithoutFormat)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] GPU does not support shaderStorageImage(Read|Write)WithoutFormat " +
|
||||
"translated shaders using unformatted storage image load/store will fail.");
|
||||
}
|
||||
|
||||
var maintenance8Features = new PhysicalDeviceMaintenance8FeaturesKHR
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceMaintenance8FeaturesKhr,
|
||||
};
|
||||
var robustness2Features = new PhysicalDeviceRobustness2FeaturesEXT
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
|
||||
PNext = &maintenance8Features,
|
||||
};
|
||||
var featuresQuery = new PhysicalDeviceFeatures2
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceFeatures2,
|
||||
PNext = &robustness2Features,
|
||||
};
|
||||
_vk.GetPhysicalDeviceFeatures2(_physicalDevice, &featuresQuery);
|
||||
var supportsMaintenance8 = maintenance8Features.Maintenance8;
|
||||
var supportsRobustImageAccess2 = robustness2Features.RobustImageAccess2;
|
||||
var supportsNullDescriptor = robustness2Features.NullDescriptor;
|
||||
var supportsRobustness2 = supportsRobustImageAccess2 || supportsNullDescriptor;
|
||||
if (!supportsMaintenance8)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] GPU does not support VK_KHR_maintenance8 " +
|
||||
"translated shaders using a dynamic texel offset on non-gather image samples will fail.");
|
||||
}
|
||||
|
||||
if (!supportsRobustImageAccess2)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] GPU does not support VK_EXT_robustness2 robustImageAccess2 " +
|
||||
"translated shaders performing out-of-bounds image access may cause device loss.");
|
||||
}
|
||||
|
||||
var swapchainExtension = (byte*)SilkMarshal.StringToPtr("VK_KHR_swapchain");
|
||||
var maintenance8Extension = (byte*)SilkMarshal.StringToPtr("VK_KHR_maintenance8");
|
||||
var robustness2Extension = (byte*)SilkMarshal.StringToPtr("VK_EXT_robustness2");
|
||||
try
|
||||
{
|
||||
var extensions = stackalloc byte*[3];
|
||||
var extensionCount = 0u;
|
||||
extensions[extensionCount++] = swapchainExtension;
|
||||
if (supportsMaintenance8)
|
||||
{
|
||||
extensions[extensionCount++] = maintenance8Extension;
|
||||
}
|
||||
|
||||
if (supportsRobustness2)
|
||||
{
|
||||
extensions[extensionCount++] = robustness2Extension;
|
||||
}
|
||||
|
||||
maintenance8Features.Maintenance8 = supportsMaintenance8;
|
||||
maintenance8Features.PNext = null;
|
||||
robustness2Features.RobustBufferAccess2 =
|
||||
supportsRobustImageAccess2 && supportedFeatures.RobustBufferAccess;
|
||||
robustness2Features.RobustImageAccess2 = supportsRobustImageAccess2;
|
||||
robustness2Features.NullDescriptor = supportsNullDescriptor;
|
||||
robustness2Features.PNext = supportsMaintenance8 ? &maintenance8Features : null;
|
||||
var features2 = new PhysicalDeviceFeatures2
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceFeatures2,
|
||||
PNext = supportsRobustness2
|
||||
? &robustness2Features
|
||||
: (supportsMaintenance8 ? &maintenance8Features : null),
|
||||
Features = enabledFeatures,
|
||||
};
|
||||
var createInfo = new DeviceCreateInfo
|
||||
{
|
||||
SType = StructureType.DeviceCreateInfo,
|
||||
PNext = &features2,
|
||||
QueueCreateInfoCount = 1,
|
||||
PQueueCreateInfos = &queueInfo,
|
||||
EnabledExtensionCount = 1,
|
||||
PpEnabledExtensionNames = &swapchainExtension,
|
||||
EnabledExtensionCount = extensionCount,
|
||||
PpEnabledExtensionNames = extensions,
|
||||
};
|
||||
|
||||
Check(_vk.CreateDevice(_physicalDevice, &createInfo, null, out _device), "vkCreateDevice");
|
||||
@@ -1376,6 +1583,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
finally
|
||||
{
|
||||
SilkMarshal.Free((nint)swapchainExtension);
|
||||
SilkMarshal.Free((nint)maintenance8Extension);
|
||||
SilkMarshal.Free((nint)robustness2Extension);
|
||||
}
|
||||
|
||||
_vk.GetDeviceQueue(_device, _queueFamilyIndex, 0, out _queue);
|
||||
@@ -1540,6 +1749,39 @@ internal static unsafe class VulkanVideoPresenter
|
||||
resources.DebugName));
|
||||
}
|
||||
|
||||
private void SubmitGuestCommandBufferAndWait(CommandBuffer commandBuffer)
|
||||
{
|
||||
var fenceInfo = new FenceCreateInfo
|
||||
{
|
||||
SType = StructureType.FenceCreateInfo,
|
||||
};
|
||||
Fence fence;
|
||||
Check(
|
||||
_vk.CreateFence(_device, &fenceInfo, null, out fence),
|
||||
"vkCreateFence(guest chunk)");
|
||||
try
|
||||
{
|
||||
var submitInfo = new SubmitInfo
|
||||
{
|
||||
SType = StructureType.SubmitInfo,
|
||||
CommandBufferCount = 1,
|
||||
PCommandBuffers = &commandBuffer,
|
||||
};
|
||||
Check(
|
||||
_vk.QueueSubmit(_queue, 1, &submitInfo, fence),
|
||||
"vkQueueSubmit(guest chunk)");
|
||||
Check(
|
||||
_vk.WaitForFences(_device, 1, &fence, true, ulong.MaxValue),
|
||||
"vkWaitForFences(guest chunk)");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_vk.DestroyFence(_device, fence, null);
|
||||
}
|
||||
|
||||
_vk.FreeCommandBuffers(_device, _commandPool, 1, &commandBuffer);
|
||||
}
|
||||
|
||||
private void EnsureGuestSubmissionCapacity()
|
||||
{
|
||||
CollectCompletedGuestSubmissions(waitForOldest: false);
|
||||
@@ -2524,6 +2766,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
var pipelineInfo = new ComputePipelineCreateInfo
|
||||
{
|
||||
SType = StructureType.ComputePipelineCreateInfo,
|
||||
Flags = PipelineCreateFlags.CreateDispatchBaseBit,
|
||||
Stage = stage,
|
||||
Layout = resources.PipelineLayout,
|
||||
};
|
||||
@@ -2869,9 +3112,13 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
_vk.UnmapMemory(_device, stagingMemory);
|
||||
|
||||
var supportsAttachmentUsage = !IsBlockCompressedFormat(vkFormat);
|
||||
var imageInfo = new ImageCreateInfo
|
||||
{
|
||||
SType = StructureType.ImageCreateInfo,
|
||||
Flags = supportsAttachmentUsage
|
||||
? ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit
|
||||
: 0,
|
||||
ImageType = ImageType.Type2D,
|
||||
Format = vkFormat,
|
||||
Extent = new Extent3D(width, height, 1),
|
||||
@@ -2879,7 +3126,13 @@ internal static unsafe class VulkanVideoPresenter
|
||||
ArrayLayers = 1,
|
||||
Samples = SampleCountFlags.Count1Bit,
|
||||
Tiling = ImageTiling.Optimal,
|
||||
Usage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
|
||||
Usage = supportsAttachmentUsage
|
||||
? ImageUsageFlags.TransferDstBit |
|
||||
ImageUsageFlags.SampledBit |
|
||||
ImageUsageFlags.ColorAttachmentBit |
|
||||
ImageUsageFlags.StorageBit |
|
||||
ImageUsageFlags.TransferSrcBit
|
||||
: ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
|
||||
SharingMode = SharingMode.Exclusive,
|
||||
InitialLayout = ImageLayout.Undefined,
|
||||
};
|
||||
@@ -3714,6 +3967,12 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_ => Format.Undefined,
|
||||
};
|
||||
|
||||
private static bool IsBlockCompressedFormat(Format format) =>
|
||||
format is Format.BC1RgbaUnormBlock or
|
||||
Format.BC1RgbaSrgbBlock or
|
||||
Format.BC7UnormBlock or
|
||||
Format.BC7SrgbBlock;
|
||||
|
||||
private VkBuffer CreateBuffer(
|
||||
ulong size,
|
||||
BufferUsageFlags usage,
|
||||
@@ -3767,8 +4026,19 @@ internal static unsafe class VulkanVideoPresenter
|
||||
throw new InvalidOperationException("No compatible Vulkan host-visible memory type was found.");
|
||||
}
|
||||
|
||||
private const uint MaxComputeZSlicesPerSubmission = 8;
|
||||
|
||||
private void ExecuteComputeDispatch(VulkanComputeGuestDispatch work)
|
||||
{
|
||||
if (AddressListContains("SHARPEMU_SKIP_COMPUTE_CS", work.ShaderAddress))
|
||||
{
|
||||
TraceVulkanShader(
|
||||
$"vk.compute_skip cs=0x{work.ShaderAddress:X16} " +
|
||||
$"groups={work.GroupCountX}x{work.GroupCountY}x{work.GroupCountZ} " +
|
||||
$"textures={work.Textures.Count}");
|
||||
return;
|
||||
}
|
||||
|
||||
TranslatedDrawResources? resources = null;
|
||||
CommandBuffer commandBuffer = default;
|
||||
var submitted = false;
|
||||
@@ -3776,59 +4046,95 @@ internal static unsafe class VulkanVideoPresenter
|
||||
{
|
||||
EnsureGuestSubmissionCapacity();
|
||||
resources = CreateComputeDispatchResources(work);
|
||||
commandBuffer = AllocateGuestCommandBuffer();
|
||||
_commandBuffer = commandBuffer;
|
||||
var beginInfo = new CommandBufferBeginInfo
|
||||
{
|
||||
SType = StructureType.CommandBufferBeginInfo,
|
||||
Flags = CommandBufferUsageFlags.OneTimeSubmitBit,
|
||||
};
|
||||
Check(
|
||||
_vk.BeginCommandBuffer(_commandBuffer, &beginInfo),
|
||||
"vkBeginCommandBuffer(compute)");
|
||||
|
||||
BeginDebugLabel(_commandBuffer, resources.DebugName);
|
||||
RecordTextureUploads(resources, PipelineStageFlags.ComputeShaderBit);
|
||||
RecordStorageImagesForWrite(resources, PipelineStageFlags.ComputeShaderBit);
|
||||
_vk.CmdBindPipeline(
|
||||
_commandBuffer,
|
||||
PipelineBindPoint.Compute,
|
||||
resources.Pipeline);
|
||||
if (resources.DescriptorSet.Handle != 0)
|
||||
var batchCount = Math.Max(
|
||||
1u,
|
||||
(uint)Math.Ceiling(work.GroupCountZ / (double)MaxComputeZSlicesPerSubmission));
|
||||
|
||||
for (var batchIndex = 0u; batchIndex < batchCount; batchIndex++)
|
||||
{
|
||||
var descriptorSet = resources.DescriptorSet;
|
||||
_vk.CmdBindDescriptorSets(
|
||||
var zStart = batchIndex * MaxComputeZSlicesPerSubmission;
|
||||
var zCount = Math.Min(MaxComputeZSlicesPerSubmission, work.GroupCountZ - zStart);
|
||||
var isFirstBatch = batchIndex == 0;
|
||||
var isLastBatch = batchIndex == batchCount - 1;
|
||||
|
||||
commandBuffer = AllocateGuestCommandBuffer();
|
||||
_commandBuffer = commandBuffer;
|
||||
var beginInfo = new CommandBufferBeginInfo
|
||||
{
|
||||
SType = StructureType.CommandBufferBeginInfo,
|
||||
Flags = CommandBufferUsageFlags.OneTimeSubmitBit,
|
||||
};
|
||||
Check(
|
||||
_vk.BeginCommandBuffer(_commandBuffer, &beginInfo),
|
||||
"vkBeginCommandBuffer(compute)");
|
||||
|
||||
BeginDebugLabel(_commandBuffer, resources.DebugName);
|
||||
if (isFirstBatch)
|
||||
{
|
||||
RecordTextureUploads(resources, PipelineStageFlags.ComputeShaderBit);
|
||||
RecordStorageImagesForWrite(resources, PipelineStageFlags.ComputeShaderBit);
|
||||
}
|
||||
|
||||
_vk.CmdBindPipeline(
|
||||
_commandBuffer,
|
||||
PipelineBindPoint.Compute,
|
||||
resources.PipelineLayout,
|
||||
0,
|
||||
1,
|
||||
&descriptorSet,
|
||||
0,
|
||||
null);
|
||||
resources.Pipeline);
|
||||
if (resources.DescriptorSet.Handle != 0)
|
||||
{
|
||||
var descriptorSet = resources.DescriptorSet;
|
||||
_vk.CmdBindDescriptorSets(
|
||||
_commandBuffer,
|
||||
PipelineBindPoint.Compute,
|
||||
resources.PipelineLayout,
|
||||
0,
|
||||
1,
|
||||
&descriptorSet,
|
||||
0,
|
||||
null);
|
||||
}
|
||||
|
||||
RecordChunkedComputeDispatch(_commandBuffer, work, zStart, zCount);
|
||||
|
||||
if (isLastBatch)
|
||||
{
|
||||
RecordStorageImagesForRead(resources, PipelineStageFlags.ComputeShaderBit);
|
||||
}
|
||||
|
||||
EndDebugLabel(_commandBuffer);
|
||||
Check(_vk.EndCommandBuffer(_commandBuffer), "vkEndCommandBuffer(compute)");
|
||||
|
||||
TraceVulkanShader(
|
||||
$"vk.compute_submit cs=0x{work.ShaderAddress:X16} " +
|
||||
$"batch={batchIndex}/{batchCount} z={zStart}..{zStart + zCount}");
|
||||
if (isLastBatch)
|
||||
{
|
||||
SubmitGuestCommandBuffer(
|
||||
commandBuffer,
|
||||
resources,
|
||||
GetTraceImages(resources));
|
||||
submitted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SubmitGuestCommandBufferAndWait(commandBuffer);
|
||||
commandBuffer = default;
|
||||
}
|
||||
}
|
||||
|
||||
RecordChunkedComputeDispatch(_commandBuffer, work);
|
||||
RecordStorageImagesForRead(resources, PipelineStageFlags.ComputeShaderBit);
|
||||
EndDebugLabel(_commandBuffer);
|
||||
|
||||
Check(_vk.EndCommandBuffer(_commandBuffer), "vkEndCommandBuffer(compute)");
|
||||
SubmitGuestCommandBuffer(
|
||||
commandBuffer,
|
||||
resources,
|
||||
GetTraceImages(resources));
|
||||
submitted = true;
|
||||
MarkSampledImagesInitialized(resources);
|
||||
MarkStorageImagesInitialized(resources, traceContents: false);
|
||||
TraceVulkanShader(
|
||||
$"vk.compute_dispatch groups={work.GroupCountX}x" +
|
||||
$"{work.GroupCountY}x{work.GroupCountZ} " +
|
||||
$"textures={work.Textures.Count} cs=0x{work.ShaderAddress:X16}");
|
||||
$"textures={work.Textures.Count} cs=0x{work.ShaderAddress:X16} " +
|
||||
$"batches={batchCount}");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][ERROR] Vulkan compute dispatch failed: {exception.Message}");
|
||||
$"[LOADER][ERROR] Vulkan compute dispatch failed " +
|
||||
$"cs=0x{work.ShaderAddress:X16}: {exception.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -3851,7 +4157,9 @@ internal static unsafe class VulkanVideoPresenter
|
||||
|
||||
private void RecordChunkedComputeDispatch(
|
||||
CommandBuffer commandBuffer,
|
||||
VulkanComputeGuestDispatch work)
|
||||
VulkanComputeGuestDispatch work,
|
||||
uint zStart,
|
||||
uint zCount)
|
||||
{
|
||||
const uint maxWorkgroupsPerCommand = 4096;
|
||||
var yChunk = Math.Max(
|
||||
@@ -3861,7 +4169,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
maxWorkgroupsPerCommand / Math.Max(work.GroupCountX, 1u)));
|
||||
var commandCount = 0u;
|
||||
|
||||
for (var z = 0u; z < work.GroupCountZ; z++)
|
||||
for (var z = zStart; z < zStart + zCount; z++)
|
||||
{
|
||||
for (var y = 0u; y < work.GroupCountY; y += yChunk)
|
||||
{
|
||||
@@ -3883,7 +4191,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
TraceVulkanShader(
|
||||
$"vk.compute_chunked cs=0x{work.ShaderAddress:X16} " +
|
||||
$"groups={work.GroupCountX}x{work.GroupCountY}x{work.GroupCountZ} " +
|
||||
$"commands={commandCount} y_chunk={yChunk}");
|
||||
$"z_range={zStart}..{zStart + zCount} commands={commandCount} y_chunk={yChunk}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4091,6 +4399,23 @@ internal static unsafe class VulkanVideoPresenter
|
||||
existing.MipLevels == mipLevels &&
|
||||
existing.Format == format)
|
||||
{
|
||||
if (existing.RenderPass.Handle == 0)
|
||||
{
|
||||
var attachmentView = existing.MipViews.Length > 0
|
||||
? existing.MipViews[0]
|
||||
: existing.View;
|
||||
var (promotedRenderPass, promotedFramebuffer) = CreateRenderPassAndFramebuffer(
|
||||
existing.Format,
|
||||
attachmentView,
|
||||
existing.Width,
|
||||
existing.Height);
|
||||
existing.RenderPass = promotedRenderPass;
|
||||
existing.Framebuffer = promotedFramebuffer;
|
||||
var promotedName = GuestImageDebugName(target, format);
|
||||
SetDebugName(ObjectType.RenderPass, promotedRenderPass.Handle, $"{promotedName} renderpass");
|
||||
SetDebugName(ObjectType.Framebuffer, promotedFramebuffer.Handle, $"{promotedName} framebuffer");
|
||||
}
|
||||
|
||||
return existing;
|
||||
}
|
||||
|
||||
@@ -4171,6 +4496,48 @@ internal static unsafe class VulkanVideoPresenter
|
||||
mipViews[mipLevel] = mipView;
|
||||
}
|
||||
|
||||
var (renderPass, framebuffer) = CreateRenderPassAndFramebuffer(
|
||||
format,
|
||||
mipViews[0],
|
||||
target.Width,
|
||||
target.Height);
|
||||
|
||||
var resource = new GuestImageResource
|
||||
{
|
||||
Address = target.Address,
|
||||
Width = target.Width,
|
||||
Height = target.Height,
|
||||
MipLevels = mipLevels,
|
||||
Format = format,
|
||||
Image = image,
|
||||
Memory = memory,
|
||||
View = view,
|
||||
MipViews = mipViews,
|
||||
RenderPass = renderPass,
|
||||
Framebuffer = framebuffer,
|
||||
};
|
||||
var debugName = GuestImageDebugName(target, format);
|
||||
SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image");
|
||||
SetDebugName(ObjectType.ImageView, view.Handle, $"{debugName} view");
|
||||
for (var mipLevel = 0; mipLevel < mipViews.Length; mipLevel++)
|
||||
{
|
||||
SetDebugName(
|
||||
ObjectType.ImageView,
|
||||
mipViews[mipLevel].Handle,
|
||||
$"{debugName} mip{mipLevel}");
|
||||
}
|
||||
SetDebugName(ObjectType.RenderPass, renderPass.Handle, $"{debugName} renderpass");
|
||||
SetDebugName(ObjectType.Framebuffer, framebuffer.Handle, $"{debugName} framebuffer");
|
||||
_guestImages.Add(target.Address, resource);
|
||||
return resource;
|
||||
}
|
||||
|
||||
private (RenderPass RenderPass, Framebuffer Framebuffer) CreateRenderPassAndFramebuffer(
|
||||
Format format,
|
||||
ImageView attachmentView,
|
||||
uint width,
|
||||
uint height)
|
||||
{
|
||||
var colorAttachment = new AttachmentDescription
|
||||
{
|
||||
Format = format,
|
||||
@@ -4205,49 +4572,22 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_vk.CreateRenderPass(_device, &renderPassInfo, null, out var renderPass),
|
||||
"vkCreateRenderPass(offscreen)");
|
||||
|
||||
var attachment = mipViews[0];
|
||||
var attachment = attachmentView;
|
||||
var framebufferInfo = new FramebufferCreateInfo
|
||||
{
|
||||
SType = StructureType.FramebufferCreateInfo,
|
||||
RenderPass = renderPass,
|
||||
AttachmentCount = 1,
|
||||
PAttachments = &attachment,
|
||||
Width = target.Width,
|
||||
Height = target.Height,
|
||||
Width = width,
|
||||
Height = height,
|
||||
Layers = 1,
|
||||
};
|
||||
Check(
|
||||
_vk.CreateFramebuffer(_device, &framebufferInfo, null, out var framebuffer),
|
||||
"vkCreateFramebuffer(offscreen)");
|
||||
|
||||
var resource = new GuestImageResource
|
||||
{
|
||||
Address = target.Address,
|
||||
Width = target.Width,
|
||||
Height = target.Height,
|
||||
MipLevels = mipLevels,
|
||||
Format = format,
|
||||
Image = image,
|
||||
Memory = memory,
|
||||
View = view,
|
||||
MipViews = mipViews,
|
||||
RenderPass = renderPass,
|
||||
Framebuffer = framebuffer,
|
||||
};
|
||||
var debugName = GuestImageDebugName(target, format);
|
||||
SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image");
|
||||
SetDebugName(ObjectType.ImageView, view.Handle, $"{debugName} view");
|
||||
for (var mipLevel = 0; mipLevel < mipViews.Length; mipLevel++)
|
||||
{
|
||||
SetDebugName(
|
||||
ObjectType.ImageView,
|
||||
mipViews[mipLevel].Handle,
|
||||
$"{debugName} mip{mipLevel}");
|
||||
}
|
||||
SetDebugName(ObjectType.RenderPass, renderPass.Handle, $"{debugName} renderpass");
|
||||
SetDebugName(ObjectType.Framebuffer, framebuffer.Handle, $"{debugName} framebuffer");
|
||||
_guestImages.Add(target.Address, resource);
|
||||
return resource;
|
||||
return (renderPass, framebuffer);
|
||||
}
|
||||
|
||||
private static uint ClampMipLevels(uint width, uint height, uint requestedMipLevels)
|
||||
@@ -5956,6 +6296,10 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return;
|
||||
}
|
||||
|
||||
if (_debugUtils is not null && _debugMessenger.Handle != 0)
|
||||
{
|
||||
_debugUtils.DestroyDebugUtilsMessenger(_instance, _debugMessenger, null);
|
||||
}
|
||||
_vulkanReady = false;
|
||||
_vk.DeviceWaitIdle(_device);
|
||||
CollectCompletedGuestSubmissions(waitForOldest: false);
|
||||
|
||||
@@ -11,6 +11,16 @@
|
||||
"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": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.23.0, )",
|
||||
|
||||
Reference in New Issue
Block a user