mirror of https://github.com/Ryujinx/Ryujinx.git
Compare commits
73 Commits
97be6afc29
...
3f3a815eb9
Author | SHA1 | Date |
---|---|---|
Asaf | 3f3a815eb9 | |
jhorv | 73f985d27c | |
gdkchan | ef81658fbd | |
gdkchan | 062ef43eb4 | |
gdkchan | eb8132b627 | |
TSRBerry | ccf96bf5e6 | |
ZenoArrows | f39e89ece7 | |
gdkchan | cf77c011e4 | |
svc64 | 20e7b0ad67 | |
Domenico V | 6225f08397 | |
Domenico V | 4b2174da95 | |
Domenico V | 1b1b584bc9 | |
Domenico V | 6bcc2cd454 | |
Domenico V | 540ec7d675 | |
svc64 | bba192b324 | |
svc64 | f9156756c9 | |
svc64 | 60e2a9dd00 | |
svc64 | 8a1c48e035 | |
svc64 | 1bf244173d | |
svc64 | eea36e8a37 | |
svc64 | 20e5eeb39e | |
svc64 | bca3939a65 | |
svc64 | 6b74bcec7c | |
svc64 | cc32ac251b | |
svc64 | e401097e1e | |
svc64 | 0396997c17 | |
svc64 | 5f5cb73baa | |
svc64 | 652423cfeb | |
svc64 | 64206c7c5e | |
svc64 | 13c53657cc | |
svc64 | 055ac70eaa | |
svc64 | 38c15faacf | |
svc64 | 1684a9d7a2 | |
svc64 | e3b8060417 | |
svc64 | 0f50273d4f | |
svc64 | f934d23de4 | |
svc64 | 144aa2f5b1 | |
svc64 | edbd4bfc29 | |
svc64 | 5d42332d75 | |
svc64 | de4ec65bd7 | |
svc64 | 5e65fd8808 | |
svc64 | 6ecc829516 | |
svc64 | bc0ba93e92 | |
svc64 | 0c57663ea3 | |
svc64 | 65d7a16a87 | |
svc64 | 81c399ec3e | |
svc64 | 40584e0e45 | |
svc64 | ac438d6572 | |
svc64 | 841aa89581 | |
svc64 | fc361f82a7 | |
svc64 | 917a292256 | |
merry | 5583a60ace | |
merry | 8bd4417b24 | |
svc64 | 7e4944cc88 | |
merry | 5a34d80f98 | |
merry | 1cef40131a | |
merry | a9538a54ff | |
merry | 3bdf9d9805 | |
merry | 09a63ba2b5 | |
svc64 | 9b9137bf0a | |
svc64 | a366890cb5 | |
svc64 | a6cbb89996 | |
svc64 | d0fbcced57 | |
merry | 54bf7507d7 | |
merry | 1b9753d42a | |
merry | a1de4f1b5b | |
merry | 1bb8f6381c | |
merry | deccf05e60 | |
merry | 6edc00ec9c | |
merry | 2a17f1314d | |
merry | 20bdea45fa | |
merry | 569d01823e | |
merry | 39a3ba8329 |
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ namespace Ryujinx.Common.Logging
|
||||||
Cpu,
|
Cpu,
|
||||||
Emulation,
|
Emulation,
|
||||||
FFmpeg,
|
FFmpeg,
|
||||||
|
GdbStub,
|
||||||
Font,
|
Font,
|
||||||
Gpu,
|
Gpu,
|
||||||
Hid,
|
Hid,
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace Ryujinx.Cpu.AppleHv.Arm
|
||||||
|
{
|
||||||
|
enum ExceptionLevel : uint
|
||||||
|
{
|
||||||
|
PstateMask = 0xfffffff0,
|
||||||
|
EL1h = 0b0101,
|
||||||
|
El1t = 0b0100,
|
||||||
|
EL0 = 0b0000,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -27,7 +27,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
CounterEventFlush,
|
CounterEventFlush,
|
||||||
|
|
||||||
ImageArrayDispose,
|
ImageArrayDispose,
|
||||||
ImageArraySetFormats,
|
|
||||||
ImageArraySetImages,
|
ImageArraySetImages,
|
||||||
|
|
||||||
ProgramDispose,
|
ProgramDispose,
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,6 @@ namespace Ryujinx.Graphics.GAL
|
||||||
Bilinear,
|
Bilinear,
|
||||||
Nearest,
|
Nearest,
|
||||||
Fsr,
|
Fsr,
|
||||||
|
Area,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
Binary file not shown.
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
public enum DebugState
|
||||||
|
{
|
||||||
|
Running,
|
||||||
|
Stopping,
|
||||||
|
Stopped,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct BreakInMessage : IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct CommandMessage : IMessage
|
||||||
|
{
|
||||||
|
public string Command;
|
||||||
|
|
||||||
|
public CommandMessage(string cmd)
|
||||||
|
{
|
||||||
|
Command = cmd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
interface IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct KillMessage : IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Ryujinx.HLE.Debugger
|
||||||
|
{
|
||||||
|
struct SendNackMessage : IMessage
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}\"."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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++)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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];
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,12 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Remove="Debugger\GdbXml\aarch64-core.xml" />
|
||||||
|
<None Remove="Debugger\GdbXml\aarch64-fpu.xml" />
|
||||||
|
<None Remove="Debugger\GdbXml\arm-core.xml" />
|
||||||
|
<None Remove="Debugger\GdbXml\arm-neon.xml" />
|
||||||
|
<None Remove="Debugger\GdbXml\target64.xml" />
|
||||||
|
<None Remove="Debugger\GdbXml\target32.xml" />
|
||||||
<None Remove="Homebrew.npdm" />
|
<None Remove="Homebrew.npdm" />
|
||||||
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
||||||
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
||||||
|
@ -41,6 +47,12 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Debugger\GdbXml\aarch64-core.xml" />
|
||||||
|
<EmbeddedResource Include="Debugger\GdbXml\aarch64-fpu.xml" />
|
||||||
|
<EmbeddedResource Include="Debugger\GdbXml\arm-core.xml" />
|
||||||
|
<EmbeddedResource Include="Debugger\GdbXml\arm-neon.xml" />
|
||||||
|
<EmbeddedResource Include="Debugger\GdbXml\target64.xml" />
|
||||||
|
<EmbeddedResource Include="Debugger\GdbXml\target32.xml" />
|
||||||
<EmbeddedResource Include="Homebrew.npdm" />
|
<EmbeddedResource Include="Homebrew.npdm" />
|
||||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
||||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue