Compare commits

...

73 Commits

Author SHA1 Message Date
Asaf 3f3a815eb9
Merge 20e7b0ad67 into 73f985d27c 2024-09-19 13:23:33 +09:00
jhorv 73f985d27c
Replace passing by IMemoryOwner<byte> with passing by concrete MemoryOwner<byte> (#7171)
* refactor(perf): pass MemoryOwner<byte> around as itself rather than IMemoryOwner<byte>

* fix(perf): get span via MemoryOwner<byte>.Span property instead of through Memory property

* fix: incorrect comment change
2024-09-18 23:00:54 -03:00
gdkchan ef81658fbd
Implement support for shader ATOM.EXCH instruction (#7320)
* Implement support for shader ATOM.EXCH instruction

* Shader cache version bump

* Check type
2024-09-18 15:48:55 -03:00
gdkchan 062ef43eb4
Revert "Wait for async task to complete (#7122)" (#7318)
This reverts commit ccf96bf5e6.
2024-09-17 16:25:26 -03:00
gdkchan eb8132b627
Change image format view handling to allow view incompatible formats (#7311)
* Allow creating texture aliases on texture pool

* Delete old image format override code

* New format incompatible alias

* Missing bounds check

* GetForBinding now takes FormatInfo

* Make FormatInfo struct more compact
2024-09-17 15:52:30 -03:00
TSRBerry ccf96bf5e6
Wait for async task to complete (#7122)
This way exceptions thrown during the execution of CheckLaunchState()
will correctly invoke the unhandled exception handler
and cause Ryujinx to crash.
2024-09-17 15:42:00 -03:00
ZenoArrows f39e89ece7
Add area sampling scaler to allow for super-sampled anti-aliasing. (#7304)
* Add area sampling scaler to allow for super-sampled anti-aliasing.

* Area scaling filter doesn't have a scaling level.

* Add further clarification to the tooltip on how to achieve supersampling.

* ShaderHelper: Merge the two CompileProgram functions.

* Convert tabs to spaces in area scaling shaders

* Fixup Vulkan and OpenGL project files.

* AreaScaling: Replace texture() by texelFetch() and use integer vectors.

No functional difference, but it cleans up the code a bit.

* AreaScaling: Delete unused sharpening level member.

Also rename _scale to _sharpeningLevel for clarity and consistency.

* AreaScaling: Delete unused scaleX/scaleY uniforms.

* AreaScaling: Force the alpha to 1 when storing the pixel.

* AreaScaling: Remove left-over sharpening buffer.
2024-09-17 15:30:50 -03:00
gdkchan cf77c011e4
Change 6GB DRAM expansion to 8GB (#7313)
* Change 6GB DRAM expansion to 8GB

* Update texts and tooltips
2024-09-17 15:09:20 -03:00
svc64 20e7b0ad67 Debugger: fix GDBServerVersion 2024-08-24 18:54:58 +03:00
Domenico V 6225f08397 Update StringStream.cs 2024-08-24 18:54:58 +03:00
Domenico V 4b2174da95 Update RegisterInformation.cs 2024-08-24 18:54:58 +03:00
Domenico V 1b1b584bc9 Update IDebuggableProcess.cs 2024-08-24 18:54:58 +03:00
Domenico V 6bcc2cd454 Update GdbSignal.cs 2024-08-24 18:54:58 +03:00
Domenico V 540ec7d675 Update DebugState.cs 2024-08-24 18:54:58 +03:00
svc64 bba192b324 Remove BOM 2024-08-24 18:54:58 +03:00
svc64 f9156756c9 Formatting 2024-08-24 18:54:58 +03:00
svc64 60e2a9dd00 Fix checkbox alignment 2024-08-24 18:54:58 +03:00
svc64 8a1c48e035 Make suspension on start work 2024-08-24 18:54:58 +03:00
svc64 1bf244173d Settings option for suspending application on start 2024-08-24 18:54:58 +03:00
svc64 eea36e8a37 Fix debugger crash when using "info thread" 2024-08-24 18:54:58 +03:00
svc64 20e5eeb39e 32-bit debugger support 2024-08-24 18:54:58 +03:00
svc64 bca3939a65 Kill the message handler thread when killing the debugger 2024-08-24 18:54:58 +03:00
svc64 6b74bcec7c Refactor stepping code, fix stepping over a breakpoint 2024-08-24 18:54:58 +03:00
svc64 cc32ac251b Improve debugger interface code 2024-08-24 18:54:58 +03:00
svc64 e401097e1e Fix crash after sending a kill command 2024-08-24 18:54:58 +03:00
svc64 0396997c17 Bring back the debugger message handler thread 2024-08-24 18:54:58 +03:00
svc64 5f5cb73baa Formatting 2024-08-24 18:54:58 +03:00
svc64 652423cfeb Debug settings in the Avalonia UI 2024-08-24 18:54:36 +03:00
svc64 64206c7c5e Fix GDB stub port in the GTK UI 2024-08-24 18:51:12 +03:00
svc64 13c53657cc Fix the kill command, improve stepping 2024-08-24 18:51:12 +03:00
svc64 055ac70eaa Kill command - stop emulation 2024-08-24 18:50:51 +03:00
svc64 38c15faacf Remove useless definitions 2024-08-24 17:53:09 +03:00
svc64 1684a9d7a2 Fix GDB server crashes 2024-08-24 17:53:09 +03:00
svc64 e3b8060417 Refactor the debugger interface
Also support stepping through blocking syscalls
2024-08-24 17:53:09 +03:00
svc64 0f50273d4f Fix GDB stub settings 2024-08-24 17:53:09 +03:00
svc64 f934d23de4 (Hopefully) Properly check if the thread is blocked when stepping 2024-08-24 17:53:09 +03:00
svc64 144aa2f5b1 Fix stepping with JIT 2024-08-24 17:53:09 +03:00
svc64 edbd4bfc29 Revert "Return the address of the current instruction in EmitSynchronization"
This reverts commit 1331589a22ccf21c02951db96d0335a10738ae4c.
2024-08-24 17:53:09 +03:00
svc64 5d42332d75 Don't step when the thread is blocked 2024-08-24 17:53:09 +03:00
svc64 de4ec65bd7 Handle GDB server reconnections 2024-08-24 17:53:09 +03:00
svc64 5e65fd8808 Reply with an error if we fail to step 2024-08-24 17:53:09 +03:00
svc64 6ecc829516 Clarify StepBarrier 2024-08-24 17:53:09 +03:00
svc64 bc0ba93e92 Remove old variable 2024-08-24 17:53:09 +03:00
svc64 0c57663ea3 Add comments on debug stuff & fix warnings 2024-08-24 17:53:09 +03:00
svc64 65d7a16a87 Debugger refactor 2024-08-24 17:53:09 +03:00
svc64 81c399ec3e Better locking in debug methods 2024-08-24 17:53:09 +03:00
svc64 40584e0e45 HvExecutionContext: fix DebugPc 2024-08-24 17:53:09 +03:00
svc64 ac438d6572 KThread based debug
This commit starts a big refactor to the original debugger PR by merryhime. The debugger now interfaces with KThreads instead of the ExecutionContext.

The corresponding KThread debug functions properly suspend/resume the thread and call the underlying debug related function in the ExecutionContext.

I also added debugging support to the AppleHV ExecutionContext.
2024-08-24 17:53:09 +03:00
svc64 841aa89581 Return the address of the current instruction in EmitSynchronization 2024-08-24 17:53:09 +03:00
svc64 fc361f82a7 Don't recreate the GDB server socket 2024-08-24 17:53:09 +03:00
svc64 917a292256 Initialize the debugger before HOS 2024-08-24 17:53:09 +03:00
merry 5583a60ace stepping is less screwed up now 2024-08-24 17:53:09 +03:00
merry 8bd4417b24 Implement qThreadExtraInfo 2024-08-24 17:53:09 +03:00
svc64 7e4944cc88 Make DebugState public and remove AppleHV's DebugState 2024-08-24 17:53:09 +03:00
merry 5a34d80f98 Restart socket when able 2024-08-24 17:53:09 +03:00
merry 1cef40131a Support vectors in write registers
# Conflicts:
#	src/Ryujinx.HLE/Debugger/Debugger.cs
2024-08-24 17:53:09 +03:00
merry a9538a54ff cleanup 2024-08-24 17:53:09 +03:00
merry 3bdf9d9805 Implement binary data escaping 2024-08-24 17:53:09 +03:00
merry 09a63ba2b5 Notify debugger on BRK instruction
# Conflicts:
#	src/Ryujinx.HLE/Debugger/Debugger.cs
#	src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs
#	src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
2024-08-24 17:53:09 +03:00
svc64 9b9137bf0a Log GDB server exceptions 2024-08-24 17:53:09 +03:00
svc64 a366890cb5 Fix spacing 2024-08-24 17:53:09 +03:00
svc64 a6cbb89996 Don't store ThreadUid in the shadow context and/or vcpu 2024-08-24 17:53:09 +03:00
svc64 d0fbcced57 Put ThreadUid in the execution context classes 2024-08-24 17:53:09 +03:00
merry 54bf7507d7 Call InvalidateCacheRegion
# Conflicts:
#	Ryujinx.HLE/HOS/ArmProcessContext.cs
#	src/Ryujinx.HLE/Debugger/Debugger.cs
#	src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs
#	src/Ryujinx.HLE/HOS/Horizon.cs
#	src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
2024-08-24 17:53:09 +03:00
merry 1b9753d42a Debugger: Fixups from testing with GDB
# Conflicts:
#	src/Ryujinx.HLE/Debugger/Debugger.cs
2024-08-24 17:53:09 +03:00
merry a1de4f1b5b Debugger: Do more stuff
# Conflicts:
#	src/Ryujinx.HLE/Debugger/Debugger.cs
#	src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml
#	src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml
#	src/Ryujinx.HLE/Debugger/GdbXml/target.xml
#	src/Ryujinx.HLE/Debugger/RegisterInformation.cs
2024-08-24 17:53:09 +03:00
merry 1bb8f6381c Debugger: Add replies for qGDBServerVersion, qHostInfo, qProcessInfo, qfThreadInfo, qsThreadInfo 2024-08-24 17:53:09 +03:00
merry deccf05e60 SDL2: Add GDB stub options to SDL2 frontend
# Conflicts:
#	src/Ryujinx.Headless.SDL2/Program.cs
2024-08-24 17:53:09 +03:00
merry 6edc00ec9c Debugger: Initial implementation
# Conflicts:
#	src/Ryujinx.HLE/Debugger/Debugger.cs
#	src/Ryujinx.HLE/Debugger/GdbSignal.cs
#	src/Ryujinx.HLE/Debugger/Message/AbortMessage.cs
#	src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
#	src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
#	src/Ryujinx.HLE/Debugger/Message/IMessage.cs
#	src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
#	src/Ryujinx.HLE/Debugger/StringStream.cs
#	src/Ryujinx.HLE/Switch.cs
2024-08-24 17:53:09 +03:00
merry 2a17f1314d Configuration: Add GDB stub related configuration
# Conflicts:
#	Ryujinx/Configuration/ConfigurationState.cs
#	src/Ryujinx.HLE/HLEConfiguration.cs
#	src/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs
#	src/Ryujinx/Ui/MainWindow.cs
#	src/Ryujinx/Ui/Windows/SettingsWindow.cs
#	src/Ryujinx/Ui/Windows/SettingsWindow.glade
2024-08-24 17:53:09 +03:00
merry 20bdea45fa Logging: Add GdbStub LogClass
# Conflicts:
#	src/Ryujinx.Common/Logging/LogClass.cs
2024-08-24 17:53:09 +03:00
merry 569d01823e ARMeilleure: Add debuggable CPU loop
# Conflicts:
#	src/ARMeilleure/Optimizations.cs
#	src/ARMeilleure/Translation/ArmEmitterContext.cs
2024-08-24 17:53:09 +03:00
merry 39a3ba8329 IDebuggableProcess: Create and expose interface to enable implementation of debugging tools
# Conflicts:
#	src/ARMeilleure/State/DebugState.cs
#	src/ARMeilleure/State/ExecutionContext.cs
TODO: Fix comments. I also tried implementing AppleHV primitives here (it's actually broken).
2024-08-24 17:53:09 +03:00
115 changed files with 3354 additions and 469 deletions

View File

@ -2,6 +2,8 @@ using ARMeilleure.Memory;
using ARMeilleure.State; using ARMeilleure.State;
using ARMeilleure.Translation; using ARMeilleure.Translation;
using System; using System;
using System.Threading;
using ExecutionContext = ARMeilleure.State.ExecutionContext;
namespace ARMeilleure.Instructions namespace ARMeilleure.Instructions
{ {
@ -175,7 +177,11 @@ namespace ARMeilleure.Instructions
ExecutionContext context = GetContext(); ExecutionContext context = GetContext();
context.CheckInterrupt(); // If debugging, we'll handle interrupts outside
if (!Optimizations.EnableDebugging)
{
context.CheckInterrupt();
}
Statistics.ResumeTimer(); Statistics.ResumeTimer();

View File

@ -9,6 +9,7 @@ namespace ARMeilleure
public static bool AllowLcqInFunctionTable { get; set; } = true; public static bool AllowLcqInFunctionTable { get; set; } = true;
public static bool UseUnmanagedDispatchLoop { get; set; } = true; public static bool UseUnmanagedDispatchLoop { get; set; } = true;
public static bool EnableDebugging { get; set; } = false;
public static bool UseAdvSimdIfAvailable { get; set; } = true; public static bool UseAdvSimdIfAvailable { get; set; } = true;
public static bool UseArm64AesIfAvailable { get; set; } = true; public static bool UseArm64AesIfAvailable { get; set; } = true;

View File

@ -1,5 +1,8 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using System; using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
namespace ARMeilleure.State namespace ARMeilleure.State
{ {
@ -11,7 +14,7 @@ namespace ARMeilleure.State
internal IntPtr NativeContextPtr => _nativeContext.BasePtr; internal IntPtr NativeContextPtr => _nativeContext.BasePtr;
private bool _interrupted; internal bool Interrupted { get; private set; }
private readonly ICounter _counter; private readonly ICounter _counter;
@ -68,6 +71,8 @@ namespace ARMeilleure.State
public bool IsAarch32 { get; set; } public bool IsAarch32 { get; set; }
public ulong ThreadUid { get; set; }
internal ExecutionMode ExecutionMode internal ExecutionMode ExecutionMode
{ {
get get
@ -93,14 +98,19 @@ namespace ARMeilleure.State
private readonly ExceptionCallbackNoArgs _interruptCallback; private readonly ExceptionCallbackNoArgs _interruptCallback;
private readonly ExceptionCallback _breakCallback; private readonly ExceptionCallback _breakCallback;
private readonly ExceptionCallbackNoArgs _stepCallback;
private readonly ExceptionCallback _supervisorCallback; private readonly ExceptionCallback _supervisorCallback;
private readonly ExceptionCallback _undefinedCallback; private readonly ExceptionCallback _undefinedCallback;
internal int ShouldStep;
public ulong DebugPc { get; set; }
public ExecutionContext( public ExecutionContext(
IJitMemoryAllocator allocator, IJitMemoryAllocator allocator,
ICounter counter, ICounter counter,
ExceptionCallbackNoArgs interruptCallback = null, ExceptionCallbackNoArgs interruptCallback = null,
ExceptionCallback breakCallback = null, ExceptionCallback breakCallback = null,
ExceptionCallbackNoArgs stepCallback = null,
ExceptionCallback supervisorCallback = null, ExceptionCallback supervisorCallback = null,
ExceptionCallback undefinedCallback = null) ExceptionCallback undefinedCallback = null)
{ {
@ -108,6 +118,7 @@ namespace ARMeilleure.State
_counter = counter; _counter = counter;
_interruptCallback = interruptCallback; _interruptCallback = interruptCallback;
_breakCallback = breakCallback; _breakCallback = breakCallback;
_stepCallback = stepCallback;
_supervisorCallback = supervisorCallback; _supervisorCallback = supervisorCallback;
_undefinedCallback = undefinedCallback; _undefinedCallback = undefinedCallback;
@ -130,9 +141,9 @@ namespace ARMeilleure.State
internal void CheckInterrupt() internal void CheckInterrupt()
{ {
if (_interrupted) if (Interrupted)
{ {
_interrupted = false; Interrupted = false;
_interruptCallback?.Invoke(this); _interruptCallback?.Invoke(this);
} }
@ -142,7 +153,18 @@ namespace ARMeilleure.State
public void RequestInterrupt() public void RequestInterrupt()
{ {
_interrupted = true; Interrupted = true;
}
public void StepHandler()
{
_stepCallback.Invoke(this);
}
public void RequestDebugStep()
{
Interlocked.Exchange(ref ShouldStep, 1);
RequestInterrupt();
} }
internal void OnBreak(ulong address, int imm) internal void OnBreak(ulong address, int imm)

View File

@ -140,7 +140,25 @@ namespace ARMeilleure.Translation
NativeInterface.RegisterThread(context, Memory, this); NativeInterface.RegisterThread(context, Memory, this);
if (Optimizations.UseUnmanagedDispatchLoop) if (Optimizations.EnableDebugging)
{
context.DebugPc = address;
do
{
if (Interlocked.CompareExchange(ref context.ShouldStep, 0, 1) == 1)
{
context.DebugPc = Step(context, context.DebugPc);
context.StepHandler();
}
else
{
context.DebugPc = ExecuteSingle(context, context.DebugPc);
}
context.CheckInterrupt();
}
while (context.Running && context.DebugPc != 0);
}
else if (Optimizations.UseUnmanagedDispatchLoop)
{ {
Stubs.DispatchLoop(context.NativeContextPtr, address); Stubs.DispatchLoop(context.NativeContextPtr, address);
} }
@ -196,7 +214,7 @@ namespace ARMeilleure.Translation
return nextAddr; return nextAddr;
} }
public ulong Step(State.ExecutionContext context, ulong address) private ulong Step(State.ExecutionContext context, ulong address)
{ {
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true); TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
@ -249,7 +267,7 @@ namespace ARMeilleure.Translation
Stubs, Stubs,
address, address,
highCq, highCq,
_ptc.State != PtcState.Disabled, _ptc.State != PtcState.Disabled && !Optimizations.EnableDebugging,
mode: Aarch32Mode.User); mode: Aarch32Mode.User);
Logger.StartPass(PassName.Decoding); Logger.StartPass(PassName.Decoding);
@ -382,9 +400,8 @@ namespace ARMeilleure.Translation
if (block.Exit) if (block.Exit)
{ {
// Left option here as it may be useful if we need to return to managed rather than tail call in // Return to managed rather than tail call.
// future. (eg. for debug) bool useReturns = Optimizations.EnableDebugging;
bool useReturns = false;
InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns); InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns);
} }

View File

@ -13,6 +13,7 @@ namespace Ryujinx.Common.Logging
Cpu, Cpu,
Emulation, Emulation,
FFmpeg, FFmpeg,
GdbStub,
Font, Font,
Gpu, Gpu,
Hid, Hid,

View File

@ -0,0 +1,10 @@
namespace Ryujinx.Cpu.AppleHv.Arm
{
enum ExceptionLevel : uint
{
PstateMask = 0xfffffff0,
EL1h = 0b0101,
El1t = 0b0100,
EL0 = 0b0000,
}
}

View File

@ -11,7 +11,18 @@ namespace Ryujinx.Cpu.AppleHv
class HvExecutionContext : IExecutionContext class HvExecutionContext : IExecutionContext
{ {
/// <inheritdoc/> /// <inheritdoc/>
public ulong Pc => _impl.ElrEl1; public ulong Pc
{
get
{
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
if (currentEl == (uint)ExceptionLevel.EL1h)
{
return _impl.ElrEl1;
}
return _impl.Pc;
}
}
/// <inheritdoc/> /// <inheritdoc/>
public long TpidrEl0 public long TpidrEl0
@ -48,6 +59,9 @@ namespace Ryujinx.Cpu.AppleHv
set => _impl.Fpsr = value; set => _impl.Fpsr = value;
} }
/// <inheritdoc/>
public ulong ThreadUid { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public bool IsAarch32 public bool IsAarch32
{ {
@ -67,6 +81,7 @@ namespace Ryujinx.Cpu.AppleHv
private readonly ICounter _counter; private readonly ICounter _counter;
private readonly IHvExecutionContext _shadowContext; private readonly IHvExecutionContext _shadowContext;
private IHvExecutionContext _impl; private IHvExecutionContext _impl;
private int _shouldStep;
private readonly ExceptionCallbacks _exceptionCallbacks; private readonly ExceptionCallbacks _exceptionCallbacks;
@ -103,6 +118,11 @@ namespace Ryujinx.Cpu.AppleHv
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm); _exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
} }
private void StepHandler()
{
_exceptionCallbacks.StepCallback?.Invoke(this);
}
private void SupervisorCallHandler(ulong address, int imm) private void SupervisorCallHandler(ulong address, int imm)
{ {
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm); _exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
@ -127,6 +147,30 @@ namespace Ryujinx.Cpu.AppleHv
return Interlocked.Exchange(ref _interruptRequested, 0) != 0; return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
} }
/// <inheritdoc/>
public void RequestDebugStep()
{
Interlocked.Exchange(ref _shouldStep, 1);
}
/// <inheritdoc/>
public ulong DebugPc
{
get => Pc;
set
{
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
if (currentEl == (uint)ExceptionLevel.EL1h)
{
_impl.ElrEl1 = value;
}
else
{
_impl.Pc = value;
}
}
}
/// <inheritdoc/> /// <inheritdoc/>
public void StopRunning() public void StopRunning()
{ {
@ -142,6 +186,22 @@ namespace Ryujinx.Cpu.AppleHv
while (Running) while (Running)
{ {
if (Interlocked.CompareExchange(ref _shouldStep, 0, 1) == 1)
{
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
if (currentEl == (uint)ExceptionLevel.EL1h)
{
HvApi.hv_vcpu_get_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
spsr |= (1 << 21);
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, spsr);
}
else
{
Pstate |= (1 << 21);
}
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.MDSCR_EL1, 1);
}
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError(); HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
HvExitReason reason = vcpu.ExitInfo->Reason; HvExitReason reason = vcpu.ExitInfo->Reason;
@ -155,7 +215,6 @@ namespace Ryujinx.Cpu.AppleHv
{ {
throw new Exception($"Unhandled exception from guest kernel with ESR 0x{hvEsr:X} ({hvEc})."); throw new Exception($"Unhandled exception from guest kernel with ESR 0x{hvEsr:X} ({hvEc}).");
} }
address = SynchronousException(memoryManager, ref vcpu); address = SynchronousException(memoryManager, ref vcpu);
HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError(); HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError();
} }
@ -209,6 +268,20 @@ namespace Ryujinx.Cpu.AppleHv
SupervisorCallHandler(elr - 4UL, id); SupervisorCallHandler(elr - 4UL, id);
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu); vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break; break;
case ExceptionClass.SoftwareStepLowerEl:
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
spsr &= ~((ulong)(1 << 21));
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, spsr).ThrowOnError();
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.MDSCR_EL1, 0);
ReturnToPool(vcpu);
StepHandler();
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break;
case ExceptionClass.BrkAarch64:
ReturnToPool(vcpu);
BreakHandler(elr, (ushort)esr);
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break;
default: default:
throw new Exception($"Unhandled guest exception {ec}."); throw new Exception($"Unhandled guest exception {ec}.");
} }
@ -219,10 +292,7 @@ namespace Ryujinx.Cpu.AppleHv
// TODO: Invalidate only the range that was modified? // TODO: Invalidate only the range that was modified?
return HvAddressSpace.KernelRegionTlbiEretAddress; return HvAddressSpace.KernelRegionTlbiEretAddress;
} }
else return HvAddressSpace.KernelRegionEretAddress;
{
return HvAddressSpace.KernelRegionEretAddress;
}
} }
private static void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr) private static void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr)

View File

@ -18,6 +18,8 @@ namespace Ryujinx.Cpu.AppleHv
public bool IsAarch32 { get; set; } public bool IsAarch32 { get; set; }
public ulong ThreadUid { get; set; }
private readonly ulong[] _x; private readonly ulong[] _x;
private readonly V128[] _v; private readonly V128[] _v;
@ -46,5 +48,14 @@ namespace Ryujinx.Cpu.AppleHv
{ {
_v[index] = value; _v[index] = value;
} }
public void RequestInterrupt()
{
}
public bool GetAndClearInterruptRequested()
{
return false;
}
} }
} }

View File

@ -2,11 +2,10 @@ using ARMeilleure.State;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Threading;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvExecutionContextVcpu : IHvExecutionContext class HvExecutionContextVcpu : IHvExecutionContext
{ {
private static readonly MemoryBlock _setSimdFpRegFuncMem; private static readonly MemoryBlock _setSimdFpRegFuncMem;
@ -14,6 +13,8 @@ namespace Ryujinx.Cpu.AppleHv
private static readonly SetSimdFpReg _setSimdFpReg; private static readonly SetSimdFpReg _setSimdFpReg;
private static readonly IntPtr _setSimdFpRegNativePtr; private static readonly IntPtr _setSimdFpRegNativePtr;
public ulong ThreadUid { get; set; }
static HvExecutionContextVcpu() static HvExecutionContextVcpu()
{ {
// .NET does not support passing vectors by value, so we need to pass a pointer and use a native // .NET does not support passing vectors by value, so we need to pass a pointer and use a native
@ -136,6 +137,7 @@ namespace Ryujinx.Cpu.AppleHv
} }
private readonly ulong _vcpu; private readonly ulong _vcpu;
private int _interruptRequested;
public HvExecutionContextVcpu(ulong vcpu) public HvExecutionContextVcpu(ulong vcpu)
{ {
@ -181,8 +183,16 @@ namespace Ryujinx.Cpu.AppleHv
public void RequestInterrupt() public void RequestInterrupt()
{ {
ulong vcpu = _vcpu; if (Interlocked.Exchange(ref _interruptRequested, 1) == 0)
HvApi.hv_vcpus_exit(ref vcpu, 1); {
ulong vcpu = _vcpu;
HvApi.hv_vcpus_exit(ref vcpu, 1);
}
}
public bool GetAndClearInterruptRequested()
{
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
} }
} }
} }

View File

@ -15,7 +15,7 @@ namespace Ryujinx.Cpu.AppleHv
uint Fpcr { get; set; } uint Fpcr { get; set; }
uint Fpsr { get; set; } uint Fpsr { get; set; }
ulong ThreadUid { get; set; }
ulong GetX(int index); ulong GetX(int index);
void SetX(int index, ulong value); void SetX(int index, ulong value);
@ -39,5 +39,8 @@ namespace Ryujinx.Cpu.AppleHv
SetV(i, context.GetV(i)); SetV(i, context.GetV(i));
} }
} }
void RequestInterrupt();
bool GetAndClearInterruptRequested();
} }
} }

View File

@ -29,6 +29,11 @@ namespace Ryujinx.Cpu
/// </summary> /// </summary>
public readonly ExceptionCallback BreakCallback; public readonly ExceptionCallback BreakCallback;
/// <summary>
/// Handler for CPU software interrupts caused by single-stepping.
/// </summary>
public readonly ExceptionCallbackNoArgs StepCallback;
/// <summary> /// <summary>
/// Handler for CPU software interrupts caused by the Arm SVC instruction. /// Handler for CPU software interrupts caused by the Arm SVC instruction.
/// </summary> /// </summary>
@ -47,16 +52,19 @@ namespace Ryujinx.Cpu
/// </remarks> /// </remarks>
/// <param name="interruptCallback">Handler for CPU interrupts triggered using <see cref="IExecutionContext.RequestInterrupt"/></param> /// <param name="interruptCallback">Handler for CPU interrupts triggered using <see cref="IExecutionContext.RequestInterrupt"/></param>
/// <param name="breakCallback">Handler for CPU software interrupts caused by the Arm BRK instruction</param> /// <param name="breakCallback">Handler for CPU software interrupts caused by the Arm BRK instruction</param>
/// <param name="stepCallback">Handler for CPU software interrupts caused by single-stepping</param>
/// <param name="supervisorCallback">Handler for CPU software interrupts caused by the Arm SVC instruction</param> /// <param name="supervisorCallback">Handler for CPU software interrupts caused by the Arm SVC instruction</param>
/// <param name="undefinedCallback">Handler for CPU software interrupts caused by any undefined Arm instruction</param> /// <param name="undefinedCallback">Handler for CPU software interrupts caused by any undefined Arm instruction</param>
public ExceptionCallbacks( public ExceptionCallbacks(
ExceptionCallbackNoArgs interruptCallback = null, ExceptionCallbackNoArgs interruptCallback = null,
ExceptionCallback breakCallback = null, ExceptionCallback breakCallback = null,
ExceptionCallbackNoArgs stepCallback = null,
ExceptionCallback supervisorCallback = null, ExceptionCallback supervisorCallback = null,
ExceptionCallback undefinedCallback = null) ExceptionCallback undefinedCallback = null)
{ {
InterruptCallback = interruptCallback; InterruptCallback = interruptCallback;
BreakCallback = breakCallback; BreakCallback = breakCallback;
StepCallback = stepCallback;
SupervisorCallback = supervisorCallback; SupervisorCallback = supervisorCallback;
UndefinedCallback = undefinedCallback; UndefinedCallback = undefinedCallback;
} }

View File

@ -1,5 +1,6 @@
using ARMeilleure.State; using ARMeilleure.State;
using System; using System;
using System.Threading;
namespace Ryujinx.Cpu namespace Ryujinx.Cpu
{ {
@ -46,6 +47,11 @@ namespace Ryujinx.Cpu
/// </summary> /// </summary>
bool IsAarch32 { get; set; } bool IsAarch32 { get; set; }
/// <summary>
/// Thread UID.
/// </summary>
public ulong ThreadUid { get; set; }
/// <summary> /// <summary>
/// Indicates whenever the CPU is still running code. /// Indicates whenever the CPU is still running code.
/// </summary> /// </summary>
@ -108,5 +114,23 @@ namespace Ryujinx.Cpu
/// If you only need to pause the thread temporarily, use <see cref="RequestInterrupt"/> instead. /// If you only need to pause the thread temporarily, use <see cref="RequestInterrupt"/> instead.
/// </remarks> /// </remarks>
void StopRunning(); void StopRunning();
/// <summary>
/// Requests the thread to stop running temporarily and call <see cref="ExceptionCallbacks.InterruptCallback"/>.
/// </summary>
/// <remarks>
/// The thread might not pause immediately.
/// One must not assume that guest code is no longer being executed by the thread after calling this function.
/// After single stepping, the thread should call call <see cref="ExceptionCallbacks.StepCallback"/>.
/// </remarks>
void RequestDebugStep();
/// <summary>
/// Current Program Counter (for debugging).
/// </summary>
/// <remarks>
/// PC register for the debugger. Must not be accessed while the thread isn't stopped for debugging.
/// </remarks>
ulong DebugPc { get; set; }
} }
} }

View File

@ -1,5 +1,7 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using ARMeilleure.State; using ARMeilleure.State;
using System.Threading;
using ExecutionContext = ARMeilleure.State.ExecutionContext;
namespace Ryujinx.Cpu.Jit namespace Ryujinx.Cpu.Jit
{ {
@ -53,6 +55,13 @@ namespace Ryujinx.Cpu.Jit
set => _impl.IsAarch32 = value; set => _impl.IsAarch32 = value;
} }
/// <inheritdoc/>
public ulong ThreadUid
{
get => _impl.ThreadUid;
set => _impl.ThreadUid = value;
}
/// <inheritdoc/> /// <inheritdoc/>
public bool Running => _impl.Running; public bool Running => _impl.Running;
@ -65,6 +74,7 @@ namespace Ryujinx.Cpu.Jit
counter, counter,
InterruptHandler, InterruptHandler,
BreakHandler, BreakHandler,
StepHandler,
SupervisorCallHandler, SupervisorCallHandler,
UndefinedHandler); UndefinedHandler);
@ -93,6 +103,11 @@ namespace Ryujinx.Cpu.Jit
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm); _exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
} }
private void StepHandler(ExecutionContext context)
{
_exceptionCallbacks.StepCallback?.Invoke(this);
}
private void SupervisorCallHandler(ExecutionContext context, ulong address, int imm) private void SupervisorCallHandler(ExecutionContext context, ulong address, int imm)
{ {
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm); _exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
@ -109,6 +124,16 @@ namespace Ryujinx.Cpu.Jit
_impl.RequestInterrupt(); _impl.RequestInterrupt();
} }
/// <inheritdoc/>
public void RequestDebugStep() => _impl.RequestDebugStep();
/// <inheritdoc/>
public ulong DebugPc
{
get => _impl.DebugPc;
set => _impl.DebugPc = value;
}
/// <inheritdoc/> /// <inheritdoc/>
public void StopRunning() public void StopRunning()
{ {

View File

@ -4,7 +4,6 @@ namespace Ryujinx.Graphics.GAL
{ {
public interface IImageArray : IDisposable public interface IImageArray : IDisposable
{ {
void SetFormats(int index, Format[] imageFormats);
void SetImages(int index, ITexture[] images); void SetImages(int index, ITexture[] images);
} }
} }

View File

@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.GAL
void SetIndexBuffer(BufferRange buffer, IndexType type); void SetIndexBuffer(BufferRange buffer, IndexType type);
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat); void SetImage(ShaderStage stage, int binding, ITexture texture);
void SetImageArray(ShaderStage stage, int binding, IImageArray array); void SetImageArray(ShaderStage stage, int binding, IImageArray array);
void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array); void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array);

View File

@ -1,4 +1,4 @@
using System.Buffers; using Ryujinx.Common.Memory;
namespace Ryujinx.Graphics.GAL namespace Ryujinx.Graphics.GAL
{ {
@ -18,30 +18,30 @@ namespace Ryujinx.Graphics.GAL
PinnedSpan<byte> GetData(int layer, int level); PinnedSpan<byte> GetData(int layer, int level);
/// <summary> /// <summary>
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when /// Sets the texture data. The data passed as a <see cref="MemoryOwner{Byte}" /> will be disposed when
/// the operation completes. /// the operation completes.
/// </summary> /// </summary>
/// <param name="data">Texture data bytes</param> /// <param name="data">Texture data bytes</param>
void SetData(IMemoryOwner<byte> data); void SetData(MemoryOwner<byte> data);
/// <summary> /// <summary>
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when /// Sets the texture data. The data passed as a <see cref="MemoryOwner{Byte}" /> will be disposed when
/// the operation completes. /// the operation completes.
/// </summary> /// </summary>
/// <param name="data">Texture data bytes</param> /// <param name="data">Texture data bytes</param>
/// <param name="layer">Target layer</param> /// <param name="layer">Target layer</param>
/// <param name="level">Target level</param> /// <param name="level">Target level</param>
void SetData(IMemoryOwner<byte> data, int layer, int level); void SetData(MemoryOwner<byte> data, int layer, int level);
/// <summary> /// <summary>
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when /// Sets the texture data. The data passed as a <see cref="MemoryOwner{Byte}" /> will be disposed when
/// the operation completes. /// the operation completes.
/// </summary> /// </summary>
/// <param name="data">Texture data bytes</param> /// <param name="data">Texture data bytes</param>
/// <param name="layer">Target layer</param> /// <param name="layer">Target layer</param>
/// <param name="level">Target level</param> /// <param name="level">Target level</param>
/// <param name="region">Target sub-region of the texture to update</param> /// <param name="region">Target sub-region of the texture to update</param>
void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region); void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region);
void SetStorage(BufferRange buffer); void SetStorage(BufferRange buffer);

View File

@ -67,7 +67,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<CounterEventFlushCommand>(CommandType.CounterEventFlush); Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
Register<ImageArrayDisposeCommand>(CommandType.ImageArrayDispose); Register<ImageArrayDisposeCommand>(CommandType.ImageArrayDispose);
Register<ImageArraySetFormatsCommand>(CommandType.ImageArraySetFormats);
Register<ImageArraySetImagesCommand>(CommandType.ImageArraySetImages); Register<ImageArraySetImagesCommand>(CommandType.ImageArraySetImages);
Register<ProgramDisposeCommand>(CommandType.ProgramDispose); Register<ProgramDisposeCommand>(CommandType.ProgramDispose);

View File

@ -27,7 +27,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
CounterEventFlush, CounterEventFlush,
ImageArrayDispose, ImageArrayDispose,
ImageArraySetFormats,
ImageArraySetImages, ImageArraySetImages,
ProgramDispose, ProgramDispose,

View File

@ -1,26 +0,0 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray
{
struct ImageArraySetFormatsCommand : IGALCommand, IGALCommand<ImageArraySetFormatsCommand>
{
public readonly CommandType CommandType => CommandType.ImageArraySetFormats;
private TableRef<ThreadedImageArray> _imageArray;
private int _index;
private TableRef<Format[]> _imageFormats;
public void Set(TableRef<ThreadedImageArray> imageArray, int index, TableRef<Format[]> imageFormats)
{
_imageArray = imageArray;
_index = index;
_imageFormats = imageFormats;
}
public static void Run(ref ImageArraySetFormatsCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ThreadedImageArray imageArray = command._imageArray.Get(threaded);
imageArray.Base.SetFormats(command._index, command._imageFormats.Get(threaded));
}
}
}

View File

@ -10,19 +10,17 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
private ShaderStage _stage; private ShaderStage _stage;
private int _binding; private int _binding;
private TableRef<ITexture> _texture; private TableRef<ITexture> _texture;
private Format _imageFormat;
public void Set(ShaderStage stage, int binding, TableRef<ITexture> texture, Format imageFormat) public void Set(ShaderStage stage, int binding, TableRef<ITexture> texture)
{ {
_stage = stage; _stage = stage;
_binding = binding; _binding = binding;
_texture = texture; _texture = texture;
_imageFormat = imageFormat;
} }
public static void Run(ref SetImageCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref SetImageCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
renderer.Pipeline.SetImage(command._stage, command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._imageFormat); renderer.Pipeline.SetImage(command._stage, command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base);
} }
} }
} }

View File

@ -1,6 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources; using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
@ -8,9 +8,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
public readonly CommandType CommandType => CommandType.TextureSetData; public readonly CommandType CommandType => CommandType.TextureSetData;
private TableRef<ThreadedTexture> _texture; private TableRef<ThreadedTexture> _texture;
private TableRef<IMemoryOwner<byte>> _data; private TableRef<MemoryOwner<byte>> _data;
public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data) public void Set(TableRef<ThreadedTexture> texture, TableRef<MemoryOwner<byte>> data)
{ {
_texture = texture; _texture = texture;
_data = data; _data = data;

View File

@ -1,6 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources; using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
public readonly CommandType CommandType => CommandType.TextureSetDataSlice; public readonly CommandType CommandType => CommandType.TextureSetDataSlice;
private TableRef<ThreadedTexture> _texture; private TableRef<ThreadedTexture> _texture;
private TableRef<IMemoryOwner<byte>> _data; private TableRef<MemoryOwner<byte>> _data;
private int _layer; private int _layer;
private int _level; private int _level;
public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data, int layer, int level) public void Set(TableRef<ThreadedTexture> texture, TableRef<MemoryOwner<byte>> data, int layer, int level)
{ {
_texture = texture; _texture = texture;
_data = data; _data = data;

View File

@ -1,6 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources; using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
@ -8,12 +8,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{ {
public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion; public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion;
private TableRef<ThreadedTexture> _texture; private TableRef<ThreadedTexture> _texture;
private TableRef<IMemoryOwner<byte>> _data; private TableRef<MemoryOwner<byte>> _data;
private int _layer; private int _layer;
private int _level; private int _level;
private Rectangle<int> _region; private Rectangle<int> _region;
public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data, int layer, int level, Rectangle<int> region) public void Set(TableRef<ThreadedTexture> texture, TableRef<MemoryOwner<byte>> data, int layer, int level, Rectangle<int> region)
{ {
_texture = texture; _texture = texture;
_data = data; _data = data;

View File

@ -27,12 +27,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetFormats(int index, Format[] imageFormats)
{
_renderer.New<ImageArraySetFormatsCommand>().Set(Ref(this), index, Ref(imageFormats));
_renderer.QueueCommand();
}
public void SetImages(int index, ITexture[] images) public void SetImages(int index, ITexture[] images)
{ {
_renderer.New<ImageArraySetImagesCommand>().Set(Ref(this), index, Ref(images)); _renderer.New<ImageArraySetImagesCommand>().Set(Ref(this), index, Ref(images));

View File

@ -1,6 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Model;
using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Resources namespace Ryujinx.Graphics.GAL.Multithreading.Resources
{ {
@ -111,21 +111,21 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data) public void SetData(MemoryOwner<byte> data)
{ {
_renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data)); _renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data));
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level) public void SetData(MemoryOwner<byte> data, int layer, int level)
{ {
_renderer.New<TextureSetDataSliceCommand>().Set(Ref(this), Ref(data), layer, level); _renderer.New<TextureSetDataSliceCommand>().Set(Ref(this), Ref(data), layer, level);
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region) public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
_renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data), layer, level, region); _renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data), layer, level, region);
_renderer.QueueCommand(); _renderer.QueueCommand();

View File

@ -177,9 +177,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat) public void SetImage(ShaderStage stage, int binding, ITexture texture)
{ {
_renderer.New<SetImageCommand>().Set(stage, binding, Ref(texture), imageFormat); _renderer.New<SetImageCommand>().Set(stage, binding, Ref(texture));
_renderer.QueueCommand(); _renderer.QueueCommand();
} }

View File

@ -5,5 +5,6 @@ namespace Ryujinx.Graphics.GAL
Bilinear, Bilinear,
Nearest, Nearest,
Fsr, Fsr,
Area,
} }
} }

View File

@ -1,10 +1,10 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Device; using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.Gpu.Engine.Threed; using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -353,7 +353,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
if (target != null) if (target != null)
{ {
IMemoryOwner<byte> data; MemoryOwner<byte> data;
if (srcLinear) if (srcLinear)
{ {
data = LayoutConverter.ConvertLinearStridedToLinear( data = LayoutConverter.ConvertLinearStridedToLinear(

View File

@ -1,5 +1,6 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.Gpu.Engine namespace Ryujinx.Graphics.Gpu.Engine
@ -61,51 +62,51 @@ namespace Ryujinx.Graphics.Gpu.Engine
/// </summary> /// </summary>
/// <param name="format">Shader image format</param> /// <param name="format">Shader image format</param>
/// <returns>Texture format</returns> /// <returns>Texture format</returns>
public static Format GetFormat(TextureFormat format) public static FormatInfo GetFormatInfo(TextureFormat format)
{ {
return format switch return format switch
{ {
#pragma warning disable IDE0055 // Disable formatting #pragma warning disable IDE0055 // Disable formatting
TextureFormat.R8Unorm => Format.R8Unorm, TextureFormat.R8Unorm => new(Format.R8Unorm, 1, 1, 1, 1),
TextureFormat.R8Snorm => Format.R8Snorm, TextureFormat.R8Snorm => new(Format.R8Snorm, 1, 1, 1, 1),
TextureFormat.R8Uint => Format.R8Uint, TextureFormat.R8Uint => new(Format.R8Uint, 1, 1, 1, 1),
TextureFormat.R8Sint => Format.R8Sint, TextureFormat.R8Sint => new(Format.R8Sint, 1, 1, 1, 1),
TextureFormat.R16Float => Format.R16Float, TextureFormat.R16Float => new(Format.R16Float, 1, 1, 2, 1),
TextureFormat.R16Unorm => Format.R16Unorm, TextureFormat.R16Unorm => new(Format.R16Unorm, 1, 1, 2, 1),
TextureFormat.R16Snorm => Format.R16Snorm, TextureFormat.R16Snorm => new(Format.R16Snorm, 1, 1, 2, 1),
TextureFormat.R16Uint => Format.R16Uint, TextureFormat.R16Uint => new(Format.R16Uint, 1, 1, 2, 1),
TextureFormat.R16Sint => Format.R16Sint, TextureFormat.R16Sint => new(Format.R16Sint, 1, 1, 2, 1),
TextureFormat.R32Float => Format.R32Float, TextureFormat.R32Float => new(Format.R32Float, 1, 1, 4, 1),
TextureFormat.R32Uint => Format.R32Uint, TextureFormat.R32Uint => new(Format.R32Uint, 1, 1, 4, 1),
TextureFormat.R32Sint => Format.R32Sint, TextureFormat.R32Sint => new(Format.R32Sint, 1, 1, 4, 1),
TextureFormat.R8G8Unorm => Format.R8G8Unorm, TextureFormat.R8G8Unorm => new(Format.R8G8Unorm, 1, 1, 2, 2),
TextureFormat.R8G8Snorm => Format.R8G8Snorm, TextureFormat.R8G8Snorm => new(Format.R8G8Snorm, 1, 1, 2, 2),
TextureFormat.R8G8Uint => Format.R8G8Uint, TextureFormat.R8G8Uint => new(Format.R8G8Uint, 1, 1, 2, 2),
TextureFormat.R8G8Sint => Format.R8G8Sint, TextureFormat.R8G8Sint => new(Format.R8G8Sint, 1, 1, 2, 2),
TextureFormat.R16G16Float => Format.R16G16Float, TextureFormat.R16G16Float => new(Format.R16G16Float, 1, 1, 4, 2),
TextureFormat.R16G16Unorm => Format.R16G16Unorm, TextureFormat.R16G16Unorm => new(Format.R16G16Unorm, 1, 1, 4, 2),
TextureFormat.R16G16Snorm => Format.R16G16Snorm, TextureFormat.R16G16Snorm => new(Format.R16G16Snorm, 1, 1, 4, 2),
TextureFormat.R16G16Uint => Format.R16G16Uint, TextureFormat.R16G16Uint => new(Format.R16G16Uint, 1, 1, 4, 2),
TextureFormat.R16G16Sint => Format.R16G16Sint, TextureFormat.R16G16Sint => new(Format.R16G16Sint, 1, 1, 4, 2),
TextureFormat.R32G32Float => Format.R32G32Float, TextureFormat.R32G32Float => new(Format.R32G32Float, 1, 1, 8, 2),
TextureFormat.R32G32Uint => Format.R32G32Uint, TextureFormat.R32G32Uint => new(Format.R32G32Uint, 1, 1, 8, 2),
TextureFormat.R32G32Sint => Format.R32G32Sint, TextureFormat.R32G32Sint => new(Format.R32G32Sint, 1, 1, 8, 2),
TextureFormat.R8G8B8A8Unorm => Format.R8G8B8A8Unorm, TextureFormat.R8G8B8A8Unorm => new(Format.R8G8B8A8Unorm, 1, 1, 4, 4),
TextureFormat.R8G8B8A8Snorm => Format.R8G8B8A8Snorm, TextureFormat.R8G8B8A8Snorm => new(Format.R8G8B8A8Snorm, 1, 1, 4, 4),
TextureFormat.R8G8B8A8Uint => Format.R8G8B8A8Uint, TextureFormat.R8G8B8A8Uint => new(Format.R8G8B8A8Uint, 1, 1, 4, 4),
TextureFormat.R8G8B8A8Sint => Format.R8G8B8A8Sint, TextureFormat.R8G8B8A8Sint => new(Format.R8G8B8A8Sint, 1, 1, 4, 4),
TextureFormat.R16G16B16A16Float => Format.R16G16B16A16Float, TextureFormat.R16G16B16A16Float => new(Format.R16G16B16A16Float, 1, 1, 8, 4),
TextureFormat.R16G16B16A16Unorm => Format.R16G16B16A16Unorm, TextureFormat.R16G16B16A16Unorm => new(Format.R16G16B16A16Unorm, 1, 1, 8, 4),
TextureFormat.R16G16B16A16Snorm => Format.R16G16B16A16Snorm, TextureFormat.R16G16B16A16Snorm => new(Format.R16G16B16A16Snorm, 1, 1, 8, 4),
TextureFormat.R16G16B16A16Uint => Format.R16G16B16A16Uint, TextureFormat.R16G16B16A16Uint => new(Format.R16G16B16A16Uint, 1, 1, 8, 4),
TextureFormat.R16G16B16A16Sint => Format.R16G16B16A16Sint, TextureFormat.R16G16B16A16Sint => new(Format.R16G16B16A16Sint, 1, 1, 8, 4),
TextureFormat.R32G32B32A32Float => Format.R32G32B32A32Float, TextureFormat.R32G32B32A32Float => new(Format.R32G32B32A32Float, 1, 1, 16, 4),
TextureFormat.R32G32B32A32Uint => Format.R32G32B32A32Uint, TextureFormat.R32G32B32A32Uint => new(Format.R32G32B32A32Uint, 1, 1, 16, 4),
TextureFormat.R32G32B32A32Sint => Format.R32G32B32A32Sint, TextureFormat.R32G32B32A32Sint => new(Format.R32G32B32A32Sint, 1, 1, 16, 4),
TextureFormat.R10G10B10A2Unorm => Format.R10G10B10A2Unorm, TextureFormat.R10G10B10A2Unorm => new(Format.R10G10B10A2Unorm, 1, 1, 4, 4),
TextureFormat.R10G10B10A2Uint => Format.R10G10B10A2Uint, TextureFormat.R10G10B10A2Uint => new(Format.R10G10B10A2Uint, 1, 1, 4, 4),
TextureFormat.R11G11B10Float => Format.R11G11B10Float, TextureFormat.R11G11B10Float => new(Format.R11G11B10Float, 1, 1, 4, 3),
_ => 0, _ => FormatInfo.Invalid,
#pragma warning restore IDE0055 #pragma warning restore IDE0055
}; };
} }

View File

@ -7,6 +7,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
readonly struct FormatInfo readonly struct FormatInfo
{ {
/// <summary>
/// An invalid texture format.
/// </summary>
public static FormatInfo Invalid { get; } = new(0, 0, 0, 0, 0);
/// <summary> /// <summary>
/// A default, generic RGBA8 texture format. /// A default, generic RGBA8 texture format.
/// </summary> /// </summary>
@ -23,7 +28,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <remarks> /// <remarks>
/// Must be 1 for non-compressed formats. /// Must be 1 for non-compressed formats.
/// </remarks> /// </remarks>
public int BlockWidth { get; } public byte BlockWidth { get; }
/// <summary> /// <summary>
/// The block height for compressed formats. /// The block height for compressed formats.
@ -31,17 +36,17 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <remarks> /// <remarks>
/// Must be 1 for non-compressed formats. /// Must be 1 for non-compressed formats.
/// </remarks> /// </remarks>
public int BlockHeight { get; } public byte BlockHeight { get; }
/// <summary> /// <summary>
/// The number of bytes occupied by a single pixel in memory of the texture data. /// The number of bytes occupied by a single pixel in memory of the texture data.
/// </summary> /// </summary>
public int BytesPerPixel { get; } public byte BytesPerPixel { get; }
/// <summary> /// <summary>
/// The maximum number of components this format has defined (in RGBA order). /// The maximum number of components this format has defined (in RGBA order).
/// </summary> /// </summary>
public int Components { get; } public byte Components { get; }
/// <summary> /// <summary>
/// Whenever or not the texture format is a compressed format. Determined from block size. /// Whenever or not the texture format is a compressed format. Determined from block size.
@ -57,10 +62,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="bytesPerPixel">The number of bytes occupied by a single pixel in memory of the texture data</param> /// <param name="bytesPerPixel">The number of bytes occupied by a single pixel in memory of the texture data</param>
public FormatInfo( public FormatInfo(
Format format, Format format,
int blockWidth, byte blockWidth,
int blockHeight, byte blockHeight,
int bytesPerPixel, byte bytesPerPixel,
int components) byte components)
{ {
Format = format; Format = format;
BlockWidth = blockWidth; BlockWidth = blockWidth;

View File

@ -7,7 +7,6 @@ using Ryujinx.Graphics.Texture.Astc;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -662,7 +661,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
IMemoryOwner<byte> result = ConvertToHostCompatibleFormat(data); MemoryOwner<byte> result = ConvertToHostCompatibleFormat(data);
if (ScaleFactor != 1f && AllowScaledSetData()) if (ScaleFactor != 1f && AllowScaledSetData())
{ {
@ -685,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Uploads new texture data to the host GPU. /// Uploads new texture data to the host GPU.
/// </summary> /// </summary>
/// <param name="data">New data</param> /// <param name="data">New data</param>
public void SetData(IMemoryOwner<byte> data) public void SetData(MemoryOwner<byte> data)
{ {
BlacklistScale(); BlacklistScale();
@ -704,7 +703,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="data">New data</param> /// <param name="data">New data</param>
/// <param name="layer">Target layer</param> /// <param name="layer">Target layer</param>
/// <param name="level">Target level</param> /// <param name="level">Target level</param>
public void SetData(IMemoryOwner<byte> data, int layer, int level) public void SetData(MemoryOwner<byte> data, int layer, int level)
{ {
BlacklistScale(); BlacklistScale();
@ -722,7 +721,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="layer">Target layer</param> /// <param name="layer">Target layer</param>
/// <param name="level">Target level</param> /// <param name="level">Target level</param>
/// <param name="region">Target sub-region of the texture to update</param> /// <param name="region">Target sub-region of the texture to update</param>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region) public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
BlacklistScale(); BlacklistScale();
@ -740,7 +739,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="level">Mip level to convert</param> /// <param name="level">Mip level to convert</param>
/// <param name="single">True to convert a single slice</param> /// <param name="single">True to convert a single slice</param>
/// <returns>Converted data</returns> /// <returns>Converted data</returns>
public IMemoryOwner<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false) public MemoryOwner<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false)
{ {
int width = Info.Width; int width = Info.Width;
int height = Info.Height; int height = Info.Height;
@ -755,7 +754,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int sliceDepth = single ? 1 : depth; int sliceDepth = single ? 1 : depth;
IMemoryOwner<byte> linear; MemoryOwner<byte> linear;
if (Info.IsLinear) if (Info.IsLinear)
{ {
@ -788,7 +787,7 @@ namespace Ryujinx.Graphics.Gpu.Image
data); data);
} }
IMemoryOwner<byte> result = linear; MemoryOwner<byte> result = linear;
// Handle compressed cases not supported by the host: // Handle compressed cases not supported by the host:
// - ASTC is usually not supported on desktop cards. // - ASTC is usually not supported on desktop cards.
@ -832,19 +831,19 @@ namespace Ryujinx.Graphics.Gpu.Image
case Format.Etc2RgbaUnorm: case Format.Etc2RgbaUnorm:
using (result) using (result)
{ {
return ETC2Decoder.DecodeRgba(result.Memory.Span, width, height, sliceDepth, levels, layers); return ETC2Decoder.DecodeRgba(result.Span, width, height, sliceDepth, levels, layers);
} }
case Format.Etc2RgbPtaSrgb: case Format.Etc2RgbPtaSrgb:
case Format.Etc2RgbPtaUnorm: case Format.Etc2RgbPtaUnorm:
using (result) using (result)
{ {
return ETC2Decoder.DecodePta(result.Memory.Span, width, height, sliceDepth, levels, layers); return ETC2Decoder.DecodePta(result.Span, width, height, sliceDepth, levels, layers);
} }
case Format.Etc2RgbSrgb: case Format.Etc2RgbSrgb:
case Format.Etc2RgbUnorm: case Format.Etc2RgbUnorm:
using (result) using (result)
{ {
return ETC2Decoder.DecodeRgb(result.Memory.Span, width, height, sliceDepth, levels, layers); return ETC2Decoder.DecodeRgb(result.Span, width, height, sliceDepth, levels, layers);
} }
} }
} }
@ -856,43 +855,43 @@ namespace Ryujinx.Graphics.Gpu.Image
case Format.Bc1RgbaUnorm: case Format.Bc1RgbaUnorm:
using (result) using (result)
{ {
return BCnDecoder.DecodeBC1(result.Memory.Span, width, height, sliceDepth, levels, layers); return BCnDecoder.DecodeBC1(result.Span, width, height, sliceDepth, levels, layers);
} }
case Format.Bc2Srgb: case Format.Bc2Srgb:
case Format.Bc2Unorm: case Format.Bc2Unorm:
using (result) using (result)
{ {
return BCnDecoder.DecodeBC2(result.Memory.Span, width, height, sliceDepth, levels, layers); return BCnDecoder.DecodeBC2(result.Span, width, height, sliceDepth, levels, layers);
} }
case Format.Bc3Srgb: case Format.Bc3Srgb:
case Format.Bc3Unorm: case Format.Bc3Unorm:
using (result) using (result)
{ {
return BCnDecoder.DecodeBC3(result.Memory.Span, width, height, sliceDepth, levels, layers); return BCnDecoder.DecodeBC3(result.Span, width, height, sliceDepth, levels, layers);
} }
case Format.Bc4Snorm: case Format.Bc4Snorm:
case Format.Bc4Unorm: case Format.Bc4Unorm:
using (result) using (result)
{ {
return BCnDecoder.DecodeBC4(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm); return BCnDecoder.DecodeBC4(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
} }
case Format.Bc5Snorm: case Format.Bc5Snorm:
case Format.Bc5Unorm: case Format.Bc5Unorm:
using (result) using (result)
{ {
return BCnDecoder.DecodeBC5(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm); return BCnDecoder.DecodeBC5(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
} }
case Format.Bc6HSfloat: case Format.Bc6HSfloat:
case Format.Bc6HUfloat: case Format.Bc6HUfloat:
using (result) using (result)
{ {
return BCnDecoder.DecodeBC6(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat); return BCnDecoder.DecodeBC6(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
} }
case Format.Bc7Srgb: case Format.Bc7Srgb:
case Format.Bc7Unorm: case Format.Bc7Unorm:
using (result) using (result)
{ {
return BCnDecoder.DecodeBC7(result.Memory.Span, width, height, sliceDepth, levels, layers); return BCnDecoder.DecodeBC7(result.Span, width, height, sliceDepth, levels, layers);
} }
} }
} }
@ -900,7 +899,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
using (result) using (result)
{ {
var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Memory.Span, width); var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Span, width);
if (_context.Capabilities.SupportsR4G4B4A4Format) if (_context.Capabilities.SupportsR4G4B4A4Format)
{ {
@ -910,7 +909,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
using (converted) using (converted)
{ {
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Memory.Span, width); return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Span, width);
} }
} }
} }
@ -921,7 +920,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
using (result) using (result)
{ {
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width); return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Span, width);
} }
} }
} }
@ -933,24 +932,24 @@ namespace Ryujinx.Graphics.Gpu.Image
case Format.R5G6B5Unorm: case Format.R5G6B5Unorm:
using (result) using (result)
{ {
return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Memory.Span, width); return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Span, width);
} }
case Format.B5G5R5A1Unorm: case Format.B5G5R5A1Unorm:
case Format.R5G5B5X1Unorm: case Format.R5G5B5X1Unorm:
case Format.R5G5B5A1Unorm: case Format.R5G5B5A1Unorm:
using (result) using (result)
{ {
return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Memory.Span, width, Format == Format.R5G5B5X1Unorm); return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Span, width, Format == Format.R5G5B5X1Unorm);
} }
case Format.A1B5G5R5Unorm: case Format.A1B5G5R5Unorm:
using (result) using (result)
{ {
return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Memory.Span, width); return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Span, width);
} }
case Format.R4G4B4A4Unorm: case Format.R4G4B4A4Unorm:
using (result) using (result)
{ {
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width); return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Span, width);
} }
} }
} }

View File

@ -17,7 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// For images, indicates the format specified on the shader. /// For images, indicates the format specified on the shader.
/// </summary> /// </summary>
public Format Format { get; } public FormatInfo FormatInfo { get; }
/// <summary> /// <summary>
/// Shader texture host set index. /// Shader texture host set index.
@ -58,17 +58,17 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Constructs the texture binding information structure. /// Constructs the texture binding information structure.
/// </summary> /// </summary>
/// <param name="target">The shader sampler target type</param> /// <param name="target">The shader sampler target type</param>
/// <param name="format">Format of the image as declared on the shader</param> /// <param name="formatInfo">Format of the image as declared on the shader</param>
/// <param name="set">Shader texture host set index</param> /// <param name="set">Shader texture host set index</param>
/// <param name="binding">The shader texture binding point</param> /// <param name="binding">The shader texture binding point</param>
/// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param> /// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param>
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param> /// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param> /// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param> /// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
public TextureBindingInfo(Target target, Format format, int set, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags) public TextureBindingInfo(Target target, FormatInfo formatInfo, int set, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags)
{ {
Target = target; Target = target;
Format = format; FormatInfo = formatInfo;
Set = set; Set = set;
Binding = binding; Binding = binding;
ArrayLength = arrayLength; ArrayLength = arrayLength;
@ -96,7 +96,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int cbufSlot, int cbufSlot,
int handle, int handle,
TextureUsageFlags flags, TextureUsageFlags flags,
bool isSamplerOnly) : this(target, 0, set, binding, arrayLength, cbufSlot, handle, flags) bool isSamplerOnly) : this(target, FormatInfo.Invalid, set, binding, arrayLength, cbufSlot, handle, flags)
{ {
IsSamplerOnly = isSamplerOnly; IsSamplerOnly = isSamplerOnly;
} }

View File

@ -659,7 +659,6 @@ namespace Ryujinx.Graphics.Gpu.Image
int length = (isSampler ? samplerPool.MaximumId : texturePool.MaximumId) + 1; int length = (isSampler ? samplerPool.MaximumId : texturePool.MaximumId) + 1;
length = Math.Min(length, bindingInfo.ArrayLength); length = Math.Min(length, bindingInfo.ArrayLength);
Format[] formats = isImage ? new Format[bindingInfo.ArrayLength] : null;
ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength]; ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
ITexture[] textures = new ITexture[bindingInfo.ArrayLength]; ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
@ -674,7 +673,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
else else
{ {
ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, out texture); ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, bindingInfo.FormatInfo, out texture);
if (texture != null) if (texture != null)
{ {
@ -697,8 +696,6 @@ namespace Ryujinx.Graphics.Gpu.Image
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
ISampler hostSampler = sampler?.GetHostSampler(texture); ISampler hostSampler = sampler?.GetHostSampler(texture);
Format format = bindingInfo.Format;
if (hostTexture != null && texture.Target == Target.TextureBuffer) if (hostTexture != null && texture.Target == Target.TextureBuffer)
{ {
// Ensure that the buffer texture is using the correct buffer as storage. // Ensure that the buffer texture is using the correct buffer as storage.
@ -706,26 +703,15 @@ namespace Ryujinx.Graphics.Gpu.Image
// to ensure we're not using a old buffer that was already deleted. // to ensure we're not using a old buffer that was already deleted.
if (isImage) if (isImage)
{ {
if (format == 0 && texture != null) _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
{
format = texture.Format;
}
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
} }
else else
{ {
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format); _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index);
} }
} }
else if (isImage) else if (isImage)
{ {
if (format == 0 && texture != null)
{
format = texture.Format;
}
formats[index] = format;
textures[index] = hostTexture; textures[index] = hostTexture;
} }
else else
@ -737,7 +723,6 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isImage) if (isImage)
{ {
entry.ImageArray.SetFormats(0, formats);
entry.ImageArray.SetImages(0, textures); entry.ImageArray.SetImages(0, textures);
SetImageArray(stage, bindingInfo, entry.ImageArray); SetImageArray(stage, bindingInfo, entry.ImageArray);
@ -863,7 +848,6 @@ namespace Ryujinx.Graphics.Gpu.Image
entry.UpdateData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer); entry.UpdateData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer);
Format[] formats = isImage ? new Format[bindingInfo.ArrayLength] : null;
ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength]; ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
ITexture[] textures = new ITexture[bindingInfo.ArrayLength]; ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
@ -883,7 +867,7 @@ namespace Ryujinx.Graphics.Gpu.Image
samplerId = TextureHandle.UnpackSamplerId(packedId); samplerId = TextureHandle.UnpackSamplerId(packedId);
} }
ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, out Texture texture); ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, bindingInfo.FormatInfo, out Texture texture);
if (texture != null) if (texture != null)
{ {
@ -916,8 +900,6 @@ namespace Ryujinx.Graphics.Gpu.Image
hostSampler = sampler?.GetHostSampler(texture); hostSampler = sampler?.GetHostSampler(texture);
} }
Format format = bindingInfo.Format;
if (hostTexture != null && texture.Target == Target.TextureBuffer) if (hostTexture != null && texture.Target == Target.TextureBuffer)
{ {
// Ensure that the buffer texture is using the correct buffer as storage. // Ensure that the buffer texture is using the correct buffer as storage.
@ -925,26 +907,15 @@ namespace Ryujinx.Graphics.Gpu.Image
// to ensure we're not using a old buffer that was already deleted. // to ensure we're not using a old buffer that was already deleted.
if (isImage) if (isImage)
{ {
if (format == 0 && texture != null) _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
{
format = texture.Format;
}
_channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
} }
else else
{ {
_channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format); _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index);
} }
} }
else if (isImage) else if (isImage)
{ {
if (format == 0 && texture != null)
{
format = texture.Format;
}
formats[index] = format;
textures[index] = hostTexture; textures[index] = hostTexture;
} }
else else
@ -956,7 +927,6 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isImage) if (isImage)
{ {
entry.ImageArray.SetFormats(0, formats);
entry.ImageArray.SetImages(0, textures); entry.ImageArray.SetImages(0, textures);
SetImageArray(stage, bindingInfo, entry.ImageArray); SetImageArray(stage, bindingInfo, entry.ImageArray);

View File

@ -522,7 +522,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// Ensure that the buffer texture is using the correct buffer as storage. // Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind // Buffers are frequently re-created to accommodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted. // to ensure we're not using a old buffer that was already deleted.
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, bindingInfo.Format, false); _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, false);
// Cache is not used for buffer texture, it must always rebind. // Cache is not used for buffer texture, it must always rebind.
state.CachedTexture = null; state.CachedTexture = null;
@ -616,6 +616,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (!poolModified && if (!poolModified &&
state.TextureHandle == textureId && state.TextureHandle == textureId &&
state.ImageFormat == bindingInfo.FormatInfo.Format &&
state.CachedTexture != null && state.CachedTexture != null &&
state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence) state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence)
{ {
@ -629,26 +630,22 @@ namespace Ryujinx.Graphics.Gpu.Image
cachedTexture.SignalModified(); cachedTexture.SignalModified();
} }
Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format; if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 && UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage))
if (state.ImageFormat != format ||
((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 &&
UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage)))
{ {
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target); ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
state.Texture = hostTextureRebind; state.Texture = hostTextureRebind;
state.ImageFormat = format;
_context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTextureRebind, format); _context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTextureRebind);
} }
continue; continue;
} }
state.TextureHandle = textureId; state.TextureHandle = textureId;
state.ImageFormat = bindingInfo.FormatInfo.Format;
ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture); ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, bindingInfo.FormatInfo, out Texture texture);
specStateMatches &= specState.MatchesImage(stage, index, descriptor); specStateMatches &= specState.MatchesImage(stage, index, descriptor);
@ -660,14 +657,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind // Buffers are frequently re-created to accommodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted. // to ensure we're not using a old buffer that was already deleted.
Format format = bindingInfo.Format; _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, true);
if (format == 0 && texture != null)
{
format = texture.Format;
}
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, format, true);
// Cache is not used for buffer texture, it must always rebind. // Cache is not used for buffer texture, it must always rebind.
state.CachedTexture = null; state.CachedTexture = null;
@ -689,16 +679,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
state.Texture = hostTexture; state.Texture = hostTexture;
Format format = bindingInfo.Format; _context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTexture);
if (format == 0 && texture != null)
{
format = texture.Format;
}
state.ImageFormat = format;
_context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTexture, format);
} }
state.CachedTexture = texture; state.CachedTexture = texture;

View File

@ -739,7 +739,8 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
return (lhsFormat.Format == Format.R8G8B8A8Unorm && rhsFormat.Format == Format.R32G32B32A32Float) || return (lhsFormat.Format == Format.R8G8B8A8Unorm && rhsFormat.Format == Format.R32G32B32A32Float) ||
(lhsFormat.Format == Format.R8Unorm && rhsFormat.Format == Format.R8G8B8A8Unorm); (lhsFormat.Format == Format.R8Unorm && rhsFormat.Format == Format.R8G8B8A8Unorm) ||
(lhsFormat.Format == Format.R8Unorm && rhsFormat.Format == Format.R32Uint);
} }
/// <summary> /// <summary>

View File

@ -1,3 +1,4 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
@ -5,7 +6,6 @@ using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Image
ReadOnlySpan<byte> data = dataSpan[(offset - spanBase)..]; ReadOnlySpan<byte> data = dataSpan[(offset - spanBase)..];
IMemoryOwner<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true); MemoryOwner<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level); Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level);
} }

View File

@ -75,6 +75,76 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly ConcurrentQueue<DereferenceRequest> _dereferenceQueue = new(); private readonly ConcurrentQueue<DereferenceRequest> _dereferenceQueue = new();
private TextureDescriptor _defaultDescriptor; private TextureDescriptor _defaultDescriptor;
/// <summary>
/// List of textures that shares the same memory region, but have different formats.
/// </summary>
private class TextureAliasList
{
/// <summary>
/// Alias texture.
/// </summary>
/// <param name="Format">Texture format</param>
/// <param name="Texture">Texture</param>
private readonly record struct Alias(Format Format, Texture Texture);
/// <summary>
/// List of texture aliases.
/// </summary>
private readonly List<Alias> _aliases;
/// <summary>
/// Creates a new instance of the texture alias list.
/// </summary>
public TextureAliasList()
{
_aliases = new List<Alias>();
}
/// <summary>
/// Adds a new texture alias.
/// </summary>
/// <param name="format">Alias format</param>
/// <param name="texture">Alias texture</param>
public void Add(Format format, Texture texture)
{
_aliases.Add(new Alias(format, texture));
texture.IncrementReferenceCount();
}
/// <summary>
/// Finds a texture with the requested format, or returns null if not found.
/// </summary>
/// <param name="format">Format to find</param>
/// <returns>Texture with the requested format, or null if not found</returns>
public Texture Find(Format format)
{
foreach (var alias in _aliases)
{
if (alias.Format == format)
{
return alias.Texture;
}
}
return null;
}
/// <summary>
/// Removes all alias textures.
/// </summary>
public void Destroy()
{
foreach (var entry in _aliases)
{
entry.Texture.DecrementReferenceCount();
}
_aliases.Clear();
}
}
private readonly Dictionary<Texture, TextureAliasList> _aliasLists;
/// <summary> /// <summary>
/// Linked list node used on the texture pool cache. /// Linked list node used on the texture pool cache.
/// </summary> /// </summary>
@ -95,6 +165,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public TexturePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) : base(context, channel.MemoryManager.Physical, address, maximumId) public TexturePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) : base(context, channel.MemoryManager.Physical, address, maximumId)
{ {
_channel = channel; _channel = channel;
_aliasLists = new Dictionary<Texture, TextureAliasList>();
} }
/// <summary> /// <summary>
@ -115,14 +186,13 @@ namespace Ryujinx.Graphics.Gpu.Image
if (texture == null) if (texture == null)
{ {
TextureInfo info = GetInfo(descriptor, out int layerSize);
// The dereference queue can put our texture back on the cache. // The dereference queue can put our texture back on the cache.
if ((texture = ProcessDereferenceQueue(id)) != null) if ((texture = ProcessDereferenceQueue(id)) != null)
{ {
return ref descriptor; return ref descriptor;
} }
TextureInfo info = GetInfo(descriptor, out int layerSize);
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize); texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
// If this happens, then the texture address is invalid, we can't add it to the cache. // If this happens, then the texture address is invalid, we can't add it to the cache.
@ -197,6 +267,51 @@ namespace Ryujinx.Graphics.Gpu.Image
return ref GetInternal(id, out texture); return ref GetInternal(id, out texture);
} }
/// <summary>
/// Gets the texture descriptor and texture with the given ID.
/// </summary>
/// <remarks>
/// This method assumes that the pool has been manually synchronized before doing binding.
/// </remarks>
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
/// <param name="formatInfo">Texture format information</param>
/// <param name="texture">The texture with the given ID</param>
/// <returns>The texture descriptor with the given ID</returns>
public ref readonly TextureDescriptor GetForBinding(int id, FormatInfo formatInfo, out Texture texture)
{
if ((uint)id >= Items.Length)
{
texture = null;
return ref _defaultDescriptor;
}
ref readonly TextureDescriptor descriptor = ref GetInternal(id, out texture);
if (texture != null && formatInfo.Format != 0 && texture.Format != formatInfo.Format)
{
if (!_aliasLists.TryGetValue(texture, out TextureAliasList aliasList))
{
_aliasLists.Add(texture, aliasList = new TextureAliasList());
}
texture = aliasList.Find(formatInfo.Format);
if (texture == null)
{
TextureInfo info = GetInfo(descriptor, out int layerSize);
info = ChangeFormat(info, formatInfo);
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
if (texture != null)
{
aliasList.Add(formatInfo.Format, texture);
}
}
}
return ref descriptor;
}
/// <summary> /// <summary>
/// Checks if the pool was modified, and returns the last sequence number where a modification was detected. /// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
/// </summary> /// </summary>
@ -234,6 +349,7 @@ namespace Ryujinx.Graphics.Gpu.Image
else else
{ {
texture.DecrementReferenceCount(); texture.DecrementReferenceCount();
RemoveAliasList(texture);
} }
} }
@ -327,6 +443,8 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
texture.DecrementReferenceCount(); texture.DecrementReferenceCount();
} }
RemoveAliasList(texture);
} }
return null; return null;
@ -369,6 +487,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (Interlocked.Exchange(ref Items[id], null) != null) if (Interlocked.Exchange(ref Items[id], null) != null)
{ {
texture.DecrementReferenceCount(this, id); texture.DecrementReferenceCount(this, id);
RemoveAliasList(texture);
} }
} }
} }
@ -622,6 +741,57 @@ namespace Ryujinx.Graphics.Gpu.Image
component == SwizzleComponent.Green; component == SwizzleComponent.Green;
} }
/// <summary>
/// Changes the format on the texture information structure, and also adjusts the width for the new format if needed.
/// </summary>
/// <param name="info">Texture information</param>
/// <param name="dstFormat">New format</param>
/// <returns>Texture information with the new format</returns>
private static TextureInfo ChangeFormat(in TextureInfo info, FormatInfo dstFormat)
{
int width = info.Width;
if (info.FormatInfo.BytesPerPixel != dstFormat.BytesPerPixel)
{
int stride = width * info.FormatInfo.BytesPerPixel;
width = stride / dstFormat.BytesPerPixel;
}
return new TextureInfo(
info.GpuAddress,
width,
info.Height,
info.DepthOrLayers,
info.Levels,
info.SamplesInX,
info.SamplesInY,
info.Stride,
info.IsLinear,
info.GobBlocksInY,
info.GobBlocksInZ,
info.GobBlocksInTileX,
info.Target,
dstFormat,
info.DepthStencilMode,
info.SwizzleR,
info.SwizzleG,
info.SwizzleB,
info.SwizzleA);
}
/// <summary>
/// Removes all aliases for a texture.
/// </summary>
/// <param name="texture">Texture to have the aliases removed</param>
private void RemoveAliasList(Texture texture)
{
if (_aliasLists.TryGetValue(texture, out TextureAliasList aliasList))
{
_aliasLists.Remove(texture);
aliasList.Destroy();
}
}
/// <summary> /// <summary>
/// Decrements the reference count of the texture. /// Decrements the reference count of the texture.
/// This indicates that the texture pool is not using it anymore. /// This indicates that the texture pool is not using it anymore.
@ -629,7 +799,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="item">The texture to be deleted</param> /// <param name="item">The texture to be deleted</param>
protected override void Delete(Texture item) protected override void Delete(Texture item)
{ {
item?.DecrementReferenceCount(this); if (item != null)
{
item.DecrementReferenceCount(this);
RemoveAliasList(item);
}
} }
public override void Dispose() public override void Dispose()

View File

@ -509,7 +509,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (binding.IsImage) if (binding.IsImage)
{ {
_context.Renderer.Pipeline.SetImage(binding.Stage, binding.BindingInfo.Binding, binding.Texture, binding.Format); _context.Renderer.Pipeline.SetImage(binding.Stage, binding.BindingInfo.Binding, binding.Texture);
} }
else else
{ {
@ -873,12 +873,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
ITexture texture, ITexture texture,
MultiRange range, MultiRange range,
TextureBindingInfo bindingInfo, TextureBindingInfo bindingInfo,
Format format,
bool isImage) bool isImage)
{ {
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags)); _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage)); _bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, isImage));
} }
/// <summary> /// <summary>
@ -897,12 +896,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
ITexture texture, ITexture texture,
MultiRange range, MultiRange range,
TextureBindingInfo bindingInfo, TextureBindingInfo bindingInfo,
int index, int index)
Format format)
{ {
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags)); _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index, format)); _bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index));
} }
/// <summary> /// <summary>
@ -921,12 +919,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
ITexture texture, ITexture texture,
MultiRange range, MultiRange range,
TextureBindingInfo bindingInfo, TextureBindingInfo bindingInfo,
int index, int index)
Format format)
{ {
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags)); _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index, format)); _bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index));
} }
/// <summary> /// <summary>

View File

@ -34,33 +34,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
public int Index { get; } public int Index { get; }
/// <summary>
/// The image format for the binding.
/// </summary>
public Format Format { get; }
/// <summary> /// <summary>
/// Create a new buffer texture binding. /// Create a new buffer texture binding.
/// </summary> /// </summary>
/// <param name="array">Array</param>
/// <param name="texture">Buffer texture</param> /// <param name="texture">Buffer texture</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param> /// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
/// <param name="bindingInfo">Binding info</param> /// <param name="bindingInfo">Binding info</param>
/// <param name="index">Index of the binding on the array</param> /// <param name="index">Index of the binding on the array</param>
/// <param name="format">Binding format</param>
public BufferTextureArrayBinding( public BufferTextureArrayBinding(
T array, T array,
ITexture texture, ITexture texture,
MultiRange range, MultiRange range,
TextureBindingInfo bindingInfo, TextureBindingInfo bindingInfo,
int index, int index)
Format format)
{ {
Array = array; Array = array;
Texture = texture; Texture = texture;
Range = range; Range = range;
BindingInfo = bindingInfo; BindingInfo = bindingInfo;
Index = index; Index = index;
Format = format;
} }
} }
} }

View File

@ -30,11 +30,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
public TextureBindingInfo BindingInfo { get; } public TextureBindingInfo BindingInfo { get; }
/// <summary>
/// The image format for the binding.
/// </summary>
public Format Format { get; }
/// <summary> /// <summary>
/// Whether the binding is for an image or a sampler. /// Whether the binding is for an image or a sampler.
/// </summary> /// </summary>
@ -47,21 +42,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="texture">Buffer texture</param> /// <param name="texture">Buffer texture</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param> /// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
/// <param name="bindingInfo">Binding info</param> /// <param name="bindingInfo">Binding info</param>
/// <param name="format">Binding format</param>
/// <param name="isImage">Whether the binding is for an image or a sampler</param> /// <param name="isImage">Whether the binding is for an image or a sampler</param>
public BufferTextureBinding( public BufferTextureBinding(
ShaderStage stage, ShaderStage stage,
ITexture texture, ITexture texture,
MultiRange range, MultiRange range,
TextureBindingInfo bindingInfo, TextureBindingInfo bindingInfo,
Format format,
bool isImage) bool isImage)
{ {
Stage = stage; Stage = stage;
Texture = texture; Texture = texture;
Range = range; Range = range;
BindingInfo = bindingInfo; BindingInfo = bindingInfo;
Format = format;
IsImage = isImage; IsImage = isImage;
} }
} }

View File

@ -86,11 +86,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
ImageBindings[i] = stage.Info.Images.Select(descriptor => ImageBindings[i] = stage.Info.Images.Select(descriptor =>
{ {
Target target = ShaderTexture.GetTarget(descriptor.Type); Target target = ShaderTexture.GetTarget(descriptor.Type);
Format format = ShaderTexture.GetFormat(descriptor.Format); FormatInfo formatInfo = ShaderTexture.GetFormatInfo(descriptor.Format);
var result = new TextureBindingInfo( var result = new TextureBindingInfo(
target, target,
format, formatInfo,
descriptor.Set, descriptor.Set,
descriptor.Binding, descriptor.Binding,
descriptor.ArrayLength, descriptor.ArrayLength,

View File

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2; private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 7131; private const uint CodeGenVersion = 7320;
private const string SharedTocFileName = "shared.toc"; private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";

View File

@ -131,7 +131,7 @@ namespace Ryujinx.Graphics.Gpu
bool isLinear, bool isLinear,
int gobBlocksInY, int gobBlocksInY,
Format format, Format format,
int bytesPerPixel, byte bytesPerPixel,
ImageCrop crop, ImageCrop crop,
Action<GpuContext, object> acquireCallback, Action<GpuContext, object> acquireCallback,
Action<object> releaseCallback, Action<object> releaseCallback,

View File

@ -0,0 +1,106 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Image;
using System;
using static Ryujinx.Graphics.OpenGL.Effects.ShaderHelper;
namespace Ryujinx.Graphics.OpenGL.Effects
{
internal class AreaScalingFilter : IScalingFilter
{
private readonly OpenGLRenderer _renderer;
private int _inputUniform;
private int _outputUniform;
private int _srcX0Uniform;
private int _srcX1Uniform;
private int _srcY0Uniform;
private int _scalingShaderProgram;
private int _srcY1Uniform;
private int _dstX0Uniform;
private int _dstX1Uniform;
private int _dstY0Uniform;
private int _dstY1Uniform;
public float Level { get; set; }
public AreaScalingFilter(OpenGLRenderer renderer)
{
Initialize();
_renderer = renderer;
}
public void Dispose()
{
if (_scalingShaderProgram != 0)
{
GL.DeleteProgram(_scalingShaderProgram);
}
}
private void Initialize()
{
var scalingShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/area_scaling.glsl");
_scalingShaderProgram = CompileProgram(scalingShader, ShaderType.ComputeShader);
_inputUniform = GL.GetUniformLocation(_scalingShaderProgram, "Source");
_outputUniform = GL.GetUniformLocation(_scalingShaderProgram, "imgOutput");
_srcX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX0");
_srcX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX1");
_srcY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY0");
_srcY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY1");
_dstX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX0");
_dstX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX1");
_dstY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY0");
_dstY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY1");
}
public void Run(
TextureView view,
TextureView destinationTexture,
int width,
int height,
Extents2D source,
Extents2D destination)
{
int previousProgram = GL.GetInteger(GetPName.CurrentProgram);
int previousUnit = GL.GetInteger(GetPName.ActiveTexture);
GL.ActiveTexture(TextureUnit.Texture0);
int previousTextureBinding = GL.GetInteger(GetPName.TextureBinding2D);
GL.BindImageTexture(0, destinationTexture.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
int threadGroupWorkRegionDim = 16;
int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
// Scaling pass
GL.UseProgram(_scalingShaderProgram);
view.Bind(0);
GL.Uniform1(_inputUniform, 0);
GL.Uniform1(_outputUniform, 0);
GL.Uniform1(_srcX0Uniform, (float)source.X1);
GL.Uniform1(_srcX1Uniform, (float)source.X2);
GL.Uniform1(_srcY0Uniform, (float)source.Y1);
GL.Uniform1(_srcY1Uniform, (float)source.Y2);
GL.Uniform1(_dstX0Uniform, (float)destination.X1);
GL.Uniform1(_dstX1Uniform, (float)destination.X2);
GL.Uniform1(_dstY0Uniform, (float)destination.Y1);
GL.Uniform1(_dstY1Uniform, (float)destination.Y2);
GL.DispatchCompute(dispatchX, dispatchY, 1);
GL.UseProgram(previousProgram);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
(_renderer.Pipeline as Pipeline).RestoreImages1And2();
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding);
GL.ActiveTexture((TextureUnit)previousUnit);
}
}
}

View File

@ -18,7 +18,7 @@ namespace Ryujinx.Graphics.OpenGL.Effects
private int _srcY0Uniform; private int _srcY0Uniform;
private int _scalingShaderProgram; private int _scalingShaderProgram;
private int _sharpeningShaderProgram; private int _sharpeningShaderProgram;
private float _scale = 1; private float _sharpeningLevel = 1;
private int _srcY1Uniform; private int _srcY1Uniform;
private int _dstX0Uniform; private int _dstX0Uniform;
private int _dstX1Uniform; private int _dstX1Uniform;
@ -30,10 +30,10 @@ namespace Ryujinx.Graphics.OpenGL.Effects
public float Level public float Level
{ {
get => _scale; get => _sharpeningLevel;
set set
{ {
_scale = MathF.Max(0.01f, value); _sharpeningLevel = MathF.Max(0.01f, value);
} }
} }

View File

@ -1,4 +1,5 @@
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging;
namespace Ryujinx.Graphics.OpenGL.Effects namespace Ryujinx.Graphics.OpenGL.Effects
{ {
@ -6,18 +7,7 @@ namespace Ryujinx.Graphics.OpenGL.Effects
{ {
public static int CompileProgram(string shaderCode, ShaderType shaderType) public static int CompileProgram(string shaderCode, ShaderType shaderType)
{ {
var shader = GL.CreateShader(shaderType); return CompileProgram(new string[] { shaderCode }, shaderType);
GL.ShaderSource(shader, shaderCode);
GL.CompileShader(shader);
var program = GL.CreateProgram();
GL.AttachShader(program, shader);
GL.LinkProgram(program);
GL.DetachShader(program, shader);
GL.DeleteShader(shader);
return program;
} }
public static int CompileProgram(string[] shaders, ShaderType shaderType) public static int CompileProgram(string[] shaders, ShaderType shaderType)
@ -26,6 +16,15 @@ namespace Ryujinx.Graphics.OpenGL.Effects
GL.ShaderSource(shader, shaders.Length, shaders, (int[])null); GL.ShaderSource(shader, shaders.Length, shaders, (int[])null);
GL.CompileShader(shader); GL.CompileShader(shader);
GL.GetShader(shader, ShaderParameter.CompileStatus, out int isCompiled);
if (isCompiled == 0)
{
string log = GL.GetShaderInfoLog(shader);
Logger.Error?.Print(LogClass.Gpu, $"Failed to compile effect shader:\n\n{log}\n");
GL.DeleteShader(shader);
return 0;
}
var program = GL.CreateProgram(); var program = GL.CreateProgram();
GL.AttachShader(program, shader); GL.AttachShader(program, shader);
GL.LinkProgram(program); GL.LinkProgram(program);

View File

@ -0,0 +1,119 @@
#version 430 core
precision mediump float;
layout (local_size_x = 16, local_size_y = 16) in;
layout(rgba8, binding = 0, location=0) uniform image2D imgOutput;
layout( location=1 ) uniform sampler2D Source;
layout( location=2 ) uniform float srcX0;
layout( location=3 ) uniform float srcX1;
layout( location=4 ) uniform float srcY0;
layout( location=5 ) uniform float srcY1;
layout( location=6 ) uniform float dstX0;
layout( location=7 ) uniform float dstX1;
layout( location=8 ) uniform float dstY0;
layout( location=9 ) uniform float dstY1;
/***** Area Sampling *****/
// By Sam Belliveau and Filippo Tarpini. Public Domain license.
// Effectively a more accurate sharp bilinear filter when upscaling,
// that also works as a mathematically perfect downscale filter.
// https://entropymine.com/imageworsener/pixelmixing/
// https://github.com/obsproject/obs-studio/pull/1715
// https://legacy.imagemagick.org/Usage/filter/
vec4 AreaSampling(vec2 xy)
{
// Determine the sizes of the source and target images.
vec2 source_size = vec2(abs(srcX1 - srcX0), abs(srcY1 - srcY0));
vec2 target_size = vec2(abs(dstX1 - dstX0), abs(dstY1 - dstY0));
vec2 inverted_target_size = vec2(1.0) / target_size;
// Compute the top-left and bottom-right corners of the target pixel box.
vec2 t_beg = floor(xy - vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1));
vec2 t_end = t_beg + vec2(1.0, 1.0);
// Convert the target pixel box to source pixel box.
vec2 beg = t_beg * inverted_target_size * source_size;
vec2 end = t_end * inverted_target_size * source_size;
// Compute the top-left and bottom-right corners of the pixel box.
ivec2 f_beg = ivec2(beg);
ivec2 f_end = ivec2(end);
// Compute how much of the start and end pixels are covered horizontally & vertically.
float area_w = 1.0 - fract(beg.x);
float area_n = 1.0 - fract(beg.y);
float area_e = fract(end.x);
float area_s = fract(end.y);
// Compute the areas of the corner pixels in the pixel box.
float area_nw = area_n * area_w;
float area_ne = area_n * area_e;
float area_sw = area_s * area_w;
float area_se = area_s * area_e;
// Initialize the color accumulator.
vec4 avg_color = vec4(0.0, 0.0, 0.0, 0.0);
// Accumulate corner pixels.
avg_color += area_nw * texelFetch(Source, ivec2(f_beg.x, f_beg.y), 0);
avg_color += area_ne * texelFetch(Source, ivec2(f_end.x, f_beg.y), 0);
avg_color += area_sw * texelFetch(Source, ivec2(f_beg.x, f_end.y), 0);
avg_color += area_se * texelFetch(Source, ivec2(f_end.x, f_end.y), 0);
// Determine the size of the pixel box.
int x_range = int(f_end.x - f_beg.x - 0.5);
int y_range = int(f_end.y - f_beg.y - 0.5);
// Accumulate top and bottom edge pixels.
for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
{
avg_color += area_n * texelFetch(Source, ivec2(x, f_beg.y), 0);
avg_color += area_s * texelFetch(Source, ivec2(x, f_end.y), 0);
}
// Accumulate left and right edge pixels and all the pixels in between.
for (int y = f_beg.y + 1; y <= f_beg.y + y_range; ++y)
{
avg_color += area_w * texelFetch(Source, ivec2(f_beg.x, y), 0);
avg_color += area_e * texelFetch(Source, ivec2(f_end.x, y), 0);
for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
{
avg_color += texelFetch(Source, ivec2(x, y), 0);
}
}
// Compute the area of the pixel box that was sampled.
float area_corners = area_nw + area_ne + area_sw + area_se;
float area_edges = float(x_range) * (area_n + area_s) + float(y_range) * (area_w + area_e);
float area_center = float(x_range) * float(y_range);
// Return the normalized average color.
return avg_color / (area_corners + area_edges + area_center);
}
float insideBox(vec2 v, vec2 bLeft, vec2 tRight) {
vec2 s = step(bLeft, v) - step(tRight, v);
return s.x * s.y;
}
vec2 translateDest(vec2 pos) {
vec2 translatedPos = vec2(pos.x, pos.y);
translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x;
translatedPos.y = dstY0 > dstY1 ? dstY0 + dstY1 - translatedPos.y - 1 : translatedPos.y;
return translatedPos;
}
void main()
{
vec2 bLeft = vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1);
vec2 tRight = vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0);
ivec2 loc = ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y);
if (insideBox(loc, bLeft, tRight) == 0) {
imageStore(imgOutput, loc, vec4(0, 0, 0, 1));
return;
}
vec4 outColor = AreaSampling(loc);
imageStore(imgOutput, ivec2(translateDest(loc)), vec4(outColor.rgb, 1));
}

View File

@ -85,4 +85,4 @@ void main() {
CurrFilter(gxy); CurrFilter(gxy);
gxy.x -= 8u; gxy.x -= 8u;
CurrFilter(gxy); CurrFilter(gxy);
} }

View File

@ -1,6 +1,5 @@
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.OpenGL.Image namespace Ryujinx.Graphics.OpenGL.Image
{ {
@ -19,14 +18,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
_images = new TextureRef[size]; _images = new TextureRef[size];
} }
public void SetFormats(int index, GAL.Format[] imageFormats)
{
for (int i = 0; i < imageFormats.Length; i++)
{
_images[index + i].Format = imageFormats[i];
}
}
public void SetImages(int index, ITexture[] images) public void SetImages(int index, ITexture[] images)
{ {
for (int i = 0; i < images.Length; i++) for (int i = 0; i < images.Length; i++)
@ -36,6 +27,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
if (image is TextureBase imageBase) if (image is TextureBase imageBase)
{ {
_images[index + i].Handle = imageBase.Handle; _images[index + i].Handle = imageBase.Handle;
_images[index + i].Format = imageBase.Format;
} }
else else
{ {

View File

@ -1,7 +1,7 @@
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using System; using System;
using System.Buffers;
namespace Ryujinx.Graphics.OpenGL.Image namespace Ryujinx.Graphics.OpenGL.Image
{ {
@ -55,9 +55,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data) public void SetData(MemoryOwner<byte> data)
{ {
var dataSpan = data.Memory.Span; var dataSpan = data.Span;
Buffer.SetData(_buffer, _bufferOffset, dataSpan[..Math.Min(dataSpan.Length, _bufferSize)]); Buffer.SetData(_buffer, _bufferOffset, dataSpan[..Math.Min(dataSpan.Length, _bufferSize)]);
@ -65,13 +65,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level) public void SetData(MemoryOwner<byte> data, int layer, int level)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region) public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }

View File

@ -1,8 +1,8 @@
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using System; using System;
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
namespace Ryujinx.Graphics.OpenGL.Image namespace Ryujinx.Graphics.OpenGL.Image
@ -448,13 +448,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
} }
} }
public void SetData(IMemoryOwner<byte> data) public void SetData(MemoryOwner<byte> data)
{ {
using (data = EnsureDataFormat(data)) using (data = EnsureDataFormat(data))
{ {
unsafe unsafe
{ {
var dataSpan = data.Memory.Span; var dataSpan = data.Span;
fixed (byte* ptr = dataSpan) fixed (byte* ptr = dataSpan)
{ {
ReadFrom((IntPtr)ptr, dataSpan.Length); ReadFrom((IntPtr)ptr, dataSpan.Length);
@ -463,13 +463,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
} }
} }
public void SetData(IMemoryOwner<byte> data, int layer, int level) public void SetData(MemoryOwner<byte> data, int layer, int level)
{ {
using (data = EnsureDataFormat(data)) using (data = EnsureDataFormat(data))
{ {
unsafe unsafe
{ {
fixed (byte* ptr = data.Memory.Span) fixed (byte* ptr = data.Span)
{ {
int width = Math.Max(Info.Width >> level, 1); int width = Math.Max(Info.Width >> level, 1);
int height = Math.Max(Info.Height >> level, 1); int height = Math.Max(Info.Height >> level, 1);
@ -480,7 +480,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
} }
} }
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region) public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
using (data = EnsureDataFormat(data)) using (data = EnsureDataFormat(data))
{ {
@ -489,7 +489,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
unsafe unsafe
{ {
fixed (byte* ptr = data.Memory.Span) fixed (byte* ptr = data.Span)
{ {
ReadFrom2D( ReadFrom2D(
(IntPtr)ptr, (IntPtr)ptr,
@ -522,13 +522,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
ReadFrom2D(data, layer, level, x, y, width, height, mipSize); ReadFrom2D(data, layer, level, x, y, width, height, mipSize);
} }
private IMemoryOwner<byte> EnsureDataFormat(IMemoryOwner<byte> data) private MemoryOwner<byte> EnsureDataFormat(MemoryOwner<byte> data)
{ {
if (Format == Format.S8UintD24Unorm) if (Format == Format.S8UintD24Unorm)
{ {
using (data) using (data)
{ {
return FormatConverter.ConvertS8D24ToD24S8(data.Memory.Span); return FormatConverter.ConvertS8D24ToD24S8(data.Span);
} }
} }

View File

@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.OpenGL
private readonly Vector4<int>[] _fpIsBgra = new Vector4<int>[SupportBuffer.FragmentIsBgraCount]; private readonly Vector4<int>[] _fpIsBgra = new Vector4<int>[SupportBuffer.FragmentIsBgraCount];
private readonly (TextureBase, Format)[] _images; private readonly TextureBase[] _images;
private TextureBase _unit0Texture; private TextureBase _unit0Texture;
private Sampler _unit0Sampler; private Sampler _unit0Sampler;
@ -78,7 +78,7 @@ namespace Ryujinx.Graphics.OpenGL
_fragmentOutputMap = uint.MaxValue; _fragmentOutputMap = uint.MaxValue;
_componentMasks = uint.MaxValue; _componentMasks = uint.MaxValue;
_images = new (TextureBase, Format)[SavedImages]; _images = new TextureBase[SavedImages];
_tfbs = new BufferHandle[Constants.MaxTransformFeedbackBuffers]; _tfbs = new BufferHandle[Constants.MaxTransformFeedbackBuffers];
_tfbTargets = new BufferRange[Constants.MaxTransformFeedbackBuffers]; _tfbTargets = new BufferRange[Constants.MaxTransformFeedbackBuffers];
@ -935,11 +935,11 @@ namespace Ryujinx.Graphics.OpenGL
SetFrontFace(_frontFace = frontFace.Convert()); SetFrontFace(_frontFace = frontFace.Convert());
} }
public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat) public void SetImage(ShaderStage stage, int binding, ITexture texture)
{ {
if ((uint)binding < SavedImages) if ((uint)binding < SavedImages)
{ {
_images[binding] = (texture as TextureBase, imageFormat); _images[binding] = texture as TextureBase;
} }
if (texture == null) if (texture == null)
@ -950,7 +950,7 @@ namespace Ryujinx.Graphics.OpenGL
TextureBase texBase = (TextureBase)texture; TextureBase texBase = (TextureBase)texture;
SizedInternalFormat format = FormatTable.GetImageFormat(imageFormat); SizedInternalFormat format = FormatTable.GetImageFormat(texBase.Format);
if (format != 0) if (format != 0)
{ {
@ -1622,11 +1622,11 @@ namespace Ryujinx.Graphics.OpenGL
{ {
for (int i = 0; i < SavedImages; i++) for (int i = 0; i < SavedImages; i++)
{ {
(TextureBase texBase, Format imageFormat) = _images[i]; TextureBase texBase = _images[i];
if (texBase != null) if (texBase != null)
{ {
SizedInternalFormat format = FormatTable.GetImageFormat(imageFormat); SizedInternalFormat format = FormatTable.GetImageFormat(texBase.Format);
if (format != 0) if (format != 0)
{ {

View File

@ -21,6 +21,7 @@
<EmbeddedResource Include="Effects\Shaders\ffx_fsr1.h" /> <EmbeddedResource Include="Effects\Shaders\ffx_fsr1.h" />
<EmbeddedResource Include="Effects\Shaders\ffx_a.h" /> <EmbeddedResource Include="Effects\Shaders\ffx_a.h" />
<EmbeddedResource Include="Effects\Shaders\fsr_scaling.glsl" /> <EmbeddedResource Include="Effects\Shaders\fsr_scaling.glsl" />
<EmbeddedResource Include="Effects\Shaders\area_scaling.glsl" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -373,6 +373,16 @@ namespace Ryujinx.Graphics.OpenGL
_isLinear = false; _isLinear = false;
_scalingFilter.Level = _scalingFilterLevel; _scalingFilter.Level = _scalingFilterLevel;
RecreateUpscalingTexture();
break;
case ScalingFilter.Area:
if (_scalingFilter is not AreaScalingFilter)
{
_scalingFilter?.Dispose();
_scalingFilter = new AreaScalingFilter(_renderer);
}
_isLinear = false;
RecreateUpscalingTexture(); RecreateUpscalingTexture();
break; break;
} }

View File

@ -222,30 +222,14 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}."); context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
} }
break; break;
case AtomOp.And: case AtomOp.Min:
if (type == AtomSize.S32 || type == AtomSize.U32) if (type == AtomSize.S32)
{ {
res = context.AtomicAnd(storageKind, e0, e1, value); res = context.AtomicMinS32(storageKind, e0, e1, value);
} }
else else if (type == AtomSize.U32)
{ {
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}."); res = context.AtomicMinU32(storageKind, e0, e1, value);
}
break;
case AtomOp.Xor:
if (type == AtomSize.S32 || type == AtomSize.U32)
{
res = context.AtomicXor(storageKind, e0, e1, value);
}
else
{
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
case AtomOp.Or:
if (type == AtomSize.S32 || type == AtomSize.U32)
{
res = context.AtomicOr(storageKind, e0, e1, value);
} }
else else
{ {
@ -266,20 +250,49 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}."); context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
} }
break; break;
case AtomOp.Min: case AtomOp.And:
if (type == AtomSize.S32) if (type == AtomSize.S32 || type == AtomSize.U32)
{ {
res = context.AtomicMinS32(storageKind, e0, e1, value); res = context.AtomicAnd(storageKind, e0, e1, value);
}
else if (type == AtomSize.U32)
{
res = context.AtomicMinU32(storageKind, e0, e1, value);
} }
else else
{ {
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}."); context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
} }
break; break;
case AtomOp.Or:
if (type == AtomSize.S32 || type == AtomSize.U32)
{
res = context.AtomicOr(storageKind, e0, e1, value);
}
else
{
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
case AtomOp.Xor:
if (type == AtomSize.S32 || type == AtomSize.U32)
{
res = context.AtomicXor(storageKind, e0, e1, value);
}
else
{
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
case AtomOp.Exch:
if (type == AtomSize.S32 || type == AtomSize.U32)
{
res = context.AtomicSwap(storageKind, e0, e1, value);
}
else
{
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
default:
context.TranslatorContext.GpuAccessor.Log($"Invalid atomic operation: {op}.");
break;
} }
return res; return res;

View File

@ -82,7 +82,6 @@ namespace Ryujinx.Graphics.Vulkan
private readonly ImageRef[] _imageRefs; private readonly ImageRef[] _imageRefs;
private readonly TextureBuffer[] _bufferTextureRefs; private readonly TextureBuffer[] _bufferTextureRefs;
private readonly TextureBuffer[] _bufferImageRefs; private readonly TextureBuffer[] _bufferImageRefs;
private readonly Format[] _bufferImageFormats;
private ArrayRef<TextureArray>[] _textureArrayRefs; private ArrayRef<TextureArray>[] _textureArrayRefs;
private ArrayRef<ImageArray>[] _imageArrayRefs; private ArrayRef<ImageArray>[] _imageArrayRefs;
@ -141,7 +140,6 @@ namespace Ryujinx.Graphics.Vulkan
_imageRefs = new ImageRef[Constants.MaxImageBindings * 2]; _imageRefs = new ImageRef[Constants.MaxImageBindings * 2];
_bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2]; _bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2];
_bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2]; _bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2];
_bufferImageFormats = new Format[Constants.MaxImageBindings * 2];
_textureArrayRefs = Array.Empty<ArrayRef<TextureArray>>(); _textureArrayRefs = Array.Empty<ArrayRef<TextureArray>>();
_imageArrayRefs = Array.Empty<ArrayRef<ImageArray>>(); _imageArrayRefs = Array.Empty<ArrayRef<ImageArray>>();
@ -391,17 +389,11 @@ namespace Ryujinx.Graphics.Vulkan
_dirty = DirtyFlags.All; _dirty = DirtyFlags.All;
} }
public void SetImage( public void SetImage(CommandBufferScoped cbs, ShaderStage stage, int binding, ITexture image)
CommandBufferScoped cbs,
ShaderStage stage,
int binding,
ITexture image,
Format imageFormat)
{ {
if (image is TextureBuffer imageBuffer) if (image is TextureBuffer imageBuffer)
{ {
_bufferImageRefs[binding] = imageBuffer; _bufferImageRefs[binding] = imageBuffer;
_bufferImageFormats[binding] = imageFormat;
} }
else if (image is TextureView view) else if (image is TextureView view)
{ {
@ -410,13 +402,12 @@ namespace Ryujinx.Graphics.Vulkan
iRef.View?.ClearUsage(FeedbackLoopHazards); iRef.View?.ClearUsage(FeedbackLoopHazards);
view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
iRef = new(stage, view, view.GetView(imageFormat).GetIdentityImageView()); iRef = new(stage, view, view.GetIdentityImageView());
} }
else else
{ {
_imageRefs[binding] = default; _imageRefs[binding] = default;
_bufferImageRefs[binding] = null; _bufferImageRefs[binding] = null;
_bufferImageFormats[binding] = default;
} }
SignalDirty(DirtyFlags.Image); SignalDirty(DirtyFlags.Image);
@ -923,7 +914,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i], true) ?? default; bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, true) ?? default;
} }
tu.Push<BufferView>(bufferImages[..count]); tu.Push<BufferView>(bufferImages[..count]);

View File

@ -0,0 +1,101 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using Silk.NET.Vulkan;
using System;
using Extent2D = Ryujinx.Graphics.GAL.Extents2D;
using Format = Silk.NET.Vulkan.Format;
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
namespace Ryujinx.Graphics.Vulkan.Effects
{
internal class AreaScalingFilter : IScalingFilter
{
private readonly VulkanRenderer _renderer;
private PipelineHelperShader _pipeline;
private ISampler _sampler;
private ShaderCollection _scalingProgram;
private Device _device;
public float Level { get; set; }
public AreaScalingFilter(VulkanRenderer renderer, Device device)
{
_device = device;
_renderer = renderer;
Initialize();
}
public void Dispose()
{
_pipeline.Dispose();
_scalingProgram.Dispose();
_sampler.Dispose();
}
public void Initialize()
{
_pipeline = new PipelineHelperShader(_renderer, _device);
_pipeline.Initialize();
var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.spv");
var scalingResourceLayout = new ResourceLayoutBuilder()
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
_sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
_scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(scalingShader, ShaderStage.Compute, TargetLanguage.Spirv),
}, scalingResourceLayout);
}
public void Run(
TextureView view,
CommandBufferScoped cbs,
Auto<DisposableImageView> destinationTexture,
Format format,
int width,
int height,
Extent2D source,
Extent2D destination)
{
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetProgram(_scalingProgram);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _sampler);
ReadOnlySpan<float> dimensionsBuffer = stackalloc float[]
{
source.X1,
source.X2,
source.Y1,
source.Y2,
destination.X1,
destination.X2,
destination.Y1,
destination.Y2,
};
int rangeSize = dimensionsBuffer.Length * sizeof(float);
using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize);
buffer.Holder.SetDataUnchecked(buffer.Offset, dimensionsBuffer);
int threadGroupWorkRegionDim = 16;
int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
_pipeline.SetImage(0, destinationTexture);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
_pipeline.Finish();
}
}
}

View File

@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
_pipeline.SetImage(ShaderStage.Compute, 0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); _pipeline.SetImage(ShaderStage.Compute, 0, _intermediaryTexture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier(); _pipeline.ComputeBarrier();

View File

@ -75,7 +75,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize); var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize); var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
_pipeline.SetImage(ShaderStage.Compute, 0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); _pipeline.SetImage(ShaderStage.Compute, 0, _texture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier(); _pipeline.ComputeBarrier();

View File

@ -0,0 +1,122 @@
// Scaling
#version 430 core
layout (local_size_x = 16, local_size_y = 16) in;
layout( rgba8, binding = 0, set = 3) uniform image2D imgOutput;
layout( binding = 1, set = 2) uniform sampler2D Source;
layout( binding = 2 ) uniform dimensions{
float srcX0;
float srcX1;
float srcY0;
float srcY1;
float dstX0;
float dstX1;
float dstY0;
float dstY1;
};
/***** Area Sampling *****/
// By Sam Belliveau and Filippo Tarpini. Public Domain license.
// Effectively a more accurate sharp bilinear filter when upscaling,
// that also works as a mathematically perfect downscale filter.
// https://entropymine.com/imageworsener/pixelmixing/
// https://github.com/obsproject/obs-studio/pull/1715
// https://legacy.imagemagick.org/Usage/filter/
vec4 AreaSampling(vec2 xy)
{
// Determine the sizes of the source and target images.
vec2 source_size = vec2(abs(srcX1 - srcX0), abs(srcY1 - srcY0));
vec2 target_size = vec2(abs(dstX1 - dstX0), abs(dstY1 - dstY0));
vec2 inverted_target_size = vec2(1.0) / target_size;
// Compute the top-left and bottom-right corners of the target pixel box.
vec2 t_beg = floor(xy - vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1));
vec2 t_end = t_beg + vec2(1.0, 1.0);
// Convert the target pixel box to source pixel box.
vec2 beg = t_beg * inverted_target_size * source_size;
vec2 end = t_end * inverted_target_size * source_size;
// Compute the top-left and bottom-right corners of the pixel box.
ivec2 f_beg = ivec2(beg);
ivec2 f_end = ivec2(end);
// Compute how much of the start and end pixels are covered horizontally & vertically.
float area_w = 1.0 - fract(beg.x);
float area_n = 1.0 - fract(beg.y);
float area_e = fract(end.x);
float area_s = fract(end.y);
// Compute the areas of the corner pixels in the pixel box.
float area_nw = area_n * area_w;
float area_ne = area_n * area_e;
float area_sw = area_s * area_w;
float area_se = area_s * area_e;
// Initialize the color accumulator.
vec4 avg_color = vec4(0.0, 0.0, 0.0, 0.0);
// Accumulate corner pixels.
avg_color += area_nw * texelFetch(Source, ivec2(f_beg.x, f_beg.y), 0);
avg_color += area_ne * texelFetch(Source, ivec2(f_end.x, f_beg.y), 0);
avg_color += area_sw * texelFetch(Source, ivec2(f_beg.x, f_end.y), 0);
avg_color += area_se * texelFetch(Source, ivec2(f_end.x, f_end.y), 0);
// Determine the size of the pixel box.
int x_range = int(f_end.x - f_beg.x - 0.5);
int y_range = int(f_end.y - f_beg.y - 0.5);
// Accumulate top and bottom edge pixels.
for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
{
avg_color += area_n * texelFetch(Source, ivec2(x, f_beg.y), 0);
avg_color += area_s * texelFetch(Source, ivec2(x, f_end.y), 0);
}
// Accumulate left and right edge pixels and all the pixels in between.
for (int y = f_beg.y + 1; y <= f_beg.y + y_range; ++y)
{
avg_color += area_w * texelFetch(Source, ivec2(f_beg.x, y), 0);
avg_color += area_e * texelFetch(Source, ivec2(f_end.x, y), 0);
for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
{
avg_color += texelFetch(Source, ivec2(x, y), 0);
}
}
// Compute the area of the pixel box that was sampled.
float area_corners = area_nw + area_ne + area_sw + area_se;
float area_edges = float(x_range) * (area_n + area_s) + float(y_range) * (area_w + area_e);
float area_center = float(x_range) * float(y_range);
// Return the normalized average color.
return avg_color / (area_corners + area_edges + area_center);
}
float insideBox(vec2 v, vec2 bLeft, vec2 tRight) {
vec2 s = step(bLeft, v) - step(tRight, v);
return s.x * s.y;
}
vec2 translateDest(vec2 pos) {
vec2 translatedPos = vec2(pos.x, pos.y);
translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x;
translatedPos.y = dstY0 < dstY1 ? dstY1 + dstY0 - translatedPos.y - 1 : translatedPos.y;
return translatedPos;
}
void main()
{
vec2 bLeft = vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1);
vec2 tRight = vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0);
ivec2 loc = ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y);
if (insideBox(loc, bLeft, tRight) == 0) {
imageStore(imgOutput, loc, vec4(0, 0, 0, 1));
return;
}
vec4 outColor = AreaSampling(loc);
imageStore(imgOutput, ivec2(translateDest(loc)), vec4(outColor.rgb, 1));
}

View File

@ -219,7 +219,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer); buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
_pipeline.SetImage(ShaderStage.Compute, 0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); _pipeline.SetImage(ShaderStage.Compute, 0, _edgeOutputTexture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier(); _pipeline.ComputeBarrier();
@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear);
_pipeline.SetImage(ShaderStage.Compute, 0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); _pipeline.SetImage(ShaderStage.Compute, 0, _blendOutputTexture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier(); _pipeline.ComputeBarrier();
@ -238,7 +238,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
_pipeline.Specialize(_specConstants); _pipeline.Specialize(_specConstants);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
_pipeline.SetImage(ShaderStage.Compute, 0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); _pipeline.SetImage(ShaderStage.Compute, 0, _outputTexture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier(); _pipeline.ComputeBarrier();

View File

@ -1039,7 +1039,7 @@ namespace Ryujinx.Graphics.Vulkan
var dstView = Create2DLayerView(dst, dstLayer + z, dstLevel + l); var dstView = Create2DLayerView(dst, dstLayer + z, dstLevel + l);
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null); _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null);
_pipeline.SetImage(ShaderStage.Compute, 0, dstView, dstFormat); _pipeline.SetImage(ShaderStage.Compute, 0, dstView.GetView(dstFormat));
int dispatchX = (Math.Min(srcView.Info.Width, dstView.Info.Width) + 31) / 32; int dispatchX = (Math.Min(srcView.Info.Width, dstView.Info.Width) + 31) / 32;
int dispatchY = (Math.Min(srcView.Info.Height, dstView.Info.Height) + 31) / 32; int dispatchY = (Math.Min(srcView.Info.Height, dstView.Info.Height) + 31) / 32;
@ -1168,7 +1168,7 @@ namespace Ryujinx.Graphics.Vulkan
var dstView = Create2DLayerView(dst, dstLayer + z, 0); var dstView = Create2DLayerView(dst, dstLayer + z, 0);
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null); _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null);
_pipeline.SetImage(ShaderStage.Compute, 0, dstView, format); _pipeline.SetImage(ShaderStage.Compute, 0, dstView.GetView(format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.DispatchCompute(dispatchX, dispatchY, 1);

View File

@ -13,7 +13,6 @@ namespace Ryujinx.Graphics.Vulkan
{ {
public TextureStorage Storage; public TextureStorage Storage;
public TextureView View; public TextureView View;
public GAL.Format ImageFormat;
} }
private readonly TextureRef[] _textureRefs; private readonly TextureRef[] _textureRefs;
@ -52,16 +51,6 @@ namespace Ryujinx.Graphics.Vulkan
_isBuffer = isBuffer; _isBuffer = isBuffer;
} }
public void SetFormats(int index, GAL.Format[] imageFormats)
{
for (int i = 0; i < imageFormats.Length; i++)
{
_textureRefs[index + i].ImageFormat = imageFormats[i];
}
SetDirty();
}
public void SetImages(int index, ITexture[] images) public void SetImages(int index, ITexture[] images)
{ {
for (int i = 0; i < images.Length; i++) for (int i = 0; i < images.Length; i++)
@ -142,7 +131,7 @@ namespace Ryujinx.Graphics.Vulkan
ref var texture = ref textures[i]; ref var texture = ref textures[i];
ref var refs = ref _textureRefs[i]; ref var refs = ref _textureRefs[i];
if (i > 0 && _textureRefs[i - 1].View == refs.View && _textureRefs[i - 1].ImageFormat == refs.ImageFormat) if (i > 0 && _textureRefs[i - 1].View == refs.View)
{ {
texture = textures[i - 1]; texture = textures[i - 1];
@ -150,7 +139,7 @@ namespace Ryujinx.Graphics.Vulkan
} }
texture.ImageLayout = ImageLayout.General; texture.ImageLayout = ImageLayout.General;
texture.ImageView = refs.View?.GetView(refs.ImageFormat).GetIdentityImageView().Get(cbs).Value ?? default; texture.ImageView = refs.View?.GetIdentityImageView().Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0) if (texture.ImageView.Handle == 0)
{ {
@ -167,7 +156,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < bufferTextures.Length; i++) for (int i = 0; i < bufferTextures.Length; i++)
{ {
bufferTextures[i] = _bufferTextureRefs[i]?.GetBufferView(cbs, _textureRefs[i].ImageFormat, true) ?? default; bufferTextures[i] = _bufferTextureRefs[i]?.GetBufferView(cbs, true) ?? default;
} }
return bufferTextures; return bufferTextures;

View File

@ -836,9 +836,9 @@ namespace Ryujinx.Graphics.Vulkan
SignalStateChange(); SignalStateChange();
} }
public void SetImage(ShaderStage stage, int binding, ITexture image, Format imageFormat) public void SetImage(ShaderStage stage, int binding, ITexture image)
{ {
_descriptorSetUpdater.SetImage(Cbs, stage, binding, image, imageFormat); _descriptorSetUpdater.SetImage(Cbs, stage, binding, image);
} }
public void SetImage(int binding, Auto<DisposableImageView> image) public void SetImage(int binding, Auto<DisposableImageView> image)

View File

@ -15,6 +15,7 @@
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Effects\Textures\SmaaAreaTexture.bin" /> <EmbeddedResource Include="Effects\Textures\SmaaAreaTexture.bin" />
<EmbeddedResource Include="Effects\Textures\SmaaSearchTexture.bin" /> <EmbeddedResource Include="Effects\Textures\SmaaSearchTexture.bin" />
<EmbeddedResource Include="Effects\Shaders\AreaScaling.spv" />
<EmbeddedResource Include="Effects\Shaders\FsrScaling.spv" /> <EmbeddedResource Include="Effects\Shaders\FsrScaling.spv" />
<EmbeddedResource Include="Effects\Shaders\FsrSharpening.spv" /> <EmbeddedResource Include="Effects\Shaders\FsrSharpening.spv" />
<EmbeddedResource Include="Effects\Shaders\Fxaa.spv" /> <EmbeddedResource Include="Effects\Shaders\Fxaa.spv" />

View File

@ -1,7 +1,7 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using Format = Ryujinx.Graphics.GAL.Format; using Format = Ryujinx.Graphics.GAL.Format;
using VkFormat = Silk.NET.Vulkan.Format; using VkFormat = Silk.NET.Vulkan.Format;
@ -16,7 +16,6 @@ namespace Ryujinx.Graphics.Vulkan
private int _offset; private int _offset;
private int _size; private int _size;
private Auto<DisposableBufferView> _bufferView; private Auto<DisposableBufferView> _bufferView;
private Dictionary<Format, Auto<DisposableBufferView>> _selfManagedViews;
private int _bufferCount; private int _bufferCount;
@ -80,35 +79,25 @@ namespace Ryujinx.Graphics.Vulkan
private void ReleaseImpl() private void ReleaseImpl()
{ {
if (_selfManagedViews != null)
{
foreach (var bufferView in _selfManagedViews.Values)
{
bufferView.Dispose();
}
_selfManagedViews = null;
}
_bufferView?.Dispose(); _bufferView?.Dispose();
_bufferView = null; _bufferView = null;
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data) public void SetData(MemoryOwner<byte> data)
{ {
_gd.SetBufferData(_bufferHandle, _offset, data.Memory.Span); _gd.SetBufferData(_bufferHandle, _offset, data.Span);
data.Dispose(); data.Dispose();
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level) public void SetData(MemoryOwner<byte> data, int layer, int level)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region) public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
@ -137,28 +126,5 @@ namespace Ryujinx.Graphics.Vulkan
return _bufferView?.Get(cbs, _offset, _size, write).Value ?? default; return _bufferView?.Get(cbs, _offset, _size, write).Value ?? default;
} }
public BufferView GetBufferView(CommandBufferScoped cbs, Format format, bool write)
{
var vkFormat = FormatTable.GetFormat(format);
if (vkFormat == VkFormat)
{
return GetBufferView(cbs, write);
}
if (_selfManagedViews != null && _selfManagedViews.TryGetValue(format, out var bufferView))
{
return bufferView.Get(cbs, _offset, _size, write).Value;
}
bufferView = _gd.BufferManager.CreateView(_bufferHandle, vkFormat, _offset, _size, ReleaseImpl);
if (bufferView != null)
{
(_selfManagedViews ??= new Dictionary<Format, Auto<DisposableBufferView>>()).Add(format, bufferView);
}
return bufferView?.Get(cbs, _offset, _size, write).Value ?? default;
}
} }
} }

View File

@ -1,7 +1,7 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -746,23 +746,23 @@ namespace Ryujinx.Graphics.Vulkan
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data) public void SetData(MemoryOwner<byte> data)
{ {
SetData(data.Memory.Span, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false); SetData(data.Span, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false);
data.Dispose(); data.Dispose();
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level) public void SetData(MemoryOwner<byte> data, int layer, int level)
{ {
SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true); SetData(data.Span, layer, level, 1, 1, singleSlice: true);
data.Dispose(); data.Dispose();
} }
/// <inheritdoc/> /// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region) public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{ {
SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true, region); SetData(data.Span, layer, level, 1, 1, singleSlice: true, region);
data.Dispose(); data.Dispose();
} }

View File

@ -568,6 +568,13 @@ namespace Ryujinx.Graphics.Vulkan
_scalingFilter.Level = _scalingFilterLevel; _scalingFilter.Level = _scalingFilterLevel;
break; break;
case ScalingFilter.Area:
if (_scalingFilter is not AreaScalingFilter)
{
_scalingFilter?.Dispose();
_scalingFilter = new AreaScalingFilter(_gd, _device);
}
break;
} }
} }
} }

View File

@ -647,7 +647,7 @@ namespace Ryujinx.UI
} }
var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value
? HLE.MemoryConfiguration.MemoryConfiguration6GiB ? HLE.MemoryConfiguration.MemoryConfiguration8GiB
: HLE.MemoryConfiguration.MemoryConfiguration4GiB; : HLE.MemoryConfiguration.MemoryConfiguration4GiB;
IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
@ -677,7 +677,10 @@ namespace Ryujinx.UI
ConfigurationState.Instance.System.AudioVolume, ConfigurationState.Instance.System.AudioVolume,
ConfigurationState.Instance.System.UseHypervisor, ConfigurationState.Instance.System.UseHypervisor,
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
ConfigurationState.Instance.Multiplayer.Mode); ConfigurationState.Instance.Multiplayer.Mode,
ConfigurationState.Instance.Debug.EnableGdbStub,
ConfigurationState.Instance.Debug.GdbStubPort,
ConfigurationState.Instance.Debug.DebuggerSuspendOnStart);
_emulationContext = new HLE.Switch(configuration); _emulationContext = new HLE.Switch(configuration);
} }
@ -790,6 +793,24 @@ namespace Ryujinx.UI
shadersDumpWarningDialog.Dispose(); shadersDumpWarningDialog.Dispose();
} }
if (ConfigurationState.Instance.Debug.EnableGdbStub.Value)
{
MessageDialog gdbStubWarningDialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null)
{
Title = "Ryujinx - Warning",
Text = "You have the GDB stub enabled, which is designed to be used by developers only.",
SecondaryText = "For optimal performance, it's recommended to disable the GDB stub. Would you like to disable the GDB stub now?"
};
if (gdbStubWarningDialog.Run() == (int)ResponseType.Yes)
{
ConfigurationState.Instance.Debug.EnableGdbStub.Value = false;
SaveConfig();
}
gdbStubWarningDialog.Dispose();
}
} }
private bool LoadApplication(string path, ulong applicationId, bool isFirmwareTitle) private bool LoadApplication(string path, ulong applicationId, bool isFirmwareTitle)
@ -1056,9 +1077,11 @@ namespace Ryujinx.UI
RendererWidget.WaitEvent.WaitOne(); RendererWidget.WaitEvent.WaitOne();
RendererWidget.Start(); RendererWidget.Start();
_pauseEmulation.Sensitive = false;
_resumeEmulation.Sensitive = false;
UpdateMenuItem.Sensitive = true;
_emulationContext.Dispose(); _emulationContext.Dispose();
_deviceExitStatus.Set();
// NOTE: Everything that is here will not be executed when you close the UI. // NOTE: Everything that is here will not be executed when you close the UI.
Application.Invoke(delegate Application.Invoke(delegate
@ -1153,7 +1176,7 @@ namespace Ryujinx.UI
RendererWidget.Exit(); RendererWidget.Exit();
// Wait for the other thread to dispose the HLE context before exiting. // Wait for the other thread to dispose the HLE context before exiting.
_deviceExitStatus.WaitOne(); _emulationContext.ExitStatus.WaitOne();
RendererWidget.Dispose(); RendererWidget.Dispose();
} }
} }
@ -1491,9 +1514,6 @@ namespace Ryujinx.UI
UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText); UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText);
} }
_pauseEmulation.Sensitive = false;
_resumeEmulation.Sensitive = false;
UpdateMenuItem.Sensitive = true;
RendererWidget?.Exit(); RendererWidget?.Exit();
} }

View File

@ -46,7 +46,6 @@ namespace Ryujinx.UI
public static event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent; public static event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
private bool _isActive;
private bool _isStopped; private bool _isStopped;
private bool _toggleFullscreen; private bool _toggleFullscreen;
@ -464,7 +463,7 @@ namespace Ryujinx.UI
(Toplevel as MainWindow)?.ActivatePauseMenu(); (Toplevel as MainWindow)?.ActivatePauseMenu();
while (_isActive) while (Device.IsActive)
{ {
if (_isStopped) if (_isStopped)
{ {
@ -524,7 +523,7 @@ namespace Ryujinx.UI
{ {
_chrono.Restart(); _chrono.Restart();
_isActive = true; Device.IsActive = true;
Gtk.Window parent = Toplevel as Gtk.Window; Gtk.Window parent = Toplevel as Gtk.Window;
@ -578,9 +577,9 @@ namespace Ryujinx.UI
_isStopped = true; _isStopped = true;
if (_isActive) if (Device.IsActive)
{ {
_isActive = false; Device.IsActive = false;
_exitEvent.WaitOne(); _exitEvent.WaitOne();
_exitEvent.Dispose(); _exitEvent.Dispose();
@ -589,7 +588,7 @@ namespace Ryujinx.UI
private void NvidiaStutterWorkaround() private void NvidiaStutterWorkaround()
{ {
while (_isActive) while (Device.IsActive)
{ {
// When NVIDIA Threaded Optimization is on, the driver will snapshot all threads in the system whenever the application creates any new ones. // When NVIDIA Threaded Optimization is on, the driver will snapshot all threads in the system whenever the application creates any new ones.
// The ThreadPool has something called a "GateThread" which terminates itself after some inactivity. // The ThreadPool has something called a "GateThread" which terminates itself after some inactivity.
@ -608,7 +607,7 @@ namespace Ryujinx.UI
public void MainLoop() public void MainLoop()
{ {
while (_isActive) while (Device.IsActive)
{ {
UpdateFrame(); UpdateFrame();
@ -621,7 +620,7 @@ namespace Ryujinx.UI
private bool UpdateFrame() private bool UpdateFrame()
{ {
if (!_isActive) if (!Device.IsActive)
{ {
return true; return true;
} }

View File

@ -117,7 +117,9 @@ namespace Ryujinx.UI.Windows
[GUI] ToggleButton _configureController7; [GUI] ToggleButton _configureController7;
[GUI] ToggleButton _configureController8; [GUI] ToggleButton _configureController8;
[GUI] ToggleButton _configureControllerH; [GUI] ToggleButton _configureControllerH;
[GUI] ToggleButton _gdbStubToggle;
[GUI] ToggleButton _suspendOnStartToggle;
[GUI] Adjustment _gdbStubPortSpinAdjustment;
#pragma warning restore CS0649, IDE0044 #pragma warning restore CS0649, IDE0044
public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Gtk3.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Gtk3.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { }
@ -316,6 +318,16 @@ namespace Ryujinx.UI.Windows
_custThemeToggle.Click(); _custThemeToggle.Click();
} }
if (ConfigurationState.Instance.Debug.EnableGdbStub)
{
_gdbStubToggle.Click();
}
if (ConfigurationState.Instance.Debug.DebuggerSuspendOnStart)
{
_suspendOnStartToggle.Click();
}
// Custom EntryCompletion Columns. If added to glade, need to override more signals // Custom EntryCompletion Columns. If added to glade, need to override more signals
ListStore tzList = new(typeof(string), typeof(string), typeof(string)); ListStore tzList = new(typeof(string), typeof(string), typeof(string));
_systemTimeZoneCompletion.Model = tzList; _systemTimeZoneCompletion.Model = tzList;
@ -375,6 +387,8 @@ namespace Ryujinx.UI.Windows
_fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode; _fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode;
_systemTimeOffset = ConfigurationState.Instance.System.SystemTimeOffset; _systemTimeOffset = ConfigurationState.Instance.System.SystemTimeOffset;
_gdbStubPortSpinAdjustment.Value = ConfigurationState.Instance.Debug.GdbStubPort;
_gameDirsBox.AppendColumn("", new CellRendererText(), "text", 0); _gameDirsBox.AppendColumn("", new CellRendererText(), "text", 0);
_gameDirsBoxStore = new ListStore(typeof(string)); _gameDirsBoxStore = new ListStore(typeof(string));
_gameDirsBox.Model = _gameDirsBoxStore; _gameDirsBox.Model = _gameDirsBoxStore;
@ -659,6 +673,9 @@ namespace Ryujinx.UI.Windows
ConfigurationState.Instance.Graphics.ScalingFilter.Value = Enum.Parse<ScalingFilter>(_scalingFilter.ActiveId); ConfigurationState.Instance.Graphics.ScalingFilter.Value = Enum.Parse<ScalingFilter>(_scalingFilter.ActiveId);
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value = (int)_scalingFilterLevel.Value; ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value = (int)_scalingFilterLevel.Value;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId; ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId;
ConfigurationState.Instance.Debug.EnableGdbStub.Value = _gdbStubToggle.Active;
ConfigurationState.Instance.Debug.GdbStubPort.Value = (ushort)_gdbStubPortSpinAdjustment.Value;
ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Value = _suspendOnStartToggle.Active;
_previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value; _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value;

View File

@ -46,6 +46,12 @@
<property name="inline-completion">True</property> <property name="inline-completion">True</property>
<property name="inline-selection">True</property> <property name="inline-selection">True</property>
</object> </object>
<object class="GtkAdjustment" id="_gdbStubPortSpinAdjustment">
<property name="lower">1</property>
<property name="upper">65535</property>
<property name="step-increment">1</property>
<property name="page-increment">5</property>
</object>
<object class="GtkWindow" id="_settingsWin"> <object class="GtkWindow" id="_settingsWin">
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="title" translatable="yes">Ryujinx - Settings</property> <property name="title" translatable="yes">Ryujinx - Settings</property>
@ -3146,6 +3152,155 @@
<property name="tab-fill">False</property> <property name="tab-fill">False</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkBox" id="TabDebug">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">10</property>
<property name="margin-top">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="CatDebug">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes">Debug (WARNING: For Developer Use Only)</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="DebugOptions">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="margin-left">10</property>
<property name="margin-right">10</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkCheckButton" id="_gdbStubToggle">
<property name="label" translatable="yes">Enable GDB Stub</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="tooltip-text" translatable="yes">Enables or disables GDB stub (for developer use only)</property>
<property name="halign">start</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Specifies which TCP port for the GDB stub to listen on. Possible values are 1-65535.</property>
<property name="label" translatable="yes">GDB Stub Port</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="tooltip-text" translatable="yes">Specifies which TCP port for the GDB stub to listen on. Possible values are 1-65535.</property>
<property name="text" translatable="yes">55555</property>
<property name="input-purpose">number</property>
<property name="adjustment">_gdbStubPortSpinAdjustment</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">9</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="_suspendOnStartToggle">
<property name="label" translatable="yes">Suspend application on start</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="tooltip-text" translatable="yes">Suspends the application before executing the first instruction, allowing for debugging from the earliest point.</property>
<property name="halign">start</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="position">6</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Debug</property>
</object>
<packing>
<property name="position">6</property>
<property name="tab-fill">False</property>
</packing>
</child>
</object> </object>
</child> </child>
</object> </object>

View File

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.Debugger
{
public enum DebugState
{
Running,
Stopping,
Stopped,
}
}

View File

@ -0,0 +1,900 @@
using ARMeilleure.State;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using IExecutionContext = Ryujinx.Cpu.IExecutionContext;
namespace Ryujinx.HLE.Debugger
{
public class Debugger : IDisposable
{
internal Switch Device { get; private set; }
public ushort GdbStubPort { get; private set; }
private TcpListener ListenerSocket;
private Socket ClientSocket = null;
private NetworkStream ReadStream = null;
private NetworkStream WriteStream = null;
private BlockingCollection<IMessage> Messages = new BlockingCollection<IMessage>(1);
private Thread DebuggerThread;
private Thread MessageHandlerThread;
private bool _shuttingDown = false;
private ulong? cThread;
private ulong? gThread;
public Debugger(Switch device, ushort port)
{
Device = device;
GdbStubPort = port;
ARMeilleure.Optimizations.EnableDebugging = true;
DebuggerThread = new Thread(DebuggerThreadMain);
DebuggerThread.Start();
MessageHandlerThread = new Thread(MessageHandlerMain);
MessageHandlerThread.Start();
}
private IDebuggableProcess DebugProcess => Device.System.DebugGetApplicationProcess();
private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray();
private bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
private KernelContext KernelContext => Device.System.KernelContext;
const int GdbRegisterCount64 = 68;
const int GdbRegisterCount32 = 66;
/* FPCR = FPSR & ~FpcrMask
All of FPCR's bits are reserved in FPCR and vice versa,
see ARM's documentation. */
private const uint FpcrMask = 0xfc1fffff;
private string GdbReadRegister64(IExecutionContext state, int gdbRegId)
{
switch (gdbRegId)
{
case >= 0 and <= 31:
return ToHex(BitConverter.GetBytes(state.GetX(gdbRegId)));
case 32:
return ToHex(BitConverter.GetBytes(state.DebugPc));
case 33:
return ToHex(BitConverter.GetBytes(state.Pstate));
case >= 34 and <= 65:
return ToHex(state.GetV(gdbRegId - 34).ToArray());
case 66:
return ToHex(BitConverter.GetBytes((uint)state.Fpsr));
case 67:
return ToHex(BitConverter.GetBytes((uint)state.Fpcr));
default:
return null;
}
}
private bool GdbWriteRegister64(IExecutionContext state, int gdbRegId, StringStream ss)
{
switch (gdbRegId)
{
case >= 0 and <= 31:
{
ulong value = ss.ReadLengthAsHex(16);
state.SetX(gdbRegId, value);
return true;
}
case 32:
{
ulong value = ss.ReadLengthAsHex(16);
state.DebugPc = value;
return true;
}
case 33:
{
ulong value = ss.ReadLengthAsHex(8);
state.Pstate = (uint)value;
return true;
}
case >= 34 and <= 65:
{
ulong value0 = ss.ReadLengthAsHex(16);
ulong value1 = ss.ReadLengthAsHex(16);
state.SetV(gdbRegId - 34, new V128(value0, value1));
return true;
}
case 66:
{
ulong value = ss.ReadLengthAsHex(8);
state.Fpsr = (uint)value;
return true;
}
case 67:
{
ulong value = ss.ReadLengthAsHex(8);
state.Fpcr = (uint)value;
return true;
}
default:
return false;
}
}
private string GdbReadRegister32(IExecutionContext state, int gdbRegId)
{
switch (gdbRegId)
{
case >= 0 and <= 14:
return ToHex(BitConverter.GetBytes((uint)state.GetX(gdbRegId)));
case 15:
return ToHex(BitConverter.GetBytes((uint)state.DebugPc));
case 16:
return ToHex(BitConverter.GetBytes((uint)state.Pstate));
case >= 17 and <= 32:
return ToHex(state.GetV(gdbRegId - 17).ToArray());
case >= 33 and <= 64:
int reg = (gdbRegId - 33);
int n = reg / 2;
int shift = reg % 2;
ulong value = state.GetV(n).Extract<ulong>(shift);
return ToHex(BitConverter.GetBytes(value));
case 65:
uint fpscr = (uint)state.Fpsr | (uint)state.Fpcr;
return ToHex(BitConverter.GetBytes(fpscr));
default:
return null;
}
}
private bool GdbWriteRegister32(IExecutionContext state, int gdbRegId, StringStream ss)
{
switch (gdbRegId)
{
case >= 0 and <= 14:
{
ulong value = ss.ReadLengthAsHex(8);
state.SetX(gdbRegId, value);
return true;
}
case 15:
{
ulong value = ss.ReadLengthAsHex(8);
state.DebugPc = value;
return true;
}
case 16:
{
ulong value = ss.ReadLengthAsHex(8);
state.Pstate = (uint)value;
return true;
}
case >= 17 and <= 32:
{
ulong value0 = ss.ReadLengthAsHex(16);
ulong value1 = ss.ReadLengthAsHex(16);
state.SetV(gdbRegId - 17, new V128(value0, value1));
return true;
}
case >= 33 and <= 64:
{
ulong value = ss.ReadLengthAsHex(16);
int regId = (gdbRegId - 33);
int regNum = regId / 2;
int shift = regId % 2;
V128 reg = state.GetV(regNum);
reg.Insert(shift, value);
return true;
}
case 65:
{
ulong value = ss.ReadLengthAsHex(8);
state.Fpsr = (uint)value & FpcrMask;
state.Fpcr = (uint)value & ~FpcrMask;
return true;
}
default:
return false;
}
}
private void MessageHandlerMain()
{
while (!_shuttingDown)
{
IMessage msg = Messages.Take();
switch (msg)
{
case BreakInMessage:
Logger.Notice.Print(LogClass.GdbStub, "Break-in requested");
CommandQuery();
break;
case SendNackMessage:
WriteStream.WriteByte((byte)'-');
break;
case CommandMessage { Command: var cmd }:
Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}");
WriteStream.WriteByte((byte)'+');
ProcessCommand(cmd);
break;
case ThreadBreakMessage { Context: var ctx }:
DebugProcess.DebugStop();
Reply($"T05thread:{ctx.ThreadUid:x};");
break;
case KillMessage:
return;
}
}
}
private void ProcessCommand(string cmd)
{
StringStream ss = new StringStream(cmd);
switch (ss.ReadChar())
{
case '!':
if (!ss.IsEmpty())
{
goto unknownCommand;
}
// Enable extended mode
ReplyOK();
break;
case '?':
if (!ss.IsEmpty())
{
goto unknownCommand;
}
CommandQuery();
break;
case 'c':
CommandContinue(ss.IsEmpty() ? null : ss.ReadRemainingAsHex());
break;
case 'D':
if (!ss.IsEmpty())
{
goto unknownCommand;
}
CommandDetach();
break;
case 'g':
if (!ss.IsEmpty())
{
goto unknownCommand;
}
CommandReadRegisters();
break;
case 'G':
CommandWriteRegisters(ss);
break;
case 'H':
{
char op = ss.ReadChar();
ulong? threadId = ss.ReadRemainingAsThreadUid();
CommandSetThread(op, threadId);
break;
}
case 'k':
Logger.Notice.Print(LogClass.GdbStub, "Kill request received");
Reply("");
Device.IsActive = false;
Device.ExitStatus.WaitOne();
break;
case 'm':
{
ulong addr = ss.ReadUntilAsHex(',');
ulong len = ss.ReadRemainingAsHex();
CommandReadMemory(addr, len);
break;
}
case 'M':
{
ulong addr = ss.ReadUntilAsHex(',');
ulong len = ss.ReadUntilAsHex(':');
CommandWriteMemory(addr, len, ss);
break;
}
case 'p':
{
ulong gdbRegId = ss.ReadRemainingAsHex();
CommandReadRegister((int)gdbRegId);
break;
}
case 'P':
{
ulong gdbRegId = ss.ReadUntilAsHex('=');
CommandWriteRegister((int)gdbRegId, ss);
break;
}
case 'q':
if (ss.ConsumeRemaining("GDBServerVersion"))
{
Reply($"name:Ryujinx;version:{ReleaseInformation.Version};");
break;
}
if (ss.ConsumeRemaining("HostInfo"))
{
if (IsProcessAarch32)
{
Reply(
$"triple:{ToHex("arm-unknown-linux-android")};endian:little;ptrsize:4;hostname:{ToHex("Ryujinx")};");
}
else
{
Reply(
$"triple:{ToHex("aarch64-unknown-linux-android")};endian:little;ptrsize:8;hostname:{ToHex("Ryujinx")};");
}
break;
}
if (ss.ConsumeRemaining("ProcessInfo"))
{
if (IsProcessAarch32)
{
Reply(
$"pid:1;cputype:12;cpusubtype:0;triple:{ToHex("arm-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:4;");
}
else
{
Reply(
$"pid:1;cputype:100000c;cpusubtype:0;triple:{ToHex("aarch64-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:8;");
}
break;
}
if (ss.ConsumePrefix("Supported:") || ss.ConsumeRemaining("Supported"))
{
Reply("PacketSize=10000;qXfer:features:read+");
break;
}
if (ss.ConsumeRemaining("fThreadInfo"))
{
Reply($"m{string.Join(",", DebugProcess.GetThreadUids().Select(x => $"{x:x}"))}");
break;
}
if (ss.ConsumeRemaining("sThreadInfo"))
{
Reply("l");
break;
}
if (ss.ConsumePrefix("ThreadExtraInfo,"))
{
ulong? threadId = ss.ReadRemainingAsThreadUid();
if (threadId == null)
{
ReplyError();
break;
}
if (DebugProcess.GetDebugState() == DebugState.Stopped)
{
Reply(ToHex("Stopped"));
}
else
{
Reply(ToHex("Not stopped"));
}
break;
}
if (ss.ConsumePrefix("Xfer:features:read:"))
{
string feature = ss.ReadUntil(':');
ulong addr = ss.ReadUntilAsHex(',');
ulong len = ss.ReadRemainingAsHex();
if (feature == "target.xml")
{
feature = IsProcessAarch32 ? "target32.xml" : "target64.xml";
}
string data;
if (RegisterInformation.Features.TryGetValue(feature, out data))
{
if (addr >= (ulong)data.Length)
{
Reply("l");
break;
}
if (len >= (ulong)data.Length - addr)
{
Reply("l" + ToBinaryFormat(data.Substring((int)addr)));
break;
}
else
{
Reply("m" + ToBinaryFormat(data.Substring((int)addr, (int)len)));
break;
}
}
else
{
Reply("E00"); // Invalid annex
break;
}
}
goto unknownCommand;
case 'Q':
goto unknownCommand;
case 's':
CommandStep(ss.IsEmpty() ? null : ss.ReadRemainingAsHex());
break;
case 'T':
{
ulong? threadId = ss.ReadRemainingAsThreadUid();
CommandIsAlive(threadId);
break;
}
default:
unknownCommand:
Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}");
Reply("");
break;
}
}
void CommandQuery()
{
// GDB is performing initial contact. Stop everything.
DebugProcess.DebugStop();
gThread = cThread = DebugProcess.GetThreadUids().First();
Reply($"T05thread:{cThread:x};");
}
void CommandContinue(ulong? newPc)
{
if (newPc.HasValue)
{
if (cThread == null)
{
ReplyError();
return;
}
DebugProcess.GetThread(cThread.Value).Context.DebugPc = newPc.Value;
}
DebugProcess.DebugContinue();
}
void CommandDetach()
{
// TODO: Remove all breakpoints
CommandContinue(null);
}
void CommandReadRegisters()
{
if (gThread == null)
{
ReplyError();
return;
}
var ctx = DebugProcess.GetThread(gThread.Value).Context;
string registers = "";
if (IsProcessAarch32)
{
for (int i = 0; i < GdbRegisterCount32; i++)
{
registers += GdbReadRegister32(ctx, i);
}
}
else
{
for (int i = 0; i < GdbRegisterCount64; i++)
{
registers += GdbReadRegister64(ctx, i);
}
}
Reply(registers);
}
void CommandWriteRegisters(StringStream ss)
{
if (gThread == null)
{
ReplyError();
return;
}
var ctx = DebugProcess.GetThread(gThread.Value).Context;
if (IsProcessAarch32)
{
for (int i = 0; i < GdbRegisterCount32; i++)
{
if (!GdbWriteRegister32(ctx, i, ss))
{
ReplyError();
return;
}
}
}
else
{
for (int i = 0; i < GdbRegisterCount64; i++)
{
if (!GdbWriteRegister64(ctx, i, ss))
{
ReplyError();
return;
}
}
}
if (ss.IsEmpty())
{
ReplyOK();
}
else
{
ReplyError();
}
}
void CommandSetThread(char op, ulong? threadId)
{
if (threadId == 0 || threadId == null)
{
threadId = GetThreads().First().ThreadUid;
}
if (DebugProcess.GetThread(threadId.Value) == null)
{
ReplyError();
return;
}
switch (op)
{
case 'c':
cThread = threadId;
ReplyOK();
return;
case 'g':
gThread = threadId;
ReplyOK();
return;
default:
ReplyError();
return;
}
}
void CommandReadMemory(ulong addr, ulong len)
{
try
{
var data = new byte[len];
DebugProcess.CpuMemory.Read(addr, data);
Reply(ToHex(data));
}
catch (InvalidMemoryRegionException)
{
ReplyError();
}
}
void CommandWriteMemory(ulong addr, ulong len, StringStream ss)
{
try
{
var data = new byte[len];
for (ulong i = 0; i < len; i++)
{
data[i] = (byte)ss.ReadLengthAsHex(2);
}
DebugProcess.CpuMemory.Write(addr, data);
DebugProcess.InvalidateCacheRegion(addr, len);
ReplyOK();
}
catch (InvalidMemoryRegionException)
{
ReplyError();
}
}
void CommandReadRegister(int gdbRegId)
{
if (gThread == null)
{
ReplyError();
return;
}
var ctx = DebugProcess.GetThread(gThread.Value).Context;
string result;
if (IsProcessAarch32)
{
result = GdbReadRegister32(ctx, gdbRegId);
if (result != null)
{
Reply(result);
}
else
{
ReplyError();
}
}
else
{
result = GdbReadRegister64(ctx, gdbRegId);
if (result != null)
{
Reply(result);
}
else
{
ReplyError();
}
}
}
void CommandWriteRegister(int gdbRegId, StringStream ss)
{
if (gThread == null)
{
ReplyError();
return;
}
var ctx = DebugProcess.GetThread(gThread.Value).Context;
if (IsProcessAarch32)
{
if (GdbWriteRegister32(ctx, gdbRegId, ss) && ss.IsEmpty())
{
ReplyOK();
}
else
{
ReplyError();
}
}
else
{
if (GdbWriteRegister64(ctx, gdbRegId, ss) && ss.IsEmpty())
{
ReplyOK();
}
else
{
ReplyError();
}
}
}
private void CommandStep(ulong? newPc)
{
if (cThread == null)
{
ReplyError();
return;
}
var thread = DebugProcess.GetThread(cThread.Value);
if (newPc.HasValue)
{
thread.Context.DebugPc = newPc.Value;
}
if (!DebugProcess.DebugStep(thread))
{
ReplyError();
}
else
{
Reply($"T05thread:{thread.ThreadUid:x};");
}
}
private void CommandIsAlive(ulong? threadId)
{
if (GetThreads().Any(x => x.ThreadUid == threadId))
{
ReplyOK();
}
else
{
Reply("E00");
}
}
private void Reply(string cmd)
{
Logger.Debug?.Print(LogClass.GdbStub, $"Reply: {cmd}");
WriteStream.Write(Encoding.ASCII.GetBytes($"${cmd}#{CalculateChecksum(cmd):x2}"));
}
private void ReplyOK()
{
Reply("OK");
}
private void ReplyError()
{
Reply("E01");
}
private void DebuggerThreadMain()
{
var endpoint = new IPEndPoint(IPAddress.Any, GdbStubPort);
ListenerSocket = new TcpListener(endpoint);
ListenerSocket.Start();
Logger.Notice.Print(LogClass.GdbStub, $"Currently waiting on {endpoint} for GDB client");
while (!_shuttingDown)
{
try
{
ClientSocket = ListenerSocket.AcceptSocket();
}
catch (SocketException)
{
return;
}
ClientSocket.NoDelay = true;
ReadStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Read);
WriteStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Write);
Logger.Notice.Print(LogClass.GdbStub, "GDB client connected");
while (true)
{
try
{
switch (ReadStream.ReadByte())
{
case -1:
goto eof;
case '+':
continue;
case '-':
Logger.Notice.Print(LogClass.GdbStub, "NACK received!");
continue;
case '\x03':
Messages.Add(new BreakInMessage());
break;
case '$':
string cmd = "";
while (true)
{
int x = ReadStream.ReadByte();
if (x == -1)
goto eof;
if (x == '#')
break;
cmd += (char)x;
}
string checksum = $"{(char)ReadStream.ReadByte()}{(char)ReadStream.ReadByte()}";
if (checksum == $"{CalculateChecksum(cmd):x2}")
{
Messages.Add(new CommandMessage(cmd));
}
else
{
Messages.Add(new SendNackMessage());
}
break;
}
}
catch (IOException)
{
goto eof;
}
}
eof:
Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection");
ReadStream.Close();
ReadStream = null;
WriteStream.Close();
WriteStream = null;
ClientSocket.Close();
ClientSocket = null;
}
}
private byte CalculateChecksum(string cmd)
{
byte checksum = 0;
foreach (char x in cmd)
{
unchecked
{
checksum += (byte)x;
}
}
return checksum;
}
private string ToHex(byte[] bytes)
{
return string.Join("", bytes.Select(x => $"{x:x2}"));
}
private string ToHex(string str)
{
return ToHex(Encoding.ASCII.GetBytes(str));
}
private string ToBinaryFormat(byte[] bytes)
{
return string.Join("", bytes.Select(x =>
x switch
{
(byte)'#' => "}\x03",
(byte)'$' => "}\x04",
(byte)'*' => "}\x0a",
(byte)'}' => "}\x5d",
_ => Convert.ToChar(x).ToString(),
}
));
}
private string ToBinaryFormat(string str)
{
return ToBinaryFormat(Encoding.ASCII.GetBytes(str));
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_shuttingDown = true;
ListenerSocket.Stop();
ClientSocket?.Shutdown(SocketShutdown.Both);
ClientSocket?.Close();
ReadStream?.Close();
WriteStream?.Close();
DebuggerThread.Join();
Messages.Add(new KillMessage());
MessageHandlerThread.Join();
Messages.Dispose();
}
}
public void BreakHandler(IExecutionContext ctx, ulong address, int imm)
{
Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}");
Messages.Add(new ThreadBreakMessage(ctx, address, imm));
DebugProcess.DebugInterruptHandler(ctx);
}
public void StepHandler(IExecutionContext ctx)
{
DebugProcess.DebugInterruptHandler(ctx);
}
}
}

View File

@ -0,0 +1,15 @@
namespace Ryujinx.HLE.Debugger
{
enum GdbSignal
{
Zero = 0,
Int = 2,
Quit = 3,
Trap = 5,
Abort = 6,
Alarm = 14,
IO = 23,
XCPU = 24,
Unknown = 143
}
}

View File

@ -0,0 +1,93 @@
<?xml version="1.0"?>
<!-- Copyright (C) 2009-2022 Free Software Foundation, Inc.
Contributed by ARM Ltd.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. -->
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
<feature name="org.gnu.gdb.aarch64.core">
<reg name="x0" bitsize="64"/>
<reg name="x1" bitsize="64"/>
<reg name="x2" bitsize="64"/>
<reg name="x3" bitsize="64"/>
<reg name="x4" bitsize="64"/>
<reg name="x5" bitsize="64"/>
<reg name="x6" bitsize="64"/>
<reg name="x7" bitsize="64"/>
<reg name="x8" bitsize="64"/>
<reg name="x9" bitsize="64"/>
<reg name="x10" bitsize="64"/>
<reg name="x11" bitsize="64"/>
<reg name="x12" bitsize="64"/>
<reg name="x13" bitsize="64"/>
<reg name="x14" bitsize="64"/>
<reg name="x15" bitsize="64"/>
<reg name="x16" bitsize="64"/>
<reg name="x17" bitsize="64"/>
<reg name="x18" bitsize="64"/>
<reg name="x19" bitsize="64"/>
<reg name="x20" bitsize="64"/>
<reg name="x21" bitsize="64"/>
<reg name="x22" bitsize="64"/>
<reg name="x23" bitsize="64"/>
<reg name="x24" bitsize="64"/>
<reg name="x25" bitsize="64"/>
<reg name="x26" bitsize="64"/>
<reg name="x27" bitsize="64"/>
<reg name="x28" bitsize="64"/>
<reg name="x29" bitsize="64"/>
<reg name="x30" bitsize="64"/>
<reg name="sp" bitsize="64" type="data_ptr"/>
<reg name="pc" bitsize="64" type="code_ptr"/>
<flags id="cpsr_flags" size="4">
<!-- Stack Pointer. -->
<field name="SP" start="0" end="0"/>
<!-- Exception Level. -->
<field name="EL" start="2" end="3"/>
<!-- Execution state. -->
<field name="nRW" start="4" end="4"/>
<!-- FIQ interrupt mask. -->
<field name="F" start="6" end="6"/>
<!-- IRQ interrupt mask. -->
<field name="I" start="7" end="7"/>
<!-- SError interrupt mask. -->
<field name="A" start="8" end="8"/>
<!-- Debug exception mask. -->
<field name="D" start="9" end="9"/>
<!-- ARMv8.5-A: Branch Target Identification BTYPE. -->
<field name="BTYPE" start="10" end="11"/>
<!-- ARMv8.0-A: Speculative Store Bypass. -->
<field name="SSBS" start="12" end="12"/>
<!-- Illegal Execution state. -->
<field name="IL" start="20" end="20"/>
<!-- Software Step. -->
<field name="SS" start="21" end="21"/>
<!-- ARMv8.1-A: Privileged Access Never. -->
<field name="PAN" start="22" end="22"/>
<!-- ARMv8.2-A: User Access Override. -->
<field name="UAO" start="23" end="23"/>
<!-- ARMv8.4-A: Data Independent Timing. -->
<field name="DIT" start="24" end="24"/>
<!-- ARMv8.5-A: Tag Check Override. -->
<field name="TCO" start="25" end="25"/>
<!-- Overflow Condition flag. -->
<field name="V" start="28" end="28"/>
<!-- Carry Condition flag. -->
<field name="C" start="29" end="29"/>
<!-- Zero Condition flag. -->
<field name="Z" start="30" end="30"/>
<!-- Negative Condition flag. -->
<field name="N" start="31" end="31"/>
</flags>
<reg name="cpsr" bitsize="32" type="cpsr_flags"/>
</feature>

View File

@ -0,0 +1,159 @@
<?xml version="1.0"?>
<!-- Copyright (C) 2009-2022 Free Software Foundation, Inc.
Contributed by ARM Ltd.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. -->
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
<feature name="org.gnu.gdb.aarch64.fpu">
<vector id="v2d" type="ieee_double" count="2"/>
<vector id="v2u" type="uint64" count="2"/>
<vector id="v2i" type="int64" count="2"/>
<vector id="v4f" type="ieee_single" count="4"/>
<vector id="v4u" type="uint32" count="4"/>
<vector id="v4i" type="int32" count="4"/>
<vector id="v8f" type="ieee_half" count="8"/>
<vector id="v8u" type="uint16" count="8"/>
<vector id="v8i" type="int16" count="8"/>
<vector id="v8bf16" type="bfloat16" count="8"/>
<vector id="v16u" type="uint8" count="16"/>
<vector id="v16i" type="int8" count="16"/>
<vector id="v1u" type="uint128" count="1"/>
<vector id="v1i" type="int128" count="1"/>
<union id="vnd">
<field name="f" type="v2d"/>
<field name="u" type="v2u"/>
<field name="s" type="v2i"/>
</union>
<union id="vns">
<field name="f" type="v4f"/>
<field name="u" type="v4u"/>
<field name="s" type="v4i"/>
</union>
<union id="vnh">
<field name="bf" type="v8bf16"/>
<field name="f" type="v8f"/>
<field name="u" type="v8u"/>
<field name="s" type="v8i"/>
</union>
<union id="vnb">
<field name="u" type="v16u"/>
<field name="s" type="v16i"/>
</union>
<union id="vnq">
<field name="u" type="v1u"/>
<field name="s" type="v1i"/>
</union>
<union id="aarch64v">
<field name="d" type="vnd"/>
<field name="s" type="vns"/>
<field name="h" type="vnh"/>
<field name="b" type="vnb"/>
<field name="q" type="vnq"/>
</union>
<reg name="v0" bitsize="128" type="aarch64v" regnum="34"/>
<reg name="v1" bitsize="128" type="aarch64v" />
<reg name="v2" bitsize="128" type="aarch64v" />
<reg name="v3" bitsize="128" type="aarch64v" />
<reg name="v4" bitsize="128" type="aarch64v" />
<reg name="v5" bitsize="128" type="aarch64v" />
<reg name="v6" bitsize="128" type="aarch64v" />
<reg name="v7" bitsize="128" type="aarch64v" />
<reg name="v8" bitsize="128" type="aarch64v" />
<reg name="v9" bitsize="128" type="aarch64v" />
<reg name="v10" bitsize="128" type="aarch64v"/>
<reg name="v11" bitsize="128" type="aarch64v"/>
<reg name="v12" bitsize="128" type="aarch64v"/>
<reg name="v13" bitsize="128" type="aarch64v"/>
<reg name="v14" bitsize="128" type="aarch64v"/>
<reg name="v15" bitsize="128" type="aarch64v"/>
<reg name="v16" bitsize="128" type="aarch64v"/>
<reg name="v17" bitsize="128" type="aarch64v"/>
<reg name="v18" bitsize="128" type="aarch64v"/>
<reg name="v19" bitsize="128" type="aarch64v"/>
<reg name="v20" bitsize="128" type="aarch64v"/>
<reg name="v21" bitsize="128" type="aarch64v"/>
<reg name="v22" bitsize="128" type="aarch64v"/>
<reg name="v23" bitsize="128" type="aarch64v"/>
<reg name="v24" bitsize="128" type="aarch64v"/>
<reg name="v25" bitsize="128" type="aarch64v"/>
<reg name="v26" bitsize="128" type="aarch64v"/>
<reg name="v27" bitsize="128" type="aarch64v"/>
<reg name="v28" bitsize="128" type="aarch64v"/>
<reg name="v29" bitsize="128" type="aarch64v"/>
<reg name="v30" bitsize="128" type="aarch64v"/>
<reg name="v31" bitsize="128" type="aarch64v"/>
<flags id="fpsr_flags" size="4">
<!-- Invalid Operation cumulative floating-point exception bit. -->
<field name="IOC" start="0" end="0"/>
<!-- Divide by Zero cumulative floating-point exception bit. -->
<field name="DZC" start="1" end="1"/>
<!-- Overflow cumulative floating-point exception bit. -->
<field name="OFC" start="2" end="2"/>
<!-- Underflow cumulative floating-point exception bit. -->
<field name="UFC" start="3" end="3"/>
<!-- Inexact cumulative floating-point exception bit.. -->
<field name="IXC" start="4" end="4"/>
<!-- Input Denormal cumulative floating-point exception bit. -->
<field name="IDC" start="7" end="7"/>
<!-- Cumulative saturation bit, Advanced SIMD only. -->
<field name="QC" start="27" end="27"/>
<!-- When AArch32 is supported at any Exception level and AArch32
floating-point is implemented: Overflow condition flag for AArch32
floating-point comparison operations. -->
<field name="V" start="28" end="28"/>
<!-- When AArch32 is supported at any Exception level and AArch32
floating-point is implemented:
Carry condition flag for AArch32 floating-point comparison operations.
-->
<field name="C" start="29" end="29"/>
<!-- When AArch32 is supported at any Exception level and AArch32
floating-point is implemented:
Zero condition flag for AArch32 floating-point comparison operations.
-->
<field name="Z" start="30" end="30"/>
<!-- When AArch32 is supported at any Exception level and AArch32
floating-point is implemented:
Negative condition flag for AArch32 floating-point comparison
operations. -->
<field name="N" start="31" end="31"/>
</flags>
<reg name="fpsr" bitsize="32" type="fpsr_flags"/>
<flags id="fpcr_flags" size="4">
<!-- Flush Inputs to Zero (part of Armv8.7). -->
<field name="FIZ" start="0" end="0"/>
<!-- Alternate Handling (part of Armv8.7). -->
<field name="AH" start="1" end="1"/>
<!-- Controls how the output elements other than the lowest element of the
vector are determined for Advanced SIMD scalar instructions (part of
Armv8.7). -->
<field name="NEP" start="2" end="2"/>
<!-- Invalid Operation floating-point exception trap enable. -->
<field name="IOE" start="8" end="8"/>
<!-- Divide by Zero floating-point exception trap enable. -->
<field name="DZE" start="9" end="9"/>
<!-- Overflow floating-point exception trap enable. -->
<field name="OFE" start="10" end="10"/>
<!-- Underflow floating-point exception trap enable. -->
<field name="UFE" start="11" end="11"/>
<!-- Inexact floating-point exception trap enable. -->
<field name="IXE" start="12" end="12"/>
<!-- Input Denormal floating-point exception trap enable. -->
<field name="IDE" start="15" end="15"/>
<!-- Flush-to-zero mode control bit on half-precision data-processing
instructions. -->
<field name="FZ16" start="19" end="19"/>
<!-- Rounding Mode control field. -->
<field name="RMode" start="22" end="23"/>
<!-- Flush-to-zero mode control bit. -->
<field name="FZ" start="24" end="24"/>
<!-- Default NaN mode control bit. -->
<field name="DN" start="25" end="25"/>
<!-- Alternative half-precision control bit. -->
<field name="AHP" start="26" end="26"/>
</flags>
<reg name="fpcr" bitsize="32" type="fpcr_flags"/>
</feature>

View File

@ -0,0 +1,27 @@
<?xml version="1.0"?>
<!-- Copyright (C) 2008 Free Software Foundation, Inc.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. -->
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
<feature name="org.gnu.gdb.arm.core">
<reg name="r0" bitsize="32"/>
<reg name="r1" bitsize="32"/>
<reg name="r2" bitsize="32"/>
<reg name="r3" bitsize="32"/>
<reg name="r4" bitsize="32"/>
<reg name="r5" bitsize="32"/>
<reg name="r6" bitsize="32"/>
<reg name="r7" bitsize="32"/>
<reg name="r8" bitsize="32"/>
<reg name="r9" bitsize="32"/>
<reg name="r10" bitsize="32"/>
<reg name="r11" bitsize="32"/>
<reg name="r12" bitsize="32"/>
<reg name="sp" bitsize="32" type="data_ptr"/>
<reg name="lr" bitsize="32"/>
<reg name="pc" bitsize="32" type="code_ptr"/>
<reg name="cpsr" bitsize="32" />
</feature>

View File

@ -0,0 +1,86 @@
<?xml version="1.0"?>
<!-- Copyright (C) 2008 Free Software Foundation, Inc.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. -->
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
<feature name="org.gnu.gdb.arm.vfp">
<vector id="neon_uint8x8" type="uint8" count="8"/>
<vector id="neon_uint16x4" type="uint16" count="4"/>
<vector id="neon_uint32x2" type="uint32" count="2"/>
<vector id="neon_float32x2" type="ieee_single" count="2"/>
<union id="neon_d">
<field name="u8" type="neon_uint8x8"/>
<field name="u16" type="neon_uint16x4"/>
<field name="u32" type="neon_uint32x2"/>
<field name="u64" type="uint64"/>
<field name="f32" type="neon_float32x2"/>
<field name="f64" type="ieee_double"/>
</union>
<vector id="neon_uint8x16" type="uint8" count="16"/>
<vector id="neon_uint16x8" type="uint16" count="8"/>
<vector id="neon_uint32x4" type="uint32" count="4"/>
<vector id="neon_uint64x2" type="uint64" count="2"/>
<vector id="neon_float32x4" type="ieee_single" count="4"/>
<vector id="neon_float64x2" type="ieee_double" count="2"/>
<union id="neon_q">
<field name="u8" type="neon_uint8x16"/>
<field name="u16" type="neon_uint16x8"/>
<field name="u32" type="neon_uint32x4"/>
<field name="u64" type="neon_uint64x2"/>
<field name="f32" type="neon_float32x4"/>
<field name="f64" type="neon_float64x2"/>
</union>
<reg name="d0" bitsize="64" type="neon_d"/>
<reg name="d1" bitsize="64" type="neon_d"/>
<reg name="d2" bitsize="64" type="neon_d"/>
<reg name="d3" bitsize="64" type="neon_d"/>
<reg name="d4" bitsize="64" type="neon_d"/>
<reg name="d5" bitsize="64" type="neon_d"/>
<reg name="d6" bitsize="64" type="neon_d"/>
<reg name="d7" bitsize="64" type="neon_d"/>
<reg name="d8" bitsize="64" type="neon_d"/>
<reg name="d9" bitsize="64" type="neon_d"/>
<reg name="d10" bitsize="64" type="neon_d"/>
<reg name="d11" bitsize="64" type="neon_d"/>
<reg name="d12" bitsize="64" type="neon_d"/>
<reg name="d13" bitsize="64" type="neon_d"/>
<reg name="d14" bitsize="64" type="neon_d"/>
<reg name="d15" bitsize="64" type="neon_d"/>
<reg name="d16" bitsize="64" type="neon_d"/>
<reg name="d17" bitsize="64" type="neon_d"/>
<reg name="d18" bitsize="64" type="neon_d"/>
<reg name="d19" bitsize="64" type="neon_d"/>
<reg name="d20" bitsize="64" type="neon_d"/>
<reg name="d21" bitsize="64" type="neon_d"/>
<reg name="d22" bitsize="64" type="neon_d"/>
<reg name="d23" bitsize="64" type="neon_d"/>
<reg name="d24" bitsize="64" type="neon_d"/>
<reg name="d25" bitsize="64" type="neon_d"/>
<reg name="d26" bitsize="64" type="neon_d"/>
<reg name="d27" bitsize="64" type="neon_d"/>
<reg name="d28" bitsize="64" type="neon_d"/>
<reg name="d29" bitsize="64" type="neon_d"/>
<reg name="d30" bitsize="64" type="neon_d"/>
<reg name="d31" bitsize="64" type="neon_d"/>
<reg name="q0" bitsize="128" type="neon_q"/>
<reg name="q1" bitsize="128" type="neon_q"/>
<reg name="q2" bitsize="128" type="neon_q"/>
<reg name="q3" bitsize="128" type="neon_q"/>
<reg name="q4" bitsize="128" type="neon_q"/>
<reg name="q5" bitsize="128" type="neon_q"/>
<reg name="q6" bitsize="128" type="neon_q"/>
<reg name="q7" bitsize="128" type="neon_q"/>
<reg name="q8" bitsize="128" type="neon_q"/>
<reg name="q9" bitsize="128" type="neon_q"/>
<reg name="q10" bitsize="128" type="neon_q"/>
<reg name="q11" bitsize="128" type="neon_q"/>
<reg name="q12" bitsize="128" type="neon_q"/>
<reg name="q13" bitsize="128" type="neon_q"/>
<reg name="q14" bitsize="128" type="neon_q"/>
<reg name="q15" bitsize="128" type="neon_q"/>
<reg name="fpscr" bitsize="32" type="int" group="float"/>
</feature>

View File

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<!-- Copyright (C) 2009-2013 Free Software Foundation, Inc.
Contributed by ARM Ltd.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. -->
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target>
<architecture>arm</architecture>
<xi:include href="arm-core.xml"/>
<xi:include href="arm-neon.xml"/>
</target>

View File

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<!-- Copyright (C) 2009-2013 Free Software Foundation, Inc.
Contributed by ARM Ltd.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. -->
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target>
<architecture>aarch64</architecture>
<xi:include href="aarch64-core.xml"/>
<xi:include href="aarch64-fpu.xml"/>
</target>

View File

@ -0,0 +1,19 @@
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Memory;
namespace Ryujinx.HLE.Debugger
{
internal interface IDebuggableProcess
{
void DebugStop();
void DebugContinue();
bool DebugStep(KThread thread);
KThread GetThread(ulong threadUid);
DebugState GetDebugState();
ulong[] GetThreadUids();
public void DebugInterruptHandler(IExecutionContext ctx);
IVirtualMemoryManager CpuMemory { get; }
void InvalidateCacheRegion(ulong address, ulong size);
}
}

View File

@ -0,0 +1,6 @@
namespace Ryujinx.HLE.Debugger
{
struct BreakInMessage : IMessage
{
}
}

View File

@ -0,0 +1,12 @@
namespace Ryujinx.HLE.Debugger
{
struct CommandMessage : IMessage
{
public string Command;
public CommandMessage(string cmd)
{
Command = cmd;
}
}
}

View File

@ -0,0 +1,6 @@
namespace Ryujinx.HLE.Debugger
{
interface IMessage
{
}
}

View File

@ -0,0 +1,6 @@
namespace Ryujinx.HLE.Debugger
{
struct KillMessage : IMessage
{
}
}

View File

@ -0,0 +1,6 @@
namespace Ryujinx.HLE.Debugger
{
struct SendNackMessage : IMessage
{
}
}

View File

@ -0,0 +1,18 @@
using IExecutionContext = Ryujinx.Cpu.IExecutionContext;
namespace Ryujinx.HLE.Debugger
{
public class ThreadBreakMessage : IMessage
{
public IExecutionContext Context { get; }
public ulong Address { get; }
public int Opcode { get; }
public ThreadBreakMessage(IExecutionContext context, ulong address, int opcode)
{
Context = context;
Address = address;
Opcode = opcode;
}
}
}

View File

@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.HLE.Debugger
{
class RegisterInformation
{
public static readonly Dictionary<string, string> Features = new()
{
{ "target64.xml", GetEmbeddedResourceContent("target64.xml") },
{ "target32.xml", GetEmbeddedResourceContent("target32.xml") },
{ "aarch64-core.xml", GetEmbeddedResourceContent("aarch64-core.xml") },
{ "aarch64-fpu.xml", GetEmbeddedResourceContent("aarch64-fpu.xml") },
{ "arm-core.xml", GetEmbeddedResourceContent("arm-core.xml") },
{ "arm-neon.xml", GetEmbeddedResourceContent("arm-neon.xml") },
};
private static string GetEmbeddedResourceContent(string resourceName)
{
Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.HLE.Debugger.GdbXml." + resourceName);
StreamReader reader = new StreamReader(stream);
string result = reader.ReadToEnd();
reader.Dispose();
stream.Dispose();
return result;
}
}
}

View File

@ -0,0 +1,109 @@
using System.Diagnostics;
using System.Globalization;
namespace Ryujinx.HLE.Debugger
{
class StringStream
{
private readonly string Data;
private int Position;
public StringStream(string s)
{
Data = s;
}
public char ReadChar()
{
return Data[Position++];
}
public string ReadUntil(char needle)
{
int needlePos = Data.IndexOf(needle, Position);
if (needlePos == -1)
{
needlePos = Data.Length;
}
string result = Data.Substring(Position, needlePos - Position);
Position = needlePos + 1;
return result;
}
public string ReadLength(int len)
{
string result = Data.Substring(Position, len);
Position += len;
return result;
}
public string ReadRemaining()
{
string result = Data.Substring(Position);
Position = Data.Length;
return result;
}
public ulong ReadRemainingAsHex()
{
return ulong.Parse(ReadRemaining(), NumberStyles.HexNumber);
}
public ulong ReadUntilAsHex(char needle)
{
return ulong.Parse(ReadUntil(needle), NumberStyles.HexNumber);
}
public ulong ReadLengthAsHex(int len)
{
return ulong.Parse(ReadLength(len), NumberStyles.HexNumber);
}
public ulong ReadLengthAsLEHex(int len)
{
Debug.Assert(len % 2 == 0);
ulong result = 0;
int pos = 0;
while (pos < len)
{
result += ReadLengthAsHex(2) << (4 * pos);
pos += 2;
}
return result;
}
public ulong? ReadRemainingAsThreadUid()
{
string s = ReadRemaining();
return s == "-1" ? null : ulong.Parse(s, NumberStyles.HexNumber);
}
public bool ConsumePrefix(string prefix)
{
if (Data.Substring(Position).StartsWith(prefix))
{
Position += prefix.Length;
return true;
}
return false;
}
public bool ConsumeRemaining(string match)
{
if (Data.Substring(Position) == match)
{
Position += match.Length;
return true;
}
return false;
}
public bool IsEmpty()
{
return Position >= Data.Length;
}
}
}

View File

@ -169,6 +169,21 @@ namespace Ryujinx.HLE
/// </summary> /// </summary>
public Action RefreshInputConfig { internal get; set; } public Action RefreshInputConfig { internal get; set; }
/// <summary>
/// Enables gdbstub to allow for debugging of the guest .
/// </summary>
public bool EnableGdbStub { get; internal set; }
/// <summary>
/// A TCP port to use to expose a gdbstub for a debugger to connect to.
/// </summary>
public ushort GdbStubPort { get; internal set; }
/// <summary>
/// Suspend execution when starting an application
/// </summary>
public bool DebuggerSuspendOnStart { get; internal set; }
public HLEConfiguration(VirtualFileSystem virtualFileSystem, public HLEConfiguration(VirtualFileSystem virtualFileSystem,
LibHacHorizonManager libHacHorizonManager, LibHacHorizonManager libHacHorizonManager,
ContentManager contentManager, ContentManager contentManager,
@ -194,7 +209,10 @@ namespace Ryujinx.HLE
float audioVolume, float audioVolume,
bool useHypervisor, bool useHypervisor,
string multiplayerLanInterfaceId, string multiplayerLanInterfaceId,
MultiplayerMode multiplayerMode) MultiplayerMode multiplayerMode,
bool enableGdbStub,
ushort gdbStubPort,
bool debuggerSuspendOnStart)
{ {
VirtualFileSystem = virtualFileSystem; VirtualFileSystem = virtualFileSystem;
LibHacHorizonManager = libHacHorizonManager; LibHacHorizonManager = libHacHorizonManager;
@ -222,6 +240,9 @@ namespace Ryujinx.HLE
UseHypervisor = useHypervisor; UseHypervisor = useHypervisor;
MultiplayerLanInterfaceId = multiplayerLanInterfaceId; MultiplayerLanInterfaceId = multiplayerLanInterfaceId;
MultiplayerMode = multiplayerMode; MultiplayerMode = multiplayerMode;
EnableGdbStub = enableGdbStub;
GdbStubPort = gdbStubPort;
DebuggerSuspendOnStart = debuggerSuspendOnStart;
} }
} }
} }

View File

@ -5,6 +5,7 @@ using LibHac.Fs.Shim;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.HLE.Debugger;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Memory;
@ -473,5 +474,13 @@ namespace Ryujinx.HLE.HOS
} }
IsPaused = pause; IsPaused = pause;
} }
internal IDebuggableProcess DebugGetApplicationProcess()
{
lock (KernelContext.Processes)
{
return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.DebugInterface;
}
}
} }
} }

View File

@ -28,8 +28,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
MemoryArrange.MemoryArrange4GiBSystemDev or MemoryArrange.MemoryArrange4GiBSystemDev or
MemoryArrange.MemoryArrange6GiBAppletDev => 3285 * MiB, MemoryArrange.MemoryArrange6GiBAppletDev => 3285 * MiB,
MemoryArrange.MemoryArrange4GiBAppletDev => 2048 * MiB, MemoryArrange.MemoryArrange4GiBAppletDev => 2048 * MiB,
MemoryArrange.MemoryArrange6GiB or MemoryArrange.MemoryArrange6GiB => 4916 * MiB,
MemoryArrange.MemoryArrange8GiB => 4916 * MiB, MemoryArrange.MemoryArrange8GiB => 6964 * MiB,
_ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."), _ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."),
}; };
} }
@ -42,8 +42,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
MemoryArrange.MemoryArrange4GiBAppletDev => 1554 * MiB, MemoryArrange.MemoryArrange4GiBAppletDev => 1554 * MiB,
MemoryArrange.MemoryArrange4GiBSystemDev => 448 * MiB, MemoryArrange.MemoryArrange4GiBSystemDev => 448 * MiB,
MemoryArrange.MemoryArrange6GiB => 562 * MiB, MemoryArrange.MemoryArrange6GiB => 562 * MiB,
MemoryArrange.MemoryArrange6GiBAppletDev or MemoryArrange.MemoryArrange6GiBAppletDev => 2193 * MiB,
MemoryArrange.MemoryArrange8GiB => 2193 * MiB, MemoryArrange.MemoryArrange8GiB => 562 * MiB,
_ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."), _ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."),
}; };
} }

View File

@ -1,6 +1,7 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.HLE.Debugger;
using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Memory;
@ -11,6 +12,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using ExceptionCallback = Ryujinx.Cpu.ExceptionCallback;
using ExceptionCallbackNoArgs = Ryujinx.Cpu.ExceptionCallbackNoArgs;
namespace Ryujinx.HLE.HOS.Kernel.Process namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
@ -89,6 +92,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public IVirtualMemoryManager CpuMemory => Context.AddressSpace; public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
public HleProcessDebugger Debugger { get; private set; } public HleProcessDebugger Debugger { get; private set; }
public IDebuggableProcess DebugInterface { get; private set; }
protected int debugState = (int)DebugState.Running;
public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context) public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
{ {
@ -110,6 +115,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
_threads = new LinkedList<KThread>(); _threads = new LinkedList<KThread>();
Debugger = new HleProcessDebugger(this); Debugger = new HleProcessDebugger(this);
DebugInterface = new DebuggerInterface(this);
} }
public Result InitializeKip( public Result InitializeKip(
@ -680,6 +686,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
SetState(newState); SetState(newState);
if (KernelContext.Device.Configuration.DebuggerSuspendOnStart && IsApplication)
{
mainThread.Suspend(ThreadSchedState.ThreadPauseFlag);
debugState = (int)DebugState.Stopped;
}
result = mainThread.Start(); result = mainThread.Start();
if (result != Result.Success) if (result != Result.Success)
@ -728,9 +740,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public IExecutionContext CreateExecutionContext() public IExecutionContext CreateExecutionContext()
{ {
ExceptionCallback breakCallback = null;
ExceptionCallbackNoArgs stepCallback = null;
if (KernelContext.Device.Configuration.EnableGdbStub)
{
breakCallback = KernelContext.Device.Debugger.BreakHandler;
stepCallback = KernelContext.Device.Debugger.StepHandler;
}
return Context?.CreateExecutionContext(new ExceptionCallbacks( return Context?.CreateExecutionContext(new ExceptionCallbacks(
InterruptHandler, InterruptHandler,
null, breakCallback,
stepCallback,
KernelContext.SyscallHandler.SvcCall, KernelContext.SyscallHandler.SvcCall,
UndefinedInstructionHandler)); UndefinedInstructionHandler));
} }
@ -1175,5 +1197,154 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
return Capabilities.IsSvcPermitted(svcId); return Capabilities.IsSvcPermitted(svcId);
} }
private class DebuggerInterface : IDebuggableProcess
{
private Barrier StepBarrier;
private readonly KProcess _parent;
private readonly KernelContext _kernelContext;
private KThread steppingThread;
public DebuggerInterface(KProcess p)
{
_parent = p;
_kernelContext = p.KernelContext;
StepBarrier = new(2);
}
public void DebugStop()
{
if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Stopping,
(int)DebugState.Running) != (int)DebugState.Running)
{
return;
}
_kernelContext.CriticalSection.Enter();
lock (_parent._threadingLock)
{
foreach (KThread thread in _parent._threads)
{
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
thread.Context.RequestInterrupt();
thread.DebugHalt.WaitOne();
}
}
_parent.debugState = (int)DebugState.Stopped;
_kernelContext.CriticalSection.Leave();
}
public void DebugContinue()
{
if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Running,
(int)DebugState.Stopped) != (int)DebugState.Stopped)
{
return;
}
_kernelContext.CriticalSection.Enter();
lock (_parent._threadingLock)
{
foreach (KThread thread in _parent._threads)
{
thread.Resume(ThreadSchedState.ThreadPauseFlag);
}
}
_kernelContext.CriticalSection.Leave();
}
public bool DebugStep(KThread target)
{
if (_parent.debugState != (int)DebugState.Stopped)
{
return false;
}
_kernelContext.CriticalSection.Enter();
steppingThread = target;
bool waiting = target.MutexOwner != null || target.WaitingSync || target.WaitingInArbitration;
target.Context.RequestDebugStep();
if (waiting)
{
lock (_parent._threadingLock)
{
foreach (KThread thread in _parent._threads)
{
thread.Resume(ThreadSchedState.ThreadPauseFlag);
}
}
}
else
{
target.Resume(ThreadSchedState.ThreadPauseFlag);
}
_kernelContext.CriticalSection.Leave();
StepBarrier.SignalAndWait();
_kernelContext.CriticalSection.Enter();
steppingThread = null;
if (waiting)
{
lock (_parent._threadingLock)
{
foreach (KThread thread in _parent._threads)
{
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
}
}
}
else
{
target.Suspend(ThreadSchedState.ThreadPauseFlag);
}
_kernelContext.CriticalSection.Leave();
StepBarrier.SignalAndWait();
return true;
}
public DebugState GetDebugState()
{
return (DebugState)_parent.debugState;
}
public ulong[] GetThreadUids()
{
lock (_parent._threadingLock)
{
var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
return threads.Select(x => x.ThreadUid).ToArray();
}
}
public KThread GetThread(ulong threadUid)
{
lock (_parent._threadingLock)
{
var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
return threads.FirstOrDefault(x => x.ThreadUid == threadUid);
}
}
public void DebugInterruptHandler(IExecutionContext ctx)
{
_kernelContext.CriticalSection.Enter();
bool stepping = steppingThread != null;
_kernelContext.CriticalSection.Leave();
if (stepping)
{
StepBarrier.SignalAndWait();
StepBarrier.SignalAndWait();
}
_parent.InterruptHandler(ctx);
}
public IVirtualMemoryManager CpuMemory { get { return _parent.CpuMemory; } }
public void InvalidateCacheRegion(ulong address, ulong size)
{
_parent.Context.InvalidateCacheRegion(address, size);
}
}
} }
} }

View File

@ -1,5 +1,6 @@
using ARMeilleure.State; using ARMeilleure.State;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Process namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
@ -17,10 +18,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public bool IsAarch32 { get => false; set { } } public bool IsAarch32 { get => false; set { } }
public ulong ThreadUid { get; set; }
public bool Running { get; private set; } = true; public bool Running { get; private set; } = true;
private readonly ulong[] _x = new ulong[32]; private readonly ulong[] _x = new ulong[32];
public ulong DebugPc { get; set; }
public ulong GetX(int index) => _x[index]; public ulong GetX(int index) => _x[index];
public void SetX(int index, ulong value) => _x[index] = value; public void SetX(int index, ulong value) => _x[index] = value;
@ -31,6 +36,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
} }
public void RequestDebugStep()
{
}
public void StopRunning() public void StopRunning()
{ {
Running = false; Running = false;

View File

@ -296,6 +296,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.SchedulerWaitEvent.Reset(); currentThread.SchedulerWaitEvent.Reset();
currentThread.ThreadContext.Unlock(); currentThread.ThreadContext.Unlock();
currentThread.DebugHalt.Set();
// Wake all the threads that might be waiting until this thread context is unlocked. // Wake all the threads that might be waiting until this thread context is unlocked.
for (int core = 0; core < CpuCoresCount; core++) for (int core = 0; core < CpuCoresCount; core++)

View File

@ -1,5 +1,6 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.HLE.Debugger;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.SupervisorCall; using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
@ -114,6 +115,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private readonly object _activityOperationLock = new(); private readonly object _activityOperationLock = new();
internal readonly ManualResetEvent DebugHalt = new(false);
public KThread(KernelContext context) : base(context) public KThread(KernelContext context) : base(context)
{ {
WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects]; WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects];
@ -202,8 +205,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
} }
Context.TpidrroEl0 = (long)_tlsAddress; Context.TpidrroEl0 = (long)_tlsAddress;
Context.DebugPc = _entrypoint;
ThreadUid = KernelContext.NewThreadUid(); ThreadUid = KernelContext.NewThreadUid();
Context.ThreadUid = ThreadUid;
HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}"; HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}";
@ -307,7 +312,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{ {
KernelContext.CriticalSection.Enter(); KernelContext.CriticalSection.Enter();
if (Owner != null && Owner.PinnedThreads[KernelStatic.GetCurrentThread().CurrentCore] == this) KThread currentThread = KernelStatic.GetCurrentThread();
if (Owner != null && currentThread != null && Owner.PinnedThreads[currentThread.CurrentCore] == this)
{ {
Owner.UnpinThread(this); Owner.UnpinThread(this);
} }
@ -362,7 +369,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{ {
ThreadSchedState state = PrepareForTermination(); ThreadSchedState state = PrepareForTermination();
if (state != ThreadSchedState.TerminationPending) if (KernelStatic.GetCurrentThread() == this && state != ThreadSchedState.TerminationPending)
{ {
KernelContext.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _); KernelContext.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _);
} }
@ -1248,6 +1255,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private void ThreadStart() private void ThreadStart()
{ {
_schedulerWaitEvent.WaitOne(); _schedulerWaitEvent.WaitOne();
DebugHalt.Reset();
KernelStatic.SetKernelContext(KernelContext, this); KernelStatic.SetKernelContext(KernelContext, this);
if (_customThreadStart != null) if (_customThreadStart != null)

View File

@ -14,7 +14,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{ {
private readonly MemoryOwner<byte> _rawDataOwner; private readonly MemoryOwner<byte> _rawDataOwner;
private Span<byte> Raw => _rawDataOwner.Memory.Span; private Span<byte> Raw => _rawDataOwner.Span;
private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(Raw)[0]; private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(Raw)[0];

View File

@ -412,9 +412,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
Format format = ConvertColorFormat(item.GraphicBuffer.Object.Buffer.Surfaces[0].ColorFormat); Format format = ConvertColorFormat(item.GraphicBuffer.Object.Buffer.Surfaces[0].ColorFormat);
int bytesPerPixel = byte bytesPerPixel =
format == Format.B5G6R5Unorm || format == Format.B5G6R5Unorm ||
format == Format.R4G4B4A4Unorm ? 2 : 4; format == Format.R4G4B4A4Unorm ? (byte)2 : (byte)4;
int gobBlocksInY = 1 << item.GraphicBuffer.Object.Buffer.Surfaces[0].BlockHeightLog2; int gobBlocksInY = 1 << item.GraphicBuffer.Object.Buffer.Surfaces[0].BlockHeightLog2;

Some files were not shown because too many files have changed in this diff Show More