Compare commits

...

6 Commits

Author SHA1 Message Date
cc95e80ee9 misc: chore: Move converters into a directory in Helpers. Namespace unchanged 2025-01-10 20:24:53 -06:00
d75ce52bd4 UI: Show play time in one time unit, maxing out at hours. 2025-01-10 20:23:47 -06:00
4a4ea557de UI: compat: show last updated date on entry hover 2025-01-10 01:43:34 -06:00
33f42adb11 Merge remote-tracking branch 'origin/master' 2025-01-09 22:09:01 -06:00
918ec1bde3 cores rework (#505)
This PR changes the core count to be defined in the device instead of
being a const value.
This is mostly a change for future features I want to implement and
should not impact any functionality.
The console will now log the range of cores requested from the
application, and for now, if the requested range is not 0 to 2 (the 3
cores used for application emulation), it will give an error message
which tells the user to contact me on discord. I'm doing this because
I'm interested in finding applications/games that don't use 3 cores and
the error will be removed in the future once I've gotten enough data.
2025-01-09 21:43:18 -06:00
cca429d46a misc: chore: restore not enable 2025-01-09 21:42:54 -06:00
30 changed files with 106 additions and 42 deletions

View File

@ -42,7 +42,7 @@
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" /> <PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" /> <PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="Gommon" Version="2.7.0.1" /> <PackageVersion Include="Gommon" Version="2.7.0.2" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" /> <PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="Sep" Version="0.6.0" /> <PackageVersion Include="Sep" Version="0.6.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" /> <PackageVersion Include="shaderc.net" Version="0.1.0" />

View File

@ -284,7 +284,7 @@ namespace Ryujinx.HLE.HOS
ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0); ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0);
uint[] defaultCapabilities = { uint[] defaultCapabilities = {
0x030363F7, (((uint)KScheduler.CpuCoresCount - 1) << 24) + (((uint)KScheduler.CpuCoresCount - 1) << 16) + 0x63F7u,
0x1FFFFFCF, 0x1FFFFFCF,
0x207FFFEF, 0x207FFFEF,
0x47E0060F, 0x47E0060F,

View File

@ -63,6 +63,7 @@ namespace Ryujinx.HLE.HOS.Kernel
TickSource = tickSource; TickSource = tickSource;
Device = device; Device = device;
Memory = memory; Memory = memory;
KScheduler.CpuCoresCount = device.CpuCoresCount;
Running = true; Running = true;

View File

@ -37,7 +37,7 @@ namespace Ryujinx.HLE.HOS.Kernel
return result; return result;
} }
process.DefaultCpuCore = 3; process.DefaultCpuCore = KScheduler.CpuCoresCount - 1;
context.Processes.TryAdd(process.Pid, process); context.Processes.TryAdd(process.Pid, process);

View File

@ -277,7 +277,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return result; return result;
} }
result = Capabilities.InitializeForUser(capabilities, MemoryManager); result = Capabilities.InitializeForUser(capabilities, MemoryManager, IsApplication);
if (result != Result.Success) if (result != Result.Success)
{ {

View File

@ -35,15 +35,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
DebuggingFlags &= ~3u; DebuggingFlags &= ~3u;
KernelReleaseVersion = KProcess.KernelVersionPacked; KernelReleaseVersion = KProcess.KernelVersionPacked;
return Parse(capabilities, memoryManager); return Parse(capabilities, memoryManager, false);
} }
public Result InitializeForUser(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager) public Result InitializeForUser(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager, bool isApplication)
{ {
return Parse(capabilities, memoryManager); return Parse(capabilities, memoryManager, isApplication);
} }
private Result Parse(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager) private Result Parse(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager, bool isApplication)
{ {
int mask0 = 0; int mask0 = 0;
int mask1 = 0; int mask1 = 0;
@ -54,7 +54,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
if (cap.GetCapabilityType() != CapabilityType.MapRange) if (cap.GetCapabilityType() != CapabilityType.MapRange)
{ {
Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager); Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager, isApplication);
if (result != Result.Success) if (result != Result.Success)
{ {
@ -120,7 +120,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return Result.Success; return Result.Success;
} }
private Result ParseCapability(uint cap, ref int mask0, ref int mask1, KPageTableBase memoryManager) private Result ParseCapability(uint cap, ref int mask0, ref int mask1, KPageTableBase memoryManager, bool isApplication)
{ {
CapabilityType code = cap.GetCapabilityType(); CapabilityType code = cap.GetCapabilityType();
@ -176,6 +176,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
AllowedCpuCoresMask = GetMaskFromMinMax(lowestCpuCore, highestCpuCore); AllowedCpuCoresMask = GetMaskFromMinMax(lowestCpuCore, highestCpuCore);
AllowedThreadPriosMask = GetMaskFromMinMax(lowestThreadPrio, highestThreadPrio); AllowedThreadPriosMask = GetMaskFromMinMax(lowestThreadPrio, highestThreadPrio);
if (isApplication && lowestCpuCore == 0 && highestCpuCore != 2)
Ryujinx.Common.Logging.Logger.Error?.Print(Ryujinx.Common.Logging.LogClass.Application, $"Application requested cores with index range {lowestCpuCore} to {highestCpuCore}! Report this to @LotP on the Ryujinx/Ryubing discord server (discord.gg/ryujinx)!");
else if (isApplication)
Ryujinx.Common.Logging.Logger.Info?.Print(Ryujinx.Common.Logging.LogClass.Application, $"Application requested cores with index range {lowestCpuCore} to {highestCpuCore}");
break; break;
} }

View File

@ -2683,7 +2683,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.InvalidCombination; return KernelResult.InvalidCombination;
} }
if ((uint)preferredCore > 3) if ((uint)preferredCore > KScheduler.CpuCoresCount - 1)
{ {
if ((preferredCore | 2) != -1) if ((preferredCore | 2) != -1)
{ {

View File

@ -9,13 +9,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
partial class KScheduler : IDisposable partial class KScheduler : IDisposable
{ {
public const int PrioritiesCount = 64; public const int PrioritiesCount = 64;
public const int CpuCoresCount = 4; public static int CpuCoresCount;
private const int RoundRobinTimeQuantumMs = 10; private const int RoundRobinTimeQuantumMs = 10;
private static readonly int[] _preemptionPriorities = { 59, 59, 59, 63 }; private static int[] _srcCoresHighestPrioThreads;
private static readonly int[] _srcCoresHighestPrioThreads = new int[CpuCoresCount];
private readonly KernelContext _context; private readonly KernelContext _context;
private readonly int _coreId; private readonly int _coreId;
@ -47,6 +45,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
_coreId = coreId; _coreId = coreId;
_currentThread = null; _currentThread = null;
if (_srcCoresHighestPrioThreads == null)
{
_srcCoresHighestPrioThreads = new int[CpuCoresCount];
}
}
private static int PreemptionPriorities(int index)
{
return index == CpuCoresCount - 1 ? 63 : 59;
} }
public static ulong SelectThreads(KernelContext context) public static ulong SelectThreads(KernelContext context)
@ -437,7 +445,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
for (int core = 0; core < CpuCoresCount; core++) for (int core = 0; core < CpuCoresCount; core++)
{ {
RotateScheduledQueue(context, core, _preemptionPriorities[core]); RotateScheduledQueue(context, core, PreemptionPriorities(core));
} }
context.CriticalSection.Leave(); context.CriticalSection.Leave();

View File

@ -24,14 +24,14 @@ namespace Ryujinx.HLE.HOS.Services
// not large enough. // not large enough.
private const int PointerBufferSize = 0x8000; private const int PointerBufferSize = 0x8000;
private readonly static uint[] _defaultCapabilities = { private static uint[] _defaultCapabilities => [
0x030363F7, (((uint)KScheduler.CpuCoresCount - 1) << 24) + (((uint)KScheduler.CpuCoresCount - 1) << 16) + 0x63F7u,
0x1FFFFFCF, 0x1FFFFFCF,
0x207FFFEF, 0x207FFFEF,
0x47E0060F, 0x47E0060F,
0x0048BFFF, 0x0048BFFF,
0x01007FFF, 0x01007FFF,
}; ];
// The amount of time Dispose() will wait to Join() the thread executing the ServerLoop() // The amount of time Dispose() will wait to Join() the thread executing the ServerLoop()
private static readonly TimeSpan _threadJoinTimeout = TimeSpan.FromSeconds(3); private static readonly TimeSpan _threadJoinTimeout = TimeSpan.FromSeconds(3);

View File

@ -32,6 +32,8 @@ namespace Ryujinx.HLE
public TamperMachine TamperMachine { get; } public TamperMachine TamperMachine { get; }
public IHostUIHandler UIHandler { get; } public IHostUIHandler UIHandler { get; }
public int CpuCoresCount = 4; //Switch 1 has 4 cores
public VSyncMode VSyncMode { get; set; } = VSyncMode.Switch; public VSyncMode VSyncMode { get; set; } = VSyncMode.Switch;
public bool CustomVSyncIntervalEnabled { get; set; } = false; public bool CustomVSyncIntervalEnabled { get; set; } = false;
public int CustomVSyncInterval { get; set; } public int CustomVSyncInterval { get; set; }

View File

@ -22597,6 +22597,31 @@
"zh_TW": "" "zh_TW": ""
} }
}, },
{
"ID": "CompatibilityListLastUpdated",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Last updated: {0}",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{ {
"ID": "CompatibilityListWarning", "ID": "CompatibilityListWarning",
"Translations": { "Translations": {

View File

@ -1,4 +1,4 @@
using DiscordRPC; using DiscordRPC;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.SDL2; using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Ava; using Ryujinx.Ava;

View File

@ -133,12 +133,13 @@
Spacing="5"> Spacing="5">
<TextBlock <TextBlock
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Text="{Binding TimePlayedString}" Text="{Binding LastPlayedString}"
TextAlignment="End" TextAlignment="End"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Text="{Binding LastPlayedString}" Text="{Binding TimePlayedString}"
IsVisible="{Binding HasPlayedPreviously}"
TextAlignment="End" TextAlignment="End"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock

View File

@ -743,7 +743,7 @@ namespace Ryujinx.Ava.UI.ViewModels
.Sort(GetComparer()) .Sort(GetComparer())
#pragma warning disable MVVMTK0034 #pragma warning disable MVVMTK0034
.Bind(out _appsObservableList) .Bind(out _appsObservableList)
#pragma warning enable MVVMTK0034 #pragma warning restore MVVMTK0034
.AsObservableList(); .AsObservableList();
OnPropertyChanged(nameof(AppsObservableList)); OnPropertyChanged(nameof(AppsObservableList));

View File

@ -1,4 +1,6 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input;
using FluentAvalonia.Core; using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
@ -23,6 +25,11 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent(); InitializeComponent();
Load(); Load();
#if DEBUG
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
#endif
} }
public SettingsWindow() public SettingsWindow()

View File

@ -37,6 +37,8 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed); public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
public bool HasPlayedPreviously => TimePlayedString != string.Empty;
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n"); public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n");
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize); public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);

View File

@ -1,4 +1,5 @@
using Gommon; using Gommon;
using Humanizer;
using nietras.SeparatedValues; using nietras.SeparatedValues;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
@ -83,6 +84,9 @@ namespace Ryujinx.Ava.Utilities.Compat
public LocaleKeys? Status { get; } public LocaleKeys? Status { get; }
public DateTime LastUpdated { get; } public DateTime LastUpdated { get; }
public string LocalizedLastUpdated =>
LocaleManager.FormatDynamicValue(LocaleKeys.CompatibilityListLastUpdated, LastUpdated.Humanize());
public string LocalizedStatus => LocaleManager.Instance[Status!.Value]; public string LocalizedStatus => LocaleManager.Instance[Status!.Value];
public string FormattedTitleId => TitleId public string FormattedTitleId => TitleId
.OrElse(new string(' ', 16)); .OrElse(new string(' ', 16));
@ -97,7 +101,9 @@ namespace Ryujinx.Ava.Utilities.Compat
StringBuilder sb = new("CompatibilityEntry: {"); StringBuilder sb = new("CompatibilityEntry: {");
sb.Append($"{nameof(GameName)}=\"{GameName}\", "); sb.Append($"{nameof(GameName)}=\"{GameName}\", ");
sb.Append($"{nameof(TitleId)}={TitleId}, "); sb.Append($"{nameof(TitleId)}={TitleId}, ");
sb.Append($"{nameof(Labels)}=\"{Labels}\", "); sb.Append($"{nameof(Labels)}={
Labels.FormatCollection(it => $"\"{it}\"", separator: ", ", prefix: "[", suffix: "]")
}, ");
sb.Append($"{nameof(Status)}=\"{Status}\", "); sb.Append($"{nameof(Status)}=\"{Status}\", ");
sb.Append($"{nameof(LastUpdated)}=\"{LastUpdated}\""); sb.Append($"{nameof(LastUpdated)}=\"{LastUpdated}\"");
sb.Append('}'); sb.Append('}');

View File

@ -44,8 +44,11 @@
ItemsSource="{Binding CurrentEntries}"> ItemsSource="{Binding CurrentEntries}">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:CompatibilityEntry}"> <DataTemplate DataType="{x:Type local:CompatibilityEntry}">
<Grid Width="750" ColumnDefinitions="Auto,Auto,Auto,*" <Grid Width="750"
Margin="5"> Margin="5"
ColumnDefinitions="Auto,Auto,Auto,*"
Background="Transparent"
ToolTip.Tip="{Binding LocalizedLastUpdated}">
<TextBlock Grid.Column="0" <TextBlock Grid.Column="0"
Text="{Binding GameName}" Text="{Binding GameName}"
Width="320" Width="320"

View File

@ -1,3 +1,5 @@
using Humanizer;
using Humanizer.Localisation;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using System; using System;
using System.Globalization; using System.Globalization;
@ -31,7 +33,7 @@ namespace Ryujinx.Ava.Utilities
Gigabytes = 9, Gigabytes = 9,
Terabytes = 10, Terabytes = 10,
Petabytes = 11, Petabytes = 11,
Exabytes = 12, Exabytes = 12
} }
private const double SizeBase10 = 1000; private const double SizeBase10 = 1000;
@ -48,22 +50,24 @@ namespace Ryujinx.Ava.Utilities
public static string FormatTimeSpan(TimeSpan? timeSpan) public static string FormatTimeSpan(TimeSpan? timeSpan)
{ {
if (!timeSpan.HasValue || timeSpan.Value.TotalSeconds < 1) if (!timeSpan.HasValue || timeSpan.Value.TotalSeconds < 1)
{ return string.Empty;
// Game was never played
return TimeSpan.Zero.ToString("c", CultureInfo.InvariantCulture);
}
if (timeSpan.Value.TotalDays < 1) if (timeSpan.Value.TotalSeconds < 60)
{ return timeSpan.Value.Humanize(1,
// Game was played for less than a day countEmptyUnits: false,
return timeSpan.Value.ToString("c", CultureInfo.InvariantCulture); maxUnit: TimeUnit.Second,
} minUnit: TimeUnit.Second);
// Game was played for more than a day if (timeSpan.Value.TotalMinutes < 60)
TimeSpan onlyTime = timeSpan.Value.Subtract(TimeSpan.FromDays(timeSpan.Value.Days)); return timeSpan.Value.Humanize(1,
string onlyTimeString = onlyTime.ToString("c", CultureInfo.InvariantCulture); countEmptyUnits: false,
maxUnit: TimeUnit.Minute,
minUnit: TimeUnit.Minute);
return $"{timeSpan.Value.Days}d, {onlyTimeString}"; return timeSpan.Value.Humanize(1,
countEmptyUnits: false,
maxUnit: TimeUnit.Hour,
minUnit: TimeUnit.Hour);
} }
/// <summary> /// <summary>