Compare commits

..

11 Commits

Author SHA1 Message Date
ParantezTech 9ab1e2f21a reuse 2026-07-04 19:14:44 +03:00
ParantezTech 340eb9e0ab [dotnet] configure local nuget package cache 2026-07-04 19:12:18 +03:00
Berk 03e3d25e85 [fixes-assets] Add SharpEmu icon and fix glfw dependency (temporary fix) (#19)
* [fixes-assets] Add SharpEmu icon and fix glfw dependency (temporary fix)

* [reuse] I forgot to add comma

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

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

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

* [shader-decoder] RDNA2 decoder improvements

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

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

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

+1 -1
View File
@@ -126,7 +126,7 @@ jobs:
needs: needs:
- init - init
- build - build
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
+1
View File
@@ -26,6 +26,7 @@ arm64/
*.userosscache *.userosscache
*.sln.docstates *.sln.docstates
.packages
packages/ packages/
*.nupkg *.nupkg
.nuget/ .nuget/
BIN
View File
Binary file not shown.
+3 -1
View File
@@ -3,12 +3,14 @@ version = 1
[[annotations]] [[annotations]]
path = [ path = [
"REUSE.toml", "REUSE.toml",
"nuget.config",
"global.json", "global.json",
"**/packages.lock.json", "**/packages.lock.json",
"scripts/ps5_names.txt", "scripts/ps5_names.txt",
"src/SharpEmu.HLE/Aerolib/aerolib.bin", "src/SharpEmu.HLE/Aerolib/aerolib.bin",
"_logs/**", "_logs/**",
".github/images/**" ".github/images/**",
"assets/images/**"
] ]
precedence = "aggregate" precedence = "aggregate"
SPDX-FileCopyrightText = "SharpEmu Emulator Project" SPDX-FileCopyrightText = "SharpEmu Emulator Project"
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="globalPackagesFolder" value=".packages" />
</config>
<packageSources>
<clear />
<add key="Nuget" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="Nuget">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
+6 -1
View File
@@ -16,7 +16,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RuntimeIdentifiers>win-x64;linux-x64;osx-arm64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;linux-x64;osx-arm64</RuntimeIdentifiers>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile> <PublishSingleFile>true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> <!-- <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> -->
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile> <EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -30,6 +30,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile> <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-x64' Or '$(RuntimeIdentifier)' == ''">
<ApplicationIcon>..\..\assets\images\SharpEmu.ico</ApplicationIcon>
<Win32Icon>..\..\assets\images\SharpEmu.ico</Win32Icon>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="..\..\LICENSE.txt"> <Content Include="..\..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+102 -71
View File
@@ -81,7 +81,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterWriteLock(); _gate.EnterWriteLock();
try try
{ {
_regions.Add(new MemoryRegion InsertRegionSorted(new MemoryRegion
{ {
VirtualAddress = actualAddress, VirtualAddress = actualAddress,
Size = alignedSize, Size = alignedSize,
@@ -210,7 +210,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterWriteLock(); _gate.EnterWriteLock();
try try
{ {
_regions.Add(new MemoryRegion InsertRegionSorted(new MemoryRegion
{ {
VirtualAddress = actualAddress, VirtualAddress = actualAddress,
Size = alignedSize, Size = alignedSize,
@@ -473,30 +473,34 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock(); _gate.EnterReadLock();
try try
{ {
foreach (var region in _regions) var region = FindRegion(virtualAddress, (ulong)destination.Length);
if (region is not null &&
TryResolveRegionOffset(
virtualAddress,
(ulong)destination.Length,
region,
out var offset))
{ {
if (TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset)) var srcPtr = (void*)(region.VirtualAddress + offset);
if (destination.IsEmpty)
{ {
var srcPtr = (void*)(region.VirtualAddress + offset); return true;
if (destination.IsEmpty) }
{
return true;
}
if (region.IsReservedOnly) if (region.IsReservedOnly)
{
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
{ {
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region)) return false;
{
return false;
}
}
if (!CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region))
{
requiresExclusiveAccess = true;
break;
} }
}
if (!CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region))
{
requiresExclusiveAccess = true;
}
else
{
fixed (byte* destPtr = destination) fixed (byte* destPtr = destination)
{ {
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length); Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length);
@@ -533,30 +537,34 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock(); _gate.EnterReadLock();
try try
{ {
foreach (var region in _regions) var region = FindRegion(virtualAddress, (ulong)source.Length);
if (region is not null &&
TryResolveRegionOffset(
virtualAddress,
(ulong)source.Length,
region,
out var offset))
{ {
if (TryResolveRegionOffset(virtualAddress, (ulong)source.Length, region, out var offset)) var destPtr = (void*)(region.VirtualAddress + offset);
if (source.IsEmpty)
{ {
var destPtr = (void*)(region.VirtualAddress + offset); return true;
if (source.IsEmpty) }
{
return true;
}
if (region.IsReservedOnly) if (region.IsReservedOnly)
{
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
{ {
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region)) return false;
{
return false;
}
}
if (!CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region))
{
requiresExclusiveAccess = true;
break;
} }
}
if (!CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region))
{
requiresExclusiveAccess = true;
}
else
{
fixed (byte* srcPtr = source) fixed (byte* srcPtr = source)
{ {
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)source.Length, (nuint)source.Length); Buffer.MemoryCopy(srcPtr, destPtr, (nuint)source.Length, (nuint)source.Length);
@@ -589,13 +597,14 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private bool TryReadExclusive(ulong virtualAddress, Span<byte> destination) private bool TryReadExclusive(ulong virtualAddress, Span<byte> destination)
{ {
foreach (var region in _regions) var region = FindRegion(virtualAddress, (ulong)destination.Length);
if (region is not null &&
TryResolveRegionOffset(
virtualAddress,
(ulong)destination.Length,
region,
out var offset))
{ {
if (!TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset))
{
continue;
}
var srcPtr = (void*)(region.VirtualAddress + offset); var srcPtr = (void*)(region.VirtualAddress + offset);
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region)) if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
{ {
@@ -637,13 +646,14 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private bool TryWriteExclusive(ulong virtualAddress, ReadOnlySpan<byte> source) private bool TryWriteExclusive(ulong virtualAddress, ReadOnlySpan<byte> source)
{ {
foreach (var region in _regions) var region = FindRegion(virtualAddress, (ulong)source.Length);
if (region is not null &&
TryResolveRegionOffset(
virtualAddress,
(ulong)source.Length,
region,
out var offset))
{ {
if (!TryResolveRegionOffset(virtualAddress, (ulong)source.Length, region, out var offset))
{
continue;
}
var destPtr = (void*)(region.VirtualAddress + offset); var destPtr = (void*)(region.VirtualAddress + offset);
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region)) if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
{ {
@@ -699,15 +709,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock(); _gate.EnterReadLock();
try try
{ {
foreach (var region in _regions) return FindRegion(virtualAddress, 1) is not null
{ ? (void*)virtualAddress
if (virtualAddress >= region.VirtualAddress && : null;
virtualAddress < region.VirtualAddress + region.Size)
{
return (void*)virtualAddress;
}
}
return null;
} }
finally finally
{ {
@@ -720,14 +724,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock(); _gate.EnterReadLock();
try try
{ {
foreach (var region in _regions) return FindRegion(virtualAddress, size) is not null;
{
if (TryResolveRegionOffset(virtualAddress, size, region, out _))
{
return true;
}
}
return false;
} }
finally finally
{ {
@@ -737,14 +734,48 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private MemoryRegion? FindRegion(ulong address, ulong size) private MemoryRegion? FindRegion(ulong address, ulong size)
{ {
foreach (var region in _regions) var low = 0;
var high = _regions.Count - 1;
MemoryRegion? candidate = null;
while (low <= high)
{ {
if (TryResolveRegionOffset(address, size, region, out _)) var middle = low + ((high - low) >> 1);
var region = _regions[middle];
if (region.VirtualAddress <= address)
{ {
return region; candidate = region;
low = middle + 1;
}
else
{
high = middle - 1;
} }
} }
return null;
return candidate is not null &&
TryResolveRegionOffset(address, size, candidate, out _)
? candidate
: null;
}
private void InsertRegionSorted(MemoryRegion region)
{
var low = 0;
var high = _regions.Count;
while (low < high)
{
var middle = low + ((high - low) >> 1);
if (_regions[middle].VirtualAddress < region.VirtualAddress)
{
low = middle + 1;
}
else
{
high = middle;
}
}
_regions.Insert(low, region);
} }
private bool TryGetOverlappingRegionEnd(ulong address, ulong size, out ulong overlapEnd) private bool TryGetOverlappingRegionEnd(ulong address, ulong size, out ulong overlapEnd)
+112 -10
View File
@@ -142,7 +142,7 @@ public static class AgcExports
private static readonly HashSet<uint> _tracedDcbSizes = new(); private static readonly HashSet<uint> _tracedDcbSizes = new();
private static readonly HashSet<(ulong Es, ulong Ps, GuestDrawKind Kind)> _tracedShaderTranslations = new(); private static readonly HashSet<(ulong Es, ulong Ps, GuestDrawKind Kind)> _tracedShaderTranslations = new();
private static readonly HashSet<(ulong Es, ulong Ps)> _tracedShaderDecodePairs = new(); private static readonly HashSet<(ulong Es, ulong Ps)> _tracedShaderDecodePairs = new();
private static readonly HashSet<(ulong Es, ulong Ps, ulong Target)> _tracedShaderDraws = new(); private static readonly HashSet<(ulong Es, ulong Ps, ulong Target, ulong Texture, uint VertexCount)> _tracedShaderDraws = new();
private static readonly HashSet<(ulong Ps, string Error)> _tracedShaderFailures = new(); private static readonly HashSet<(ulong Ps, string Error)> _tracedShaderFailures = new();
private static readonly HashSet<(int Handle, int Index, ulong Address, string Path)> _tracedDisplayBuffers = new(); private static readonly HashSet<(int Handle, int Index, ulong Address, string Path)> _tracedDisplayBuffers = new();
private static readonly HashSet<ulong> _tracedComputeShaders = new(); private static readonly HashSet<ulong> _tracedComputeShaders = new();
@@ -155,6 +155,16 @@ public static class AgcExports
(ulong Cs, ulong State, uint LocalX, uint LocalY, uint LocalZ), (ulong Cs, ulong State, uint LocalX, uint LocalY, uint LocalZ),
byte[]> _computeSpirvCache = new(); byte[]> _computeSpirvCache = new();
private static readonly Dictionary<ulong, ulong> _shaderHeadersByCode = new(); private static readonly Dictionary<ulong, ulong> _shaderHeadersByCode = new();
private static readonly bool _traceAgc = string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"),
"1",
StringComparison.Ordinal);
private static readonly bool _traceAgcShader =
_traceAgc ||
string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC_SHADER"),
"1",
StringComparison.Ordinal);
private static long _dcbWriteDataTraceCount; private static long _dcbWriteDataTraceCount;
private static long _dcbWaitRegMemTraceCount; private static long _dcbWaitRegMemTraceCount;
private static long _createShaderTraceCount; private static long _createShaderTraceCount;
@@ -2788,7 +2798,7 @@ public static class AgcExports
$"ps=0x{(hasPixelShader ? pixelShaderAddress : 0):X16}"); $"ps=0x{(hasPixelShader ? pixelShaderAddress : 0):X16}");
} }
if (vertexCount is not (3 or 4 or 6)) if (vertexCount == 0 || vertexCount > 1_048_576)
{ {
return; return;
} }
@@ -2872,8 +2882,9 @@ public static class AgcExports
lock (_submitTraceGate) lock (_submitTraceGate)
{ {
var firstTextureAddress = translatedDraw.Textures.FirstOrDefault()?.Descriptor.Address ?? 0;
if (_tracedShaderDraws.Add( if (_tracedShaderDraws.Add(
(exportShaderAddress, pixelShaderAddress, firstTarget.Address))) (exportShaderAddress, pixelShaderAddress, firstTarget.Address, firstTextureAddress, vertexCount)))
{ {
TraceTranslatedGuestDraw( TraceTranslatedGuestDraw(
ctx, ctx,
@@ -3033,6 +3044,11 @@ public static class AgcExports
return false; return false;
} }
TraceAgcShader(
$"agc.texture_binding ps=0x{pixelShaderAddress:X16} es=0x{exportShaderAddress:X16} " +
$"pc=0x{binding.Pc:X} op={binding.Opcode} storage={(Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode) ? 1 : 0)} " +
$"decoded={FormatTextureDescriptor(texture)} " +
$"raw={FormatShaderDwords(binding.ResourceDescriptor)} sampler={FormatShaderDwords(binding.SamplerDescriptor)}");
textures.Add( textures.Add(
new TranslatedImageBinding( new TranslatedImageBinding(
texture, texture,
@@ -3477,7 +3493,8 @@ public static class AgcExports
',', ',',
draw.VertexInputs.Select(input => draw.VertexInputs.Select(input =>
$"{input.Location}:pc=0x{input.Pc:X}:0x{input.BaseAddress:X16}" + $"{input.Location}:pc=0x{input.Pc:X}:0x{input.BaseAddress:X16}" +
$":stride{input.Stride}:off{input.OffsetBytes}:c{input.ComponentCount}")); $":stride{input.Stride}:off{input.OffsetBytes}:c{input.ComponentCount}" +
$":fmt{input.DataFormat}/num{input.NumberFormat}"));
var scissor = draw.RenderState.Scissor is { } drawScissor var scissor = draw.RenderState.Scissor is { } drawScissor
? $"{drawScissor.X},{drawScissor.Y},{drawScissor.Width}x{drawScissor.Height}" ? $"{drawScissor.X},{drawScissor.Y},{drawScissor.Width}x{drawScissor.Height}"
: "full"; : "full";
@@ -3486,6 +3503,29 @@ public static class AgcExports
$"{drawViewport.Width:0.###}x{drawViewport.Height:0.###}:" + $"{drawViewport.Width:0.###}x{drawViewport.Height:0.###}:" +
$"{drawViewport.MinDepth:0.###}-{drawViewport.MaxDepth:0.###}" $"{drawViewport.MinDepth:0.###}-{drawViewport.MaxDepth:0.###}"
: "full"; : "full";
var rasterRegisters = new (string Name, uint Offset)[]
{
("screen_tl", PaScScreenScissorTl),
("screen_br", PaScScreenScissorBr),
("window_off", PaScWindowOffset),
("window_tl", PaScWindowScissorTl),
("window_br", PaScWindowScissorBr),
("generic_tl", PaScGenericScissorTl),
("generic_br", PaScGenericScissorBr),
("vport_tl", PaScVportScissor0Tl),
("vport_br", PaScVportScissor0Br),
("mode", PaScModeCntl0),
("xscale", PaClVportXScale),
("xoffset", PaClVportXOffset),
("yscale", PaClVportYScale),
("yoffset", PaClVportYOffset),
};
var raster = string.Join(
',',
rasterRegisters.Select(entry =>
state.CxRegisters.TryGetValue(entry.Offset, out var value)
? $"{entry.Name}=0x{value:X8}"
: $"{entry.Name}=missing"));
var blend = draw.RenderState.Blend; var blend = draw.RenderState.Blend;
TraceAgcShader( TraceAgcShader(
$"agc.shader_draw es=0x{draw.ExportShaderAddress:X16} " + $"agc.shader_draw es=0x{draw.ExportShaderAddress:X16} " +
@@ -3493,6 +3533,7 @@ public static class AgcExports
$"primitive=0x{draw.PrimitiveType:X} " + $"primitive=0x{draw.PrimitiveType:X} " +
$"blend={(blend.Enable ? 1 : 0)}:{blend.ColorSrcFactor}/{blend.ColorDstFactor}/{blend.ColorFunc} " + $"blend={(blend.Enable ? 1 : 0)}:{blend.ColorSrcFactor}/{blend.ColorDstFactor}/{blend.ColorFunc} " +
$"write_mask=0x{blend.WriteMask:X} scissor={scissor} viewport={viewport} " + $"write_mask=0x{blend.WriteMask:X} scissor={scissor} viewport={viewport} " +
$"raster=[{raster}] " +
$"ps_ena=0x{psInputEna:X8} ps_addr=0x{psInputAddr:X8} " + $"ps_ena=0x{psInputEna:X8} ps_addr=0x{psInputAddr:X8} " +
$"targets=[{targets}] textures=[{textures}] " + $"targets=[{targets}] textures=[{textures}] " +
$"buffers=[{buffers}] vertex=[{vertexInputs}] indices=[{indices}]"); $"buffers=[{buffers}] vertex=[{vertexInputs}] indices=[{indices}]");
@@ -3550,6 +3591,8 @@ public static class AgcExports
buffers[index] = new VulkanGuestVertexBuffer( buffers[index] = new VulkanGuestVertexBuffer(
binding.Location, binding.Location,
binding.ComponentCount, binding.ComponentCount,
binding.DataFormat,
binding.NumberFormat,
binding.BaseAddress, binding.BaseAddress,
binding.Stride, binding.Stride,
binding.OffsetBytes, binding.OffsetBytes,
@@ -3579,7 +3622,10 @@ public static class AgcExports
} }
var sourceWidth = descriptor.TileMode == 0 var sourceWidth = descriptor.TileMode == 0
? Math.Max(descriptor.Width, descriptor.Pitch) ? GetLinearTexturePitch(
Math.Max(descriptor.Width, descriptor.Pitch),
descriptor.Height,
descriptor.Format)
: descriptor.Width; : descriptor.Width;
var sourceByteCount = GetTextureByteCount( var sourceByteCount = GetTextureByteCount(
descriptor.Format, descriptor.Format,
@@ -3593,6 +3639,31 @@ public static class AgcExports
return true; 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) if (isStorage)
{ {
var initialPixels = Array.Empty<byte>(); var initialPixels = Array.Empty<byte>();
@@ -3617,7 +3688,7 @@ public static class AgcExports
IsStorage: true, IsStorage: true,
MipLevels: descriptor.MipLevels, MipLevels: descriptor.MipLevels,
MipLevel: mipLevel, MipLevel: mipLevel,
Pitch: descriptor.Pitch, Pitch: sourceWidth,
TileMode: descriptor.TileMode, TileMode: descriptor.TileMode,
DstSelect: descriptor.DstSelect, DstSelect: descriptor.DstSelect,
Sampler: ToVulkanSampler(samplerDescriptor)); Sampler: ToVulkanSampler(samplerDescriptor));
@@ -3663,7 +3734,7 @@ public static class AgcExports
IsStorage: isStorage, IsStorage: isStorage,
MipLevels: descriptor.MipLevels, MipLevels: descriptor.MipLevels,
MipLevel: mipLevel, MipLevel: mipLevel,
Pitch: descriptor.Pitch, Pitch: sourceWidth,
TileMode: descriptor.TileMode, TileMode: descriptor.TileMode,
DstSelect: descriptor.DstSelect, DstSelect: descriptor.DstSelect,
Sampler: ToVulkanSampler(samplerDescriptor)); Sampler: ToVulkanSampler(samplerDescriptor));
@@ -4326,6 +4397,28 @@ public static class AgcExports
: checked(((ulong)width + 3) / 4 * (((ulong)height + 3) / 4) * blockBytes); : checked(((ulong)width + 3) / 4 * (((ulong)height + 3) / 4) * blockBytes);
} }
private static uint GetLinearTexturePitch(uint pitch, uint height, uint format)
{
var bytesPerTexel = GetTextureBytesPerTexel(format);
if (bytesPerTexel == 0 || height == 0)
{
return pitch;
}
var pitchAlignment = Math.Max(8UL, 64UL / bytesPerTexel);
var alignedPitch = AlignUp(pitch, pitchAlignment);
var sliceAlignment = Math.Max(64UL, 256UL / bytesPerTexel);
while ((alignedPitch * height) % sliceAlignment != 0)
{
alignedPitch += pitchAlignment;
}
return checked((uint)alignedPitch);
}
private static ulong AlignUp(ulong value, ulong alignment) =>
(value + alignment - 1) & ~(alignment - 1);
private static void TraceShaderTranslationMiss( private static void TraceShaderTranslationMiss(
CpuContext ctx, CpuContext ctx,
SubmittedDcbState state, SubmittedDcbState state,
@@ -4594,6 +4687,10 @@ public static class AgcExports
return false; return false;
} }
// GFX10/RDNA2 T# layout: WIDTH is split across word1[31:30] (lo 2 bits)
// and word2[11:0] (hi 12 bits); FORMAT is the combined 9-bit field at
// word1[28:20]. Verified against Kyty's decode of the same game
// descriptors (fmt=56=8_8_8_8_UNORM, extent 1280x720, sw_mode 27).
// GNM T# exposes a 38-bit baseaddr256 field, but RPCSX and the // GNM T# exposes a 38-bit baseaddr256 field, but RPCSX and the
// Demon's Souls descriptors both show that only the low 32 bits are // Demon's Souls descriptors both show that only the low 32 bits are
// part of the guest GPU VA. The upper baseaddr bits carry resource // part of the guest GPU VA. The upper baseaddr bits carry resource
@@ -5343,7 +5440,7 @@ public static class AgcExports
private static void TraceAgc(string message) private static void TraceAgc(string message)
{ {
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal)) if (!_traceAgc)
{ {
return; return;
} }
@@ -5353,8 +5450,7 @@ public static class AgcExports
private static void TraceAgcShader(string message) private static void TraceAgcShader(string message)
{ {
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal) && if (!_traceAgcShader)
!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC_SHADER"), "1", StringComparison.Ordinal))
{ {
return; return;
} }
@@ -5367,6 +5463,12 @@ public static class AgcExports
? "none" ? "none"
: string.Join(',', values.Select(static value => $"{value:X8}")); : string.Join(',', values.Select(static value => $"{value:X8}"));
private static string FormatTextureDescriptor(TextureDescriptor descriptor) =>
$"addr=0x{descriptor.Address:X16} {descriptor.Width}x{descriptor.Height} " +
$"fmt={descriptor.Format} num={descriptor.NumberType} tile={descriptor.TileMode} " +
$"type={descriptor.Type} levels={descriptor.BaseLevel}-{descriptor.LastLevel} " +
$"pitch={descriptor.Pitch} dst=0x{descriptor.DstSelect:X3}";
private static void DumpSpirv( private static void DumpSpirv(
string stage, string stage,
ulong shaderAddress, ulong shaderAddress,
+2
View File
@@ -276,6 +276,8 @@ internal sealed record Gen5VertexInputBinding(
uint Pc, uint Pc,
uint Location, uint Location,
uint ComponentCount, uint ComponentCount,
uint DataFormat,
uint NumberFormat,
ulong BaseAddress, ulong BaseAddress,
uint Stride, uint Stride,
uint OffsetBytes, uint OffsetBytes,
@@ -20,7 +20,9 @@ internal static class Gen5ShaderScalarEvaluator
ulong BaseAddress, ulong BaseAddress,
uint Stride, uint Stride,
uint NumRecords, uint NumRecords,
ulong SizeBytes); ulong SizeBytes,
uint NumberFormat,
uint DataFormat);
public static bool TryResolveImageBindings( public static bool TryResolveImageBindings(
CpuContext ctx, CpuContext ctx,
@@ -400,14 +402,21 @@ internal static class Gen5ShaderScalarEvaluator
return false; return false;
} }
var bindingData = data;
var bindingStride = descriptor.Stride;
var bindingOffset = unchecked((uint)control.OffsetBytes + scalarOffset);
var bindingDataFormat = descriptor.DataFormat;
var bindingNumberFormat = descriptor.NumberFormat;
binding = new Gen5VertexInputBinding( binding = new Gen5VertexInputBinding(
instruction.Pc, instruction.Pc,
location, location,
control.DwordCount, control.DwordCount,
bindingDataFormat,
bindingNumberFormat,
descriptor.BaseAddress, descriptor.BaseAddress,
descriptor.Stride, bindingStride,
unchecked((uint)control.OffsetBytes + scalarOffset), bindingOffset,
data); bindingData);
return true; return true;
} }
@@ -803,7 +812,11 @@ internal static class Gen5ShaderScalarEvaluator
"SFF1I32B32" => left == 0 ? uint.MaxValue : (uint)BitOperations.TrailingZeroCount(left), "SFF1I32B32" => left == 0 ? uint.MaxValue : (uint)BitOperations.TrailingZeroCount(left),
_ => registers[destination.Value] | (1u << ((int)left & 31)), _ => registers[destination.Value] | (1u << ((int)left & 31)),
}; };
scalarConditionCode = registers[destination.Value] != 0; if (instruction.Opcode != "SBitset1B32")
{
scalarConditionCode = registers[destination.Value] != 0;
}
return true; return true;
} }
@@ -829,13 +842,15 @@ internal static class Gen5ShaderScalarEvaluator
} }
case "SSubU32": case "SSubU32":
result = left - right; result = left - right;
scalarConditionCode = left >= right; scalarConditionCode = right > left;
break; break;
case "SAddI32": case "SAddI32":
result = unchecked((uint)((int)left + (int)right)); result = unchecked((uint)((int)left + (int)right));
scalarConditionCode = SignedAddOverflow(left, right, result);
break; break;
case "SSubI32": case "SSubI32":
result = unchecked((uint)((int)left - (int)right)); result = unchecked((uint)((int)left - (int)right));
scalarConditionCode = SignedSubOverflow(left, right, result);
break; break;
case "SAddcU32": case "SAddcU32":
{ {
@@ -846,23 +861,27 @@ internal static class Gen5ShaderScalarEvaluator
} }
case "SSubbU32": case "SSubbU32":
{ {
var borrow = scalarConditionCode ? 0UL : 1UL; var borrow = scalarConditionCode ? 1UL : 0UL;
var subtrahend = (ulong)right + borrow; var subtrahend = (ulong)right + borrow;
result = unchecked(left - (uint)subtrahend); result = unchecked(left - (uint)subtrahend);
scalarConditionCode = left >= subtrahend; scalarConditionCode = subtrahend > left;
break; break;
} }
case "SMinI32": case "SMinI32":
result = unchecked((uint)Math.Min((int)left, (int)right)); result = unchecked((uint)Math.Min((int)left, (int)right));
scalarConditionCode = (int)left < (int)right;
break; break;
case "SMinU32": case "SMinU32":
result = Math.Min(left, right); result = Math.Min(left, right);
scalarConditionCode = left < right;
break; break;
case "SMaxI32": case "SMaxI32":
result = unchecked((uint)Math.Max((int)left, (int)right)); result = unchecked((uint)Math.Max((int)left, (int)right));
scalarConditionCode = (int)left > (int)right;
break; break;
case "SMaxU32": case "SMaxU32":
result = Math.Max(left, right); result = Math.Max(left, right);
scalarConditionCode = left > right;
break; break;
case "SCselectB32": case "SCselectB32":
result = scalarConditionCode ? left : right; result = scalarConditionCode ? left : right;
@@ -926,6 +945,7 @@ internal static class Gen5ShaderScalarEvaluator
var offset = (int)right & 31; var offset = (int)right & 31;
var width = Math.Min(((int)right >> 16) & 0x7F, 32 - offset); var width = Math.Min(((int)right >> 16) & 0x7F, 32 - offset);
result = width == 0 ? 0 : left >> offset & (uint.MaxValue >> (32 - width)); result = width == 0 ? 0 : left >> offset & (uint.MaxValue >> (32 - width));
scalarConditionCode = result != 0;
break; break;
} }
case "SBfeI32": case "SBfeI32":
@@ -935,23 +955,41 @@ internal static class Gen5ShaderScalarEvaluator
result = width == 0 result = width == 0
? 0 ? 0
: unchecked((uint)(((int)(left << (32 - width - offset))) >> (32 - width))); : unchecked((uint)(((int)(left << (32 - width - offset))) >> (32 - width)));
scalarConditionCode = result != 0;
break; break;
} }
case "SAbsdiffI32": case "SAbsdiffI32":
result = unchecked((uint)Math.Abs((long)(int)left - (int)right)); result = unchecked((uint)Math.Abs((long)(int)left - (int)right));
scalarConditionCode = result != 0;
break; break;
case "SLshl1AddU32": case "SLshl1AddU32":
result = (left << 1) + right; {
break; var wide = ((ulong)left << 1) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SLshl2AddU32": case "SLshl2AddU32":
result = (left << 2) + right; {
break; var wide = ((ulong)left << 2) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SLshl3AddU32": case "SLshl3AddU32":
result = (left << 3) + right; {
break; var wide = ((ulong)left << 3) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SLshl4AddU32": case "SLshl4AddU32":
result = (left << 4) + right; {
break; var wide = ((ulong)left << 4) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SPackLlB32B16": case "SPackLlB32B16":
result = (left & 0xFFFFu) | (right << 16); result = (left & 0xFFFFu) | (right << 16);
break; break;
@@ -993,7 +1031,8 @@ internal static class Gen5ShaderScalarEvaluator
"SNandSaveexecB64" or "SNandSaveexecB64" or
"SNorSaveexecB64" or "SNorSaveexecB64" or
"SXnorSaveexecB64" or "SXnorSaveexecB64" or
"SAndn1SaveexecB64")) "SAndn1SaveexecB64" or
"SOrn1SaveexecB64"))
{ {
return false; return false;
} }
@@ -1021,11 +1060,12 @@ internal static class Gen5ShaderScalarEvaluator
"SAndSaveexecB64" => oldExec & source, "SAndSaveexecB64" => oldExec & source,
"SOrSaveexecB64" => oldExec | source, "SOrSaveexecB64" => oldExec | source,
"SXorSaveexecB64" => oldExec ^ source, "SXorSaveexecB64" => oldExec ^ source,
"SAndn1SaveexecB64" => ~oldExec & source, "SAndn1SaveexecB64" => ~source & oldExec,
"SAndn2SaveexecB64" => oldExec & ~source, "SAndn2SaveexecB64" => source & ~oldExec,
"SOrn2SaveexecB64" => oldExec | ~source, "SOrn1SaveexecB64" => ~source | oldExec,
"SNandSaveexecB64" => ~(oldExec & source), "SOrn2SaveexecB64" => source | ~oldExec,
"SNorSaveexecB64" => ~(oldExec | source), "SNandSaveexecB64" => ~(source & oldExec),
"SNorSaveexecB64" => ~(source | oldExec),
_ => ~(oldExec ^ source), _ => ~(oldExec ^ source),
}; };
@@ -1095,6 +1135,12 @@ internal static class Gen5ShaderScalarEvaluator
private static ulong MaskWaveValue(ulong value) => value & RdnaWaveMask; private static ulong MaskWaveValue(ulong value) => value & RdnaWaveMask;
private static bool SignedAddOverflow(uint left, uint right, uint result) =>
((left ^ result) & (right ^ result) & 0x80000000u) != 0;
private static bool SignedSubOverflow(uint left, uint right, uint result) =>
((left ^ right) & (left ^ result) & 0x80000000u) != 0;
private static bool TryExecuteScalarCompare( private static bool TryExecuteScalarCompare(
Gen5ShaderInstruction instruction, Gen5ShaderInstruction instruction,
uint[] registers, uint[] registers,
@@ -1416,7 +1462,7 @@ internal static class Gen5ShaderScalarEvaluator
word2 == 0 && word2 == 0 &&
word3 == 0) word3 == 0)
{ {
descriptor = new BufferDescriptor(0, 0, 0, 0); descriptor = new BufferDescriptor(0, 0, 0, 0, 0, 0);
return true; return true;
} }
@@ -1428,19 +1474,64 @@ internal static class Gen5ShaderScalarEvaluator
return false; return false;
} }
descriptor = new BufferDescriptor(0, 0, 0, 0); descriptor = new BufferDescriptor(0, 0, 0, 0, 0, 0);
return true; return true;
} }
var baseAddress = word0 | ((ulong)(word1 & 0x0FFFu) << 32); var baseAddress = word0 | ((ulong)(word1 & 0xFFFFu) << 32);
var stride = (word1 >> 16) & 0x3FFFu; var stride = (word1 >> 16) & 0x3FFFu;
var unifiedFormat = (word3 >> 12) & 0x7Fu;
var (dataFormat, numberFormat) =
DecodeGfx10BufferFormat(unifiedFormat);
var sizeBytes = stride == 0 var sizeBytes = stride == 0
? word2 ? word2
: (ulong)stride * word2; : (ulong)stride * word2;
descriptor = new BufferDescriptor(baseAddress, stride, word2, sizeBytes); descriptor = new BufferDescriptor(baseAddress, stride, word2, sizeBytes, numberFormat, dataFormat);
return true; return true;
} }
private static (uint DataFormat, uint NumberFormat)
DecodeGfx10BufferFormat(uint format) =>
format switch
{
0 => (0, 0),
>= 1 and <= 6 => (1, format - 1),
>= 7 and <= 13 => (2, DecodeUnifiedNumber(format - 7, 7)),
>= 14 and <= 19 => (3, format - 14),
>= 20 and <= 22 => (4, DecodeIntegerOrFloatNumber(format - 20)),
>= 23 and <= 29 => (5, DecodeUnifiedNumber(format - 23, 7)),
>= 30 and <= 36 => (6, DecodeUnifiedNumber(format - 30, 7)),
>= 37 and <= 43 => (7, DecodeUnifiedNumber(format - 37, 7)),
>= 44 and <= 49 => (8, format - 44),
>= 50 and <= 55 => (9, format - 50),
>= 56 and <= 61 => (10, format - 56),
>= 62 and <= 64 => (11, DecodeIntegerOrFloatNumber(format - 62)),
>= 65 and <= 71 => (12, DecodeUnifiedNumber(format - 65, 7)),
>= 72 and <= 74 => (13, DecodeIntegerOrFloatNumber(format - 72)),
>= 75 and <= 77 => (14, DecodeIntegerOrFloatNumber(format - 75)),
128 => (1, 9),
129 => (3, 9),
130 => (10, 9),
132 => (34, 7),
133 => (16, 0),
134 => (17, 0),
135 => (18, 0),
136 => (19, 0),
140 => (4, 7),
_ => (0, 0),
};
private static uint DecodeUnifiedNumber(uint offset, uint formatCount) =>
offset == formatCount - 1 ? 7u : offset;
private static uint DecodeIntegerOrFloatNumber(uint offset) =>
offset switch
{
0 => 4,
1 => 5,
_ => 7,
};
private static bool TryReadUserDataScalarLoad( private static bool TryReadUserDataScalarLoad(
Gen5ShaderState state, Gen5ShaderState state,
Gen5ShaderInstruction instruction, Gen5ShaderInstruction instruction,
+50 -5
View File
@@ -4,6 +4,7 @@
using SharpEmu.HLE; using SharpEmu.HLE;
using SharpEmu.Libs.VideoOut; using SharpEmu.Libs.VideoOut;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
namespace SharpEmu.Libs.Agc; namespace SharpEmu.Libs.Agc;
@@ -13,6 +14,14 @@ internal static class Gen5ShaderTranslator
private const int MaxInstructions = 4096; private const int MaxInstructions = 4096;
private const int MinimumUserDataDwords = 16; private const int MinimumUserDataDwords = 16;
private const int MaximumUserDataDwords = 256; private const int MaximumUserDataDwords = 256;
private static readonly ConditionalWeakTable<object, ShaderDecodeCache> _decodeCaches = new();
private sealed class ShaderDecodeCache
{
public object Gate { get; } = new();
public Dictionary<ulong, Gen5ShaderProgram> Programs { get; } = new();
public Dictionary<ulong, Gen5ShaderMetadata?> Metadata { get; } = new();
}
private static readonly uint[] FullscreenBarycentricEs = private static readonly uint[] FullscreenBarycentricEs =
[ [
@@ -117,16 +126,51 @@ internal static class Gen5ShaderTranslator
uint userDataScalarRegisterBase = 0) uint userDataScalarRegisterBase = 0)
{ {
state = default!; state = default!;
if (!TryDecodeProgram(ctx, shaderAddress, out var program, out error)) error = string.Empty;
var cache = _decodeCaches.GetValue(ctx.Memory, static _ => new ShaderDecodeCache());
Gen5ShaderProgram? program;
lock (cache.Gate)
{ {
return false; cache.Programs.TryGetValue(shaderAddress, out program);
}
if (program is null)
{
if (!TryDecodeProgram(ctx, shaderAddress, out program, out error))
{
return false;
}
lock (cache.Gate)
{
cache.Programs.TryAdd(shaderAddress, program);
}
} }
Gen5ShaderMetadata? metadata = null; Gen5ShaderMetadata? metadata = null;
if (shaderHeaderAddress != 0 && if (shaderHeaderAddress != 0)
Gen5ShaderMetadataReader.TryRead(ctx, shaderHeaderAddress, out var decodedMetadata))
{ {
metadata = decodedMetadata; var metadataCached = false;
lock (cache.Gate)
{
metadataCached = cache.Metadata.TryGetValue(shaderHeaderAddress, out metadata);
}
if (!metadataCached)
{
if (Gen5ShaderMetadataReader.TryRead(
ctx,
shaderHeaderAddress,
out var decodedMetadata))
{
metadata = decodedMetadata;
}
lock (cache.Gate)
{
cache.Metadata.TryAdd(shaderHeaderAddress, metadata);
}
}
} }
var userData = new uint[GetUserDataDwordCount(metadata)]; var userData = new uint[GetUserDataDwordCount(metadata)];
@@ -452,6 +496,7 @@ internal static class Gen5ShaderTranslator
0x2A => "SNorSaveexecB64", 0x2A => "SNorSaveexecB64",
0x2B => "SXnorSaveexecB64", 0x2B => "SXnorSaveexecB64",
0x37 => "SAndn1SaveexecB64", 0x37 => "SAndn1SaveexecB64",
0x38 => "SOrn1SaveexecB64",
_ => string.Empty, _ => string.Empty,
}; };
+267 -25
View File
@@ -212,12 +212,26 @@ internal static partial class Gen5SpirvTranslator
case "VSinF32": case "VSinF32":
result = EmitFloatResult( result = EmitFloatResult(
instruction, instruction,
Ext(13, _floatType, GetFloatSource(instruction, 0))); Ext(
13,
_floatType,
_module.AddInstruction(
SpirvOp.FMul,
_floatType,
GetFloatSource(instruction, 0),
Float(MathF.Tau))));
break; break;
case "VCosF32": case "VCosF32":
result = EmitFloatResult( result = EmitFloatResult(
instruction, instruction,
Ext(14, _floatType, GetFloatSource(instruction, 0))); Ext(
14,
_floatType,
_module.AddInstruction(
SpirvOp.FMul,
_floatType,
GetFloatSource(instruction, 0),
Float(MathF.Tau))));
break; break;
case "VAddF32": case "VAddF32":
result = EmitFloatBinary(instruction, SpirvOp.FAdd); result = EmitFloatBinary(instruction, SpirvOp.FAdd);
@@ -635,13 +649,29 @@ internal static partial class Gen5SpirvTranslator
width); width);
break; break;
} }
case "VBfiB32":
{
var mask = GetRawSource(instruction, 0);
var insert = GetRawSource(instruction, 1);
var source = GetRawSource(instruction, 2);
result = _module.AddInstruction(
SpirvOp.BitwiseOr,
_uintType,
BitwiseAnd(mask, insert),
BitwiseAnd(
_module.AddInstruction(SpirvOp.Not, _uintType, mask),
source));
break;
}
case "VCvtPkrtzF16F32": case "VCvtPkrtzF16F32":
{ {
var first = TruncateFloat32ForPack(GetFloatSource(instruction, 0));
var second = TruncateFloat32ForPack(GetFloatSource(instruction, 1));
var vector = _module.AddInstruction( var vector = _module.AddInstruction(
SpirvOp.CompositeConstruct, SpirvOp.CompositeConstruct,
_vec2Type, _vec2Type,
GetFloatSource(instruction, 0), first,
GetFloatSource(instruction, 1)); second);
result = Ext(58, _uintType, vector); result = Ext(58, _uintType, vector);
break; break;
} }
@@ -945,6 +975,16 @@ internal static partial class Gen5SpirvTranslator
StoreS(destination, result); StoreS(destination, result);
Store(_scc, IsNotZero(result)); Store(_scc, IsNotZero(result));
return true; return true;
case "SBitset1B32":
result = _module.AddInstruction(
SpirvOp.BitFieldInsert,
_uintType,
LoadS(destination),
UInt(1),
BitwiseAnd(left, UInt(31)),
UInt(1));
StoreS(destination, result);
return true;
default: default:
{ {
if (instruction.Sources.Count < 2) if (instruction.Sources.Count < 2)
@@ -971,13 +1011,14 @@ internal static partial class Gen5SpirvTranslator
left, left,
right); right);
Store(_scc, _module.AddInstruction( Store(_scc, _module.AddInstruction(
SpirvOp.UGreaterThanEqual, SpirvOp.UGreaterThan,
_boolType, _boolType,
left, right,
right)); left));
break; break;
case "SAddI32": case "SAddI32":
result = IAdd(left, right); result = IAdd(left, right);
Store(_scc, SignedAddOverflow(left, right, result));
break; break;
case "SSubI32": case "SSubI32":
result = _module.AddInstruction( result = _module.AddInstruction(
@@ -985,6 +1026,7 @@ internal static partial class Gen5SpirvTranslator
_uintType, _uintType,
left, left,
right); right);
Store(_scc, SignedSubOverflow(left, right, result));
break; break;
case "SAddcU32": case "SAddcU32":
{ {
@@ -1021,8 +1063,8 @@ internal static partial class Gen5SpirvTranslator
SpirvOp.Select, SpirvOp.Select,
_uintType, _uintType,
Load(_boolType, _scc), Load(_boolType, _scc),
UInt(0), UInt(1),
UInt(1)); UInt(0));
var partial = _module.AddInstruction( var partial = _module.AddInstruction(
SpirvOp.ISub, SpirvOp.ISub,
_uintType, _uintType,
@@ -1033,23 +1075,31 @@ internal static partial class Gen5SpirvTranslator
_uintType, _uintType,
partial, partial,
borrow); borrow);
var firstNoBorrow = _module.AddInstruction( var firstBorrow = _module.AddInstruction(
SpirvOp.UGreaterThanEqual, SpirvOp.UGreaterThan,
_boolType, _boolType,
left, right,
right); left);
var secondNoBorrow = _module.AddInstruction( var secondBorrow = _module.AddInstruction(
SpirvOp.UGreaterThanEqual, SpirvOp.LogicalAnd,
_boolType, _boolType,
partial, _module.AddInstruction(
borrow); SpirvOp.IEqual,
_boolType,
borrow,
UInt(1)),
_module.AddInstruction(
SpirvOp.IEqual,
_boolType,
right,
left));
Store( Store(
_scc, _scc,
_module.AddInstruction( _module.AddInstruction(
SpirvOp.LogicalAnd, SpirvOp.LogicalOr,
_boolType, _boolType,
firstNoBorrow, firstBorrow,
secondNoBorrow)); secondBorrow));
break; break;
} }
case "SMulI32": case "SMulI32":
@@ -1061,6 +1111,7 @@ internal static partial class Gen5SpirvTranslator
break; break;
case "SAndB32": case "SAndB32":
result = BitwiseAnd(left, right); result = BitwiseAnd(left, right);
Store(_scc, IsNotZero(result));
break; break;
case "SOrB32": case "SOrB32":
result = _module.AddInstruction( result = _module.AddInstruction(
@@ -1068,6 +1119,7 @@ internal static partial class Gen5SpirvTranslator
_uintType, _uintType,
left, left,
right); right);
Store(_scc, IsNotZero(result));
break; break;
case "SXorB32": case "SXorB32":
result = _module.AddInstruction( result = _module.AddInstruction(
@@ -1075,22 +1127,64 @@ internal static partial class Gen5SpirvTranslator
_uintType, _uintType,
left, left,
right); right);
Store(_scc, IsNotZero(result));
break; break;
case "SAndn2B32": case "SAndn2B32":
result = BitwiseAnd( result = BitwiseAnd(
left, left,
_module.AddInstruction(SpirvOp.Not, _uintType, right)); _module.AddInstruction(SpirvOp.Not, _uintType, right));
Store(_scc, IsNotZero(result));
break;
case "SOrn2B32":
result = _module.AddInstruction(
SpirvOp.BitwiseOr,
_uintType,
left,
_module.AddInstruction(SpirvOp.Not, _uintType, right));
Store(_scc, IsNotZero(result));
break;
case "SNandB32":
result = _module.AddInstruction(
SpirvOp.Not,
_uintType,
BitwiseAnd(left, right));
Store(_scc, IsNotZero(result));
break;
case "SNorB32":
result = _module.AddInstruction(
SpirvOp.Not,
_uintType,
_module.AddInstruction(
SpirvOp.BitwiseOr,
_uintType,
left,
right));
Store(_scc, IsNotZero(result));
break;
case "SXnorB32":
result = _module.AddInstruction(
SpirvOp.Not,
_uintType,
_module.AddInstruction(
SpirvOp.BitwiseXor,
_uintType,
left,
right));
Store(_scc, IsNotZero(result));
break; break;
case "SLshlB32": case "SLshlB32":
result = ShiftLeftLogical(left, right); result = ShiftLeftLogical(left, right);
Store(_scc, IsNotZero(result));
break; break;
case "SLshrB32": case "SLshrB32":
result = ShiftRightLogical( result = ShiftRightLogical(
left, left,
BitwiseAnd(right, UInt(31))); BitwiseAnd(right, UInt(31)));
Store(_scc, IsNotZero(result));
break; break;
case "SAshrI32": case "SAshrI32":
result = ShiftRightArithmetic(left, right); result = ShiftRightArithmetic(left, right);
Store(_scc, IsNotZero(result));
break; break;
case "SBfmB32": case "SBfmB32":
result = _module.AddInstruction( result = _module.AddInstruction(
@@ -1133,6 +1227,7 @@ internal static partial class Gen5SpirvTranslator
left, left,
offset, offset,
width); width);
Store(_scc, IsNotZero(result));
break; break;
} }
case "SCselectB32": case "SCselectB32":
@@ -1145,9 +1240,47 @@ internal static partial class Gen5SpirvTranslator
break; break;
case "SMinU32": case "SMinU32":
result = Ext(38, _uintType, left, right); result = Ext(38, _uintType, left, right);
Store(
_scc,
_module.AddInstruction(
SpirvOp.ULessThan,
_boolType,
left,
right));
break;
case "SMinI32":
result = Bitcast(
_uintType,
Ext(39, _intType, Bitcast(_intType, left), Bitcast(_intType, right)));
Store(
_scc,
_module.AddInstruction(
SpirvOp.SLessThan,
_boolType,
Bitcast(_intType, left),
Bitcast(_intType, right)));
break; break;
case "SMaxU32": case "SMaxU32":
result = Ext(41, _uintType, left, right); result = Ext(41, _uintType, left, right);
Store(
_scc,
_module.AddInstruction(
SpirvOp.UGreaterThan,
_boolType,
left,
right));
break;
case "SMaxI32":
result = Bitcast(
_uintType,
Ext(42, _intType, Bitcast(_intType, left), Bitcast(_intType, right)));
Store(
_scc,
_module.AddInstruction(
SpirvOp.SGreaterThan,
_boolType,
Bitcast(_intType, left),
Bitcast(_intType, right)));
break; break;
case "SLshl1AddU32": case "SLshl1AddU32":
case "SLshl2AddU32": case "SLshl2AddU32":
@@ -1292,17 +1425,55 @@ internal static partial class Gen5SpirvTranslator
"SXorSaveexecB64" => _module.AddInstruction( "SXorSaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseXor, _ulongType, oldExec, left), SpirvOp.BitwiseXor, _ulongType, oldExec, left),
"SAndn2SaveexecB64" => _module.AddInstruction( "SAndn2SaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseAnd, _ulongType, oldExec, notLeft),
"SAndn1SaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseAnd, SpirvOp.BitwiseAnd,
_ulongType, _ulongType,
left,
_module.AddInstruction( _module.AddInstruction(
SpirvOp.Not, SpirvOp.Not,
_ulongType, _ulongType,
oldExec), oldExec)),
left), "SAndn1SaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseAnd,
_ulongType,
notLeft,
oldExec),
"SOrn1SaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseOr,
_ulongType,
notLeft,
oldExec),
"SOrn2SaveexecB64" => _module.AddInstruction( "SOrn2SaveexecB64" => _module.AddInstruction(
SpirvOp.BitwiseOr, _ulongType, oldExec, notLeft), SpirvOp.BitwiseOr,
_ulongType,
left,
_module.AddInstruction(
SpirvOp.Not,
_ulongType,
oldExec)),
"SNandSaveexecB64" => _module.AddInstruction(
SpirvOp.Not,
_ulongType,
_module.AddInstruction(
SpirvOp.BitwiseAnd,
_ulongType,
left,
oldExec)),
"SNorSaveexecB64" => _module.AddInstruction(
SpirvOp.Not,
_ulongType,
_module.AddInstruction(
SpirvOp.BitwiseOr,
_ulongType,
left,
oldExec)),
"SXnorSaveexecB64" => _module.AddInstruction(
SpirvOp.Not,
_ulongType,
_module.AddInstruction(
SpirvOp.BitwiseXor,
_ulongType,
left,
oldExec)),
_ => 0u, _ => 0u,
}; };
if (newExec == 0) if (newExec == 0)
@@ -1508,6 +1679,22 @@ internal static partial class Gen5SpirvTranslator
} }
StoreS64(destination, value); StoreS64(destination, value);
if (instruction.Opcode is
"SNotB64" or
"SAndB64" or
"SOrB64" or
"SXorB64" or
"SAndn1B64" or
"SAndn2B64" or
"SOrn1B64" or
"SOrn2B64" or
"SNandB64" or
"SNorB64" or
"SXnorB64")
{
Store(_scc, IsNotZero64(value));
}
return true; return true;
} }
@@ -2067,6 +2254,14 @@ internal static partial class Gen5SpirvTranslator
return Bitcast(_uintType, value); return Bitcast(_uintType, value);
} }
private uint TruncateFloat32ForPack(uint value)
{
var raw = BitwiseAnd(
Bitcast(_uintType, value),
UInt(0xFFFF_E000));
return Bitcast(_floatType, raw);
}
private uint Ext(uint operation, uint resultType, params uint[] operands) private uint Ext(uint operation, uint resultType, params uint[] operands)
{ {
var values = new uint[2 + operands.Length]; var values = new uint[2 + operands.Length];
@@ -2086,6 +2281,53 @@ internal static partial class Gen5SpirvTranslator
value, value,
_module.Constant64(_ulongType, 0)); _module.Constant64(_ulongType, 0));
private uint SignBit(uint value) =>
ShiftRightLogical(value, UInt(31));
private uint SignedAddOverflow(uint left, uint right, uint result)
{
var leftSign = SignBit(left);
var rightSign = SignBit(right);
var resultSign = SignBit(result);
var sameSourceSign = _module.AddInstruction(
SpirvOp.IEqual,
_boolType,
leftSign,
rightSign);
var resultSignChanged = _module.AddInstruction(
SpirvOp.INotEqual,
_boolType,
leftSign,
resultSign);
return _module.AddInstruction(
SpirvOp.LogicalAnd,
_boolType,
sameSourceSign,
resultSignChanged);
}
private uint SignedSubOverflow(uint left, uint right, uint result)
{
var leftSign = SignBit(left);
var rightSign = SignBit(right);
var resultSign = SignBit(result);
var differentSourceSign = _module.AddInstruction(
SpirvOp.INotEqual,
_boolType,
leftSign,
rightSign);
var resultSignChanged = _module.AddInstruction(
SpirvOp.INotEqual,
_boolType,
leftSign,
resultSign);
return _module.AddInstruction(
SpirvOp.LogicalAnd,
_boolType,
differentSourceSign,
resultSignChanged);
}
private static bool TryDecodeInlineConstant(uint encoded, out uint value) private static bool TryDecodeInlineConstant(uint encoded, out uint value)
{ {
if (encoded == 125) if (encoded == 125)
+69 -4
View File
@@ -4,6 +4,7 @@
using SharpEmu.HLE; using SharpEmu.HLE;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices;
namespace SharpEmu.Libs.Pad; namespace SharpEmu.Libs.Pad;
@@ -58,6 +59,7 @@ public static class PadExports
return SetReturn(ctx, OrbisPadErrorDeviceNotConnected); return SetReturn(ctx, OrbisPadErrorDeviceNotConnected);
} }
Console.Error.WriteLine("[LOADER][INFO] Keyboard controls: Arrow keys = D-pad, WASD = left stick, IJKL = right stick, Z/Enter = Cross, X/Esc = Circle, C = Square, V = Triangle, Q = L1, E = R1, R = L2, F = R2, Tab/Backspace = Options");
return SetReturn(ctx, PrimaryPadHandle); return SetReturn(ctx, PrimaryPadHandle);
} }
@@ -162,10 +164,19 @@ public static class PadExports
{ {
Span<byte> data = stackalloc byte[PadDataSize]; Span<byte> data = stackalloc byte[PadDataSize];
data.Clear(); data.Clear();
data[0x04] = 128; var acceptsKeyboardInput = IsEmulatorWindowFocused();
data[0x05] = 128; var buttons = acceptsKeyboardInput ? ReadKeyboardButtons() : 0;
data[0x06] = 128; BinaryPrimitives.WriteUInt32LittleEndian(data[0x00..], buttons);
data[0x07] = 128; var leftX = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x41), IsKeyDown(0x44)) : (byte)128;
var leftY = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x57), IsKeyDown(0x53)) : (byte)128;
var rightX = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x4A), IsKeyDown(0x4C)) : (byte)128;
var rightY = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x49), IsKeyDown(0x4B)) : (byte)128;
data[0x04] = leftX;
data[0x05] = leftY;
data[0x06] = rightX;
data[0x07] = rightY;
data[0x08] = acceptsKeyboardInput && IsKeyDown(0x52) ? (byte)255 : (byte)0;
data[0x09] = acceptsKeyboardInput && IsKeyDown(0x46) ? (byte)255 : (byte)0;
BinaryPrimitives.WriteSingleLittleEndian(data[0x18..], 1.0f); BinaryPrimitives.WriteSingleLittleEndian(data[0x18..], 1.0f);
data[0x4C] = 1; data[0x4C] = 1;
var timestampTicks = Stopwatch.GetTimestamp(); var timestampTicks = Stopwatch.GetTimestamp();
@@ -185,4 +196,58 @@ public static class PadExports
ctx[CpuRegister.Rax] = unchecked((ulong)result); ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result; return result;
} }
[DllImport("user32.dll")]
private static extern short GetAsyncKeyState(int vKey);
[DllImport("user32.dll")]
private static extern nint GetForegroundWindow();
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(nint hWnd, out uint processId);
private static bool IsKeyDown(int vk) =>
(GetAsyncKeyState(vk) & 0x8000) != 0;
private static bool IsEmulatorWindowFocused()
{
var foregroundWindow = GetForegroundWindow();
if (foregroundWindow == 0)
{
return false;
}
GetWindowThreadProcessId(foregroundWindow, out var processId);
return processId == (uint)Environment.ProcessId;
}
private static uint ReadKeyboardButtons()
{
uint buttons = 0;
// D-pad
if (IsKeyDown(0x25)) buttons |= 0x0080; // Left
if (IsKeyDown(0x27)) buttons |= 0x0020; // Right
if (IsKeyDown(0x26)) buttons |= 0x0010; // Up
if (IsKeyDown(0x28)) buttons |= 0x0040; // Down
// Face buttons
if (IsKeyDown(0x5A) || IsKeyDown(0x0D)) buttons |= 0x4000; // Z / Enter = Cross
if (IsKeyDown(0x58) || IsKeyDown(0x1B)) buttons |= 0x2000; // X / Escape = Circle
if (IsKeyDown(0x43)) buttons |= 0x8000; // C = Square
if (IsKeyDown(0x56)) buttons |= 0x1000; // V = Triangle
// Shoulder buttons
if (IsKeyDown(0x51)) buttons |= 0x0400; // Q = L1
if (IsKeyDown(0x45)) buttons |= 0x0800; // E = R1
if (IsKeyDown(0x52)) buttons |= 0x0100; // R = L2 (digital)
if (IsKeyDown(0x46)) buttons |= 0x0200; // F = R2 (digital)
// Options (Start)
if (IsKeyDown(0x09) || IsKeyDown(0x08)) buttons |= 0x0008; // Tab / Backspace = Options
return buttons;
}
private static byte ReadAnalogStick(bool negative, bool positive)
{
if (negative && !positive) return 0;
if (positive && !negative) return 255;
return 128;
}
} }
@@ -400,7 +400,7 @@ public static class SaveDataExports
{ {
var configured = Environment.GetEnvironmentVariable("SHARPEMU_SAVEDATA_DIR"); var configured = Environment.GetEnvironmentVariable("SHARPEMU_SAVEDATA_DIR");
var root = string.IsNullOrWhiteSpace(configured) var root = string.IsNullOrWhiteSpace(configured)
? Path.Combine(Environment.CurrentDirectory, "user", "savedata") ? Path.Combine(AppContext.BaseDirectory, "user", "savedata")
: configured; : configured;
return Path.GetFullPath(root); return Path.GetFullPath(root);
} }
@@ -4,6 +4,7 @@
using SharpEmu.HLE; using SharpEmu.HLE;
using SharpEmu.Libs.Kernel; using SharpEmu.Libs.Kernel;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Threading; using System.Threading;
@@ -48,6 +49,13 @@ public static class VideoOutExports
private static int _frameDumpCount; private static int _frameDumpCount;
private static long _nextFrameDumpIndex; private static long _nextFrameDumpIndex;
private static string _windowTitle = "SharpEmu VideoOut"; private static string _windowTitle = "SharpEmu VideoOut";
private static readonly bool _logFrameRate = string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_VIDEOOUT_FPS"),
"1",
StringComparison.Ordinal);
private static long _frameRateWindowStart = Stopwatch.GetTimestamp();
private static long _submittedFrameCount;
private static long _presentedFrameCount;
public static void ConfigureApplicationInfo(string? title, string? titleId, string? version) public static void ConfigureApplicationInfo(string? title, string? titleId, string? version)
{ {
@@ -812,9 +820,46 @@ public static class VideoOutExports
} }
TraceVideoOut($"videoout.submit_flip handle={handle} index={bufferIndex} mode={flipMode} arg={flipArg} events={flipEvents.Count}"); TraceVideoOut($"videoout.submit_flip handle={handle} index={bufferIndex} mode={flipMode} arg={flipArg} events={flipEvents.Count}");
ReportFrameRate(presented: false);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
internal static void ReportPresentedFrame() =>
ReportFrameRate(presented: true);
private static void ReportFrameRate(bool presented)
{
if (!_logFrameRate)
{
return;
}
if (presented)
{
Interlocked.Increment(ref _presentedFrameCount);
}
else
{
Interlocked.Increment(ref _submittedFrameCount);
}
var started = Volatile.Read(ref _frameRateWindowStart);
var now = Stopwatch.GetTimestamp();
var elapsedTicks = now - started;
if (elapsedTicks < Stopwatch.Frequency ||
Interlocked.CompareExchange(ref _frameRateWindowStart, now, started) != started)
{
return;
}
var elapsedSeconds = (double)elapsedTicks / Stopwatch.Frequency;
var submitted = Interlocked.Exchange(ref _submittedFrameCount, 0);
var presentedCount = Interlocked.Exchange(ref _presentedFrameCount, 0);
Console.Error.WriteLine(
$"[LOADER][PERF] videoout submitted_fps={submitted / elapsedSeconds:F1} " +
$"presented_fps={presentedCount / elapsedSeconds:F1}");
}
private static int RegisterBufferRange(VideoOutPortState port, int startIndex, ReadOnlySpan<ulong> addresses, BufferAttribute attribute, int requestedGroupIndex = -1) private static int RegisterBufferRange(VideoOutPortState port, int startIndex, ReadOnlySpan<ulong> addresses, BufferAttribute attribute, int requestedGroupIndex = -1)
{ {
lock (_stateGate) lock (_stateGate)
File diff suppressed because it is too large Load Diff