Compare commits

..

6 Commits

Author SHA1 Message Date
ParantezTech 78fcdeeae0 [unity-fixes] support for Media/Modules/*.prx files 2026-07-05 18:49:08 +03:00
ParantezTech 9ab1e2f21a reuse 2026-07-04 19:14:44 +03:00
ParantezTech 340eb9e0ab [dotnet] configure local nuget package cache 2026-07-04 19:12:18 +03:00
Berk 03e3d25e85 [fixes-assets] Add SharpEmu icon and fix glfw dependency (temporary fix) (#19)
* [fixes-assets] Add SharpEmu icon and fix glfw dependency (temporary fix)

* [reuse] I forgot to add comma

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

* [sceSaveData] user folder sometimes appeared in the previous folder has been fixed.
2026-07-04 15:18:21 +03:00
17 changed files with 682 additions and 198 deletions
+1 -1
View File
@@ -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
+1
View File
@@ -26,6 +26,7 @@ arm64/
*.userosscache
*.sln.docstates
.packages
packages/
*.nupkg
.nuget/
BIN
View File
Binary file not shown.
+3 -1
View File
@@ -3,12 +3,14 @@ version = 1
[[annotations]]
path = [
"REUSE.toml",
"nuget.config",
"global.json",
"**/packages.lock.json",
"scripts/ps5_names.txt",
"src/SharpEmu.HLE/Aerolib/aerolib.bin",
"_logs/**",
".github/images/**"
".github/images/**",
"assets/images/**"
]
precedence = "aggregate"
SPDX-FileCopyrightText = "SharpEmu Emulator Project"
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="globalPackagesFolder" value=".packages" />
</config>
<packageSources>
<clear />
<add key="Nuget" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="Nuget">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
+6 -1
View File
@@ -16,7 +16,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RuntimeIdentifiers>win-x64;linux-x64;osx-arm64</RuntimeIdentifiers>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<!-- <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> -->
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -30,6 +30,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-x64' Or '$(RuntimeIdentifier)' == ''">
<ApplicationIcon>..\..\assets\images\SharpEmu.ico</ApplicationIcon>
<Win32Icon>..\..\assets\images\SharpEmu.ico</Win32Icon>
</PropertyGroup>
<ItemGroup>
<Content Include="..\..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
@@ -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;
+102 -71
View File
@@ -81,7 +81,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterWriteLock();
try
{
_regions.Add(new MemoryRegion
InsertRegionSorted(new MemoryRegion
{
VirtualAddress = actualAddress,
Size = alignedSize,
@@ -210,7 +210,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterWriteLock();
try
{
_regions.Add(new MemoryRegion
InsertRegionSorted(new MemoryRegion
{
VirtualAddress = actualAddress,
Size = alignedSize,
@@ -473,30 +473,34 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock();
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);
if (destination.IsEmpty)
{
return true;
}
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;
}
}
if (!CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region))
{
requiresExclusiveAccess = true;
break;
return false;
}
}
if (!CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region))
{
requiresExclusiveAccess = true;
}
else
{
fixed (byte* destPtr = destination)
{
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length);
@@ -533,30 +537,34 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock();
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);
if (source.IsEmpty)
{
return true;
}
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;
}
}
if (!CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region))
{
requiresExclusiveAccess = true;
break;
return false;
}
}
if (!CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region))
{
requiresExclusiveAccess = true;
}
else
{
fixed (byte* srcPtr = source)
{
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)source.Length, (nuint)source.Length);
@@ -589,13 +597,14 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
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);
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
{
@@ -637,13 +646,14 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
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);
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
{
@@ -699,15 +709,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock();
try
{
foreach (var region in _regions)
{
if (virtualAddress >= region.VirtualAddress &&
virtualAddress < region.VirtualAddress + region.Size)
{
return (void*)virtualAddress;
}
}
return null;
return FindRegion(virtualAddress, 1) is not null
? (void*)virtualAddress
: null;
}
finally
{
@@ -720,14 +724,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock();
try
{
foreach (var region in _regions)
{
if (TryResolveRegionOffset(virtualAddress, size, region, out _))
{
return true;
}
}
return false;
return FindRegion(virtualAddress, size) is not null;
}
finally
{
@@ -737,14 +734,48 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
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)
+4 -2
View File
@@ -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
+37 -3
View File
@@ -155,6 +155,16 @@ public static class AgcExports
(ulong Cs, ulong State, uint LocalX, uint LocalY, uint LocalZ),
byte[]> _computeSpirvCache = new();
private static readonly Dictionary<ulong, ulong> _shaderHeadersByCode = new();
private static readonly bool _traceAgc = string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"),
"1",
StringComparison.Ordinal);
private static readonly bool _traceAgcShader =
_traceAgc ||
string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC_SHADER"),
"1",
StringComparison.Ordinal);
private static long _dcbWriteDataTraceCount;
private static long _dcbWaitRegMemTraceCount;
private static long _createShaderTraceCount;
@@ -3629,6 +3639,31 @@ public static class AgcExports
return true;
}
if (!isStorage &&
descriptor.Address != 0 &&
VulkanVideoPresenter.IsGuestImageAvailable(
descriptor.Address,
descriptor.Format,
descriptor.NumberType))
{
texture = new VulkanGuestDrawTexture(
descriptor.Address,
descriptor.Width,
descriptor.Height,
descriptor.Format,
descriptor.NumberType,
[],
IsFallback: false,
IsStorage: false,
MipLevels: descriptor.MipLevels,
MipLevel: mipLevel,
Pitch: sourceWidth,
TileMode: descriptor.TileMode,
DstSelect: descriptor.DstSelect,
Sampler: ToVulkanSampler(samplerDescriptor));
return true;
}
if (isStorage)
{
var initialPixels = Array.Empty<byte>();
@@ -5405,7 +5440,7 @@ public static class AgcExports
private static void TraceAgc(string message)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal))
if (!_traceAgc)
{
return;
}
@@ -5415,8 +5450,7 @@ public static class AgcExports
private static void TraceAgcShader(string message)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal) &&
!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC_SHADER"), "1", StringComparison.Ordinal))
if (!_traceAgcShader)
{
return;
}
+49 -5
View File
@@ -4,6 +4,7 @@
using SharpEmu.HLE;
using SharpEmu.Libs.VideoOut;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Text;
namespace SharpEmu.Libs.Agc;
@@ -13,6 +14,14 @@ internal static class Gen5ShaderTranslator
private const int MaxInstructions = 4096;
private const int MinimumUserDataDwords = 16;
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 =
[
@@ -117,16 +126,51 @@ internal static class Gen5ShaderTranslator
uint userDataScalarRegisterBase = 0)
{
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;
if (shaderHeaderAddress != 0 &&
Gen5ShaderMetadataReader.TryRead(ctx, shaderHeaderAddress, out var decodedMetadata))
if (shaderHeaderAddress != 0)
{
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)];
@@ -400,7 +400,7 @@ public static class SaveDataExports
{
var configured = Environment.GetEnvironmentVariable("SHARPEMU_SAVEDATA_DIR");
var root = string.IsNullOrWhiteSpace(configured)
? Path.Combine(Environment.CurrentDirectory, "user", "savedata")
? Path.Combine(AppContext.BaseDirectory, "user", "savedata")
: configured;
return Path.GetFullPath(root);
}
@@ -4,6 +4,7 @@
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
@@ -48,6 +49,13 @@ public static class VideoOutExports
private static int _frameDumpCount;
private static long _nextFrameDumpIndex;
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)
{
@@ -812,9 +820,46 @@ public static class VideoOutExports
}
TraceVideoOut($"videoout.submit_flip handle={handle} index={bufferIndex} mode={flipMode} arg={flipArg} events={flipEvents.Count}");
ReportFrameRate(presented: false);
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)
{
lock (_stateGate)
+349 -111
View File
@@ -8,7 +8,9 @@ using SharpEmu.Libs.Agc;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using Silk.NET.Windowing;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using VkBuffer = Silk.NET.Vulkan.Buffer;
using VkSemaphore = Silk.NET.Vulkan.Semaphore;
@@ -597,6 +599,24 @@ internal static unsafe class VulkanVideoPresenter
return true;
}
internal static bool IsGuestImageAvailable(
ulong address,
uint format,
uint numberType)
{
var guestFormat = GetGuestTextureFormat(format, numberType);
if (address == 0 || guestFormat == 0)
{
return false;
}
lock (_gate)
{
return _availableGuestImages.TryGetValue(address, out var availableFormat) &&
availableFormat == guestFormat;
}
}
public static bool TrySubmitGuestImageBlit(
ulong sourceAddress,
uint sourceWidth,
@@ -867,14 +887,50 @@ internal static unsafe class VulkanVideoPresenter
private int _tracedVertexBufferCount;
private readonly Dictionary<byte[], Pipeline> _computePipelines =
new(ReferenceEqualityComparer.Instance);
private readonly Dictionary<GraphicsPipelineKey, Pipeline> _graphicsPipelines = new();
private readonly Dictionary<VulkanGuestSampler, Sampler> _samplers = new();
private readonly Dictionary<byte[], string> _shaderDigests =
new(ReferenceEqualityComparer.Instance);
private readonly Dictionary<DescriptorLayoutKey, DescriptorLayoutBundle>
_descriptorLayouts = new();
private readonly Dictionary<HostBufferPoolKey, Stack<HostBufferAllocation>>
_hostBufferPool = new();
private readonly Dictionary<ulong, HostBufferAllocation> _hostBufferAllocations = new();
private readonly Queue<PendingGuestSubmission> _pendingGuestSubmissions = new();
private readonly record struct GraphicsPipelineKey(
string VertexShader,
string FragmentShader,
ulong RenderPass,
PrimitiveTopology Topology,
VulkanGuestBlendState Blend,
string ResourceLayout,
string VertexLayout);
private readonly record struct HostBufferPoolKey(
BufferUsageFlags Usage,
ulong Capacity);
private readonly record struct DescriptorLayoutKey(
ShaderStageFlags Stages,
string Resources);
private sealed record DescriptorLayoutBundle(
DescriptorSetLayout DescriptorSetLayout,
PipelineLayout PipelineLayout);
private sealed record HostBufferAllocation(
VkBuffer Buffer,
DeviceMemory Memory,
HostBufferPoolKey Key);
private sealed class TranslatedDrawResources
{
public string DebugName = "SharpEmu translated";
public PipelineLayout PipelineLayout;
public Pipeline Pipeline;
public bool PipelineCached;
public bool DescriptorLayoutCached;
public DescriptorSetLayout DescriptorSetLayout;
public DescriptorPool DescriptorPool;
public DescriptorSet DescriptorSet;
@@ -1978,81 +2034,16 @@ internal static unsafe class VulkanVideoPresenter
var storageImageCount = textureCount - sampledImageCount;
var globalBufferCount = resources.GlobalMemoryBuffers.Length;
var bindingCount = textureCount + (globalBufferCount == 0 ? 0 : 1);
var layout = GetOrCreateDescriptorLayout(resources, stageFlags, bindingCount);
resources.DescriptorSetLayout = layout.DescriptorSetLayout;
resources.PipelineLayout = layout.PipelineLayout;
resources.DescriptorLayoutCached = true;
if (bindingCount == 0)
{
var layoutInfo = new PipelineLayoutCreateInfo
{
SType = StructureType.PipelineLayoutCreateInfo,
};
PipelineLayout pipelineLayout;
Check(
_vk.CreatePipelineLayout(_device, &layoutInfo, null, out pipelineLayout),
"vkCreatePipelineLayout");
resources.PipelineLayout = pipelineLayout;
return;
}
var bindings = new DescriptorSetLayoutBinding[bindingCount];
var bindingOffset = 0;
if (globalBufferCount != 0)
{
bindings[bindingOffset++] = new DescriptorSetLayoutBinding
{
Binding = 0,
DescriptorType = DescriptorType.StorageBuffer,
DescriptorCount = (uint)globalBufferCount,
StageFlags = stageFlags,
};
}
for (var index = 0; index < textureCount; index++)
{
bindings[bindingOffset + index] = new DescriptorSetLayoutBinding
{
Binding = (uint)(index + 1),
DescriptorType = resources.Textures[index].IsStorage
? DescriptorType.StorageImage
: DescriptorType.CombinedImageSampler,
DescriptorCount = 1,
StageFlags = stageFlags,
};
}
fixed (DescriptorSetLayoutBinding* bindingPointer = bindings)
{
var layoutInfo = new DescriptorSetLayoutCreateInfo
{
SType = StructureType.DescriptorSetLayoutCreateInfo,
BindingCount = (uint)bindings.Length,
PBindings = bindingPointer,
};
DescriptorSetLayout descriptorSetLayout;
Check(
_vk.CreateDescriptorSetLayout(
_device,
&layoutInfo,
null,
out descriptorSetLayout),
"vkCreateDescriptorSetLayout");
resources.DescriptorSetLayout = descriptorSetLayout;
}
var setLayout = resources.DescriptorSetLayout;
var pipelineLayoutInfo = new PipelineLayoutCreateInfo
{
SType = StructureType.PipelineLayoutCreateInfo,
SetLayoutCount = 1,
PSetLayouts = &setLayout,
};
PipelineLayout translatedPipelineLayout;
Check(
_vk.CreatePipelineLayout(
_device,
&pipelineLayoutInfo,
null,
out translatedPipelineLayout),
"vkCreatePipelineLayout");
resources.PipelineLayout = translatedPipelineLayout;
var setLayout = layout.DescriptorSetLayout;
var poolSizes = new DescriptorPoolSize[
(sampledImageCount == 0 ? 0 : 1) +
@@ -2202,6 +2193,21 @@ internal static unsafe class VulkanVideoPresenter
RenderPass renderPass,
Extent2D extent)
{
var pipelineKey = new GraphicsPipelineKey(
GetShaderDigest(vertexSpirv),
GetShaderDigest(fragmentSpirv),
renderPass.Handle,
resources.Topology,
resources.Blend,
GetResourceLayoutKey(resources),
GetVertexLayoutKey(resources));
if (_graphicsPipelines.TryGetValue(pipelineKey, out var cachedPipeline))
{
resources.Pipeline = cachedPipeline;
resources.PipelineCached = true;
return;
}
var vertexModule = CreateShaderModule(vertexSpirv);
var fragmentModule = CreateShaderModule(fragmentSpirv);
var entryPoint = (byte*)SilkMarshal.StringToPtr("main");
@@ -2353,6 +2359,8 @@ internal static unsafe class VulkanVideoPresenter
out pipeline),
"vkCreateGraphicsPipelines(translated)");
resources.Pipeline = pipeline;
resources.PipelineCached = true;
_graphicsPipelines.Add(pipelineKey, pipeline);
SetDebugName(
ObjectType.Pipeline,
pipeline.Handle,
@@ -2367,6 +2375,129 @@ internal static unsafe class VulkanVideoPresenter
}
}
private DescriptorLayoutBundle GetOrCreateDescriptorLayout(
TranslatedDrawResources resources,
ShaderStageFlags stageFlags,
int bindingCount)
{
var key = new DescriptorLayoutKey(stageFlags, GetResourceLayoutKey(resources));
if (_descriptorLayouts.TryGetValue(key, out var cached))
{
return cached;
}
DescriptorSetLayout descriptorSetLayout = default;
if (bindingCount != 0)
{
var bindings = new DescriptorSetLayoutBinding[bindingCount];
var bindingOffset = 0;
if (resources.GlobalMemoryBuffers.Length != 0)
{
bindings[bindingOffset++] = new DescriptorSetLayoutBinding
{
Binding = 0,
DescriptorType = DescriptorType.StorageBuffer,
DescriptorCount = (uint)resources.GlobalMemoryBuffers.Length,
StageFlags = stageFlags,
};
}
for (var index = 0; index < resources.Textures.Length; index++)
{
bindings[bindingOffset + index] = new DescriptorSetLayoutBinding
{
Binding = (uint)(index + 1),
DescriptorType = resources.Textures[index].IsStorage
? DescriptorType.StorageImage
: DescriptorType.CombinedImageSampler,
DescriptorCount = 1,
StageFlags = stageFlags,
};
}
fixed (DescriptorSetLayoutBinding* bindingPointer = bindings)
{
var descriptorInfo = new DescriptorSetLayoutCreateInfo
{
SType = StructureType.DescriptorSetLayoutCreateInfo,
BindingCount = (uint)bindings.Length,
PBindings = bindingPointer,
};
Check(
_vk.CreateDescriptorSetLayout(
_device,
&descriptorInfo,
null,
out descriptorSetLayout),
"vkCreateDescriptorSetLayout");
}
}
var pipelineInfo = new PipelineLayoutCreateInfo
{
SType = StructureType.PipelineLayoutCreateInfo,
};
if (descriptorSetLayout.Handle != 0)
{
pipelineInfo.SetLayoutCount = 1;
pipelineInfo.PSetLayouts = &descriptorSetLayout;
}
PipelineLayout pipelineLayout;
Check(
_vk.CreatePipelineLayout(
_device,
&pipelineInfo,
null,
out pipelineLayout),
"vkCreatePipelineLayout");
var created = new DescriptorLayoutBundle(descriptorSetLayout, pipelineLayout);
_descriptorLayouts.Add(key, created);
return created;
}
private string GetShaderDigest(byte[] spirv)
{
if (_shaderDigests.TryGetValue(spirv, out var digest))
{
return digest;
}
digest = Convert.ToHexString(SHA256.HashData(spirv));
_shaderDigests.Add(spirv, digest);
return digest;
}
private static string GetResourceLayoutKey(TranslatedDrawResources resources)
{
var key = new StringBuilder();
key.Append(resources.GlobalMemoryBuffers.Length).Append(':');
foreach (var texture in resources.Textures)
{
key.Append(texture.IsStorage ? 'S' : 'T');
}
return key.ToString();
}
private static string GetVertexLayoutKey(TranslatedDrawResources resources)
{
var key = new StringBuilder();
foreach (var buffer in resources.VertexBuffers)
{
key.Append(buffer.Location).Append(',')
.Append(buffer.ComponentCount).Append(',')
.Append(buffer.DataFormat).Append(',')
.Append(buffer.NumberFormat).Append(',')
.Append(buffer.Stride == 0
? Math.Max(buffer.ComponentCount, 1) * sizeof(float)
: buffer.Stride)
.Append(';');
}
return key.ToString();
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void CreateComputePipeline(
TranslatedDrawResources resources,
@@ -2779,7 +2910,7 @@ internal static unsafe class VulkanVideoPresenter
SetDebugName(ObjectType.Buffer, stagingBuffer.Handle, $"{debugName} staging");
SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image");
SetDebugName(ObjectType.ImageView, view.Handle, $"{debugName} view");
return new TextureResource
var resource = new TextureResource
{
Address = texture.Address,
StagingBuffer = stagingBuffer,
@@ -2795,6 +2926,38 @@ internal static unsafe class VulkanVideoPresenter
OwnsStorage = true,
SamplerState = texture.Sampler,
};
if (texture.Address != 0 &&
!_guestImages.ContainsKey(texture.Address))
{
var guestImage = new GuestImageResource
{
Address = texture.Address,
Width = width,
Height = height,
MipLevels = 1,
Format = vkFormat,
Image = image,
Memory = imageMemory,
View = view,
InitialUploadPending = true,
};
_guestImages.Add(texture.Address, guestImage);
resource.OwnsStorage = false;
resource.GuestImage = guestImage;
lock (_gate)
{
var guestFormat = VulkanVideoPresenter.GetGuestTextureFormat(
texture.Format,
texture.NumberType);
if (guestFormat != 0)
{
_availableGuestImages[texture.Address] = guestFormat;
}
}
}
return resource;
}
private void DumpTextureUpload(
@@ -2895,6 +3058,11 @@ internal static unsafe class VulkanVideoPresenter
private Sampler CreateSampler(VulkanGuestSampler sampler)
{
if (_samplers.TryGetValue(sampler, out var cachedSampler))
{
return cachedSampler;
}
var minLod = DecodeSamplerMipFilter(sampler) == 0
? 0f
: DecodeSamplerMinLod(sampler);
@@ -2921,6 +3089,7 @@ internal static unsafe class VulkanVideoPresenter
Check(
_vk.CreateSampler(_device, &samplerInfo, null, out vkSampler),
"vkCreateSampler(texture)");
_samplers.Add(sampler, vkSampler);
return vkSampler;
}
@@ -3035,11 +3204,31 @@ internal static unsafe class VulkanVideoPresenter
out DeviceMemory memory)
{
var size = (ulong)Math.Max(data.Length, sizeof(uint));
var buffer = CreateBuffer(
size,
usage,
MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit,
out memory);
var capacity = BitOperations.RoundUpToPowerOf2(size);
var key = new HostBufferPoolKey(usage, capacity);
if (!_hostBufferPool.TryGetValue(key, out var available))
{
available = new Stack<HostBufferAllocation>();
_hostBufferPool.Add(key, available);
}
HostBufferAllocation allocation;
if (available.TryPop(out var pooled))
{
allocation = pooled;
}
else
{
var buffer = CreateBuffer(
capacity,
usage,
MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit,
out var allocatedMemory);
allocation = new HostBufferAllocation(buffer, allocatedMemory, key);
_hostBufferAllocations.Add(buffer.Handle, allocation);
}
memory = allocation.Memory;
void* mapped;
Check(_vk.MapMemory(_device, memory, 0, size, 0, &mapped), "vkMapMemory(host)");
try
@@ -3058,7 +3247,28 @@ internal static unsafe class VulkanVideoPresenter
_vk.UnmapMemory(_device, memory);
}
return buffer;
return allocation.Buffer;
}
private void RecycleHostBuffer(VkBuffer buffer, DeviceMemory memory)
{
if (buffer.Handle == 0)
{
return;
}
if (_hostBufferAllocations.TryGetValue(buffer.Handle, out var allocation) &&
allocation.Memory.Handle == memory.Handle)
{
_hostBufferPool[allocation.Key].Push(allocation);
return;
}
_vk.DestroyBuffer(_device, buffer, null);
if (memory.Handle != 0)
{
_vk.FreeMemory(_device, memory, null);
}
}
private static PrimitiveTopology GetPrimitiveTopology(uint primitiveType) =>
@@ -3608,6 +3818,7 @@ internal static unsafe class VulkanVideoPresenter
resources,
GetTraceImages(resources));
submitted = true;
MarkSampledImagesInitialized(resources);
MarkStorageImagesInitialized(resources, traceContents: false);
TraceVulkanShader(
$"vk.compute_dispatch groups={work.GroupCountX}x" +
@@ -3796,6 +4007,7 @@ internal static unsafe class VulkanVideoPresenter
GetTraceImages(resources, target));
submitted = true;
target.Initialized = true;
MarkSampledImagesInitialized(resources);
MarkStorageImagesInitialized(resources, traceContents: false);
var guestTextureFormat = GetGuestTextureFormat(target.Format);
@@ -4498,6 +4710,7 @@ internal static unsafe class VulkanVideoPresenter
CheckSwapchainResult(presentResult, "vkQueuePresentKHR");
recreateAfterPresent |= presentResult == Result.SuboptimalKhr;
Check(_vk.QueueWaitIdle(_queue), "vkQueueWaitIdle");
VideoOutExports.ReportPresentedFrame();
if (_swapchainReadbackPending)
{
TraceSwapchainReadback();
@@ -4505,6 +4718,7 @@ internal static unsafe class VulkanVideoPresenter
CollectCompletedGuestSubmissions(waitForOldest: false);
if (translatedResources is not null)
{
MarkSampledImagesInitialized(translatedResources);
MarkStorageImagesInitialized(translatedResources);
DestroyTranslatedDrawResources(translatedResources);
}
@@ -5010,6 +5224,27 @@ internal static unsafe class VulkanVideoPresenter
}
}
private static void MarkSampledImagesInitialized(
TranslatedDrawResources resources)
{
lock (_gate)
{
foreach (var texture in resources.Textures)
{
if (!texture.NeedsUpload ||
texture.IsStorage ||
texture.Address == 0 ||
texture.GuestImage is not { } guestImage)
{
continue;
}
guestImage.Initialized = true;
guestImage.InitialUploadPending = false;
}
}
}
private bool ShouldTraceGuestImageContents(GuestImageResource image)
{
if (image.Address == 0)
@@ -5248,11 +5483,6 @@ internal static unsafe class VulkanVideoPresenter
_vk.FreeMemory(_device, texture.StagingMemory, null);
}
if (texture.Sampler.Handle != 0)
{
_vk.DestroySampler(_device, texture.Sampler, null);
}
if (texture.NeedsUpload &&
texture.GuestImage is { Initialized: false } guestImage)
{
@@ -5267,15 +5497,7 @@ internal static unsafe class VulkanVideoPresenter
continue;
}
if (globalBuffer.Buffer.Handle != 0)
{
_vk.DestroyBuffer(_device, globalBuffer.Buffer, null);
}
if (globalBuffer.Memory.Handle != 0)
{
_vk.FreeMemory(_device, globalBuffer.Memory, null);
}
RecycleHostBuffer(globalBuffer.Buffer, globalBuffer.Memory);
}
foreach (var vertexBuffer in resources.VertexBuffers)
@@ -5285,26 +5507,10 @@ internal static unsafe class VulkanVideoPresenter
continue;
}
if (vertexBuffer.Buffer.Handle != 0)
{
_vk.DestroyBuffer(_device, vertexBuffer.Buffer, null);
}
if (vertexBuffer.Memory.Handle != 0)
{
_vk.FreeMemory(_device, vertexBuffer.Memory, null);
}
RecycleHostBuffer(vertexBuffer.Buffer, vertexBuffer.Memory);
}
if (resources.IndexBuffer.Handle != 0)
{
_vk.DestroyBuffer(_device, resources.IndexBuffer, null);
}
if (resources.IndexMemory.Handle != 0)
{
_vk.FreeMemory(_device, resources.IndexMemory, null);
}
RecycleHostBuffer(resources.IndexBuffer, resources.IndexMemory);
if (!resources.PipelineCached && resources.Pipeline.Handle != 0)
{
@@ -5316,12 +5522,14 @@ internal static unsafe class VulkanVideoPresenter
_vk.DestroyDescriptorPool(_device, resources.DescriptorPool, null);
}
if (resources.PipelineLayout.Handle != 0)
if (!resources.DescriptorLayoutCached &&
resources.PipelineLayout.Handle != 0)
{
_vk.DestroyPipelineLayout(_device, resources.PipelineLayout, null);
}
if (resources.DescriptorSetLayout.Handle != 0)
if (!resources.DescriptorLayoutCached &&
resources.DescriptorSetLayout.Handle != 0)
{
_vk.DestroyDescriptorSetLayout(_device, resources.DescriptorSetLayout, null);
}
@@ -5756,6 +5964,36 @@ internal static unsafe class VulkanVideoPresenter
_vk.DestroyPipeline(_device, pipeline, null);
}
_computePipelines.Clear();
foreach (var pipeline in _graphicsPipelines.Values)
{
_vk.DestroyPipeline(_device, pipeline, null);
}
_graphicsPipelines.Clear();
foreach (var layout in _descriptorLayouts.Values)
{
_vk.DestroyPipelineLayout(_device, layout.PipelineLayout, null);
if (layout.DescriptorSetLayout.Handle != 0)
{
_vk.DestroyDescriptorSetLayout(
_device,
layout.DescriptorSetLayout,
null);
}
}
_descriptorLayouts.Clear();
foreach (var sampler in _samplers.Values)
{
_vk.DestroySampler(_device, sampler, null);
}
_samplers.Clear();
_shaderDigests.Clear();
foreach (var allocation in _hostBufferAllocations.Values)
{
_vk.DestroyBuffer(_device, allocation.Buffer, null);
_vk.FreeMemory(_device, allocation.Memory, null);
}
_hostBufferAllocations.Clear();
_hostBufferPool.Clear();
foreach (var guestImage in _guestImages.Values)
{
DestroyGuestImage(guestImage);