Compare commits

..

6 Commits

Author SHA1 Message Date
GreemDev
47559cd311 Revert game list rounding
The selected highlight was bugged

https://fs.ryujinx.app/40cl4Ih9RiOWLVi7e4lJsw.png
2025-10-14 15:59:02 -05:00
Coxxs
51584a083b Flush the error log before exit (ryubing/ryujinx!163)
See merge request ryubing/ryujinx!163
2025-10-13 17:40:15 -05:00
Coxxs
ceec9617ef gdb: Fix the crash that occurs when GDB is connected early (ryubing/ryujinx!159)
See merge request ryubing/ryujinx!159
2025-10-11 19:06:14 -05:00
Coxxs
1865be47cf gdb: Add monitor minidump command (ryubing/ryujinx!158)
See merge request ryubing/ryujinx!158
2025-10-11 10:01:30 -05:00
LotP
ef9810582a Sync thread name on Schedule (ryubing/ryujinx!157)
See merge request ryubing/ryujinx!157
2025-10-11 07:47:45 -05:00
KeatonTheBot
13878acdb2 Avoid lookup of invalid textures if pool did not change (ryubing/ryujinx!113)
See merge request ryubing/ryujinx!113
2025-10-11 02:56:13 -05:00
12 changed files with 357 additions and 15 deletions

View File

@@ -0,0 +1,226 @@
namespace Ryujinx.Common.Collections
{
/// <summary>
/// Represents a collection that can store 1 bit values.
/// </summary>
public struct BitMap
{
/// <summary>
/// Size in bits of the integer used internally for the groups of bits.
/// </summary>
public const int IntSize = 64;
private const int IntShift = 6;
private const int IntMask = IntSize - 1;
private readonly long[] _masks;
/// <summary>
/// Gets or sets the value of a bit.
/// </summary>
/// <param name="bit">Bit to access</param>
/// <returns>Bit value</returns>
public bool this[int bit]
{
get => IsSet(bit);
set
{
if (value)
{
Set(bit);
}
else
{
Clear(bit);
}
}
}
/// <summary>
/// Creates a new bitmap.
/// </summary>
/// <param name="count">Total number of bits</param>
public BitMap(int count)
{
_masks = new long[(count + IntMask) / IntSize];
}
/// <summary>
/// Checks if any bit is set.
/// </summary>
/// <returns>True if any bit is set, false otherwise</returns>
public bool AnySet()
{
for (int i = 0; i < _masks.Length; i++)
{
if (_masks[i] != 0)
{
return true;
}
}
return false;
}
/// <summary>
/// Checks if a specific bit is set.
/// </summary>
/// <param name="bit">Bit to be checked</param>
/// <returns>True if set, false otherwise</returns>
public bool IsSet(int bit)
{
int wordIndex = bit >> IntShift;
int wordBit = bit & IntMask;
long wordMask = 1L << wordBit;
return (_masks[wordIndex] & wordMask) != 0;
}
/// <summary>
/// Checks if any bit inside a given range of bits is set.
/// </summary>
/// <param name="start">Start bit of the range</param>
/// <param name="end">End bit of the range (inclusive)</param>
/// <returns>True if any bit is set, false otherwise</returns>
public bool IsSet(int start, int end)
{
if (start == end)
{
return IsSet(start);
}
int startIndex = start >> IntShift;
int startBit = start & IntMask;
long startMask = -1L << startBit;
int endIndex = end >> IntShift;
int endBit = end & IntMask;
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
if (startIndex == endIndex)
{
return (_masks[startIndex] & startMask & endMask) != 0;
}
if ((_masks[startIndex] & startMask) != 0)
{
return true;
}
for (int i = startIndex + 1; i < endIndex; i++)
{
if (_masks[i] != 0)
{
return true;
}
}
if ((_masks[endIndex] & endMask) != 0)
{
return true;
}
return false;
}
/// <summary>
/// Sets the value of a bit to 1.
/// </summary>
/// <param name="bit">Bit to be set</param>
/// <returns>True if the bit was 0 and then changed to 1, false if it was already 1</returns>
public bool Set(int bit)
{
int wordIndex = bit >> IntShift;
int wordBit = bit & IntMask;
long wordMask = 1L << wordBit;
if ((_masks[wordIndex] & wordMask) != 0)
{
return false;
}
_masks[wordIndex] |= wordMask;
return true;
}
/// <summary>
/// Sets a given range of bits to 1.
/// </summary>
/// <param name="start">Start bit of the range</param>
/// <param name="end">End bit of the range (inclusive)</param>
public void SetRange(int start, int end)
{
if (start == end)
{
Set(start);
return;
}
int startIndex = start >> IntShift;
int startBit = start & IntMask;
long startMask = -1L << startBit;
int endIndex = end >> IntShift;
int endBit = end & IntMask;
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
if (startIndex == endIndex)
{
_masks[startIndex] |= startMask & endMask;
}
else
{
_masks[startIndex] |= startMask;
for (int i = startIndex + 1; i < endIndex; i++)
{
_masks[i] |= -1;
}
_masks[endIndex] |= endMask;
}
}
/// <summary>
/// Sets a given bit to 0.
/// </summary>
/// <param name="bit">Bit to be cleared</param>
public void Clear(int bit)
{
int wordIndex = bit >> IntShift;
int wordBit = bit & IntMask;
long wordMask = 1L << wordBit;
_masks[wordIndex] &= ~wordMask;
}
/// <summary>
/// Sets all bits to 0.
/// </summary>
public void Clear()
{
for (int i = 0; i < _masks.Length; i++)
{
_masks[i] = 0;
}
}
/// <summary>
/// Sets one or more groups of bits to 0.
/// See <see cref="IntSize"/> for how many bits are inside each group.
/// </summary>
/// <param name="start">Start index of the group</param>
/// <param name="end">End index of the group (inclusive)</param>
public void ClearInt(int start, int end)
{
for (int i = start; i <= end; i++)
{
_masks[i] = 0;
}
}
}
}

View File

@@ -187,6 +187,17 @@ namespace Ryujinx.Common.Logging
}
}
public static void Flush()
{
foreach (ILogTarget target in _logTargets)
{
if (target is AsyncLogTargetWrapper asyncTarget)
{
asyncTarget.Flush();
}
}
}
public static void Shutdown()
{
Updated = null;

View File

@@ -27,6 +27,17 @@ namespace Ryujinx.Common.Logging.Targets
private readonly int _overflowTimeout;
private sealed class FlushEventArgs : LogEventArgs
{
public readonly ManualResetEventSlim SignalEvent;
public FlushEventArgs(ManualResetEventSlim signalEvent)
: base(LogLevel.Notice, TimeSpan.Zero, string.Empty, string.Empty)
{
SignalEvent = signalEvent;
}
}
string ILogTarget.Name => _target.Name;
public AsyncLogTargetWrapper(ILogTarget target, int queueLimit = -1, AsyncLogTargetOverflowAction overflowAction = AsyncLogTargetOverflowAction.Block)
@@ -41,7 +52,15 @@ namespace Ryujinx.Common.Logging.Targets
{
try
{
_target.Log(this, _messageQueue.Take());
LogEventArgs item = _messageQueue.Take();
if (item is FlushEventArgs flush)
{
flush.SignalEvent.Set();
continue;
}
_target.Log(this, item);
}
catch (InvalidOperationException)
{
@@ -68,6 +87,26 @@ namespace Ryujinx.Common.Logging.Targets
}
}
public void Flush()
{
if (_messageQueue.Count == 0 || _messageQueue.IsAddingCompleted)
{
return;
}
using var signal = new ManualResetEventSlim(false);
try
{
_messageQueue.Add(new FlushEventArgs(signal));
}
catch (InvalidOperationException)
{
return;
}
signal.Wait();
}
public void Dispose()
{
GC.SuppressFinalize(this);

View File

@@ -1,3 +1,4 @@
using Ryujinx.Common.Collections;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory;
@@ -72,6 +73,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
private readonly GpuChannel _channel;
private readonly BitMap _invalidMap;
private readonly ConcurrentQueue<DereferenceRequest> _dereferenceQueue = new();
private TextureDescriptor _defaultDescriptor;
@@ -166,6 +168,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
_channel = channel;
_aliasLists = new Dictionary<Texture, TextureAliasList>();
_invalidMap = new BitMap(maximumId + 1);
}
/// <summary>
@@ -182,6 +185,11 @@ namespace Ryujinx.Graphics.Gpu.Image
if (texture == null)
{
if (_invalidMap.IsSet(id))
{
return ref descriptor;
}
texture = PhysicalMemory.TextureCache.FindShortCache(descriptor);
if (texture == null)
@@ -198,6 +206,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// If this happens, then the texture address is invalid, we can't add it to the cache.
if (texture == null)
{
_invalidMap.Set(id);
return ref descriptor;
}
}
@@ -515,6 +524,8 @@ namespace Ryujinx.Graphics.Gpu.Image
RemoveAliasList(texture);
}
}
_invalidMap.Clear(id);
}
}

View File

@@ -859,7 +859,13 @@ namespace Ryujinx.HLE.Debugger
{
if (threadId == 0 || threadId == null)
{
threadId = GetThreads().First().ThreadUid;
var threads = GetThreads();
if (threads.Length == 0)
{
ReplyError();
return;
}
threadId = threads.First().ThreadUid;
}
if (DebugProcess.GetThread(threadId.Value) == null)
@@ -1037,12 +1043,13 @@ namespace Ryujinx.HLE.Debugger
string response = command.Trim().ToLowerInvariant() switch
{
"help" => "backtrace\nbt\nregisters\nreg\nget info\n",
"help" => "backtrace\nbt\nregisters\nreg\nget info\nminidump\n",
"get info" => GetProcessInfo(),
"backtrace" => GetStackTrace(),
"bt" => GetStackTrace(),
"registers" => GetRegisters(),
"reg" => GetRegisters(),
"minidump" => GetMinidump(),
_ => $"Unknown command: {command}\n"
};
@@ -1077,6 +1084,42 @@ namespace Ryujinx.HLE.Debugger
return Process.Debugger.GetCpuRegisterPrintout(DebugProcess.GetThread(gThread.Value));
}
private string GetMinidump()
{
var response = new StringBuilder();
response.AppendLine("=== Begin Minidump ===\n");
response.AppendLine(GetProcessInfo());
foreach (var thread in GetThreads())
{
response.AppendLine($"=== Thread {thread.ThreadUid} ===");
try
{
string stackTrace = Process.Debugger.GetGuestStackTrace(thread);
response.AppendLine(stackTrace);
}
catch (Exception e)
{
response.AppendLine($"[Error getting stack trace: {e.Message}]");
}
try
{
string registers = Process.Debugger.GetCpuRegisterPrintout(thread);
response.AppendLine(registers);
}
catch (Exception e)
{
response.AppendLine($"[Error getting registers: {e.Message}]");
}
}
response.AppendLine("=== End Minidump ===");
Logger.Info?.Print(LogClass.GdbStub, response.ToString());
return response.ToString();
}
private string GetProcessInfo()
{
try
@@ -1155,11 +1198,11 @@ namespace Ryujinx.HLE.Debugger
// If the user connects before the application is running, wait for the application to start.
int retries = 10;
while (DebugProcess == null && retries-- > 0)
while ((DebugProcess == null || GetThreads().Length == 0) && retries-- > 0)
{
Thread.Sleep(200);
}
if (DebugProcess == null)
if (DebugProcess == null || GetThreads().Length == 0)
{
Logger.Warning?.Print(LogClass.GdbStub, "Application is not running, cannot accept GDB client connection");
ClientSocket.Close();

View File

@@ -1095,6 +1095,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
Logger.Error?.Print(LogClass.Cpu, $"Invalid memory access at virtual address 0x{va:X16}.");
Logger.Flush();
return false;
}
@@ -1103,6 +1105,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
KernelStatic.GetCurrentThread().PrintGuestStackTrace();
KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout();
Logger.Flush();
throw new UndefinedInstructionException(address, opCode);
}

View File

@@ -1893,6 +1893,9 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return;
}
Logger.Error?.Print(LogClass.KernelSvc, "The guest program broke execution!");
Logger.Flush();
// TODO: Debug events.
currentThread.Owner.TerminateCurrentProcess();

View File

@@ -293,6 +293,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
KThread currentThread = KernelStatic.GetCurrentThread();
KThread selectedThread = _state.SelectedThread;
if (!currentThread.IsThreadNamed && currentThread.GetThreadName() != "")
{
currentThread.HostThread.Name = $"<{currentThread.GetThreadName()}>";
currentThread.IsThreadNamed = true;
}
// If the thread is already scheduled and running on the core, we have nothing to do.
if (currentThread == selectedThread)
{

View File

@@ -53,6 +53,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public ulong AffinityMask { get; set; }
public ulong ThreadUid { get; private set; }
public bool IsThreadNamed { get; set; }
private long _totalTimeRunning;

View File

@@ -351,7 +351,10 @@ namespace Ryujinx.Ava
if (isTerminating)
{
Logger.Flush();
Exit();
}
}
internal static void Exit()

View File

@@ -35,7 +35,7 @@
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Margin" Value="5" />
<Setter Property="CornerRadius" Value="15" />
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).GridItemSelectorSize}" />
@@ -53,7 +53,7 @@
Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}"
Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}"
ClipToBounds="True"
CornerRadius="12.5">
CornerRadius="4">
<Grid RowDefinitions="Auto,Auto">
<Image
Grid.Row="0"

View File

@@ -34,9 +34,6 @@
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="CornerRadius" Value="15" />
</Style>
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ListItemSelectorSize}" />
</Style>
@@ -49,11 +46,9 @@
Padding="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True">
ClipToBounds="True"
CornerRadius="5">
<Grid ColumnDefinitions="Auto,10,*,150,100">
<Border
ClipToBounds="True"
CornerRadius="7">
<Image
Grid.RowSpan="3"
Grid.Column="0"
@@ -63,7 +58,6 @@
Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}"
Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}"
Source="{Binding Icon, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
</Border>
<Border
Grid.Column="2"