Compare commits

...

3 Commits

Author SHA1 Message Date
TSRBerry
7870423671 Update syscall capabilites to include SVCs from FW 15.0.0 (#4530)
* Add CapabilityType enum

* Add SupervisorCallCount

* kernel: Add CapabilityExtensions & Change type of capabilities to uint

* Remove private setter from Mask arrays

* Pass ReadOnlySpan directly & Remove redundant type casts
2023-03-17 12:55:19 +01:00
dependabot[bot]
b72916fbc1 nuget: bump UnicornEngine.Unicorn (#4543)
Bumps [UnicornEngine.Unicorn](https://github.com/unicorn-engine/unicorn) from 2.0.2-rc1-f7c841d to 2.0.2-rc1-fb78016.
- [Release notes](https://github.com/unicorn-engine/unicorn/releases)
- [Changelog](https://github.com/unicorn-engine/unicorn/blob/master/ChangeLog)
- [Commits](https://github.com/unicorn-engine/unicorn/commits)

---
updated-dependencies:
- dependency-name: UnicornEngine.Unicorn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-17 12:50:52 +01:00
riperiperi
da073fce61 GPU: Fast path for adding one texture view to a group (#4528)
* GPU: Fast path for adding one texture view to a group

Texture group handles must store a list of their overlapping views, so they can be properly notified when a write is detected, and a few other things relating to texture readback. This is generally created when the group is established, with each handle looping over all views to find its overlaps. This whole process was also done when only a single view was added (and no handles were changed), however...

Sonic Frontiers had a huge cubemap array with 7350 faces (175 cubemaps * 6 faces * 7 levels), so iterating over both handles and existing views added up very fast. Since we are only adding a single view, we only need to _add_ that view to the existing overlaps, rather than recalculate them all.

This greatly improves performance during loading screens and a few seconds into gameplay on the "open zone" sections of Sonic Frontiers. May improve loading times or stutters on some other games.

Note that the current texture cache rules will cause these views to fall out of the cache, as there are more than the hard cap, so the cost will be repaid when reloading the open zone.

I also added some code to properly remove overlaps when texture views are removed, since it seems that was missing.

This can be improved further by only iterating handles that overlap the view (filter by range), but so can a few places in TextureGroup, so better to do all at once. The full generation of overlaps could probably be improved in a similar way.

I recommend testing a few games to make sure nothing breaks.

* Address feedback
2023-03-14 17:33:44 -03:00
17 changed files with 201 additions and 82 deletions

View File

@@ -46,7 +46,7 @@
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" />
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
<PackageVersion Include="System.Management" Version="7.0.0" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-f7c841d" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
</ItemGroup>
</Project>

View File

@@ -360,7 +360,7 @@ namespace Ryujinx.Graphics.Gpu.Image
texture._viewStorage = this;
Group.UpdateViews(_views);
Group.UpdateViews(_views, texture);
if (texture.Group != null && texture.Group != Group)
{
@@ -384,6 +384,8 @@ namespace Ryujinx.Graphics.Gpu.Image
{
_views.Remove(texture);
Group.RemoveView(texture);
texture._viewStorage = texture;
DecrementReferenceCount();

View File

@@ -989,7 +989,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Update the views in this texture group, rebuilding the memory tracking if required.
/// </summary>
/// <param name="views">The views list of the storage texture</param>
public void UpdateViews(List<Texture> views)
/// <param name="texture">The texture that has been added, if that is the only change, otherwise null</param>
public void UpdateViews(List<Texture> views, Texture texture)
{
// This is saved to calculate overlapping views for each handle.
_views = views;
@@ -1027,17 +1028,44 @@ namespace Ryujinx.Graphics.Gpu.Image
if (!regionsRebuilt)
{
// Must update the overlapping views on all handles, but only if they were not just recreated.
foreach (TextureGroupHandle handle in _handles)
if (texture != null)
{
handle.RecalculateOverlaps(this, views);
int offset = FindOffset(texture);
foreach (TextureGroupHandle handle in _handles)
{
handle.AddOverlap(offset, texture);
}
}
else
{
// Must update the overlapping views on all handles, but only if they were not just recreated.
foreach (TextureGroupHandle handle in _handles)
{
handle.RecalculateOverlaps(this, views);
}
}
}
SignalAllDirty();
}
/// <summary>
/// Removes a view from the group, removing it from all overlap lists.
/// </summary>
/// <param name="view">View to remove from the group</param>
public void RemoveView(Texture view)
{
int offset = FindOffset(view);
foreach (TextureGroupHandle handle in _handles)
{
handle.RemoveOverlap(offset, view);
}
}
/// <summary>
/// Inherit handle state from an old set of handles, such as modified and dirty flags.
/// </summary>

View File

@@ -159,6 +159,42 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
/// <summary>
/// Adds a single texture view as an overlap if its range overlaps.
/// </summary>
/// <param name="offset">The offset of the view in the group</param>
/// <param name="view">The texture to add as an overlap</param>
public void AddOverlap(int offset, Texture view)
{
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
if (OverlapsWith(offset, (int)view.Size))
{
lock (Overlaps)
{
Overlaps.Add(view);
}
}
}
/// <summary>
/// Removes a single texture view as an overlap if its range overlaps.
/// </summary>
/// <param name="offset">The offset of the view in the group</param>
/// <param name="view">The texture to add as an overlap</param>
public void RemoveOverlap(int offset, Texture view)
{
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
if (OverlapsWith(offset, (int)view.Size))
{
lock (Overlaps)
{
Overlaps.Remove(view);
}
}
}
/// <summary>
/// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle.
/// </summary>

View File

@@ -338,7 +338,7 @@ namespace Ryujinx.HLE.HOS
ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0);
int[] defaultCapabilities = new int[]
uint[] defaultCapabilities = new uint[]
{
0x030363F7,
0x1FFFFFCF,
@@ -552,4 +552,4 @@ namespace Ryujinx.HLE.HOS
IsPaused = pause;
}
}
}
}

View File

@@ -7,6 +7,8 @@ namespace Ryujinx.HLE.HOS.Kernel
public const int InitialKipId = 1;
public const int InitialProcessId = 0x51;
public const int SupervisorCallCount = 0xC0;
public const int MemoryBlockAllocatorSize = 0x2710;
public const ulong UserSlabHeapBase = DramMemoryMap.SlabHeapBase;
@@ -15,4 +17,4 @@ namespace Ryujinx.HLE.HOS.Kernel
public const ulong CounterFrequency = 19200000;
}
}
}

View File

@@ -18,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Kernel
public static Result StartInitialProcess(
KernelContext context,
ProcessCreationInfo creationInfo,
ReadOnlySpan<int> capabilities,
ReadOnlySpan<uint> capabilities,
int mainThreadPriority,
ThreadStart customThreadStart)
{

View File

@@ -0,0 +1,22 @@
using System.Numerics;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
static class CapabilityExtensions
{
public static CapabilityType GetCapabilityType(this uint cap)
{
return (CapabilityType)(((cap + 1) & ~cap) - 1);
}
public static uint GetFlag(this CapabilityType type)
{
return (uint)type + 1;
}
public static uint GetId(this CapabilityType type)
{
return (uint)BitOperations.TrailingZeroCount(type.GetFlag());
}
}
}

View File

@@ -0,0 +1,19 @@
namespace Ryujinx.HLE.HOS.Kernel.Process
{
enum CapabilityType : uint
{
CorePriority = (1u << 3) - 1,
SyscallMask = (1u << 4) - 1,
MapRange = (1u << 6) - 1,
MapIoPage = (1u << 7) - 1,
MapRegion = (1u << 10) - 1,
InterruptPair = (1u << 11) - 1,
ProgramType = (1u << 13) - 1,
KernelVersion = (1u << 14) - 1,
HandleTable = (1u << 15) - 1,
DebugFlags = (1u << 16) - 1,
Invalid = 0u,
Padding = ~0u
}
}

View File

@@ -19,7 +19,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
private int _activeSlotsCount;
private int _size;
private uint _size;
private ushort _idCounter;
@@ -28,9 +28,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
_context = context;
}
public Result Initialize(int size)
public Result Initialize(uint size)
{
if ((uint)size > 1024)
if (size > 1024)
{
return KernelResult.OutOfMemory;
}

View File

@@ -16,11 +16,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
class KProcess : KSynchronizationObject
{
public const int KernelVersionMajor = 10;
public const int KernelVersionMinor = 4;
public const int KernelVersionRevision = 0;
public const uint KernelVersionMajor = 10;
public const uint KernelVersionMinor = 4;
public const uint KernelVersionRevision = 0;
public const int KernelVersionPacked =
public const uint KernelVersionPacked =
(KernelVersionMajor << 19) |
(KernelVersionMinor << 15) |
(KernelVersionRevision << 0);
@@ -119,7 +119,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public Result InitializeKip(
ProcessCreationInfo creationInfo,
ReadOnlySpan<int> capabilities,
ReadOnlySpan<uint> capabilities,
KPageList pageList,
KResourceLimit resourceLimit,
MemoryRegion memRegion,
@@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public Result Initialize(
ProcessCreationInfo creationInfo,
ReadOnlySpan<int> capabilities,
ReadOnlySpan<uint> capabilities,
KResourceLimit resourceLimit,
MemoryRegion memRegion,
IProcessContextFactory contextFactory,

View File

@@ -1,4 +1,3 @@
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
@@ -9,48 +8,49 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
class KProcessCapabilities
{
public byte[] SvcAccessMask { get; private set; }
public byte[] IrqAccessMask { get; private set; }
public byte[] SvcAccessMask { get; }
public byte[] IrqAccessMask { get; }
public ulong AllowedCpuCoresMask { get; private set; }
public ulong AllowedThreadPriosMask { get; private set; }
public int DebuggingFlags { get; private set; }
public int HandleTableSize { get; private set; }
public int KernelReleaseVersion { get; private set; }
public int ApplicationType { get; private set; }
public uint DebuggingFlags { get; private set; }
public uint HandleTableSize { get; private set; }
public uint KernelReleaseVersion { get; private set; }
public uint ApplicationType { get; private set; }
public KProcessCapabilities()
{
SvcAccessMask = new byte[0x10];
// length / number of bits of the underlying type
SvcAccessMask = new byte[KernelConstants.SupervisorCallCount / 8];
IrqAccessMask = new byte[0x80];
}
public Result InitializeForKernel(ReadOnlySpan<int> capabilities, KPageTableBase memoryManager)
public Result InitializeForKernel(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
{
AllowedCpuCoresMask = 0xf;
AllowedThreadPriosMask = ulong.MaxValue;
DebuggingFlags &= ~3;
DebuggingFlags &= ~3u;
KernelReleaseVersion = KProcess.KernelVersionPacked;
return Parse(capabilities, memoryManager);
}
public Result InitializeForUser(ReadOnlySpan<int> capabilities, KPageTableBase memoryManager)
public Result InitializeForUser(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
{
return Parse(capabilities, memoryManager);
}
private Result Parse(ReadOnlySpan<int> capabilities, KPageTableBase memoryManager)
private Result Parse(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
{
int mask0 = 0;
int mask1 = 0;
for (int index = 0; index < capabilities.Length; index++)
{
int cap = capabilities[index];
uint cap = capabilities[index];
if (((cap + 1) & ~cap) != 0x40)
if (cap.GetCapabilityType() != CapabilityType.MapRange)
{
Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager);
@@ -66,7 +66,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return KernelResult.InvalidCombination;
}
int prevCap = cap;
uint prevCap = cap;
cap = capabilities[++index];
@@ -85,8 +85,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return KernelResult.InvalidSize;
}
long address = ((long)(uint)prevCap << 5) & 0xffffff000;
long size = ((long)(uint)cap << 5) & 0xfffff000;
long address = ((long)prevCap << 5) & 0xffffff000;
long size = ((long)cap << 5) & 0xfffff000;
if (((ulong)(address + size - 1) >> 36) != 0)
{
@@ -118,20 +118,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return Result.Success;
}
private Result ParseCapability(int cap, ref int mask0, ref int mask1, KPageTableBase memoryManager)
private Result ParseCapability(uint cap, ref int mask0, ref int mask1, KPageTableBase memoryManager)
{
int code = (cap + 1) & ~cap;
CapabilityType code = cap.GetCapabilityType();
if (code == 1)
if (code == CapabilityType.Invalid)
{
return KernelResult.InvalidCapability;
}
else if (code == 0)
else if (code == CapabilityType.Padding)
{
return Result.Success;
}
int codeMask = 1 << (32 - BitOperations.LeadingZeroCount((uint)code + 1));
int codeMask = 1 << (32 - BitOperations.LeadingZeroCount(code.GetFlag() + 1));
// Check if the property was already set.
if (((mask0 & codeMask) & 0x1e008) != 0)
@@ -143,23 +143,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
switch (code)
{
case 8:
case CapabilityType.CorePriority:
{
if (AllowedCpuCoresMask != 0 || AllowedThreadPriosMask != 0)
{
return KernelResult.InvalidCapability;
}
int lowestCpuCore = (cap >> 16) & 0xff;
int highestCpuCore = (cap >> 24) & 0xff;
uint lowestCpuCore = (cap >> 16) & 0xff;
uint highestCpuCore = (cap >> 24) & 0xff;
if (lowestCpuCore > highestCpuCore)
{
return KernelResult.InvalidCombination;
}
int highestThreadPrio = (cap >> 4) & 0x3f;
int lowestThreadPrio = (cap >> 10) & 0x3f;
uint highestThreadPrio = (cap >> 4) & 0x3f;
uint lowestThreadPrio = (cap >> 10) & 0x3f;
if (lowestThreadPrio > highestThreadPrio)
{
@@ -177,9 +177,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
break;
}
case 0x10:
case CapabilityType.SyscallMask:
{
int slot = (cap >> 29) & 7;
int slot = ((int)cap >> 29) & 7;
int svcSlotMask = 1 << slot;
@@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
mask1 |= svcSlotMask;
int svcMask = (cap >> 5) & 0xffffff;
uint svcMask = (cap >> 5) & 0xffffff;
int baseSvc = slot * 24;
@@ -203,7 +203,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
int svcId = baseSvc + index;
if (svcId > 0x7f)
if (svcId >= KernelConstants.SupervisorCallCount)
{
return KernelResult.MaximumExceeded;
}
@@ -214,20 +214,27 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
break;
}
case 0x80:
case CapabilityType.MapIoPage:
{
long address = ((long)(uint)cap << 4) & 0xffffff000;
long address = ((long)cap << 4) & 0xffffff000;
memoryManager.MapIoMemory(address, KPageTableBase.PageSize, KMemoryPermission.ReadAndWrite);
break;
}
case 0x800:
case CapabilityType.MapRegion:
{
// TODO: Implement capabilities for MapRegion
break;
}
case CapabilityType.InterruptPair:
{
// TODO: GIC distributor check.
int irq0 = (cap >> 12) & 0x3ff;
int irq1 = (cap >> 22) & 0x3ff;
int irq0 = ((int)cap >> 12) & 0x3ff;
int irq1 = ((int)cap >> 22) & 0x3ff;
if (irq0 != 0x3ff)
{
@@ -242,11 +249,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
break;
}
case 0x2000:
case CapabilityType.ProgramType:
{
int applicationType = cap >> 14;
uint applicationType = (cap >> 14);
if ((uint)applicationType > 7)
if (applicationType > 7)
{
return KernelResult.ReservedValue;
}
@@ -256,7 +263,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
break;
}
case 0x4000:
case CapabilityType.KernelVersion:
{
// Note: This check is bugged on kernel too, we are just replicating the bug here.
if ((KernelReleaseVersion >> 17) != 0 || cap < 0x80000)
@@ -269,11 +276,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
break;
}
case 0x8000:
case CapabilityType.HandleTable:
{
int handleTableSize = cap >> 26;
uint handleTableSize = cap >> 26;
if ((uint)handleTableSize > 0x3ff)
if (handleTableSize > 0x3ff)
{
return KernelResult.ReservedValue;
}
@@ -283,16 +290,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
break;
}
case 0x10000:
case CapabilityType.DebugFlags:
{
int debuggingFlags = cap >> 19;
uint debuggingFlags = cap >> 19;
if ((uint)debuggingFlags > 3)
if (debuggingFlags > 3)
{
return KernelResult.ReservedValue;
}
DebuggingFlags &= ~3;
DebuggingFlags &= ~3u;
DebuggingFlags |= debuggingFlags;
break;
@@ -304,18 +311,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return Result.Success;
}
private static ulong GetMaskFromMinMax(int min, int max)
private static ulong GetMaskFromMinMax(uint min, uint max)
{
int range = max - min + 1;
uint range = max - min + 1;
if (range == 64)
{
return ulong.MaxValue;
}
ulong mask = (1UL << range) - 1;
ulong mask = (1UL << (int)range) - 1;
return mask << min;
return mask << (int)min;
}
}
}

View File

@@ -1,5 +1,8 @@
namespace Ryujinx.HLE.HOS.Kernel.Process
using System;
namespace Ryujinx.HLE.HOS.Kernel.Process
{
[Flags]
enum ProcessCreationFlags
{
Is64Bit = 1 << 0,

View File

@@ -54,7 +54,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
public Result CreateProcess(
out int handle,
ProcessCreationInfo info,
ReadOnlySpan<int> capabilities,
ReadOnlySpan<uint> capabilities,
IProcessContextFactory contextFactory,
ThreadStart customThreadStart = null)
{
@@ -3002,4 +3002,4 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return (address & 3) != 0;
}
}
}
}

View File

@@ -80,7 +80,7 @@ namespace Ryujinx.HLE.HOS
ulong codeBaseAddress = kip.Is64BitAddressSpace ? 0x8000000UL : 0x200000UL;
ulong codeAddress = codeBaseAddress + (ulong)kip.TextOffset;
ulong codeAddress = codeBaseAddress + kip.TextOffset;
ProcessCreationFlags flags = 0;
@@ -231,13 +231,13 @@ namespace Ryujinx.HLE.HOS
nsoSize = BitUtils.AlignUp<uint>(nsoSize, KPageTableBase.PageSize);
nsoBase[index] = codeStart + (ulong)codeSize;
nsoBase[index] = codeStart + codeSize;
codeSize += nsoSize;
if (arguments != null && argsSize == 0)
{
argsStart = (ulong)codeSize;
argsStart = codeSize;
argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ArgsTotalSize - 1, KPageTableBase.PageSize);
@@ -318,7 +318,7 @@ namespace Ryujinx.HLE.HOS
result = process.Initialize(
creationInfo,
MemoryMarshal.Cast<byte, int>(npdm.KernelCapabilityData).ToArray(),
MemoryMarshal.Cast<byte, uint>(npdm.KernelCapabilityData),
resourceLimit,
memoryRegion,
processContextFactory);

View File

@@ -19,7 +19,7 @@ namespace Ryujinx.HLE.HOS.Services
// not large enough.
private const int PointerBufferSize = 0x8000;
private readonly static int[] DefaultCapabilities = new int[]
private readonly static uint[] DefaultCapabilities = new uint[]
{
0x030363F7,
0x1FFFFFCF,

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.HLE.Loaders.Executables
public uint DataSize { get; }
public uint BssSize { get; }
public int[] Capabilities { get; }
public uint[] Capabilities { get; }
public bool UsesSecureMemory { get; }
public bool Is64BitAddressSpace { get; }
public bool Is64Bit { get; }
@@ -57,11 +57,11 @@ namespace Ryujinx.HLE.Loaders.Executables
Version = reader.Version;
Name = reader.Name.ToString();
Capabilities = new int[32];
Capabilities = new uint[32];
for (int index = 0; index < Capabilities.Length; index++)
{
Capabilities[index] = (int)reader.Capabilities[index];
Capabilities[index] = reader.Capabilities[index];
}
reader.GetSegmentSize(KipReader.SegmentType.Data, out int uncompressedSize).ThrowIfFailure();