Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
232b1012b0 | |||
e747f5cd83 | |||
8aff17a93c | |||
f2a41b7a1c | |||
c881cd2d14 | |||
68f9091870 | |||
99ffc061d3 | |||
d987cacfb7 | |||
851f56b08a | |||
b1bd6a50b5 | |||
70895bdb04 | |||
830cbf91bb | |||
9a9349f0f4 | |||
46cc7b55f0 | |||
dd8f97ab9e | |||
633c5ec330 | |||
a3e7bb8eb4 | |||
2073ba2919 | |||
d03124a992 | |||
59490d54b5 | |||
e546e5933f | |||
0c87bf9ea4 | |||
9827dc35e1 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
- os: windows-latest
|
||||
OS_NAME: Windows x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: win-x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: win10-x64
|
||||
RELEASE_ZIP_OS_NAME: win_x64
|
||||
|
||||
fail-fast: false
|
||||
|
7
.github/workflows/nightly_pr_comment.yml
vendored
7
.github/workflows/nightly_pr_comment.yml
vendored
@ -36,19 +36,24 @@ jobs:
|
||||
return core.error(`No artifacts found`);
|
||||
}
|
||||
let body = `Download the artifacts for this pull request:\n`;
|
||||
let hidden_avalonia_artifacts = `\n\n <details><summary>Experimental GUI (Avalonia)</summary>\n`;
|
||||
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
|
||||
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
|
||||
for (const art of artifacts) {
|
||||
if(art.name.includes('Debug')) {
|
||||
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else if(art.name.includes('headless-sdl2')) {
|
||||
} else if(art.name.includes('ava-ryujinx')) {
|
||||
hidden_avalonia_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else if(art.name.includes('sdl2-ryujinx-headless')) {
|
||||
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else {
|
||||
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
}
|
||||
}
|
||||
hidden_avalonia_artifacts += `\n</details>`;
|
||||
hidden_headless_artifacts += `\n</details>`;
|
||||
hidden_debug_artifacts += `\n</details>`;
|
||||
body += hidden_avalonia_artifacts;
|
||||
body += hidden_headless_artifacts;
|
||||
body += hidden_debug_artifacts;
|
||||
|
||||
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@ -51,9 +51,9 @@ jobs:
|
||||
run: "mkdir release_output"
|
||||
- name: Publish Windows
|
||||
run: |
|
||||
dotnet publish -c Release -r win-x64 -o ./publish_windows/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx --self-contained
|
||||
dotnet publish -c Release -r win-x64 -o ./publish_windows_sdl2_headless/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained
|
||||
dotnet publish -c Release -r win-x64 -o ./publish_windows_ava/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Ava --self-contained
|
||||
dotnet publish -c Release -r win10-x64 -o ./publish_windows/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx --self-contained
|
||||
dotnet publish -c Release -r win10-x64 -o ./publish_windows_sdl2_headless/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained
|
||||
dotnet publish -c Release -r win10-x64 -o ./publish_windows_ava/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Ava --self-contained
|
||||
- name: Packing Windows builds
|
||||
run: |
|
||||
pushd publish_windows
|
||||
|
@ -33,13 +33,13 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
switch (GetPackedId(op))
|
||||
{
|
||||
case 0b11_011_0000_0000_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCtrEl0)); break;
|
||||
case 0b11_011_0000_0000_111: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0)); break;
|
||||
case 0b11_011_0100_0010_000: EmitGetNzcv(context); return;
|
||||
case 0b11_011_0100_0100_000: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpcr)); break;
|
||||
case 0b11_011_0100_0100_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpsr)); break;
|
||||
case 0b11_011_1101_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)); break;
|
||||
case 0b11_011_1101_0000_011: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr)); break;
|
||||
case 0b11_011_0000_0000_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCtrEl0)); break;
|
||||
case 0b11_011_0000_0000_111: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0)); break;
|
||||
case 0b11_011_0100_0010_000: EmitGetNzcv(context); return;
|
||||
case 0b11_011_0100_0100_000: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpcr)); break;
|
||||
case 0b11_011_0100_0100_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpsr)); break;
|
||||
case 0b11_011_1101_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)); break;
|
||||
case 0b11_011_1101_0000_011: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)); break;
|
||||
case 0b11_011_1110_0000_000: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0)); break;
|
||||
case 0b11_011_1110_0000_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); break;
|
||||
case 0b11_011_1110_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0)); break;
|
||||
|
@ -107,14 +107,14 @@ namespace ARMeilleure.Instructions
|
||||
return (uint)GetContext().TpidrEl0;
|
||||
}
|
||||
|
||||
public static ulong GetTpidr()
|
||||
public static ulong GetTpidrroEl0()
|
||||
{
|
||||
return (ulong)GetContext().Tpidr;
|
||||
return (ulong)GetContext().TpidrroEl0;
|
||||
}
|
||||
|
||||
public static uint GetTpidr32()
|
||||
{
|
||||
return (uint)GetContext().Tpidr;
|
||||
return (uint)GetContext().TpidrroEl0;
|
||||
}
|
||||
|
||||
public static ulong GetCntfrqEl0()
|
||||
|
@ -14,10 +14,11 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
public byte Kind;
|
||||
public byte Type;
|
||||
public byte SymbolType;
|
||||
public byte Padding; // Unused space.
|
||||
public ushort AssignmentsCount;
|
||||
public ushort AssignmentsCapacity;
|
||||
public ushort UsesCount;
|
||||
public ushort UsesCapacity;
|
||||
public uint UsesCount;
|
||||
public uint UsesCapacity;
|
||||
public Operation* Assignments;
|
||||
public Operation* Uses;
|
||||
public ulong Value;
|
||||
@ -84,11 +85,11 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
Debug.Assert(Kind != OperandKind.Memory);
|
||||
|
||||
return new ReadOnlySpan<Operation>(_data->Uses, _data->UsesCount);
|
||||
return new ReadOnlySpan<Operation>(_data->Uses, (int)_data->UsesCount);
|
||||
}
|
||||
}
|
||||
|
||||
public int UsesCount => _data->UsesCount;
|
||||
public int UsesCount => (int)_data->UsesCount;
|
||||
public int AssignmentsCount => _data->AssignmentsCount;
|
||||
|
||||
public bool Relocatable => Symbol.Type != SymbolType.None;
|
||||
@ -178,7 +179,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
Add(operation, ref addr._data->Assignments, ref addr._data->AssignmentsCount, ref addr._data->AssignmentsCapacity);
|
||||
}
|
||||
|
||||
|
||||
if (index != default)
|
||||
{
|
||||
Add(operation, ref index._data->Assignments, ref index._data->AssignmentsCount, ref index._data->AssignmentsCapacity);
|
||||
@ -265,6 +266,13 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
data = Allocators.References.Allocate<T>(initialCapacity);
|
||||
}
|
||||
|
||||
private static void New<T>(ref T* data, ref uint count, ref uint capacity, uint initialCapacity) where T : unmanaged
|
||||
{
|
||||
count = 0;
|
||||
capacity = initialCapacity;
|
||||
data = Allocators.References.Allocate<T>(initialCapacity);
|
||||
}
|
||||
|
||||
private static void Add<T>(T item, ref T* data, ref ushort count, ref ushort capacity) where T : unmanaged
|
||||
{
|
||||
if (count < capacity)
|
||||
@ -294,6 +302,40 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
}
|
||||
}
|
||||
|
||||
private static void Add<T>(T item, ref T* data, ref uint count, ref uint capacity) where T : unmanaged
|
||||
{
|
||||
if (count < capacity)
|
||||
{
|
||||
data[count++] = item;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Could not add item in the fast path, fallback onto the slow path.
|
||||
ExpandAdd(item, ref data, ref count, ref capacity);
|
||||
|
||||
static void ExpandAdd(T item, ref T* data, ref uint count, ref uint capacity)
|
||||
{
|
||||
uint newCount = checked(count + 1);
|
||||
uint newCapacity = (uint)Math.Min(capacity * 2, int.MaxValue);
|
||||
|
||||
if (newCapacity <= capacity)
|
||||
{
|
||||
throw new OverflowException();
|
||||
}
|
||||
|
||||
var oldSpan = new Span<T>(data, (int)count);
|
||||
|
||||
capacity = newCapacity;
|
||||
data = Allocators.References.Allocate<T>(capacity);
|
||||
|
||||
oldSpan.CopyTo(new Span<T>(data, (int)count));
|
||||
|
||||
data[count] = item;
|
||||
count = newCount;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Remove<T>(in T item, ref T* data, ref ushort count) where T : unmanaged
|
||||
{
|
||||
var span = new Span<T>(data, count);
|
||||
@ -314,6 +356,26 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
}
|
||||
}
|
||||
|
||||
private static void Remove<T>(in T item, ref T* data, ref uint count) where T : unmanaged
|
||||
{
|
||||
var span = new Span<T>(data, (int)count);
|
||||
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(span[i], item))
|
||||
{
|
||||
if (i + 1 < count)
|
||||
{
|
||||
span.Slice(i + 1).CopyTo(span.Slice(i));
|
||||
}
|
||||
|
||||
count--;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (Kind == OperandKind.LocalVariable)
|
||||
|
5
ARMeilleure/State/ExceptionCallback.cs
Normal file
5
ARMeilleure/State/ExceptionCallback.cs
Normal file
@ -0,0 +1,5 @@
|
||||
namespace ARMeilleure.State
|
||||
{
|
||||
public delegate void ExceptionCallbackNoArgs(ExecutionContext context);
|
||||
public delegate void ExceptionCallback(ExecutionContext context, ulong address, int id);
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
using ARMeilleure.Memory;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace ARMeilleure.State
|
||||
{
|
||||
@ -14,34 +13,22 @@ namespace ARMeilleure.State
|
||||
|
||||
private bool _interrupted;
|
||||
|
||||
private static Stopwatch _tickCounter;
|
||||
private readonly ICounter _counter;
|
||||
|
||||
private static double _hostTickFreq;
|
||||
public ulong Pc => _nativeContext.GetPc();
|
||||
|
||||
public uint CtrEl0 => 0x8444c004;
|
||||
public uint CtrEl0 => 0x8444c004;
|
||||
public uint DczidEl0 => 0x00000004;
|
||||
|
||||
public ulong CntfrqEl0 { get; set; }
|
||||
public ulong CntpctEl0
|
||||
{
|
||||
get
|
||||
{
|
||||
double ticks = _tickCounter.ElapsedTicks * _hostTickFreq;
|
||||
|
||||
return (ulong)(ticks * CntfrqEl0);
|
||||
}
|
||||
}
|
||||
public ulong CntfrqEl0 => _counter.Frequency;
|
||||
public ulong CntpctEl0 => _counter.Counter;
|
||||
|
||||
// CNTVCT_EL0 = CNTPCT_EL0 - CNTVOFF_EL2
|
||||
// Since EL2 isn't implemented, CNTVOFF_EL2 = 0
|
||||
public ulong CntvctEl0 => CntpctEl0;
|
||||
|
||||
public static TimeSpan ElapsedTime => _tickCounter.Elapsed;
|
||||
public static long ElapsedTicks => _tickCounter.ElapsedTicks;
|
||||
public static double TickFrequency => _hostTickFreq;
|
||||
|
||||
public long TpidrEl0 { get; set; }
|
||||
public long Tpidr { get; set; }
|
||||
public long TpidrroEl0 { get; set; }
|
||||
|
||||
public uint Pstate
|
||||
{
|
||||
@ -78,35 +65,38 @@ namespace ARMeilleure.State
|
||||
private set => _nativeContext.SetRunning(value);
|
||||
}
|
||||
|
||||
public event EventHandler<EventArgs> Interrupt;
|
||||
public event EventHandler<InstExceptionEventArgs> Break;
|
||||
public event EventHandler<InstExceptionEventArgs> SupervisorCall;
|
||||
public event EventHandler<InstUndefinedEventArgs> Undefined;
|
||||
private readonly ExceptionCallbackNoArgs _interruptCallback;
|
||||
private readonly ExceptionCallback _breakCallback;
|
||||
private readonly ExceptionCallback _supervisorCallback;
|
||||
private readonly ExceptionCallback _undefinedCallback;
|
||||
|
||||
static ExecutionContext()
|
||||
{
|
||||
_hostTickFreq = 1.0 / Stopwatch.Frequency;
|
||||
|
||||
_tickCounter = new Stopwatch();
|
||||
_tickCounter.Start();
|
||||
}
|
||||
|
||||
public ExecutionContext(IJitMemoryAllocator allocator)
|
||||
public ExecutionContext(
|
||||
IJitMemoryAllocator allocator,
|
||||
ICounter counter,
|
||||
ExceptionCallbackNoArgs interruptCallback = null,
|
||||
ExceptionCallback breakCallback = null,
|
||||
ExceptionCallback supervisorCallback = null,
|
||||
ExceptionCallback undefinedCallback = null)
|
||||
{
|
||||
_nativeContext = new NativeContext(allocator);
|
||||
_counter = counter;
|
||||
_interruptCallback = interruptCallback;
|
||||
_breakCallback = breakCallback;
|
||||
_supervisorCallback = supervisorCallback;
|
||||
_undefinedCallback = undefinedCallback;
|
||||
|
||||
Running = true;
|
||||
|
||||
_nativeContext.SetCounter(MinCountForCheck);
|
||||
}
|
||||
|
||||
public ulong GetX(int index) => _nativeContext.GetX(index);
|
||||
public void SetX(int index, ulong value) => _nativeContext.SetX(index, value);
|
||||
public ulong GetX(int index) => _nativeContext.GetX(index);
|
||||
public void SetX(int index, ulong value) => _nativeContext.SetX(index, value);
|
||||
|
||||
public V128 GetV(int index) => _nativeContext.GetV(index);
|
||||
public V128 GetV(int index) => _nativeContext.GetV(index);
|
||||
public void SetV(int index, V128 value) => _nativeContext.SetV(index, value);
|
||||
|
||||
public bool GetPstateFlag(PState flag) => _nativeContext.GetPstateFlag(flag);
|
||||
public bool GetPstateFlag(PState flag) => _nativeContext.GetPstateFlag(flag);
|
||||
public void SetPstateFlag(PState flag, bool value) => _nativeContext.SetPstateFlag(flag, value);
|
||||
|
||||
public bool GetFPstateFlag(FPState flag) => _nativeContext.GetFPStateFlag(flag);
|
||||
@ -118,7 +108,7 @@ namespace ARMeilleure.State
|
||||
{
|
||||
_interrupted = false;
|
||||
|
||||
Interrupt?.Invoke(this, EventArgs.Empty);
|
||||
_interruptCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
_nativeContext.SetCounter(MinCountForCheck);
|
||||
@ -131,17 +121,17 @@ namespace ARMeilleure.State
|
||||
|
||||
internal void OnBreak(ulong address, int imm)
|
||||
{
|
||||
Break?.Invoke(this, new InstExceptionEventArgs(address, imm));
|
||||
_breakCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
internal void OnSupervisorCall(ulong address, int imm)
|
||||
{
|
||||
SupervisorCall?.Invoke(this, new InstExceptionEventArgs(address, imm));
|
||||
_supervisorCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
internal void OnUndefined(ulong address, int opCode)
|
||||
{
|
||||
Undefined?.Invoke(this, new InstUndefinedEventArgs(address, opCode));
|
||||
_undefinedCallback?.Invoke(this, address, opCode);
|
||||
}
|
||||
|
||||
public void StopRunning()
|
||||
@ -151,16 +141,6 @@ namespace ARMeilleure.State
|
||||
_nativeContext.SetCounter(0);
|
||||
}
|
||||
|
||||
public static void SuspendCounter()
|
||||
{
|
||||
_tickCounter.Stop();
|
||||
}
|
||||
|
||||
public static void ResumeCounter()
|
||||
{
|
||||
_tickCounter.Start();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_nativeContext.Dispose();
|
||||
|
18
ARMeilleure/State/ICounter.cs
Normal file
18
ARMeilleure/State/ICounter.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace ARMeilleure.State
|
||||
{
|
||||
/// <summary>
|
||||
/// CPU Counter interface.
|
||||
/// </summary>
|
||||
public interface ICounter
|
||||
{
|
||||
/// <summary>
|
||||
/// Counter frequency in Hertz.
|
||||
/// </summary>
|
||||
ulong Frequency { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Current counter value.
|
||||
/// </summary>
|
||||
ulong Counter { get; }
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.State
|
||||
{
|
||||
public class InstExceptionEventArgs : EventArgs
|
||||
{
|
||||
public ulong Address { get; }
|
||||
public int Id { get; }
|
||||
|
||||
public InstExceptionEventArgs(ulong address, int id)
|
||||
{
|
||||
Address = address;
|
||||
Id = id;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.State
|
||||
{
|
||||
public class InstUndefinedEventArgs : EventArgs
|
||||
{
|
||||
public ulong Address { get; }
|
||||
public int OpCode { get; }
|
||||
|
||||
public InstUndefinedEventArgs(ulong address, int opCode)
|
||||
{
|
||||
Address = address;
|
||||
OpCode = opCode;
|
||||
}
|
||||
}
|
||||
}
|
@ -34,6 +34,12 @@ namespace ARMeilleure.State
|
||||
GetStorage().ExclusiveAddress = ulong.MaxValue;
|
||||
}
|
||||
|
||||
public ulong GetPc()
|
||||
{
|
||||
// TODO: More precise tracking of PC value.
|
||||
return GetStorage().DispatchAddress;
|
||||
}
|
||||
|
||||
public unsafe ulong GetX(int index)
|
||||
{
|
||||
if ((uint)index >= RegisterConsts.IntRegsCount)
|
||||
|
@ -115,7 +115,7 @@ namespace ARMeilleure.Translation
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpsr)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32))); // A32 only.
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)));
|
||||
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032))); // A32 only.
|
||||
|
@ -27,7 +27,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 3267; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 3362; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
|
@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win10-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -15,11 +15,11 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dll</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'">
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dylib</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win10-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.so</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
|
@ -523,9 +523,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
|
||||
private ulong GetSystemTicks()
|
||||
{
|
||||
double ticks = ARMeilleure.State.ExecutionContext.ElapsedTicks * ARMeilleure.State.ExecutionContext.TickFrequency;
|
||||
|
||||
return (ulong)(ticks * Constants.TargetTimerFrequency);
|
||||
return (ulong)(_manager.TickSource.ElapsedSeconds * Constants.TargetTimerFrequency);
|
||||
}
|
||||
|
||||
private uint ComputeVoiceDrop(CommandBuffer commandBuffer, long voicesEstimatedTime, long deltaTimeDsp)
|
||||
|
@ -19,6 +19,7 @@ using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Audio.Renderer.Dsp;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
@ -77,6 +78,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// </summary>
|
||||
private IHardwareDeviceDriver _deviceDriver;
|
||||
|
||||
/// <summary>
|
||||
/// Tick source used to measure elapsed time.
|
||||
/// </summary>
|
||||
public ITickSource TickSource { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AudioProcessor"/> instance associated to this manager.
|
||||
/// </summary>
|
||||
@ -90,9 +96,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <summary>
|
||||
/// Create a new <see cref="AudioRendererManager"/>.
|
||||
/// </summary>
|
||||
public AudioRendererManager()
|
||||
/// <param name="tickSource">Tick source used to measure elapsed time.</param>
|
||||
public AudioRendererManager(ITickSource tickSource)
|
||||
{
|
||||
Processor = new AudioProcessor();
|
||||
TickSource = tickSource;
|
||||
_sessionIds = new int[Constants.AudioRendererSessionCountMax];
|
||||
_sessions = new AudioRenderSystem[Constants.AudioRendererSessionCountMax];
|
||||
_activeSessionCount = 0;
|
||||
|
@ -1,9 +1,9 @@
|
||||
<Application
|
||||
x:Class="Ryujinx.Ava.App"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:sty="using:FluentAvalonia.Styling"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sty="using:FluentAvalonia.Styling">
|
||||
<Application.Styles>
|
||||
<sty:FluentAvaloniaTheme UseSystemThemeOnWindows="False"/>
|
||||
<sty:FluentAvaloniaTheme UseSystemThemeOnWindows="False" />
|
||||
</Application.Styles>
|
||||
</Application>
|
@ -2,7 +2,6 @@ using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.Styling;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.Common;
|
||||
@ -13,7 +12,7 @@ using System.IO;
|
||||
|
||||
namespace Ryujinx.Ava
|
||||
{
|
||||
public class App : Avalonia.Application
|
||||
public class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
@ -46,7 +45,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
private void ShowRestartDialog()
|
||||
{
|
||||
// TODO. Implement Restart Dialog when SettingsWindow is implemented.
|
||||
// TODO: Implement Restart Dialog when SettingsWindow is implemented.
|
||||
}
|
||||
|
||||
private void ThemeChanged_Event(object sender, ReactiveEventArgs<string> e)
|
||||
|
@ -57,7 +57,7 @@ namespace Ryujinx.Ava
|
||||
private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None);
|
||||
|
||||
private readonly AccountManager _accountManager;
|
||||
private UserChannelPersistence _userChannelPersistence;
|
||||
private readonly UserChannelPersistence _userChannelPersistence;
|
||||
|
||||
private readonly InputManager _inputManager;
|
||||
|
||||
@ -82,7 +82,6 @@ namespace Ryujinx.Ava
|
||||
private bool _dialogShown;
|
||||
|
||||
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
|
||||
private KeyboardStateSnapshot _lastKeyboardSnapshot;
|
||||
|
||||
private readonly CancellationTokenSource _gpuCancellationTokenSource;
|
||||
|
||||
@ -126,7 +125,6 @@ namespace Ryujinx.Ava
|
||||
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
|
||||
_inputManager.SetMouseDriver(new AvaloniaMouseDriver(renderer));
|
||||
_keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
|
||||
_lastKeyboardSnapshot = _keyboardInterface.GetKeyboardStateSnapshot();
|
||||
|
||||
NpadManager = _inputManager.CreateNpadManager();
|
||||
TouchScreenManager = _inputManager.CreateTouchScreenManager();
|
||||
@ -722,9 +720,7 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
|
||||
var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value
|
||||
? HLE.MemoryConfiguration.MemoryConfiguration6GB
|
||||
: HLE.MemoryConfiguration.MemoryConfiguration4GB;
|
||||
var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? HLE.MemoryConfiguration.MemoryConfiguration6GB : HLE.MemoryConfiguration.MemoryConfiguration4GB;
|
||||
|
||||
IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
|
||||
|
||||
@ -898,7 +894,7 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleScreenState(KeyboardStateSnapshot keyboard, KeyboardStateSnapshot lastKeyboard)
|
||||
private void HandleScreenState()
|
||||
{
|
||||
if (ConfigurationState.Instance.Hid.EnableMouse)
|
||||
{
|
||||
@ -935,19 +931,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
KeyboardStateSnapshot keyboard = _keyboardInterface.GetKeyboardStateSnapshot();
|
||||
HandleScreenState();
|
||||
|
||||
HandleScreenState(keyboard, _lastKeyboardSnapshot);
|
||||
|
||||
if (keyboard.IsPressed(Key.Delete))
|
||||
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _parent.WindowState != WindowState.FullScreen)
|
||||
{
|
||||
if (_parent.WindowState != WindowState.FullScreen)
|
||||
{
|
||||
Ptc.Continue();
|
||||
}
|
||||
Ptc.Continue();
|
||||
}
|
||||
|
||||
_lastKeyboardSnapshot = keyboard;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" />
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20" Height="2000">
|
||||
<Border Height="2000" Padding="20">
|
||||
<StackPanel Spacing="5">
|
||||
<TextBlock Text="Code Font Family" />
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
@ -27,8 +25,12 @@
|
||||
Name="btnRem"
|
||||
HorizontalAlignment="Right"
|
||||
Content="Add" />
|
||||
<TextBox Width="100" VerticalAlignment="Center" Text="Rrrrr" Watermark="Hello"
|
||||
UseFloatingWatermark="True" />
|
||||
<TextBox
|
||||
Width="100"
|
||||
VerticalAlignment="Center"
|
||||
Text="Rrrrr"
|
||||
UseFloatingWatermark="True"
|
||||
Watermark="Hello" />
|
||||
<CheckBox>Test Check</CheckBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
@ -1,9 +1,7 @@
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" />
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20" Height="2000">
|
||||
<Border Height="2000" Padding="20">
|
||||
<StackPanel Spacing="5">
|
||||
<TextBlock Text="Code Font Family" />
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
@ -27,8 +25,12 @@
|
||||
Name="btnRem"
|
||||
HorizontalAlignment="Right"
|
||||
Content="Add" />
|
||||
<TextBox Width="100" VerticalAlignment="Center" Text="Rrrrr" Watermark="Hello"
|
||||
UseFloatingWatermark="True" />
|
||||
<TextBox
|
||||
Width="100"
|
||||
VerticalAlignment="Center"
|
||||
Text="Rrrrr"
|
||||
UseFloatingWatermark="True"
|
||||
Watermark="Hello" />
|
||||
<CheckBox>Test Check</CheckBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:sys="clr-namespace:System;assembly=netstandard">
|
||||
xmlns:sys="clr-namespace:System;assembly=netstandard"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20" Height="2000">
|
||||
<Border Height="2000" Padding="20">
|
||||
<StackPanel Spacing="5">
|
||||
<TextBlock Text="Code Font Family" />
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
@ -22,15 +22,19 @@
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ToggleButton
|
||||
Name="btnAdd"
|
||||
HorizontalAlignment="Right"
|
||||
Height="28"
|
||||
HorizontalAlignment="Right"
|
||||
Content="Addy" />
|
||||
<Button
|
||||
Name="btnRem"
|
||||
HorizontalAlignment="Right"
|
||||
Content="Add" />
|
||||
<TextBox Width="100" VerticalAlignment="Center" Text="Rrrrr" Watermark="Hello"
|
||||
UseFloatingWatermark="True" />
|
||||
<TextBox
|
||||
Width="100"
|
||||
VerticalAlignment="Center"
|
||||
Text="Rrrrr"
|
||||
UseFloatingWatermark="True"
|
||||
Watermark="Hello" />
|
||||
<CheckBox>Test Check</CheckBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
@ -62,13 +66,10 @@
|
||||
<Style Selector="Image.huge">
|
||||
<Setter Property="Width" Value="120" />
|
||||
</Style>
|
||||
<Style Selector="RadioButton">
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="#TitleBarHost > Image">
|
||||
<Style Selector="#TitleBarHost > Image">
|
||||
<Setter Property="Margin" Value="10" />
|
||||
</Style>
|
||||
<Style Selector="#TitleBarHost > Label">
|
||||
<Style Selector="#TitleBarHost > Label">
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
</Style>
|
||||
@ -225,12 +226,12 @@
|
||||
<StaticResource x:Key="ListViewItemBackgroundPointerOver" ResourceKey="SystemAccentColorDark2" />
|
||||
<StaticResource x:Key="ListViewItemBackgroundSelectedPressed" ResourceKey="ThemeAccentColorBrush" />
|
||||
<StaticResource x:Key="ListViewItemBackgroundSelectedPointerOver" ResourceKey="SystemAccentColorDark2" />
|
||||
<SolidColorBrush x:Key="DataGridGridLinesBrush"
|
||||
Color="{DynamicResource SystemBaseMediumLowColor}"
|
||||
Opacity="0.4" />
|
||||
<SolidColorBrush
|
||||
x:Key="DataGridGridLinesBrush"
|
||||
Opacity="0.4"
|
||||
Color="{DynamicResource SystemBaseMediumLowColor}" />
|
||||
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
|
||||
<SolidColorBrush x:Key="MenuFlyoutPresenterBorderBrush"
|
||||
Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
|
||||
<SolidColorBrush x:Key="MenuFlyoutPresenterBorderBrush" Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
|
||||
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="ListBoxBackground" Color="{DynamicResource ThemeContentBackgroundColor}" />
|
||||
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="{DynamicResource ThemeForegroundColor}" />
|
||||
@ -241,7 +242,6 @@
|
||||
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedDisabled" Color="#00E81123" />
|
||||
<Thickness x:Key="PageMargin">40 0 40 0</Thickness>
|
||||
<Thickness x:Key="Margin">0 5 0 5</Thickness>
|
||||
<Thickness x:Key="TextMargin">0 4 0 0</Thickness>
|
||||
<Thickness x:Key="MenuItemPadding">5 0 5 0</Thickness>
|
||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#00000000</Color>
|
||||
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Version>1.0.0-dirty</Version>
|
||||
|
@ -1,17 +1,21 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.Ui.Applet.ErrorAppletWindow"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
CanResize="False"
|
||||
SizeToContent="Height"
|
||||
Width="450"
|
||||
Height="340"
|
||||
Title="{locale:Locale ErrorWindowTitle}">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20">
|
||||
<Window
|
||||
x:Class="Ryujinx.Ava.Ui.Applet.ErrorAppletWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Title="{locale:Locale ErrorWindowTitle}"
|
||||
Width="450"
|
||||
Height="340"
|
||||
CanResize="False"
|
||||
SizeToContent="Height"
|
||||
mc:Ignorable="d">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
@ -21,11 +25,28 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Row="1" Grid.RowSpan="2" Margin="5, 10, 20 , 10" Grid.Column="0"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="80" MinWidth="50" />
|
||||
<TextBlock Grid.Row="1" Margin="10" Grid.Column="1" VerticalAlignment="Stretch" TextWrapping="Wrap"
|
||||
Text="{Binding Message}" />
|
||||
<StackPanel Name="ButtonStack" Margin="10" Spacing="10" Grid.Row="2" Grid.Column="1"
|
||||
HorizontalAlignment="Right" Orientation="Horizontal" />
|
||||
<Image
|
||||
Grid.Row="1"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="0"
|
||||
Height="80"
|
||||
MinWidth="50"
|
||||
Margin="5,10,20,10"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="10"
|
||||
VerticalAlignment="Stretch"
|
||||
Text="{Binding Message}"
|
||||
TextWrapping="Wrap" />
|
||||
<StackPanel
|
||||
Name="ButtonStack"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10" />
|
||||
</Grid>
|
||||
</Window>
|
@ -1,12 +1,16 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.SwkbdAppletDialog"
|
||||
Width="400">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20">
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.SwkbdAppletDialog"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Width="400"
|
||||
mc:Ignorable="d">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
@ -18,15 +22,43 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Row="1" VerticalAlignment="Center" Grid.RowSpan="5" Margin="5, 10, 20 , 10"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="80"
|
||||
MinWidth="50" />
|
||||
<TextBlock Grid.Row="1" Margin="5" Grid.Column="1" Text="{Binding MainText}" TextWrapping="Wrap" />
|
||||
<TextBlock Grid.Row="2" Margin="5" Grid.Column="1" Text="{Binding SecondaryText}" TextWrapping="Wrap" />
|
||||
<TextBox Name="Input" KeyUp="Message_KeyUp" UseFloatingWatermark="True" TextInput="Message_TextInput"
|
||||
Text="{Binding Message}" Grid.Row="2"
|
||||
Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Stretch" TextWrapping="Wrap" />
|
||||
<TextBlock Name="Error" Margin="5" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Stretch"
|
||||
TextWrapping="Wrap" />
|
||||
<Image
|
||||
Grid.Row="1"
|
||||
Grid.RowSpan="5"
|
||||
Height="80"
|
||||
MinWidth="50"
|
||||
Margin="5,10,20,10"
|
||||
VerticalAlignment="Center"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="5"
|
||||
Text="{Binding MainText}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="5"
|
||||
Text="{Binding SecondaryText}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
Name="Input"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
KeyUp="Message_KeyUp"
|
||||
Text="{Binding Message}"
|
||||
TextInput="Message_TextInput"
|
||||
TextWrapping="Wrap"
|
||||
UseFloatingWatermark="True" />
|
||||
<TextBlock
|
||||
Name="Error"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,188 +1,219 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.GameGridView">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
<MenuItem
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.GameGridView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
<MenuItem
|
||||
Command="{Binding ToggleFavorite}"
|
||||
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenUserSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenDeviceSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserDeviceDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserDeviceDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenBcatSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserBcatDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserBcatDirectoryToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenTitleUpdateManager}"
|
||||
Header="{locale:Locale GameListContextMenuManageTitleUpdates}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenDlcManager}"
|
||||
Header="{locale:Locale GameListContextMenuManageDlc}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenCheatManager}"
|
||||
Header="{locale:Locale GameListContextMenuManageCheat}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenModsDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenModsDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenSdModsDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
|
||||
<MenuItem
|
||||
<Separator />
|
||||
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
|
||||
<MenuItem
|
||||
Command="{Binding PurgePtcCache}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding PurgeShaderCache}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenPtcDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenShaderCacheDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}">
|
||||
<MenuItem
|
||||
</MenuItem>
|
||||
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}">
|
||||
<MenuItem
|
||||
Command="{Binding ExtractExeFs}"
|
||||
Header="{locale:Locale GameListContextMenuExtractDataExeFS}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding ExtractRomFs}"
|
||||
Header="{locale:Locale GameListContextMenuExtractDataRomFS}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding ExtractLogo}"
|
||||
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox Grid.Row="0"
|
||||
Padding="8"
|
||||
HorizontalAlignment="Stretch"
|
||||
DoubleTapped="GameList_DoubleTapped"
|
||||
SelectionChanged="GameList_SelectionChanged"
|
||||
ContextFlyout="{StaticResource GameContextMenu}"
|
||||
VerticalAlignment="Stretch"
|
||||
Items="{Binding AppsObservableList}">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<flex:FlexPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" JustifyContent="Center"
|
||||
AlignContent="FlexStart" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
<Setter Property="Background" Value="{DynamicResource SystemAccentColorDark3}" />
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.7">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="MaxWidth" Value="0"/>
|
||||
<Setter Property="Opacity" Value="0.0"/>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="50%">
|
||||
<Setter Property="MaxWidth" Value="1000"/>
|
||||
<Setter Property="Opacity" Value="0.3"/>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="MaxWidth" Value="1000"/>
|
||||
<Setter Property="Opacity" Value="1.0"/>
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.Styles>
|
||||
<Style Selector="ui|SymbolIcon.small.icon">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.normal.icon">
|
||||
<Setter Property="FontSize" Value="19" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.large.icon">
|
||||
<Setter Property="FontSize" Value="23" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.huge.icon">
|
||||
<Setter Property="FontSize" Value="26" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<Border
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox
|
||||
Grid.Row="0"
|
||||
Padding="8"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ContextFlyout="{StaticResource GameContextMenu}"
|
||||
DoubleTapped="GameList_DoubleTapped"
|
||||
Items="{Binding AppsObservableList}"
|
||||
SelectionChanged="GameList_SelectionChanged">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<flex:FlexPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
AlignContent="FlexStart"
|
||||
JustifyContent="Center" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
<Setter Property="Background" Value="{DynamicResource SystemAccentColorDark3}" />
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.7">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="MaxWidth" Value="0" />
|
||||
<Setter Property="Opacity" Value="0.0" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="50%">
|
||||
<Setter Property="MaxWidth" Value="1000" />
|
||||
<Setter Property="Opacity" Value="0.3" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="MaxWidth" Value="1000" />
|
||||
<Setter Property="Opacity" Value="1.0" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.Styles>
|
||||
<Style Selector="ui|SymbolIcon.small.icon">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.normal.icon">
|
||||
<Setter Property="FontSize" Value="19" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.large.icon">
|
||||
<Setter Property="FontSize" Value="23" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.huge.icon">
|
||||
<Setter Property="FontSize" Value="26" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<Border
|
||||
Margin="0"
|
||||
Padding="{Binding $parent[UserControl].DataContext.GridItemPadding}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Padding="{Binding $parent[UserControl].DataContext.GridItemPadding}" CornerRadius="5"
|
||||
VerticalAlignment="Stretch" Margin="0" ClipToBounds="True">
|
||||
<Grid Margin="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="0" Grid.Row="0"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}"
|
||||
Height="50" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
Margin="5" Grid.Row="1">
|
||||
<TextBlock Text="{Binding TitleName}" TextAlignment="Center" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ui:SymbolIcon Classes.icon="true" Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Foreground="Yellow" Symbol="StarFilled"
|
||||
IsVisible="{Binding Favorite}" Margin="5" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left" />
|
||||
<ui:SymbolIcon Classes.icon="true" Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Foreground="Black" Symbol="Star"
|
||||
IsVisible="{Binding Favorite}" Margin="5" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
VerticalAlignment="Stretch"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
<Grid Margin="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Height="50"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding TitleName}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ui:SymbolIcon
|
||||
Margin="5"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.icon="true"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Foreground="Yellow"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="StarFilled" />
|
||||
<ui:SymbolIcon
|
||||
Margin="5"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.icon="true"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Foreground="Black"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="Star" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,13 +1,16 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.GameListView">
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.GameListView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
@ -88,18 +91,23 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox Grid.Row="0"
|
||||
Padding="8"
|
||||
HorizontalAlignment="Stretch"
|
||||
DoubleTapped="GameList_DoubleTapped"
|
||||
SelectionChanged="GameList_SelectionChanged"
|
||||
ContextFlyout="{StaticResource GameContextMenu}"
|
||||
VerticalAlignment="Stretch"
|
||||
Name="GameListBox"
|
||||
Items="{Binding AppsObservableList}">
|
||||
<ListBox
|
||||
Name="GameListBox"
|
||||
Grid.Row="0"
|
||||
Padding="8"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ContextFlyout="{StaticResource GameContextMenu}"
|
||||
DoubleTapped="GameList_DoubleTapped"
|
||||
Items="{Binding AppsObservableList}"
|
||||
SelectionChanged="GameList_SelectionChanged">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Orientation="Vertical" Spacing="2" />
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="2" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
@ -112,16 +120,16 @@
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.7">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="MaxHeight" Value="0"/>
|
||||
<Setter Property="Opacity" Value="0.0"/>
|
||||
<Setter Property="MaxHeight" Value="0" />
|
||||
<Setter Property="Opacity" Value="0.0" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="50%">
|
||||
<Setter Property="MaxHeight" Value="1000"/>
|
||||
<Setter Property="Opacity" Value="0.3"/>
|
||||
<Setter Property="MaxHeight" Value="1000" />
|
||||
<Setter Property="Opacity" Value="0.3" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="MaxHeight" Value="1000"/>
|
||||
<Setter Property="Opacity" Value="1.0"/>
|
||||
<Setter Property="MaxHeight" Value="1000" />
|
||||
<Setter Property="Opacity" Value="1.0" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
@ -130,54 +138,96 @@
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Border HorizontalAlignment="Stretch"
|
||||
Padding="10" CornerRadius="5"
|
||||
VerticalAlignment="Stretch" Margin="0" ClipToBounds="True">
|
||||
<Grid >
|
||||
<Border
|
||||
Margin="0"
|
||||
Padding="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="10"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Image
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
<Image
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="0"
|
||||
Margin="0"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Grid.RowSpan="3" Grid.Column="0" Margin="0"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel Orientation="Vertical" Spacing="5" VerticalAlignment="Top" HorizontalAlignment="Left"
|
||||
Grid.Column="2">
|
||||
<TextBlock Text="{Binding TitleName}" TextAlignment="Left" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<TextBlock Text="{Binding Developer}" TextAlignment="Left" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<TextBlock Text="{Binding Version}" TextAlignment="Left" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical" Spacing="5" VerticalAlignment="Top" HorizontalAlignment="Right"
|
||||
Grid.Column="3">
|
||||
<TextBlock Text="{Binding TimePlayed}" TextAlignment="Right" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<TextBlock Text="{Binding LastPlayed}" TextAlignment="Right" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<TextBlock Text="{Binding FileSize}" TextAlignment="Right" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
<ui:SymbolIcon Grid.Row="0" Grid.Column="0" FontSize="20"
|
||||
Foreground="Yellow"
|
||||
Symbol="StarFilled"
|
||||
IsVisible="{Binding Favorite}" Margin="-5, -5, 0, 0" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left" />
|
||||
<ui:SymbolIcon Grid.Row="0" Grid.Column="0" FontSize="20"
|
||||
Foreground="Black"
|
||||
Symbol="Star"
|
||||
IsVisible="{Binding Favorite}" Margin="-5, -5, 0, 0" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left" />
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Vertical"
|
||||
Spacing="5">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding TitleName}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Developer}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Version}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="3"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Vertical"
|
||||
Spacing="5">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding TimePlayed}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding LastPlayed}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding FileSize}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<ui:SymbolIcon
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="-5,-5,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="20"
|
||||
Foreground="Yellow"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="StarFilled" />
|
||||
<ui:SymbolIcon
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="-5,-5,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="20"
|
||||
Foreground="Black"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="Star" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
@ -1,18 +1,31 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.InputDialog">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,10,5, 5">
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.InputDialog"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<Grid
|
||||
Margin="5,10,5,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding Message}" />
|
||||
<TextBox MaxLength="{Binding MaxLength}" Grid.Row="1" Margin="10" Width="300" HorizontalAlignment="Center"
|
||||
Text="{Binding Input, Mode=TwoWay}" />
|
||||
<TextBlock Grid.Row="2" Margin="5, 5, 5, 10" HorizontalAlignment="Center" Text="{Binding SubMessage}" />
|
||||
<TextBox
|
||||
Grid.Row="1"
|
||||
Width="300"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
MaxLength="{Binding MaxLength}"
|
||||
Text="{Binding Input, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="5,5,5,10"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding SubMessage}" />
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,14 +1,18 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.UpdateWaitWindow"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Title="Ryujinx - Waiting">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20">
|
||||
<Window
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.UpdateWaitWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Title="Ryujinx - Waiting"
|
||||
SizeToContent="WidthAndHeight"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
@ -17,12 +21,22 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Row="1" Margin="5, 10, 20 , 10" Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common"
|
||||
Height="70"
|
||||
MinWidth="50" />
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Orientation="Vertical">
|
||||
<TextBlock Margin="5" Name="PrimaryText" />
|
||||
<TextBlock VerticalAlignment="Center" Name="SecondaryText" Margin="5" />
|
||||
<Image
|
||||
Grid.Row="1"
|
||||
Height="70"
|
||||
MinWidth="50"
|
||||
Margin="5,10,20,10"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Vertical">
|
||||
<TextBlock Name="PrimaryText" Margin="5" />
|
||||
<TextBlock
|
||||
Name="SecondaryText"
|
||||
Margin="5"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
@ -1,28 +1,41 @@
|
||||
<window:StyleableWindow xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.AboutWindow"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
CanResize="False"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Width="850" MinHeight="550" Height="550"
|
||||
SizeToContent="Width"
|
||||
MinWidth="500"
|
||||
Title="Ryujinx - About">
|
||||
<Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.AboutWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Title="Ryujinx - About"
|
||||
Width="850"
|
||||
Height="550"
|
||||
MinWidth="500"
|
||||
MinHeight="550"
|
||||
d:DesignHeight="350"
|
||||
d:DesignWidth="400"
|
||||
CanResize="False"
|
||||
SizeToContent="Width"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
<Grid
|
||||
Margin="15"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Row="1" Margin="20" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Column="0">
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
@ -40,93 +53,168 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Margin="5, 10, 20 , 10"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="110" MinWidth="50" />
|
||||
<TextBlock FontSize="35" TextAlignment="Center" Grid.Row="0" Grid.Column="1" Text="Ryujinx"
|
||||
Margin="0,20,0,0" />
|
||||
<TextBlock FontSize="16" TextAlignment="Center" Grid.Row="1" Grid.Column="1" Text="(REE-YOU-JINX)"
|
||||
Margin="0,0,0,0" />
|
||||
<Button Grid.Column="1" Background="Transparent" HorizontalAlignment="Center" Margin="0" Grid.Row="2"
|
||||
Tag="https://www.ryujinx.org/"
|
||||
Click="Button_OnClick">
|
||||
<TextBlock ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}"
|
||||
TextAlignment="Center" TextDecorations="Underline" Text="www.ryujinx.org" />
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="0"
|
||||
Height="110"
|
||||
MinWidth="50"
|
||||
Margin="5,10,20,10"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="0,20,0,0"
|
||||
FontSize="35"
|
||||
Text="Ryujinx"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,0"
|
||||
FontSize="16"
|
||||
Text="(REE-YOU-JINX)"
|
||||
TextAlignment="Center" />
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://www.ryujinx.org/">
|
||||
<TextBlock
|
||||
Text="www.ryujinx.org"
|
||||
TextAlignment="Center"
|
||||
TextDecorations="Underline"
|
||||
ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<TextBlock TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
Text="{Binding Version}" Grid.Row="1" />
|
||||
<TextBlock Grid.Row="2" TextAlignment="Center" HorizontalAlignment="Center" Margin="20"
|
||||
Text="{locale:Locale AboutDisclaimerMessage}"
|
||||
MaxLines="2" />
|
||||
<TextBlock Grid.Row="3" TextAlignment="Center" HorizontalAlignment="Center" Margin="20"
|
||||
Text="{locale:Locale AboutAmiiboDisclaimerMessage}"
|
||||
Name="AmiiboLabel"
|
||||
PointerPressed="AmiiboLabel_OnPointerPressed"
|
||||
MaxLines="2" />
|
||||
<StackPanel Spacing="10" Orientation="Horizontal" Grid.Row="4" HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Vertical"
|
||||
ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
|
||||
<Button Height="65" Background="Transparent" Tag="https://www.patreon.com/ryujinx"
|
||||
Click="Button_OnClick">
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Version}"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Center"
|
||||
MaxLines="2"
|
||||
Text="{locale:Locale AboutDisclaimerMessage}"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
Name="AmiiboLabel"
|
||||
Grid.Row="3"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Center"
|
||||
MaxLines="2"
|
||||
PointerPressed="AmiiboLabel_OnPointerPressed"
|
||||
Text="{locale:Locale AboutAmiiboDisclaimerMessage}"
|
||||
TextAlignment="Center" />
|
||||
<StackPanel
|
||||
Grid.Row="4"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
|
||||
<Button
|
||||
Height="65"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://www.patreon.com/ryujinx">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock Grid.Row="1" Margin="0,5,0,0" Text="Patreon" HorizontalAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Patreon" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical"
|
||||
ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
|
||||
<Button Height="65" Background="Transparent" Tag="https://github.com/Ryujinx/Ryujinx"
|
||||
Click="Button_OnClick">
|
||||
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
|
||||
<Button
|
||||
Height="65"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://github.com/Ryujinx/Ryujinx">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock Grid.Row="1" Margin="0,5,0,0" Text="GitHub" HorizontalAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="GitHub" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical"
|
||||
ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
|
||||
<Button Height="65" Background="Transparent" Tag="https://discordapp.com/invite/N2FmfVc"
|
||||
Click="Button_OnClick">
|
||||
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
|
||||
<Button
|
||||
Height="65"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://discordapp.com/invite/N2FmfVc">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock Grid.Row="1" Margin="0,5,0,0" Text="Discord" HorizontalAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Discord" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical"
|
||||
ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
|
||||
<Button Height="65" Background="Transparent" Tag="https://twitter.com/RyujinxEmu"
|
||||
Click="Button_OnClick">
|
||||
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
|
||||
<Button
|
||||
Height="65"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://twitter.com/RyujinxEmu">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock Grid.Row="1" Margin="0,5,0,0" Text="Twitter" HorizontalAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Twitter" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Border Grid.Row="1" Grid.Column="1" VerticalAlignment="Stretch" Margin="5" Width="2" BorderBrush="White"
|
||||
BorderThickness="1,0,0,0">
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="2"
|
||||
Margin="5"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="White"
|
||||
BorderThickness="1,0,0,0">
|
||||
<Separator Width="0" />
|
||||
</Border>
|
||||
<Grid Grid.Row="1" Margin="20" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Column="2">
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
@ -136,27 +224,58 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Text="{locale:Locale AboutRyujinxAboutTitle}" FontWeight="Bold" TextDecorations="Underline" />
|
||||
<TextBlock LineHeight="20" Grid.Row="1" Margin="20,5,5,5"
|
||||
Text="{locale:Locale AboutRyujinxAboutContent}" />
|
||||
<TextBlock Grid.Row="2" Margin="0,10,0,0" Text="{locale:Locale AboutRyujinxMaintainersTitle}"
|
||||
FontWeight="Bold"
|
||||
TextDecorations="Underline" />
|
||||
<TextBlock LineHeight="20" Grid.Row="3" Margin="20,5,5,5"
|
||||
Text="{Binding Developers}" />
|
||||
<Button Background="Transparent" HorizontalAlignment="Right" Grid.Row="4"
|
||||
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a" Click="Button_OnClick">
|
||||
<TextBlock ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}"
|
||||
TextAlignment="Right" TextDecorations="Underline"
|
||||
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}" />
|
||||
<TextBlock
|
||||
FontWeight="Bold"
|
||||
Text="{locale:Locale AboutRyujinxAboutTitle}"
|
||||
TextDecorations="Underline" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="20,5,5,5"
|
||||
LineHeight="20"
|
||||
Text="{locale:Locale AboutRyujinxAboutContent}" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="0,10,0,0"
|
||||
FontWeight="Bold"
|
||||
Text="{locale:Locale AboutRyujinxMaintainersTitle}"
|
||||
TextDecorations="Underline" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Margin="20,5,5,5"
|
||||
LineHeight="20"
|
||||
Text="{Binding Developers}" />
|
||||
<Button
|
||||
Grid.Row="4"
|
||||
HorizontalAlignment="Right"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a">
|
||||
<TextBlock
|
||||
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}"
|
||||
TextAlignment="Right"
|
||||
TextDecorations="Underline"
|
||||
ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" />
|
||||
</Button>
|
||||
<TextBlock Grid.Row="5" Margin="0,0,0,0" Text="{locale:Locale AboutRyujinxSupprtersTitle}"
|
||||
FontWeight="Bold"
|
||||
TextDecorations="Underline" />
|
||||
<Border Width="460" Grid.Row="6" VerticalAlignment="Stretch" Height="200" BorderThickness="1" Margin="20,5"
|
||||
BorderBrush="White" Padding="5">
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top" Name="SupportersTextBlock"
|
||||
Text="{Binding Supporters}" />
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Margin="0,0,0,0"
|
||||
FontWeight="Bold"
|
||||
Text="{locale:Locale AboutRyujinxSupprtersTitle}"
|
||||
TextDecorations="Underline" />
|
||||
<Border
|
||||
Grid.Row="6"
|
||||
Width="460"
|
||||
Height="200"
|
||||
Margin="20,5"
|
||||
Padding="5"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="White"
|
||||
BorderThickness="1">
|
||||
<TextBlock
|
||||
Name="SupportersTextBlock"
|
||||
VerticalAlignment="Top"
|
||||
Text="{Binding Supporters}"
|
||||
TextWrapping="Wrap" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
@ -1,25 +1,25 @@
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.MainWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Title="Ryujinx"
|
||||
Height="785"
|
||||
Width="1280"
|
||||
d:DesignHeight="720"
|
||||
d:DesignWidth="1280"
|
||||
Height="785"
|
||||
MinWidth="1024"
|
||||
MinHeight="680"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
d:DesignHeight="720"
|
||||
d:DesignWidth="1280"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:MainWindowViewModel"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<Window.Styles>
|
||||
<Style Selector="TitleBar:fullscreen">
|
||||
@ -38,23 +38,28 @@
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<controls:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" />
|
||||
<ContentControl Grid.Row="1"
|
||||
Focusable="False"
|
||||
IsVisible="False"
|
||||
KeyboardNavigation.IsTabStop="False">
|
||||
<ui:ContentDialog Name="ContentDialog"
|
||||
KeyboardNavigation.IsTabStop="False"
|
||||
IsPrimaryButtonEnabled="True"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
IsVisible="True" />
|
||||
<ContentControl
|
||||
Grid.Row="1"
|
||||
Focusable="False"
|
||||
IsVisible="False"
|
||||
KeyboardNavigation.IsTabStop="False">
|
||||
<ui:ContentDialog
|
||||
Name="ContentDialog"
|
||||
IsPrimaryButtonEnabled="True"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
IsVisible="True"
|
||||
KeyboardNavigation.IsTabStop="False" />
|
||||
</ContentControl>
|
||||
<StackPanel IsVisible="False" Grid.Row="0">
|
||||
<StackPanel Grid.Row="0" IsVisible="False">
|
||||
<controls:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" />
|
||||
<controls:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" />
|
||||
<controls:HotKeyControl Name="DockToggleHotKey" Command="{ReflectionBinding ToggleDockMode}" />
|
||||
<controls:HotKeyControl Name="ExitHotKey" Command="{ReflectionBinding ExitCurrentState}" />
|
||||
</StackPanel>
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1">
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
@ -73,47 +78,51 @@
|
||||
<DockPanel HorizontalAlignment="Stretch">
|
||||
<Menu
|
||||
Name="Menu"
|
||||
Margin="0"
|
||||
Height="35"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Left">
|
||||
<Menu.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<DockPanel HorizontalAlignment="Stretch" Margin="0" />
|
||||
<DockPanel Margin="0" HorizontalAlignment="Stretch" />
|
||||
</ItemsPanelTemplate>
|
||||
</Menu.ItemsPanel>
|
||||
<MenuItem
|
||||
VerticalAlignment="Center"
|
||||
Header="{locale:Locale MenuBarFile}">
|
||||
<MenuItem IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
Command="{ReflectionBinding OpenFile}"
|
||||
Header="{locale:Locale MenuBarFileOpenFromFile}"
|
||||
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
|
||||
<MenuItem IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
Command="{ReflectionBinding OpenFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenUnpacked}"
|
||||
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
|
||||
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}"
|
||||
IsEnabled="{Binding IsAppletMenuActive}">
|
||||
<MenuItem Command="{ReflectionBinding OpenMiiApplet}" Header="Mii Edit Applet"
|
||||
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
|
||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarFile}">
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenFile}"
|
||||
Header="{locale:Locale MenuBarFileOpenFromFile}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenUnpacked}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
|
||||
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}">
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenMiiApplet}"
|
||||
Header="Mii Edit Applet"
|
||||
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Command="{ReflectionBinding OpenRyujinxFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenEmuFolder}"
|
||||
ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" />
|
||||
<MenuItem Command="{ReflectionBinding OpenLogsFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenLogsFolder}"
|
||||
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenRyujinxFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenEmuFolder}"
|
||||
ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenLogsFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenLogsFolder}"
|
||||
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" />
|
||||
<Separator />
|
||||
<MenuItem Command="{ReflectionBinding CloseWindow}"
|
||||
Header="{locale:Locale MenuBarFileExit}"
|
||||
ToolTip.Tip="{locale:Locale ExitTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding CloseWindow}"
|
||||
Header="{locale:Locale MenuBarFileExit}"
|
||||
ToolTip.Tip="{locale:Locale ExitTooltip}" />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
VerticalAlignment="Center"
|
||||
Header="{locale:Locale MenuBarOptions}">
|
||||
<MenuItem Command="{ReflectionBinding ToggleFullscreen}"
|
||||
Header="{locale:Locale MenuBarOptionsToggleFullscreen}" InputGesture="F11" />
|
||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}">
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ToggleFullscreen}"
|
||||
Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
|
||||
InputGesture="F11" />
|
||||
<MenuItem Header="{locale:Locale MenuBarOptionsStartGamesInFullscreen}">
|
||||
<MenuItem.Icon>
|
||||
<CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}" />
|
||||
@ -126,60 +135,82 @@
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="{locale:Locale MenuBarOptionsChangeLanguage}">
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="en_US"
|
||||
Header="American English" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="pt_BR"
|
||||
Header="Brazilian Portuguese" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="es_ES"
|
||||
Header="Castilian Spanish" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="fr_FR"
|
||||
Header="French" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="de_DE"
|
||||
Header="German" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="el_GR"
|
||||
Header="Greek" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="it_IT"
|
||||
Header="Italian" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="ko_KR"
|
||||
Header="Korean" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="ru_RU"
|
||||
Header="Russian" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="tr_TR"
|
||||
Header="Turkish" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="en_US"
|
||||
Header="American English" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="pt_BR"
|
||||
Header="Brazilian Portuguese" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="es_ES"
|
||||
Header="Castilian Spanish" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="fr_FR"
|
||||
Header="French" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="de_DE"
|
||||
Header="German" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="el_GR"
|
||||
Header="Greek" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="it_IT"
|
||||
Header="Italian" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="ko_KR"
|
||||
Header="Korean" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="ru_RU"
|
||||
Header="Russian" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="tr_TR"
|
||||
Header="Turkish" />
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Command="{ReflectionBinding OpenSettings}"
|
||||
Header="{locale:Locale MenuBarOptionsSettings}"
|
||||
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
|
||||
<MenuItem Command="{ReflectionBinding ManageProfiles}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
|
||||
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenSettings}"
|
||||
Header="{locale:Locale MenuBarOptionsSettings}"
|
||||
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ManageProfiles}"
|
||||
Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
Name="ActionsMenuItem"
|
||||
VerticalAlignment="Center"
|
||||
Header="{locale:Locale MenuBarActions}"
|
||||
Name="ActionsMenuItem"
|
||||
IsEnabled="{Binding IsGameRunning}">
|
||||
<MenuItem
|
||||
Click="PauseEmulation_Click"
|
||||
Header="{locale:Locale MenuBarOptionsPauseEmulation}"
|
||||
InputGesture="{Binding PauseKey}"
|
||||
IsEnabled="{Binding !IsPaused}"
|
||||
IsVisible="{Binding !IsPaused}"
|
||||
InputGesture="{Binding PauseKey}" />
|
||||
IsVisible="{Binding !IsPaused}" />
|
||||
<MenuItem
|
||||
Click="ResumeEmulation_Click"
|
||||
Header="{locale:Locale MenuBarOptionsResumeEmulation}"
|
||||
InputGesture="{Binding PauseKey}"
|
||||
IsEnabled="{Binding IsPaused}"
|
||||
IsVisible="{Binding IsPaused}"
|
||||
InputGesture="{Binding PauseKey}" />
|
||||
IsVisible="{Binding IsPaused}" />
|
||||
<MenuItem
|
||||
Click="StopEmulation_Click"
|
||||
Header="{locale:Locale MenuBarOptionsStopEmulation}"
|
||||
ToolTip.Tip="{locale:Locale StopEmulationTooltip}"
|
||||
IsEnabled="{Binding IsGameRunning}" InputGesture="Escape" />
|
||||
<MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}"
|
||||
Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
|
||||
InputGesture="Escape"
|
||||
IsEnabled="{Binding IsGameRunning}"
|
||||
ToolTip.Tip="{locale:Locale StopEmulationTooltip}" />
|
||||
<MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Name="ScanAmiiboMenuItem"
|
||||
@ -187,41 +218,38 @@
|
||||
Command="{ReflectionBinding OpenAmiiboWindow}"
|
||||
Header="{locale:Locale MenuBarActionsScanAmiibo}"
|
||||
IsEnabled="{Binding IsAmiiboRequested}" />
|
||||
<MenuItem Command="{ReflectionBinding TakeScreenshot}"
|
||||
IsEnabled="{Binding IsGameRunning}"
|
||||
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
|
||||
InputGesture="{Binding ScreenshotKey}" />
|
||||
<MenuItem Command="{ReflectionBinding HideUi}"
|
||||
IsEnabled="{Binding IsGameRunning}"
|
||||
Header="{locale:Locale MenuBarFileToolsHideUi}"
|
||||
InputGesture="{Binding ShowUiKey}" />
|
||||
<MenuItem Command="{ReflectionBinding OpenCheatManagerForCurrentApp}"
|
||||
IsEnabled="{Binding IsGameRunning}"
|
||||
Header="{locale:Locale GameListContextMenuManageCheat}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding TakeScreenshot}"
|
||||
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
|
||||
InputGesture="{Binding ScreenshotKey}"
|
||||
IsEnabled="{Binding IsGameRunning}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding HideUi}"
|
||||
Header="{locale:Locale MenuBarFileToolsHideUi}"
|
||||
InputGesture="{Binding ShowUiKey}"
|
||||
IsEnabled="{Binding IsGameRunning}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenCheatManagerForCurrentApp}"
|
||||
Header="{locale:Locale GameListContextMenuManageCheat}"
|
||||
IsEnabled="{Binding IsGameRunning}" />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
VerticalAlignment="Center"
|
||||
Header="{locale:Locale MenuBarTools}">
|
||||
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}">
|
||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}"
|
||||
Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
|
||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}"
|
||||
Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
|
||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
|
||||
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}">
|
||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
|
||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
VerticalAlignment="Center"
|
||||
Header="{locale:Locale MenuBarHelp}">
|
||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
|
||||
<MenuItem
|
||||
Name="UpdateMenuItem"
|
||||
Command="{ReflectionBinding CheckForUpdates}"
|
||||
Header="{locale:Locale MenuBarHelpCheckForUpdates}"
|
||||
ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" />
|
||||
<Separator />
|
||||
<MenuItem Command="{ReflectionBinding OpenAboutWindow}"
|
||||
Header="{locale:Locale MenuBarHelpAbout}"
|
||||
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenAboutWindow}"
|
||||
Header="{locale:Locale MenuBarHelpAbout}"
|
||||
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</DockPanel>
|
||||
@ -230,152 +258,213 @@
|
||||
Name="Content"
|
||||
Grid.Row="1"
|
||||
Padding="0"
|
||||
IsVisible="{Binding ShowContent}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="{DynamicResource ThemeControlBorderColor}"
|
||||
BorderThickness="0,0,0,0"
|
||||
DockPanel.Dock="Top">
|
||||
DockPanel.Dock="Top"
|
||||
IsVisible="{Binding ShowContent}">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<DockPanel Grid.Row="0" HorizontalAlignment="Stretch" Margin="0,0,0,5">
|
||||
<DockPanel
|
||||
Grid.Row="0"
|
||||
Margin="0,0,0,5"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Button
|
||||
IsEnabled="{Binding IsGrid}" VerticalAlignment="Stretch" MinWidth="40" Width="40"
|
||||
Margin="5,2,0,2" Command="{ReflectionBinding SetListMode}">
|
||||
<ui:FontIcon FontFamily="avares://FluentAvalonia/Fonts#Symbols"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0"
|
||||
Glyph="{controls:GlyphValueConverter List}"
|
||||
HorizontalAlignment="Stretch" />
|
||||
Width="40"
|
||||
MinWidth="40"
|
||||
Margin="5,2,0,2"
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{ReflectionBinding SetListMode}"
|
||||
IsEnabled="{Binding IsGrid}">
|
||||
<ui:FontIcon
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
|
||||
Glyph="{controls:GlyphValueConverter List}" />
|
||||
</Button>
|
||||
<Button
|
||||
IsEnabled="{Binding IsList}" VerticalAlignment="Stretch" MinWidth="40" Width="40"
|
||||
Margin="5,2,5,2" Command="{ReflectionBinding SetGridMode}">
|
||||
<ui:FontIcon FontFamily="avares://FluentAvalonia/Fonts#Symbols"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0"
|
||||
Glyph="{controls:GlyphValueConverter Grid}"
|
||||
HorizontalAlignment="Stretch" />
|
||||
Width="40"
|
||||
MinWidth="40"
|
||||
Margin="5,2,5,2"
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{ReflectionBinding SetGridMode}"
|
||||
IsEnabled="{Binding IsList}">
|
||||
<ui:FontIcon
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
|
||||
Glyph="{controls:GlyphValueConverter Grid}" />
|
||||
</Button>
|
||||
<TextBlock Text="{locale:Locale IconSize}"
|
||||
VerticalAlignment="Center" Margin="10,0"
|
||||
ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
|
||||
<Slider Width="150" Margin="5,-10,5 ,0" Height="35"
|
||||
ToolTip.Tip="{locale:Locale IconSizeTooltip}"
|
||||
VerticalAlignment="Center" Minimum="1" Maximum="4" IsSnapToTickEnabled="True"
|
||||
TickFrequency="1" Value="{Binding GridSizeScale}" />
|
||||
<CheckBox Margin="0" IsChecked="{Binding ShowNames, Mode=TwoWay}" VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsGrid}">
|
||||
<TextBlock Text="{locale:Locale CommonShowNames}" Margin="5,3,0,0" />
|
||||
<TextBlock
|
||||
Margin="10,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale IconSize}"
|
||||
ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
|
||||
<Slider
|
||||
Width="150"
|
||||
Height="35"
|
||||
Margin="5,-10,5,0"
|
||||
VerticalAlignment="Center"
|
||||
IsSnapToTickEnabled="True"
|
||||
Maximum="4"
|
||||
Minimum="1"
|
||||
TickFrequency="1"
|
||||
ToolTip.Tip="{locale:Locale IconSizeTooltip}"
|
||||
Value="{Binding GridSizeScale}" />
|
||||
<CheckBox
|
||||
Margin="0"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding ShowNames, Mode=TwoWay}"
|
||||
IsVisible="{Binding IsGrid}">
|
||||
<TextBlock Margin="5,3,0,0" Text="{locale:Locale CommonShowNames}" />
|
||||
</CheckBox>
|
||||
<TextBox
|
||||
Name="SearchBox"
|
||||
DockPanel.Dock="Right"
|
||||
VerticalAlignment="Center"
|
||||
MinWidth="200"
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Right"
|
||||
KeyUp="SearchBox_OnKeyUp"
|
||||
Text="{Binding SearchText}"
|
||||
Watermark="{locale:Locale MenuSearch}" />
|
||||
<ui:DropDownButton DockPanel.Dock="Right"
|
||||
HorizontalAlignment="Right" Width="150" VerticalAlignment="Center"
|
||||
Content="{Binding SortName}">
|
||||
<ui:DropDownButton
|
||||
Width="150"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Content="{Binding SortName}"
|
||||
DockPanel.Dock="Right">
|
||||
<ui:DropDownButton.Flyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Margin="0">
|
||||
<StackPanel
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<StackPanel>
|
||||
<RadioButton Tag="Favorite"
|
||||
IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}"
|
||||
GroupName="Sort"
|
||||
Content="{locale:Locale CommonFavorite}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="Title" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderApplication}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="Developer" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderDeveloper}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="TotalTimePlayed" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderTimePlayed}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="LastPlayed" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderLastPlayed}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="FileType" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByType, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderFileExtension}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="FileSize" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedBySize, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderFileSize}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="Path" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByPath, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderPath}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale CommonFavorite}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}"
|
||||
Tag="Favorite" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderApplication}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
|
||||
Tag="Title" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderDeveloper}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
|
||||
Tag="Developer" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderTimePlayed}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
|
||||
Tag="TotalTimePlayed" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderLastPlayed}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
|
||||
Tag="LastPlayed" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderFileExtension}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByType, Mode=OneTime}"
|
||||
Tag="FileType" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderFileSize}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedBySize, Mode=OneTime}"
|
||||
Tag="FileSize" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderPath}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByPath, Mode=OneTime}"
|
||||
Tag="Path" />
|
||||
</StackPanel>
|
||||
<Border HorizontalAlignment="Stretch" Margin="5" Height="2" BorderBrush="White"
|
||||
Width="60" BorderThickness="0,1,0,0">
|
||||
<Separator HorizontalAlignment="Stretch" Height="0" />
|
||||
<Border
|
||||
Width="60"
|
||||
Height="2"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
BorderBrush="White"
|
||||
BorderThickness="0,1,0,0">
|
||||
<Separator Height="0" HorizontalAlignment="Stretch" />
|
||||
</Border>
|
||||
<RadioButton Tag="Ascending" IsChecked="{Binding IsAscending, Mode=OneTime}"
|
||||
GroupName="Order"
|
||||
Content="{locale:Locale OrderAscending}" Checked="Order_Checked" />
|
||||
<RadioButton Tag="Descending" GroupName="Order"
|
||||
IsChecked="{Binding !IsAscending, Mode=OneTime}"
|
||||
Content="{locale:Locale OrderDescending}" Checked="Order_Checked" />
|
||||
<RadioButton
|
||||
Checked="Order_Checked"
|
||||
Content="{locale:Locale OrderAscending}"
|
||||
GroupName="Order"
|
||||
IsChecked="{Binding IsAscending, Mode=OneTime}"
|
||||
Tag="Ascending" />
|
||||
<RadioButton
|
||||
Checked="Order_Checked"
|
||||
Content="{locale:Locale OrderDescending}"
|
||||
GroupName="Order"
|
||||
IsChecked="{Binding !IsAscending, Mode=OneTime}"
|
||||
Tag="Descending" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</ui:DropDownButton.Flyout>
|
||||
</ui:DropDownButton>
|
||||
<TextBlock DockPanel.Dock="Right" HorizontalAlignment="Right"
|
||||
Text="{locale:Locale CommonSort}" VerticalAlignment="Center" Margin="10,0" />
|
||||
<TextBlock
|
||||
Margin="10,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Right"
|
||||
Text="{locale:Locale CommonSort}" />
|
||||
</DockPanel>
|
||||
<controls:GameListView
|
||||
x:Name="GameList"
|
||||
Grid.Row="1"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
IsVisible="{Binding IsList}" />
|
||||
<controls:GameGridView
|
||||
x:Name="GameGrid"
|
||||
Grid.Row="1"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
IsVisible="{Binding IsGrid}" />
|
||||
</Grid>
|
||||
</ContentControl>
|
||||
<Grid Grid.Row="1"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
ZIndex="1000"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
ZIndex="1000">
|
||||
<Grid
|
||||
HorizontalAlignment="Center"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Margin="40"
|
||||
VerticalAlignment="Center">
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowLoadProgress}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
Grid.RowSpan="2"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Grid.Column="0"
|
||||
Width="256"
|
||||
Height="256"
|
||||
Margin="10"
|
||||
@ -383,62 +472,64 @@
|
||||
BorderBrush="Black"
|
||||
BorderThickness="2"
|
||||
BoxShadow="4 4 32 8 #40000000"
|
||||
CornerRadius="3">
|
||||
CornerRadius="3"
|
||||
IsVisible="{Binding ShowLoadProgress}">
|
||||
<Image
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Width="256"
|
||||
Height="256"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Source="{Binding SelectedIcon, Converter={StaticResource ByteImage}}" />
|
||||
</Border>
|
||||
<Grid Grid.Column="1"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowLoadProgress}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Grid.Row="0"
|
||||
Margin="10"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding LoadHeading}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Text="{Binding LoadHeading}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
CornerRadius="5"
|
||||
ClipToBounds="True"
|
||||
BorderBrush="{Binding ProgressBarBackgroundColor}"
|
||||
Margin="10"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="10"
|
||||
BorderThickness="1">
|
||||
BorderBrush="{Binding ProgressBarBackgroundColor}"
|
||||
BorderThickness="1"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5"
|
||||
IsVisible="{Binding ShowLoadProgress}">
|
||||
<ProgressBar
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Height="10"
|
||||
MinWidth="500"
|
||||
Margin="0"
|
||||
Padding="0"
|
||||
CornerRadius="5"
|
||||
ClipToBounds="True"
|
||||
MinWidth="500"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{Binding ProgressBarBackgroundColor}"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5"
|
||||
Foreground="{Binding ProgressBarForegroundColor}"
|
||||
IsIndeterminate="{Binding IsLoadingIndeterminate}"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Maximum="{Binding ProgressMaximum}"
|
||||
Minimum="0"
|
||||
IsIndeterminate="{Binding IsLoadingIndeterminate}"
|
||||
Value="{Binding ProgressValue}" />
|
||||
</Border>
|
||||
<TextBlock
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Grid.Row="2"
|
||||
Margin="10"
|
||||
FontSize="18"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Text="{Binding CacheLoadStatus}"
|
||||
TextAlignment="Left" />
|
||||
</Grid>
|
||||
@ -450,8 +541,8 @@
|
||||
Height="30"
|
||||
Margin="0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
DockPanel.Dock="Bottom"
|
||||
IsVisible="{Binding ShowMenuAndStatusBar}">
|
||||
<Grid.ColumnDefinitions>
|
||||
@ -460,8 +551,11 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" IsVisible="{Binding EnableNonGameRunningControls}"
|
||||
VerticalAlignment="Center" Margin="10,0">
|
||||
<StackPanel
|
||||
Grid.Column="0"
|
||||
Margin="10,0"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding EnableNonGameRunningControls}">
|
||||
<Grid Margin="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@ -476,7 +570,10 @@
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
Command="{ReflectionBinding LoadApplications}">
|
||||
<ui:SymbolIcon Symbol="Refresh" Height="100" Width="50" />
|
||||
<ui:SymbolIcon
|
||||
Width="50"
|
||||
Height="100"
|
||||
Symbol="Refresh" />
|
||||
</Button>
|
||||
<TextBlock
|
||||
Name="LoadStatus"
|
||||
@ -489,11 +586,11 @@
|
||||
Name="LoadProgressBar"
|
||||
Grid.Column="2"
|
||||
Height="6"
|
||||
Maximum="{Binding StatusBarProgressMaximum}"
|
||||
Value="{Binding StatusBarProgressValue}"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource HighlightColor}"
|
||||
IsVisible="{Binding EnableNonGameRunningControls}" />
|
||||
IsVisible="{Binding EnableNonGameRunningControls}"
|
||||
Maximum="{Binding StatusBarProgressMaximum}"
|
||||
Value="{Binding StatusBarProgressValue}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
@ -505,132 +602,137 @@
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Name="VsyncStatus"
|
||||
Margin="0,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{Binding VsyncColor}"
|
||||
PointerReleased="VsyncStatus_PointerReleased"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Margin="0,0,5,0"
|
||||
PointerReleased="VsyncStatus_PointerReleased"
|
||||
Text="VSync"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Margin="2,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
BorderThickness="1"
|
||||
Height="12"
|
||||
BorderBrush="Gray" />
|
||||
Margin="2,0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
Name="DockedStatus"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
PointerReleased="DockedStatus_PointerReleased"
|
||||
Text="{Binding DockedStatusText}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Margin="2,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
BorderThickness="1"
|
||||
Height="12"
|
||||
BorderBrush="Gray" />
|
||||
Margin="2,0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
Name="AspectRatioStatus"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
PointerReleased="AspectRatioStatus_PointerReleased"
|
||||
Text="{Binding AspectRatioStatusText}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Margin="2,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
BorderThickness="1"
|
||||
Height="12"
|
||||
BorderBrush="Gray" />
|
||||
Margin="2,0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<ui:ToggleSplitButton
|
||||
Name="VolumeStatus"
|
||||
Margin="-2,0,-3,0"
|
||||
Padding="5,0,0,5"
|
||||
Name="VolumeStatus"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
BorderBrush="{DynamicResource ThemeContentBackgroundColor}"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
BorderBrush="{DynamicResource ThemeContentBackgroundColor}"
|
||||
Content="{Binding VolumeStatusText}"
|
||||
IsChecked="{Binding VolumeMuted}"
|
||||
Content="{Binding VolumeStatusText}">
|
||||
IsVisible="{Binding !ShowLoadProgress}">
|
||||
<ui:ToggleSplitButton.Flyout>
|
||||
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
|
||||
<Grid Margin="0">
|
||||
<Slider Value="{Binding Volume}"
|
||||
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
|
||||
Minimum="0"
|
||||
Maximum="1"
|
||||
TickFrequency="0.05"
|
||||
IsSnapToTickEnabled="True"
|
||||
Padding="0"
|
||||
Margin="0"
|
||||
SmallChange="0.01"
|
||||
LargeChange="0.05"
|
||||
Width="150" />
|
||||
<Slider
|
||||
Width="150"
|
||||
Margin="0"
|
||||
Padding="0"
|
||||
IsSnapToTickEnabled="True"
|
||||
LargeChange="0.05"
|
||||
Maximum="1"
|
||||
Minimum="0"
|
||||
SmallChange="0.01"
|
||||
TickFrequency="0.05"
|
||||
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
|
||||
Value="{Binding Volume}" />
|
||||
</Grid>
|
||||
</Flyout>
|
||||
</ui:ToggleSplitButton.Flyout>
|
||||
</ui:ToggleSplitButton>
|
||||
<Border
|
||||
Width="2"
|
||||
Margin="2,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
BorderThickness="1"
|
||||
Height="12"
|
||||
BorderBrush="Gray" />
|
||||
Margin="2,0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Text="{Binding GameStatusText}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Margin="2,0"
|
||||
BorderThickness="1"
|
||||
Height="12"
|
||||
BorderBrush="Gray" />
|
||||
Margin="2,0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Text="{Binding FifoStatusText}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Margin="2,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
BorderThickness="1"
|
||||
Height="12"
|
||||
BorderBrush="Gray" />
|
||||
Margin="2,0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Text="{Binding GpuStatusText}"
|
||||
TextAlignment="Left" />
|
||||
</StackPanel>
|
||||
<StackPanel VerticalAlignment="Center" IsVisible="{Binding ShowFirmwareStatus}" Grid.Column="3"
|
||||
Orientation="Horizontal" Margin="10, 0">
|
||||
<StackPanel
|
||||
Grid.Column="3"
|
||||
Margin="10,0"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowFirmwareStatus}"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Name="FirmwareStatus"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0"
|
||||
Text="{locale:Locale StatusBarSystemVersion}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
@ -1,18 +1,26 @@
|
||||
<window:StyleableWindow xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.UpdaterWindow"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
CanResize="False"
|
||||
SizeToContent="Height"
|
||||
Width="500" MinHeight="500" Height="500"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
MinWidth="500"
|
||||
Title="Ryujinx Updater">
|
||||
<Grid Margin="20" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.UpdaterWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Title="Ryujinx Updater"
|
||||
Width="500"
|
||||
Height="500"
|
||||
MinWidth="500"
|
||||
MinHeight="500"
|
||||
d:DesignHeight="350"
|
||||
d:DesignWidth="400"
|
||||
CanResize="False"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
@ -20,17 +28,38 @@
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="1" HorizontalAlignment="Stretch" TextAlignment="Center" Height="20" Name="MainText" />
|
||||
<TextBlock Height="20" HorizontalAlignment="Stretch" TextAlignment="Center" Name="SecondaryText" Grid.Row="2" />
|
||||
<ProgressBar IsVisible="False" HorizontalAlignment="Stretch" Name="ProgressBar" Maximum="100" Minimum="0"
|
||||
Margin="20" Grid.Row="3" />
|
||||
<StackPanel IsVisible="False" Name="ButtonBox" Orientation="Horizontal" Spacing="20" Grid.Row="4"
|
||||
HorizontalAlignment="Right">
|
||||
<Button Command="{Binding YesPressed}" MinWidth="50">
|
||||
<TextBlock TextAlignment="Center" Text="{locale:Locale InputDialogYes}" />
|
||||
<TextBlock
|
||||
Name="MainText"
|
||||
Grid.Row="1"
|
||||
Height="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
Name="SecondaryText"
|
||||
Grid.Row="2"
|
||||
Height="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
TextAlignment="Center" />
|
||||
<ProgressBar
|
||||
Name="ProgressBar"
|
||||
Grid.Row="3"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="False"
|
||||
Maximum="100"
|
||||
Minimum="0" />
|
||||
<StackPanel
|
||||
Name="ButtonBox"
|
||||
Grid.Row="4"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="False"
|
||||
Orientation="Horizontal"
|
||||
Spacing="20">
|
||||
<Button MinWidth="50" Command="{Binding YesPressed}">
|
||||
<TextBlock Text="{locale:Locale InputDialogYes}" TextAlignment="Center" />
|
||||
</Button>
|
||||
<Button Command="{Binding NoPressed}" MinWidth="50">
|
||||
<TextBlock TextAlignment="Center" Text="{locale:Locale InputDialogNo}" />
|
||||
<Button MinWidth="50" Command="{Binding NoPressed}">
|
||||
<TextBlock Text="{locale:Locale InputDialogNo}" TextAlignment="Center" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
@ -22,7 +22,17 @@ namespace Ryujinx.Common
|
||||
|
||||
public static long AlignUp(long value, int size)
|
||||
{
|
||||
return (value + (size - 1)) & -(long)size;
|
||||
return AlignUp(value, (long)size);
|
||||
}
|
||||
|
||||
public static ulong AlignUp(ulong value, ulong size)
|
||||
{
|
||||
return (ulong)AlignUp((long)value, (long)size);
|
||||
}
|
||||
|
||||
public static long AlignUp(long value, long size)
|
||||
{
|
||||
return (value + (size - 1)) & -size;
|
||||
}
|
||||
|
||||
public static uint AlignDown(uint value, int size)
|
||||
@ -42,7 +52,17 @@ namespace Ryujinx.Common
|
||||
|
||||
public static long AlignDown(long value, int size)
|
||||
{
|
||||
return value & -(long)size;
|
||||
return AlignDown(value, (long)size);
|
||||
}
|
||||
|
||||
public static ulong AlignDown(ulong value, ulong size)
|
||||
{
|
||||
return (ulong)AlignDown((long)value, (long)size);
|
||||
}
|
||||
|
||||
public static long AlignDown(long value, long size)
|
||||
{
|
||||
return value & -size;
|
||||
}
|
||||
|
||||
public static int DivRoundUp(int value, int dividend)
|
||||
|
64
Ryujinx.Cpu/ExceptionCallbacks.cs
Normal file
64
Ryujinx.Cpu/ExceptionCallbacks.cs
Normal file
@ -0,0 +1,64 @@
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception callback without any additional arguments.
|
||||
/// </summary>
|
||||
/// <param name="context">Context for the thread where the exception was triggered</param>
|
||||
public delegate void ExceptionCallbackNoArgs(IExecutionContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Exception callback.
|
||||
/// </summary>
|
||||
/// <param name="context">Context for the thread where the exception was triggered</param>
|
||||
/// <param name="address">Address of the instruction that caused the exception</param>
|
||||
/// <param name="imm">Immediate value of the instruction that caused the exception, or for undefined instruction, the instruction itself</param>
|
||||
public delegate void ExceptionCallback(IExecutionContext context, ulong address, int imm);
|
||||
|
||||
/// <summary>
|
||||
/// Stores handlers for the various CPU exceptions.
|
||||
/// </summary>
|
||||
public struct ExceptionCallbacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Handler for CPU interrupts triggered using <see cref="IExecutionContext.RequestInterrupt"/>.
|
||||
/// </summary>
|
||||
public readonly ExceptionCallbackNoArgs InterruptCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for CPU software interrupts caused by the Arm BRK instruction.
|
||||
/// </summary>
|
||||
public readonly ExceptionCallback BreakCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for CPU software interrupts caused by the Arm SVC instruction.
|
||||
/// </summary>
|
||||
public readonly ExceptionCallback SupervisorCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for CPU software interrupts caused by any undefined Arm instruction.
|
||||
/// </summary>
|
||||
public readonly ExceptionCallback UndefinedCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new exception callbacks structure.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All handlers are optional, and if null, the CPU will just continue executing as if nothing happened.
|
||||
/// </remarks>
|
||||
/// <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="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>
|
||||
public ExceptionCallbacks(
|
||||
ExceptionCallbackNoArgs interruptCallback = null,
|
||||
ExceptionCallback breakCallback = null,
|
||||
ExceptionCallback supervisorCallback = null,
|
||||
ExceptionCallback undefinedCallback = null)
|
||||
{
|
||||
InterruptCallback = interruptCallback;
|
||||
BreakCallback = breakCallback;
|
||||
SupervisorCallback = supervisorCallback;
|
||||
UndefinedCallback = undefinedCallback;
|
||||
}
|
||||
}
|
||||
}
|
39
Ryujinx.Cpu/ICpuContext.cs
Normal file
39
Ryujinx.Cpu/ICpuContext.cs
Normal file
@ -0,0 +1,39 @@
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
/// <summary>
|
||||
/// CPU context interface.
|
||||
/// </summary>
|
||||
public interface ICpuContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new execution context that will store thread CPU register state when executing guest code.
|
||||
/// </summary>
|
||||
/// <param name="exceptionCallbacks">Optional functions to be called when the CPU receives an interrupt</param>
|
||||
/// <returns>Execution context</returns>
|
||||
IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks);
|
||||
|
||||
/// <summary>
|
||||
/// Starts executing code at a specified entry point address.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This function only returns when the execution is stopped, by calling <see cref="IExecutionContext.StopRunning"/>.
|
||||
/// </remarks>
|
||||
/// <param name="context">Execution context to be used for this run</param>
|
||||
/// <param name="address">Entry point address</param>
|
||||
void Execute(IExecutionContext context, ulong address);
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates the instruction cache for a given memory region.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should be called if code is modified to make the CPU emulator aware of the modifications,
|
||||
/// otherwise it might run stale code which will lead to errors and crashes.
|
||||
/// Calling this function is not necessary if the code memory was modified by guest code,
|
||||
/// as the expectation is that it will do it on its own using the appropriate cache invalidation instructions,
|
||||
/// except on Arm32 where those instructions can't be used in unprivileged mode.
|
||||
/// </remarks>
|
||||
/// <param name="address">Address of the region to be invalidated</param>
|
||||
/// <param name="size">Size of the region to be invalidated</param>
|
||||
void InvalidateCacheRegion(ulong address, ulong size);
|
||||
}
|
||||
}
|
18
Ryujinx.Cpu/ICpuEngine.cs
Normal file
18
Ryujinx.Cpu/ICpuEngine.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using ARMeilleure.Memory;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
/// <summary>
|
||||
/// CPU execution engine interface.
|
||||
/// </summary>
|
||||
public interface ICpuEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new CPU context that can be used to run code for multiple threads sharing an address space.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">Memory manager for the address space of the context</param>
|
||||
/// <param name="for64Bit">Indicates if the context will be used to run 64-bit or 32-bit Arm code</param>
|
||||
/// <returns>CPU context</returns>
|
||||
ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit);
|
||||
}
|
||||
}
|
112
Ryujinx.Cpu/IExecutionContext.cs
Normal file
112
Ryujinx.Cpu/IExecutionContext.cs
Normal file
@ -0,0 +1,112 @@
|
||||
using ARMeilleure.State;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
/// <summary>
|
||||
/// CPU register state interface.
|
||||
/// </summary>
|
||||
public interface IExecutionContext : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Current Program Counter.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In some implementations, this value might not be accurate and might not point to the last instruction executed.
|
||||
/// </remarks>
|
||||
ulong Pc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Thread ID Register (EL0).
|
||||
/// </summary>
|
||||
long TpidrEl0 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Thread ID Register (read-only) (EL0).
|
||||
/// </summary>
|
||||
long TpidrroEl0 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Processor State register.
|
||||
/// </summary>
|
||||
uint Pstate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Floating-point Control Register.
|
||||
/// </summary>
|
||||
uint Fpcr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Floating-point Status Register.
|
||||
/// </summary>
|
||||
uint Fpsr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whenever the CPU is running 64-bit (AArch64 mode) or 32-bit (AArch32 mode) code.
|
||||
/// </summary>
|
||||
bool IsAarch32 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whenever the CPU is still running code.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Even if this is false, the guest code might be still exiting.
|
||||
/// One must not assume that the code is no longer running from this property alone.
|
||||
/// </remarks>
|
||||
bool Running { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of a general purpose register.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The special <paramref name="index"/> of 31 can be used to access the SP (Stack Pointer) register.
|
||||
/// </remarks>
|
||||
/// <param name="index">Index of the register, in the range 0-31 (inclusive)</param>
|
||||
/// <returns>The register value</returns>
|
||||
ulong GetX(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of a general purpose register.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The special <paramref name="index"/> of 31 can be used to access the SP (Stack Pointer) register.
|
||||
/// </remarks>
|
||||
/// <param name="index">Index of the register, in the range 0-31 (inclusive)</param>
|
||||
/// <param name="value">Value to be set</param>
|
||||
void SetX(int index, ulong value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of a FP/SIMD register.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the register, in the range 0-31 (inclusive)</param>
|
||||
/// <returns>The register value</returns>
|
||||
V128 GetV(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of a FP/SIMD register.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the register, in the range 0-31 (inclusive)</param>
|
||||
/// <param name="value">Value to be set</param>
|
||||
void SetV(int index, V128 value);
|
||||
|
||||
/// <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.
|
||||
/// </remarks>
|
||||
void RequestInterrupt();
|
||||
|
||||
/// <summary>
|
||||
/// Requests the thread to stop running guest code and return as soon as possible.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The thread might not stop immediately.
|
||||
/// One must not assume that guest code is no longer being executed by the thread after calling this function.
|
||||
/// After a thread has been stopped, it can't be restarted with the same <see cref="IExecutionContext"/>.
|
||||
/// If you only need to pause the thread temporarily, use <see cref="RequestInterrupt"/> instead.
|
||||
/// </remarks>
|
||||
void StopRunning();
|
||||
}
|
||||
}
|
31
Ryujinx.Cpu/ITickSource.cs
Normal file
31
Ryujinx.Cpu/ITickSource.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using ARMeilleure.State;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
/// <summary>
|
||||
/// Tick source interface.
|
||||
/// </summary>
|
||||
public interface ITickSource : ICounter
|
||||
{
|
||||
/// <summary>
|
||||
/// Time elapsed since the counter was created.
|
||||
/// </summary>
|
||||
TimeSpan ElapsedTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Time elapsed since the counter was created, in seconds.
|
||||
/// </summary>
|
||||
double ElapsedSeconds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Stops counting.
|
||||
/// </summary>
|
||||
void Suspend();
|
||||
|
||||
/// <summary>
|
||||
/// Resumes counting after a call to <see cref="Suspend"/>.
|
||||
/// </summary>
|
||||
void Resume();
|
||||
}
|
||||
}
|
41
Ryujinx.Cpu/Jit/JitCpuContext.cs
Normal file
41
Ryujinx.Cpu/Jit/JitCpuContext.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Translation;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
class JitCpuContext : ICpuContext
|
||||
{
|
||||
private readonly ITickSource _tickSource;
|
||||
private readonly Translator _translator;
|
||||
|
||||
public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
|
||||
{
|
||||
_tickSource = tickSource;
|
||||
_translator = new Translator(new JitMemoryAllocator(), memory, for64Bit);
|
||||
memory.UnmapEvent += UnmapHandler;
|
||||
}
|
||||
|
||||
private void UnmapHandler(ulong address, ulong size)
|
||||
{
|
||||
_translator.InvalidateJitCacheRegion(address, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks)
|
||||
{
|
||||
return new JitExecutionContext(new JitMemoryAllocator(), _tickSource, exceptionCallbacks);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Execute(IExecutionContext context, ulong address)
|
||||
{
|
||||
_translator.Execute(((JitExecutionContext)context).Impl, address);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||
{
|
||||
_translator.InvalidateJitCacheRegion(address, size);
|
||||
}
|
||||
}
|
||||
}
|
20
Ryujinx.Cpu/Jit/JitEngine.cs
Normal file
20
Ryujinx.Cpu/Jit/JitEngine.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using ARMeilleure.Memory;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
public class JitEngine : ICpuEngine
|
||||
{
|
||||
private readonly ITickSource _tickSource;
|
||||
|
||||
public JitEngine(ITickSource tickSource)
|
||||
{
|
||||
_tickSource = tickSource;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit)
|
||||
{
|
||||
return new JitCpuContext(_tickSource, memoryManager, for64Bit);
|
||||
}
|
||||
}
|
||||
}
|
123
Ryujinx.Cpu/Jit/JitExecutionContext.cs
Normal file
123
Ryujinx.Cpu/Jit/JitExecutionContext.cs
Normal file
@ -0,0 +1,123 @@
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.State;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
class JitExecutionContext : IExecutionContext
|
||||
{
|
||||
private readonly ExecutionContext _impl;
|
||||
internal ExecutionContext Impl => _impl;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong Pc => _impl.Pc;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long TpidrEl0
|
||||
{
|
||||
get => _impl.TpidrEl0;
|
||||
set => _impl.TpidrEl0 = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long TpidrroEl0
|
||||
{
|
||||
get => _impl.TpidrroEl0;
|
||||
set => _impl.TpidrroEl0 = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint Pstate
|
||||
{
|
||||
get => _impl.Pstate;
|
||||
set => _impl.Pstate = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint Fpcr
|
||||
{
|
||||
get => (uint)_impl.Fpcr;
|
||||
set => _impl.Fpcr = (FPCR)value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint Fpsr
|
||||
{
|
||||
get => (uint)_impl.Fpsr;
|
||||
set => _impl.Fpsr = (FPSR)value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsAarch32
|
||||
{
|
||||
get => _impl.IsAarch32;
|
||||
set => _impl.IsAarch32 = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Running => _impl.Running;
|
||||
|
||||
private readonly ExceptionCallbacks _exceptionCallbacks;
|
||||
|
||||
public JitExecutionContext(IJitMemoryAllocator allocator, ICounter counter, ExceptionCallbacks exceptionCallbacks)
|
||||
{
|
||||
_impl = new ExecutionContext(
|
||||
allocator,
|
||||
counter,
|
||||
InterruptHandler,
|
||||
BreakHandler,
|
||||
SupervisorCallHandler,
|
||||
UndefinedHandler);
|
||||
|
||||
_exceptionCallbacks = exceptionCallbacks;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong GetX(int index) => _impl.GetX(index);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetX(int index, ulong value) => _impl.SetX(index, value);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public V128 GetV(int index) => _impl.GetV(index);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetV(int index, V128 value) => _impl.SetV(index, value);
|
||||
|
||||
private void InterruptHandler(ExecutionContext context)
|
||||
{
|
||||
_exceptionCallbacks.InterruptCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
private void BreakHandler(ExecutionContext context, ulong address, int imm)
|
||||
{
|
||||
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
private void SupervisorCallHandler(ExecutionContext context, ulong address, int imm)
|
||||
{
|
||||
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
private void UndefinedHandler(ExecutionContext context, ulong address, int opCode)
|
||||
{
|
||||
_exceptionCallbacks.UndefinedCallback?.Invoke(this, address, opCode);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
_impl.RequestInterrupt();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void StopRunning()
|
||||
{
|
||||
_impl.StopRunning();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_impl.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Memory;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
class JitMemoryAllocator : IJitMemoryAllocator
|
||||
public class JitMemoryAllocator : IJitMemoryAllocator
|
||||
{
|
||||
public IJitMemoryBlock Allocate(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.None);
|
||||
public IJitMemoryBlock Reserve(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.Reserve);
|
@ -2,9 +2,9 @@
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
class JitMemoryBlock : IJitMemoryBlock
|
||||
public class JitMemoryBlock : IJitMemoryBlock
|
||||
{
|
||||
private readonly MemoryBlock _impl;
|
||||
|
@ -10,7 +10,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a CPU memory manager.
|
@ -8,12 +8,12 @@ using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
|
||||
/// </summary>
|
||||
public class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
public sealed class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
{
|
||||
public const int PageBits = 12;
|
||||
public const int PageSize = 1 << PageBits;
|
45
Ryujinx.Cpu/TickSource.cs
Normal file
45
Ryujinx.Cpu/TickSource.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
public class TickSource : ITickSource
|
||||
{
|
||||
private static Stopwatch _tickCounter;
|
||||
|
||||
private static double _hostTickFreq;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong Frequency { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong Counter => (ulong)(ElapsedSeconds * Frequency);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TimeSpan ElapsedTime => _tickCounter.Elapsed;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double ElapsedSeconds => _tickCounter.ElapsedTicks * _hostTickFreq;
|
||||
|
||||
public TickSource(ulong frequency)
|
||||
{
|
||||
Frequency = frequency;
|
||||
_hostTickFreq = 1.0 / Stopwatch.Frequency;
|
||||
|
||||
_tickCounter = new Stopwatch();
|
||||
_tickCounter.Start();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Suspend()
|
||||
{
|
||||
_tickCounter.Stop();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Resume()
|
||||
{
|
||||
_tickCounter.Start();
|
||||
}
|
||||
}
|
||||
}
|
@ -10,9 +10,10 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
void ClearBuffer(BufferHandle destination, int offset, int size, uint value);
|
||||
|
||||
void ClearRenderTargetColor(int index, uint componentMask, ColorF color);
|
||||
void ClearRenderTargetColor(int index, int layer, uint componentMask, ColorF color);
|
||||
|
||||
void ClearRenderTargetDepthStencil(
|
||||
int layer,
|
||||
float depthValue,
|
||||
bool depthMask,
|
||||
int stencilValue,
|
||||
|
@ -4,19 +4,21 @@
|
||||
{
|
||||
public CommandType CommandType => CommandType.ClearRenderTargetColor;
|
||||
private int _index;
|
||||
private int _layer;
|
||||
private uint _componentMask;
|
||||
private ColorF _color;
|
||||
|
||||
public void Set(int index, uint componentMask, ColorF color)
|
||||
public void Set(int index, int layer, uint componentMask, ColorF color)
|
||||
{
|
||||
_index = index;
|
||||
_layer = layer;
|
||||
_componentMask = componentMask;
|
||||
_color = color;
|
||||
}
|
||||
|
||||
public static void Run(ref ClearRenderTargetColorCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
renderer.Pipeline.ClearRenderTargetColor(command._index, command._componentMask, command._color);
|
||||
renderer.Pipeline.ClearRenderTargetColor(command._index, command._layer, command._componentMask, command._color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,15 @@
|
||||
struct ClearRenderTargetDepthStencilCommand : IGALCommand
|
||||
{
|
||||
public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil;
|
||||
private int _layer;
|
||||
private float _depthValue;
|
||||
private bool _depthMask;
|
||||
private int _stencilValue;
|
||||
private int _stencilMask;
|
||||
|
||||
public void Set(float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
||||
public void Set(int layer, float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
||||
{
|
||||
_layer = layer;
|
||||
_depthValue = depthValue;
|
||||
_depthMask = depthMask;
|
||||
_stencilValue = stencilValue;
|
||||
@ -18,7 +20,7 @@
|
||||
|
||||
public static void Run(ref ClearRenderTargetDepthStencilCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
renderer.Pipeline.ClearRenderTargetDepthStencil(command._depthValue, command._depthMask, command._stencilValue, command._stencilMask);
|
||||
renderer.Pipeline.ClearRenderTargetDepthStencil(command._layer, command._depthValue, command._depthMask, command._stencilValue, command._stencilMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,15 +40,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void ClearRenderTargetColor(int index, uint componentMask, ColorF color)
|
||||
public void ClearRenderTargetColor(int index, int layer, uint componentMask, ColorF color)
|
||||
{
|
||||
_renderer.New<ClearRenderTargetColorCommand>().Set(index, componentMask, color);
|
||||
_renderer.New<ClearRenderTargetColorCommand>().Set(index, layer, componentMask, color);
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void ClearRenderTargetDepthStencil(float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
||||
public void ClearRenderTargetDepthStencil(int layer, float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
||||
{
|
||||
_renderer.New<ClearRenderTargetDepthStencilCommand>().Set(depthValue, depthMask, stencilValue, stencilMask);
|
||||
_renderer.New<ClearRenderTargetDepthStencilCommand>().Set(layer, depthValue, depthMask, stencilValue, stencilMask);
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
|
@ -13,4 +13,12 @@ namespace Ryujinx.Graphics.GAL
|
||||
CubemapArray,
|
||||
TextureBuffer
|
||||
}
|
||||
|
||||
public static class TargetExtensions
|
||||
{
|
||||
public static bool IsMultisample(this Target target)
|
||||
{
|
||||
return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray;
|
||||
}
|
||||
}
|
||||
}
|
@ -188,6 +188,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
_channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers);
|
||||
_channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers);
|
||||
|
||||
int maxTextureBinding = -1;
|
||||
int maxImageBinding = -1;
|
||||
|
||||
TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count);
|
||||
|
||||
for (int index = 0; index < info.Textures.Count; index++)
|
||||
@ -202,6 +205,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
|
||||
if (descriptor.Binding > maxTextureBinding)
|
||||
{
|
||||
maxTextureBinding = descriptor.Binding;
|
||||
}
|
||||
}
|
||||
|
||||
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count);
|
||||
@ -220,9 +228,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
|
||||
if (descriptor.Binding > maxImageBinding)
|
||||
{
|
||||
maxImageBinding = descriptor.Binding;
|
||||
}
|
||||
}
|
||||
|
||||
_channel.TextureManager.CommitComputeBindings();
|
||||
_channel.TextureManager.SetComputeMaxBindings(maxTextureBinding, maxImageBinding);
|
||||
|
||||
// Should never return false for mismatching spec state, since the shader was fetched above.
|
||||
_channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
|
||||
|
||||
_channel.BufferManager.CommitComputeBindings();
|
||||
|
||||
_context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth);
|
||||
|
@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
private bool _instancedDrawPending;
|
||||
private bool _instancedIndexed;
|
||||
private bool _instancedIndexedInline;
|
||||
|
||||
private int _instancedFirstIndex;
|
||||
private int _instancedFirstVertex;
|
||||
@ -134,13 +135,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
_instancedDrawPending = true;
|
||||
|
||||
int ibCount = _drawState.IbStreamer.InlineIndexCount;
|
||||
|
||||
_instancedIndexed = _drawState.DrawIndexed;
|
||||
_instancedIndexedInline = ibCount != 0;
|
||||
|
||||
_instancedFirstIndex = firstIndex;
|
||||
_instancedFirstVertex = (int)_state.State.FirstVertex;
|
||||
_instancedFirstInstance = (int)_state.State.FirstInstance;
|
||||
|
||||
_instancedIndexCount = indexCount;
|
||||
_instancedIndexCount = ibCount != 0 ? ibCount : indexCount;
|
||||
|
||||
var drawState = _state.State.VertexBufferDrawState;
|
||||
|
||||
@ -451,8 +455,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
_instancedDrawPending = false;
|
||||
|
||||
if (_instancedIndexed)
|
||||
bool indexedInline = _instancedIndexedInline;
|
||||
|
||||
if (_instancedIndexed || indexedInline)
|
||||
{
|
||||
if (indexedInline)
|
||||
{
|
||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount();
|
||||
BufferRange br = new BufferRange(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4);
|
||||
|
||||
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.DrawIndexed(
|
||||
_instancedIndexCount,
|
||||
_instanceIndex + 1,
|
||||
@ -491,8 +505,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
}
|
||||
|
||||
int index = (argument >> 6) & 0xf;
|
||||
int layer = (argument >> 10) & 0x3ff;
|
||||
|
||||
engine.UpdateRenderTargetState(useControl: false, singleUse: index);
|
||||
engine.UpdateRenderTargetState(useControl: false, layered: layer != 0, singleUse: index);
|
||||
|
||||
// If there is a mismatch on the host clip region and the one explicitly defined by the guest
|
||||
// on the screen scissor state, then we need to force only one texture to be bound to avoid
|
||||
@ -567,7 +582,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
ColorF color = new ColorF(clearColor.Red, clearColor.Green, clearColor.Blue, clearColor.Alpha);
|
||||
|
||||
_context.Renderer.Pipeline.ClearRenderTargetColor(index, componentMask, color);
|
||||
_context.Renderer.Pipeline.ClearRenderTargetColor(index, layer, componentMask, color);
|
||||
}
|
||||
|
||||
if (clearDepth || clearStencil)
|
||||
@ -588,6 +603,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.ClearRenderTargetDepthStencil(
|
||||
layer,
|
||||
depthValue,
|
||||
clearDepth,
|
||||
stencilValue,
|
||||
|
@ -20,6 +20,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
public bool HasInlineIndexData => _inlineIndexCount != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Total numbers of indices that have been pushed.
|
||||
/// </summary>
|
||||
public int InlineIndexCount => _inlineIndexCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle for the host buffer currently holding the inline index buffer data.
|
||||
/// </summary>
|
||||
|
@ -44,7 +44,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
private enum ReportCounterType
|
||||
{
|
||||
Zero = 0,
|
||||
Payload = 0,
|
||||
InputVertices = 1,
|
||||
InputPrimitives = 3,
|
||||
VertexShaderInvocations = 5,
|
||||
@ -169,8 +169,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ReportCounterType.Zero:
|
||||
resultHandler(null, 0);
|
||||
case ReportCounterType.Payload:
|
||||
resultHandler(null, (ulong)_state.State.SemaphorePayload);
|
||||
break;
|
||||
case ReportCounterType.SamplesPassed:
|
||||
counter = _context.Renderer.ReportCounter(CounterType.SamplesPassed, resultHandler, false);
|
||||
|
@ -201,7 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
// of the shader for the new state.
|
||||
if (_shaderSpecState != null)
|
||||
{
|
||||
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState()))
|
||||
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState(), false))
|
||||
{
|
||||
ForceShaderUpdate();
|
||||
}
|
||||
@ -275,7 +275,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
UpdateStorageBuffers();
|
||||
|
||||
_channel.TextureManager.CommitGraphicsBindings();
|
||||
if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState))
|
||||
{
|
||||
// Shader must be reloaded.
|
||||
UpdateShaderState();
|
||||
}
|
||||
|
||||
_channel.BufferManager.CommitGraphicsBindings();
|
||||
}
|
||||
|
||||
@ -362,8 +367,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// Updates render targets (color and depth-stencil buffers) based on current render target state.
|
||||
/// </summary>
|
||||
/// <param name="useControl">Use draw buffers information from render target control register</param>
|
||||
/// <param name="layered">Indicates if the texture is layered</param>
|
||||
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
|
||||
public void UpdateRenderTargetState(bool useControl, int singleUse = -1)
|
||||
public void UpdateRenderTargetState(bool useControl, bool layered = false, int singleUse = -1)
|
||||
{
|
||||
var memoryManager = _channel.MemoryManager;
|
||||
var rtControl = _state.State.RtControl;
|
||||
@ -399,7 +405,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
colorState,
|
||||
_vtgWritesRtLayer,
|
||||
_vtgWritesRtLayer || layered,
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
sizeHint);
|
||||
@ -433,6 +439,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
memoryManager,
|
||||
dsState,
|
||||
dsSize,
|
||||
_vtgWritesRtLayer || layered,
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
sizeHint);
|
||||
@ -1148,6 +1155,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
return;
|
||||
}
|
||||
|
||||
int maxTextureBinding = -1;
|
||||
int maxImageBinding = -1;
|
||||
|
||||
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
|
||||
|
||||
if (info.UsesRtLayer)
|
||||
@ -1167,6 +1177,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
|
||||
if (descriptor.Binding > maxTextureBinding)
|
||||
{
|
||||
maxTextureBinding = descriptor.Binding;
|
||||
}
|
||||
}
|
||||
|
||||
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
|
||||
@ -1185,8 +1200,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
|
||||
if (descriptor.Binding > maxImageBinding)
|
||||
{
|
||||
maxImageBinding = descriptor.Binding;
|
||||
}
|
||||
}
|
||||
|
||||
_channel.TextureManager.SetGraphicsMaxBindings(maxTextureBinding, maxImageBinding);
|
||||
|
||||
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
|
||||
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
|
||||
}
|
||||
|
@ -131,10 +131,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// Updates render targets (color and depth-stencil buffers) based on current render target state.
|
||||
/// </summary>
|
||||
/// <param name="useControl">Use draw buffers information from render target control register</param>
|
||||
/// <param name="layered">Indicates if the texture is layered</param>
|
||||
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
|
||||
public void UpdateRenderTargetState(bool useControl, int singleUse = -1)
|
||||
public void UpdateRenderTargetState(bool useControl, bool layered = false, int singleUse = -1)
|
||||
{
|
||||
_stateUpdater.UpdateRenderTargetState(useControl, singleUse);
|
||||
_stateUpdater.UpdateRenderTargetState(useControl, layered, singleUse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Ryujinx.Cpu.Tracking;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
@ -16,6 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
protected GpuContext Context;
|
||||
protected PhysicalMemory PhysicalMemory;
|
||||
protected int SequenceNumber;
|
||||
protected int ModifiedSequenceNumber;
|
||||
|
||||
protected T1[] Items;
|
||||
protected T2[] DescriptorCache;
|
||||
@ -41,6 +43,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
private readonly CpuMultiRegionHandle _memoryTracking;
|
||||
private readonly Action<ulong, ulong> _modifiedDelegate;
|
||||
|
||||
private int _modifiedSequenceOffset;
|
||||
private bool _modified;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU resource pool.
|
||||
/// </summary>
|
||||
@ -79,6 +84,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return PhysicalMemory.Read<T2>(Address + (ulong)id * DescriptorSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the descriptor for a given ID.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
|
||||
/// <returns>A reference to the descriptor</returns>
|
||||
public ref readonly T2 GetDescriptorRef(int id)
|
||||
{
|
||||
return ref MemoryMarshal.Cast<byte, T2>(PhysicalMemory.GetSpan(Address + (ulong)id * DescriptorSize, DescriptorSize))[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GPU resource with the given ID.
|
||||
/// </summary>
|
||||
@ -86,6 +101,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <returns>The GPU resource with the given ID</returns>
|
||||
public abstract T1 Get(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given ID is valid and inside the range of the pool.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
|
||||
/// <returns>True if the specified ID is valid, false otherwise</returns>
|
||||
public bool IsValidId(int id)
|
||||
{
|
||||
return (uint)id <= MaximumId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes host memory with guest memory.
|
||||
/// This causes invalidation of pool entries,
|
||||
@ -93,7 +118,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
public void SynchronizeMemory()
|
||||
{
|
||||
_modified = false;
|
||||
_memoryTracking.QueryModified(_modifiedDelegate);
|
||||
|
||||
if (_modified)
|
||||
{
|
||||
UpdateModifiedSequence();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -103,6 +134,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="mSize">Size of the modified region</param>
|
||||
private void RegionModified(ulong mAddress, ulong mSize)
|
||||
{
|
||||
_modified = true;
|
||||
|
||||
if (mAddress < Address)
|
||||
{
|
||||
mAddress = Address;
|
||||
@ -118,6 +151,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
InvalidateRangeImpl(mAddress, mSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the modified sequence number using the current sequence number and offset,
|
||||
/// indicating that it has been modified.
|
||||
/// </summary>
|
||||
protected void UpdateModifiedSequence()
|
||||
{
|
||||
ModifiedSequenceNumber = SequenceNumber + _modifiedSequenceOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An action to be performed when a precise memory access occurs to this resource.
|
||||
/// Makes sure that the dirty flags are checked.
|
||||
@ -129,6 +171,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
if (write && Context.SequenceNumber == SequenceNumber)
|
||||
{
|
||||
if (ModifiedSequenceNumber == SequenceNumber + _modifiedSequenceOffset)
|
||||
{
|
||||
// The modified sequence number is offset when PreciseActions occur so that
|
||||
// users checking it will see an increment and know the pool has changed since
|
||||
// their last look, even though the main SequenceNumber has not been changed.
|
||||
|
||||
_modifiedSequenceOffset++;
|
||||
}
|
||||
|
||||
// Force the pool to be checked again the next time it is used.
|
||||
SequenceNumber--;
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
class Sampler : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// True if the sampler is disposed, false otherwise.
|
||||
/// </summary>
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Host sampler object.
|
||||
/// </summary>
|
||||
@ -101,6 +106,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
|
||||
_hostSampler.Dispose();
|
||||
_anisoSampler?.Dispose();
|
||||
}
|
||||
|
@ -48,6 +48,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
Items[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateModifiedSequence();
|
||||
}
|
||||
|
||||
SequenceNumber = Context.SequenceNumber;
|
||||
@ -71,6 +73,39 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return sampler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
|
||||
/// </summary>
|
||||
/// <returns>A number that increments each time a modification is detected</returns>
|
||||
public int CheckModified()
|
||||
{
|
||||
if (SequenceNumber != Context.SequenceNumber)
|
||||
{
|
||||
SequenceNumber = Context.SequenceNumber;
|
||||
|
||||
if (_forcedAnisotropy != GraphicsConfig.MaxAnisotropy)
|
||||
{
|
||||
_forcedAnisotropy = GraphicsConfig.MaxAnisotropy;
|
||||
|
||||
for (int i = 0; i < Items.Length; i++)
|
||||
{
|
||||
if (Items[i] != null)
|
||||
{
|
||||
Items[i].Dispose();
|
||||
|
||||
Items[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateModifiedSequence();
|
||||
}
|
||||
|
||||
SynchronizeMemory();
|
||||
}
|
||||
|
||||
return ModifiedSequenceNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of the sampler pool range invalidation.
|
||||
/// </summary>
|
||||
|
@ -100,6 +100,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
public bool AlwaysFlushOnOverlap { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Increments when the host texture is swapped, or when the texture is removed from all pools.
|
||||
/// </summary>
|
||||
public int InvalidatedSequence { get; private set; }
|
||||
|
||||
private int _depth;
|
||||
private int _layers;
|
||||
public int FirstLayer { get; private set; }
|
||||
@ -1136,32 +1141,22 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="range">Texture view physical memory ranges</param>
|
||||
/// <param name="layerSize">Layer size on the given texture</param>
|
||||
/// <param name="caps">Host GPU capabilities</param>
|
||||
/// <param name="allowMs">Indicates that multisample textures are allowed to match non-multisample requested textures</param>
|
||||
/// <param name="firstLayer">Texture view initial layer on this texture</param>
|
||||
/// <param name="firstLevel">Texture view first mipmap level on this texture</param>
|
||||
/// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns>
|
||||
public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, Capabilities caps, bool allowMs, out int firstLayer, out int firstLevel)
|
||||
public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, Capabilities caps, out int firstLayer, out int firstLevel)
|
||||
{
|
||||
TextureViewCompatibility result = TextureViewCompatibility.Full;
|
||||
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps));
|
||||
if (result != TextureViewCompatibility.Incompatible)
|
||||
{
|
||||
bool msTargetCompatible = false;
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
|
||||
|
||||
if (allowMs)
|
||||
bool bothMs = Info.Target.IsMultisample() && info.Target.IsMultisample();
|
||||
if (bothMs && (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY))
|
||||
{
|
||||
msTargetCompatible = Info.Target == Target.Texture2DMultisample && info.Target == Target.Texture2D;
|
||||
}
|
||||
|
||||
if (!msTargetCompatible)
|
||||
{
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
|
||||
|
||||
if (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY)
|
||||
{
|
||||
result = TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
result = TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
|
||||
if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat)
|
||||
@ -1417,6 +1412,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
DisposeTextures();
|
||||
|
||||
HostTexture = hostTexture;
|
||||
InvalidatedSequence++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1545,6 +1541,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
_poolOwners.Clear();
|
||||
}
|
||||
|
||||
InvalidatedSequence++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,8 +1,12 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
@ -31,21 +35,30 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
private readonly TextureBindingInfo[][] _textureBindings;
|
||||
private readonly TextureBindingInfo[][] _imageBindings;
|
||||
|
||||
private struct TextureStatePerStage
|
||||
private struct TextureState
|
||||
{
|
||||
public ITexture Texture;
|
||||
public ISampler Sampler;
|
||||
|
||||
public int TextureHandle;
|
||||
public int SamplerHandle;
|
||||
public int InvalidatedSequence;
|
||||
public Texture CachedTexture;
|
||||
public Sampler CachedSampler;
|
||||
public int ScaleIndex;
|
||||
public TextureUsageFlags UsageFlags;
|
||||
}
|
||||
|
||||
private readonly TextureStatePerStage[][] _textureState;
|
||||
private readonly TextureStatePerStage[][] _imageState;
|
||||
private TextureState[] _textureState;
|
||||
private TextureState[] _imageState;
|
||||
|
||||
private int[] _textureBindingsCount;
|
||||
private int[] _imageBindingsCount;
|
||||
|
||||
private int _textureBufferIndex;
|
||||
private int _texturePoolSequence;
|
||||
private int _samplerPoolSequence;
|
||||
|
||||
private bool _rebind;
|
||||
private int _textureBufferIndex;
|
||||
|
||||
private readonly float[] _scales;
|
||||
private bool _scaleChanged;
|
||||
@ -72,8 +85,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_textureBindings = new TextureBindingInfo[stages][];
|
||||
_imageBindings = new TextureBindingInfo[stages][];
|
||||
|
||||
_textureState = new TextureStatePerStage[stages][];
|
||||
_imageState = new TextureStatePerStage[stages][];
|
||||
_textureState = new TextureState[InitialTextureStateSize];
|
||||
_imageState = new TextureState[InitialImageStateSize];
|
||||
|
||||
_textureBindingsCount = new int[stages];
|
||||
_imageBindingsCount = new int[stages];
|
||||
@ -82,9 +95,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
|
||||
_imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize];
|
||||
|
||||
_textureState[stage] = new TextureStatePerStage[InitialTextureStateSize];
|
||||
_imageState[stage] = new TextureStatePerStage[InitialImageStateSize];
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,15 +109,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
if (count > _textureBindings[stage].Length)
|
||||
{
|
||||
Array.Resize(ref _textureBindings[stage], count);
|
||||
Array.Resize(ref _textureState[stage], count);
|
||||
}
|
||||
|
||||
int toClear = Math.Max(_textureBindingsCount[stage], count);
|
||||
TextureStatePerStage[] state = _textureState[stage];
|
||||
|
||||
for (int i = 0; i < toClear; i++)
|
||||
{
|
||||
state[i] = new TextureStatePerStage();
|
||||
}
|
||||
|
||||
_textureBindingsCount[stage] = count;
|
||||
@ -126,15 +127,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
if (count > _imageBindings[stage].Length)
|
||||
{
|
||||
Array.Resize(ref _imageBindings[stage], count);
|
||||
Array.Resize(ref _imageState[stage], count);
|
||||
}
|
||||
|
||||
int toClear = Math.Max(_imageBindingsCount[stage], count);
|
||||
TextureStatePerStage[] state = _imageState[stage];
|
||||
|
||||
for (int i = 0; i < toClear; i++)
|
||||
{
|
||||
state[i] = new TextureStatePerStage();
|
||||
}
|
||||
|
||||
_imageBindingsCount[stage] = count;
|
||||
@ -142,6 +134,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return _imageBindings[stage];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the max binding indexes for textures and images.
|
||||
/// </summary>
|
||||
/// <param name="maxTextureBinding">The maximum texture binding</param>
|
||||
/// <param name="maxImageBinding">The maximum image binding</param>
|
||||
public void SetMaxBindings(int maxTextureBinding, int maxImageBinding)
|
||||
{
|
||||
if (maxTextureBinding >= _textureState.Length)
|
||||
{
|
||||
Array.Resize(ref _textureState, maxTextureBinding + 1);
|
||||
}
|
||||
|
||||
if (maxImageBinding >= _imageState.Length)
|
||||
{
|
||||
Array.Resize(ref _imageState, maxImageBinding + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the textures constant buffer index.
|
||||
/// The constant buffer specified holds the texture handles.
|
||||
@ -222,18 +232,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// Updates the texture scale for a given texture or image.
|
||||
/// </summary>
|
||||
/// <param name="texture">Start GPU virtual address of the pool</param>
|
||||
/// <param name="binding">The related texture binding</param>
|
||||
/// <param name="usageFlags">The related texture usage flags</param>
|
||||
/// <param name="index">The texture/image binding index</param>
|
||||
/// <param name="stage">The active shader stage</param>
|
||||
/// <returns>True if the given texture has become blacklisted, indicating that its host texture may have changed.</returns>
|
||||
private bool UpdateScale(Texture texture, TextureBindingInfo binding, int index, ShaderStage stage)
|
||||
private bool UpdateScale(Texture texture, TextureUsageFlags usageFlags, int index, ShaderStage stage)
|
||||
{
|
||||
float result = 1f;
|
||||
bool changed = false;
|
||||
|
||||
if ((binding.Flags & TextureUsageFlags.NeedsScaleValue) != 0 && texture != null)
|
||||
if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 && texture != null)
|
||||
{
|
||||
if ((binding.Flags & TextureUsageFlags.ResScaleUnsupported) != 0)
|
||||
if ((usageFlags & TextureUsageFlags.ResScaleUnsupported) != 0)
|
||||
{
|
||||
changed = texture.ScaleMode != TextureScaleMode.Blacklisted;
|
||||
texture.BlacklistScale();
|
||||
@ -323,7 +333,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// Ensures that the bindings are visible to the host GPU.
|
||||
/// Note: this actually performs the binding using the host graphics API.
|
||||
/// </summary>
|
||||
public void CommitBindings()
|
||||
/// <param name="specState">Specialization state for the bound shader</param>
|
||||
/// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
|
||||
public bool CommitBindings(ShaderSpecializationState specState)
|
||||
{
|
||||
ulong texturePoolAddress = _texturePoolAddress;
|
||||
|
||||
@ -331,10 +343,38 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId)
|
||||
: null;
|
||||
|
||||
// Check if the texture pool has been modified since bindings were last committed.
|
||||
// If it wasn't, then it's possible to avoid looking up textures again when the handle remains the same.
|
||||
bool poolModified = false;
|
||||
|
||||
if (texturePool != null)
|
||||
{
|
||||
int texturePoolSequence = texturePool.CheckModified();
|
||||
|
||||
if (_texturePoolSequence != texturePoolSequence)
|
||||
{
|
||||
poolModified = true;
|
||||
_texturePoolSequence = texturePoolSequence;
|
||||
}
|
||||
}
|
||||
|
||||
if (_samplerPool != null)
|
||||
{
|
||||
int samplerPoolSequence = _samplerPool.CheckModified();
|
||||
|
||||
if (_samplerPoolSequence != samplerPoolSequence)
|
||||
{
|
||||
poolModified = true;
|
||||
_samplerPoolSequence = samplerPoolSequence;
|
||||
}
|
||||
}
|
||||
|
||||
bool specStateMatches = true;
|
||||
|
||||
if (_isCompute)
|
||||
{
|
||||
CommitTextureBindings(texturePool, ShaderStage.Compute, 0);
|
||||
CommitImageBindings (texturePool, ShaderStage.Compute, 0);
|
||||
specStateMatches &= CommitTextureBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
|
||||
specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -342,14 +382,57 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
int stageIndex = (int)stage - 1;
|
||||
|
||||
CommitTextureBindings(texturePool, stage, stageIndex);
|
||||
CommitImageBindings (texturePool, stage, stageIndex);
|
||||
specStateMatches &= CommitTextureBindings(texturePool, stage, stageIndex, poolModified, specState);
|
||||
specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
|
||||
}
|
||||
}
|
||||
|
||||
CommitRenderScale();
|
||||
|
||||
_rebind = false;
|
||||
return specStateMatches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch the constant buffers used for a texture to cache.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Stage index of the constant buffer</param>
|
||||
/// <param name="cachedTextureBufferIndex">The currently cached texture buffer index</param>
|
||||
/// <param name="cachedSamplerBufferIndex">The currently cached sampler buffer index</param>
|
||||
/// <param name="cachedTextureBuffer">The currently cached texture buffer data</param>
|
||||
/// <param name="cachedSamplerBuffer">The currently cached sampler buffer data</param>
|
||||
/// <param name="textureBufferIndex">The new texture buffer index</param>
|
||||
/// <param name="samplerBufferIndex">The new sampler buffer index</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void UpdateCachedBuffer(
|
||||
int stageIndex,
|
||||
ref int cachedTextureBufferIndex,
|
||||
ref int cachedSamplerBufferIndex,
|
||||
ref ReadOnlySpan<int> cachedTextureBuffer,
|
||||
ref ReadOnlySpan<int> cachedSamplerBuffer,
|
||||
int textureBufferIndex,
|
||||
int samplerBufferIndex)
|
||||
{
|
||||
if (textureBufferIndex != cachedTextureBufferIndex)
|
||||
{
|
||||
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
|
||||
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
|
||||
cachedTextureBufferIndex = textureBufferIndex;
|
||||
|
||||
if (samplerBufferIndex == textureBufferIndex)
|
||||
{
|
||||
cachedSamplerBuffer = cachedTextureBuffer;
|
||||
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (samplerBufferIndex != cachedSamplerBufferIndex)
|
||||
{
|
||||
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
|
||||
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
|
||||
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -358,13 +441,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
/// <param name="pool">The current texture pool</param>
|
||||
/// <param name="stage">The shader stage using the textures to be bound</param>
|
||||
/// <param name="stageIndex">The stage number of the specified shader stage</param>
|
||||
private void CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex)
|
||||
/// <param name="stageIndex">The stage number of the specified shader stage</param
|
||||
/// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
|
||||
/// <param name="specState">Specialization state for the bound shader</param>
|
||||
/// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
|
||||
private bool CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState)
|
||||
{
|
||||
int textureCount = _textureBindingsCount[stageIndex];
|
||||
if (textureCount == 0)
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
var samplerPool = _samplerPool;
|
||||
@ -372,17 +458,27 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
if (pool == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set.");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool specStateMatches = true;
|
||||
|
||||
int cachedTextureBufferIndex = -1;
|
||||
int cachedSamplerBufferIndex = -1;
|
||||
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
|
||||
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
|
||||
|
||||
for (int index = 0; index < textureCount; index++)
|
||||
{
|
||||
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
|
||||
TextureUsageFlags usageFlags = bindingInfo.Flags;
|
||||
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
|
||||
|
||||
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex);
|
||||
int textureId = UnpackTextureId(packedId);
|
||||
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
|
||||
|
||||
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
|
||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||
int samplerId;
|
||||
|
||||
if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
|
||||
@ -391,10 +487,42 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
else
|
||||
{
|
||||
samplerId = UnpackSamplerId(packedId);
|
||||
samplerId = TextureHandle.UnpackSamplerId(packedId);
|
||||
}
|
||||
|
||||
Texture texture = pool.Get(textureId);
|
||||
ref TextureState state = ref _textureState[bindingInfo.Binding];
|
||||
|
||||
if (!poolModified &&
|
||||
state.TextureHandle == textureId &&
|
||||
state.SamplerHandle == samplerId &&
|
||||
state.CachedTexture != null &&
|
||||
state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence &&
|
||||
state.CachedSampler?.IsDisposed != true)
|
||||
{
|
||||
// The texture is already bound.
|
||||
state.CachedTexture.SynchronizeMemory();
|
||||
|
||||
if ((state.ScaleIndex != index || state.UsageFlags != usageFlags) &&
|
||||
UpdateScale(state.CachedTexture, usageFlags, index, stage))
|
||||
{
|
||||
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
|
||||
|
||||
state.Texture = hostTextureRebind;
|
||||
state.ScaleIndex = index;
|
||||
state.UsageFlags = usageFlags;
|
||||
|
||||
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTextureRebind);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
state.TextureHandle = textureId;
|
||||
state.SamplerHandle = samplerId;
|
||||
|
||||
ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture);
|
||||
|
||||
specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
|
||||
|
||||
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
|
||||
@ -407,30 +535,38 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_textureState[stageIndex][index].Texture != hostTexture || _rebind)
|
||||
if (state.Texture != hostTexture)
|
||||
{
|
||||
if (UpdateScale(texture, bindingInfo, index, stage))
|
||||
if (UpdateScale(texture, usageFlags, index, stage))
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
}
|
||||
|
||||
_textureState[stageIndex][index].Texture = hostTexture;
|
||||
state.Texture = hostTexture;
|
||||
state.ScaleIndex = index;
|
||||
state.UsageFlags = usageFlags;
|
||||
|
||||
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
|
||||
}
|
||||
|
||||
Sampler sampler = samplerPool?.Get(samplerId);
|
||||
state.CachedSampler = sampler;
|
||||
|
||||
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
||||
|
||||
if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind)
|
||||
if (state.Sampler != hostSampler)
|
||||
{
|
||||
_textureState[stageIndex][index].Sampler = hostSampler;
|
||||
state.Sampler = hostSampler;
|
||||
|
||||
_context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
|
||||
}
|
||||
|
||||
state.CachedTexture = texture;
|
||||
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
return specStateMatches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -440,38 +576,90 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="pool">The current texture pool</param>
|
||||
/// <param name="stage">The shader stage using the textures to be bound</param>
|
||||
/// <param name="stageIndex">The stage number of the specified shader stage</param>
|
||||
private void CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex)
|
||||
/// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
|
||||
/// <param name="specState">Specialization state for the bound shader</param>
|
||||
/// <returns>True if all bound images match the current shader specialiation state, false otherwise</returns>
|
||||
private bool CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState)
|
||||
{
|
||||
int imageCount = _imageBindingsCount[stageIndex];
|
||||
if (imageCount == 0)
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pool == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses images, but texture pool was not set.");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Scales for images appear after the texture ones.
|
||||
int baseScaleIndex = _textureBindingsCount[stageIndex];
|
||||
|
||||
int cachedTextureBufferIndex = -1;
|
||||
int cachedSamplerBufferIndex = -1;
|
||||
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
|
||||
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
|
||||
|
||||
bool specStateMatches = true;
|
||||
|
||||
for (int index = 0; index < imageCount; index++)
|
||||
{
|
||||
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
|
||||
TextureUsageFlags usageFlags = bindingInfo.Flags;
|
||||
int scaleIndex = baseScaleIndex + index;
|
||||
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
|
||||
|
||||
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex);
|
||||
int textureId = UnpackTextureId(packedId);
|
||||
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
|
||||
|
||||
Texture texture = pool.Get(textureId);
|
||||
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
|
||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||
|
||||
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
ref TextureState state = ref _imageState[bindingInfo.Binding];
|
||||
|
||||
bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
||||
|
||||
if (!poolModified &&
|
||||
state.TextureHandle == textureId &&
|
||||
state.CachedTexture != null &&
|
||||
state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence)
|
||||
{
|
||||
Texture cachedTexture = state.CachedTexture;
|
||||
|
||||
// The texture is already bound.
|
||||
cachedTexture.SynchronizeMemory();
|
||||
|
||||
if (isStore)
|
||||
{
|
||||
cachedTexture?.SignalModified();
|
||||
}
|
||||
|
||||
if ((state.ScaleIndex != index || state.UsageFlags != usageFlags) &&
|
||||
UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage))
|
||||
{
|
||||
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
|
||||
|
||||
Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;
|
||||
|
||||
state.Texture = hostTextureRebind;
|
||||
state.ScaleIndex = scaleIndex;
|
||||
state.UsageFlags = usageFlags;
|
||||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
state.TextureHandle = textureId;
|
||||
|
||||
ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture);
|
||||
|
||||
specStateMatches &= specState.MatchesImage(stage, index, descriptor);
|
||||
|
||||
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
|
||||
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
||||
{
|
||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
@ -494,14 +682,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
texture?.SignalModified();
|
||||
}
|
||||
|
||||
if (_imageState[stageIndex][index].Texture != hostTexture || _rebind)
|
||||
if (state.Texture != hostTexture)
|
||||
{
|
||||
if (UpdateScale(texture, bindingInfo, baseScaleIndex + index, stage))
|
||||
if (UpdateScale(texture, usageFlags, scaleIndex, stage))
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
}
|
||||
|
||||
_imageState[stageIndex][index].Texture = hostTexture;
|
||||
state.Texture = hostTexture;
|
||||
state.ScaleIndex = scaleIndex;
|
||||
state.UsageFlags = usageFlags;
|
||||
|
||||
Format format = bindingInfo.Format;
|
||||
|
||||
@ -512,8 +702,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
|
||||
}
|
||||
|
||||
state.CachedTexture = texture;
|
||||
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
return specStateMatches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -537,13 +732,28 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex);
|
||||
|
||||
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
|
||||
int textureId = UnpackTextureId(packedId);
|
||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||
|
||||
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
|
||||
|
||||
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId);
|
||||
|
||||
return texturePool.GetDescriptor(textureId);
|
||||
TextureDescriptor descriptor;
|
||||
|
||||
if (texturePool.IsValidId(textureId))
|
||||
{
|
||||
descriptor = texturePool.GetDescriptor(textureId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the ID is not valid, we just return a default descriptor with the most common state.
|
||||
// Since this is used for shader specialization, doing so might avoid the need for recompilations.
|
||||
descriptor = new TextureDescriptor();
|
||||
descriptor.Word4 |= (uint)TextureTarget.Texture2D << 23;
|
||||
descriptor.Word5 |= 1u << 31; // Coords normalized.
|
||||
}
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -555,6 +765,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="textureBufferIndex">Index of the constant buffer holding the texture handles</param>
|
||||
/// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler handles</param>
|
||||
/// <returns>The packed texture and sampler ID (the real texture handle)</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex)
|
||||
{
|
||||
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
|
||||
@ -590,32 +801,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the texture ID from the real texture handle.
|
||||
/// </summary>
|
||||
/// <param name="packedId">The real texture handle</param>
|
||||
/// <returns>The texture ID</returns>
|
||||
private static int UnpackTextureId(int packedId)
|
||||
{
|
||||
return (packedId >> 0) & 0xfffff;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the sampler ID from the real texture handle.
|
||||
/// </summary>
|
||||
/// <param name="packedId">The real texture handle</param>
|
||||
/// <returns>The sampler ID</returns>
|
||||
private static int UnpackSamplerId(int packedId)
|
||||
{
|
||||
return (packedId >> 20) & 0xfff;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
|
||||
/// </summary>
|
||||
public void Rebind()
|
||||
{
|
||||
_rebind = true;
|
||||
Array.Clear(_textureState);
|
||||
Array.Clear(_imageState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -349,6 +349,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="memoryManager">GPU memory manager where the texture is mapped</param>
|
||||
/// <param name="dsState">Depth-stencil buffer texture to find or create</param>
|
||||
/// <param name="size">Size of the depth-stencil texture</param>
|
||||
/// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param>
|
||||
/// <param name="samplesInX">Number of samples in the X direction, for MSAA</param>
|
||||
/// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param>
|
||||
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
|
||||
@ -357,6 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
MemoryManager memoryManager,
|
||||
RtDepthStencilState dsState,
|
||||
Size3D size,
|
||||
bool layered,
|
||||
int samplesInX,
|
||||
int samplesInY,
|
||||
Size sizeHint)
|
||||
@ -364,9 +366,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
|
||||
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
|
||||
|
||||
Target target = (samplesInX | samplesInY) != 1
|
||||
? Target.Texture2DMultisample
|
||||
: Target.Texture2D;
|
||||
Target target;
|
||||
|
||||
if (dsState.MemoryLayout.UnpackIsTarget3D())
|
||||
{
|
||||
target = Target.Texture3D;
|
||||
}
|
||||
else if ((samplesInX | samplesInY) != 1)
|
||||
{
|
||||
target = size.Depth > 1 && layered
|
||||
? Target.Texture2DMultisampleArray
|
||||
: Target.Texture2DMultisample;
|
||||
}
|
||||
else
|
||||
{
|
||||
target = size.Depth > 1 && layered
|
||||
? Target.Texture2DArray
|
||||
: Target.Texture2D;
|
||||
}
|
||||
|
||||
FormatInfo formatInfo = dsState.Format.Convert();
|
||||
|
||||
@ -547,7 +564,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
range.Value,
|
||||
sizeInfo.LayerSize,
|
||||
_context.Capabilities,
|
||||
flags.HasFlag(TextureSearchFlags.ForCopy),
|
||||
out int firstLayer,
|
||||
out int firstLevel);
|
||||
|
||||
@ -662,7 +678,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
overlap.Range,
|
||||
overlap.LayerSize,
|
||||
_context.Capabilities,
|
||||
false,
|
||||
out int firstLayer,
|
||||
out int firstLevel);
|
||||
|
||||
|
@ -2,7 +2,6 @@ using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
@ -657,6 +656,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
case Target.Texture2DMultisample:
|
||||
case Target.Texture2DMultisampleArray:
|
||||
if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
|
||||
{
|
||||
return TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
|
||||
result = rhs.Target == Target.Texture2DMultisample ||
|
||||
rhs.Target == Target.Texture2DMultisampleArray;
|
||||
break;
|
||||
|
@ -241,25 +241,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return (TextureMsaaMode)((Word7 >> 8) & 0xf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the equivalent of this TextureDescriptor for the shader cache.
|
||||
/// </summary>
|
||||
/// <returns>The equivalent of this TextureDescriptor for the shader cache.</returns>
|
||||
public GuestTextureDescriptor ToCache()
|
||||
{
|
||||
GuestTextureDescriptor result = new GuestTextureDescriptor
|
||||
{
|
||||
Handle = uint.MaxValue,
|
||||
Format = UnpackFormat(),
|
||||
Target = UnpackTextureTarget(),
|
||||
IsSrgb = UnpackSrgb(),
|
||||
IsTextureCoordNormalized = UnpackTextureCoordNormalized(),
|
||||
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if two descriptors are equal.
|
||||
/// </summary>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
@ -10,9 +11,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
class TextureManager : IDisposable
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
|
||||
private readonly TextureBindingsManager _cpBindingsManager;
|
||||
private readonly TextureBindingsManager _gpBindingsManager;
|
||||
private readonly TexturePoolCache _texturePoolCache;
|
||||
|
||||
private readonly Texture[] _rtColors;
|
||||
private readonly ITexture[] _rtHostColors;
|
||||
@ -35,6 +38,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
public TextureManager(GpuContext context, GpuChannel channel)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
|
||||
TexturePoolCache texturePoolCache = new TexturePoolCache(context);
|
||||
|
||||
@ -43,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: true);
|
||||
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: false);
|
||||
_texturePoolCache = texturePoolCache;
|
||||
|
||||
_rtColors = new Texture[Constants.TotalRenderTargets];
|
||||
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
|
||||
@ -99,6 +104,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_cpBindingsManager.SetTextureBufferIndex(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the max binding indexes on the compute pipeline.
|
||||
/// </summary>
|
||||
/// <param name="maxTextureBinding">The maximum texture binding</param>
|
||||
/// <param name="maxImageBinding">The maximum image binding</param>
|
||||
public void SetComputeMaxBindings(int maxTextureBinding, int maxImageBinding)
|
||||
{
|
||||
_cpBindingsManager.SetMaxBindings(maxTextureBinding, maxImageBinding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the texture constant buffer index on the graphics pipeline.
|
||||
/// </summary>
|
||||
@ -108,6 +123,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_gpBindingsManager.SetTextureBufferIndex(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the max binding indexes on the graphics pipeline.
|
||||
/// </summary>
|
||||
/// <param name="maxTextureBinding">The maximum texture binding</param>
|
||||
/// <param name="maxImageBinding">The maximum image binding</param>
|
||||
public void SetGraphicsMaxBindings(int maxTextureBinding, int maxImageBinding)
|
||||
{
|
||||
_gpBindingsManager.SetMaxBindings(maxTextureBinding, maxImageBinding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current sampler pool on the compute pipeline.
|
||||
/// </summary>
|
||||
@ -335,25 +360,48 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <summary>
|
||||
/// Commits bindings on the compute pipeline.
|
||||
/// </summary>
|
||||
public void CommitComputeBindings()
|
||||
/// <param name="specState">Specialization state for the bound shader</param>
|
||||
/// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
|
||||
public bool CommitComputeBindings(ShaderSpecializationState specState)
|
||||
{
|
||||
// Every time we switch between graphics and compute work,
|
||||
// we must rebind everything.
|
||||
// Since compute work happens less often, we always do that
|
||||
// before and after the compute dispatch.
|
||||
_cpBindingsManager.Rebind();
|
||||
_cpBindingsManager.CommitBindings();
|
||||
bool result = _cpBindingsManager.CommitBindings(specState);
|
||||
_gpBindingsManager.Rebind();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commits bindings on the graphics pipeline.
|
||||
/// </summary>
|
||||
public void CommitGraphicsBindings()
|
||||
/// <param name="specState">Specialization state for the bound shader</param>
|
||||
/// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
|
||||
public bool CommitGraphicsBindings(ShaderSpecializationState specState)
|
||||
{
|
||||
_gpBindingsManager.CommitBindings();
|
||||
bool result = _gpBindingsManager.CommitBindings(specState);
|
||||
|
||||
UpdateRenderTargets();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a texture pool from the cache, with the given address and maximum id.
|
||||
/// </summary>
|
||||
/// <param name="poolGpuVa">GPU virtual address of the texture pool</param>
|
||||
/// <param name="maximumId">Maximum ID of the texture pool</param>
|
||||
/// <returns>The texture pool</returns>
|
||||
public TexturePool GetTexturePool(ulong poolGpuVa, int maximumId)
|
||||
{
|
||||
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
|
||||
|
||||
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId);
|
||||
|
||||
return texturePool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
|
||||
private TextureDescriptor _defaultDescriptor;
|
||||
|
||||
/// <summary>
|
||||
/// Intrusive linked list node used on the texture pool cache.
|
||||
@ -32,6 +33,62 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_channel = channel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture descripor and texture with the given ID with no bounds check or synchronization.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
|
||||
/// <param name="texture">The texture with the given ID</param>
|
||||
/// <returns>The texture descriptor with the given ID</returns>
|
||||
private ref readonly TextureDescriptor GetInternal(int id, out Texture texture)
|
||||
{
|
||||
texture = Items[id];
|
||||
|
||||
ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(id);
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||
|
||||
ProcessDereferenceQueue();
|
||||
|
||||
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 (texture == null)
|
||||
{
|
||||
return ref descriptor;
|
||||
}
|
||||
|
||||
texture.IncrementReferenceCount(this, id);
|
||||
|
||||
Items[id] = texture;
|
||||
|
||||
DescriptorCache[id] = descriptor;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (texture.ChangedSize)
|
||||
{
|
||||
// Texture changed size at one point - it may be a different size than the sampler expects.
|
||||
// This can be triggered when the size is changed by a size hint on copy or draw, but the texture has been sampled before.
|
||||
|
||||
int baseLevel = descriptor.UnpackBaseLevel();
|
||||
int width = Math.Max(1, descriptor.UnpackWidth() >> baseLevel);
|
||||
int height = Math.Max(1, descriptor.UnpackHeight() >> baseLevel);
|
||||
|
||||
if (texture.Info.Width != width || texture.Info.Height != height)
|
||||
{
|
||||
texture.ChangeSize(width, height, texture.Info.DepthOrLayers);
|
||||
}
|
||||
}
|
||||
|
||||
// Memory is automatically synchronized on texture creation.
|
||||
texture.SynchronizeMemory();
|
||||
}
|
||||
|
||||
return ref descriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture with the given ID.
|
||||
/// </summary>
|
||||
@ -51,56 +108,49 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
SynchronizeMemory();
|
||||
}
|
||||
|
||||
Texture texture = Items[id];
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
TextureDescriptor descriptor = GetDescriptor(id);
|
||||
|
||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||
|
||||
ProcessDereferenceQueue();
|
||||
|
||||
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 (texture == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
texture.IncrementReferenceCount(this, id);
|
||||
|
||||
Items[id] = texture;
|
||||
|
||||
DescriptorCache[id] = descriptor;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (texture.ChangedSize)
|
||||
{
|
||||
// Texture changed size at one point - it may be a different size than the sampler expects.
|
||||
// This can be triggered when the size is changed by a size hint on copy or draw, but the texture has been sampled before.
|
||||
|
||||
TextureDescriptor descriptor = GetDescriptor(id);
|
||||
|
||||
int baseLevel = descriptor.UnpackBaseLevel();
|
||||
int width = Math.Max(1, descriptor.UnpackWidth() >> baseLevel);
|
||||
int height = Math.Max(1, descriptor.UnpackHeight() >> baseLevel);
|
||||
|
||||
if (texture.Info.Width != width || texture.Info.Height != height)
|
||||
{
|
||||
texture.ChangeSize(width, height, texture.Info.DepthOrLayers);
|
||||
}
|
||||
}
|
||||
|
||||
// Memory is automatically synchronized on texture creation.
|
||||
texture.SynchronizeMemory();
|
||||
}
|
||||
GetInternal(id, out Texture texture);
|
||||
|
||||
return 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="texture">The texture with the given ID</param>
|
||||
/// <returns>The texture descriptor with the given ID</returns>
|
||||
public ref readonly TextureDescriptor GetForBinding(int id, out Texture texture)
|
||||
{
|
||||
if ((uint)id >= Items.Length)
|
||||
{
|
||||
texture = null;
|
||||
return ref _defaultDescriptor;
|
||||
}
|
||||
|
||||
// When getting for binding, assume the pool has already been synchronized.
|
||||
|
||||
return ref GetInternal(id, out texture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
|
||||
/// </summary>
|
||||
/// <returns>A number that increments each time a modification is detected</returns>
|
||||
public int CheckModified()
|
||||
{
|
||||
if (SequenceNumber != Context.SequenceNumber)
|
||||
{
|
||||
SequenceNumber = Context.SequenceNumber;
|
||||
|
||||
SynchronizeMemory();
|
||||
}
|
||||
|
||||
return ModifiedSequenceNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forcibly remove a texture from this pool's items.
|
||||
/// If deferred, the dereference will be queued to occur on the render thread.
|
||||
@ -175,7 +225,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="descriptor">The texture descriptor</param>
|
||||
/// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param>
|
||||
/// <returns>The texture information</returns>
|
||||
private TextureInfo GetInfo(TextureDescriptor descriptor, out int layerSize)
|
||||
private TextureInfo GetInfo(in TextureDescriptor descriptor, out int layerSize)
|
||||
{
|
||||
int depthOrLayers = descriptor.UnpackDepth();
|
||||
int levels = descriptor.UnpackLevels();
|
||||
|
@ -378,6 +378,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return _gpUniformBuffers[stage].Buffers[index].Address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bounds of the uniform buffer currently bound at the given index.
|
||||
/// </summary>
|
||||
/// <param name="isCompute">Indicates whenever the uniform is requested by the 3D or compute engine</param>
|
||||
/// <param name="stage">Index of the shader stage, if the uniform is for the 3D engine</param>
|
||||
/// <param name="index">Index of the uniform buffer binding</param>
|
||||
/// <returns>The uniform buffer bounds, or an undefined value if the buffer is not currently bound</returns>
|
||||
public ref BufferBounds GetUniformBufferBounds(bool isCompute, int stage, int index)
|
||||
{
|
||||
if (isCompute)
|
||||
{
|
||||
return ref _cpUniformBuffers.Buffers[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
return ref _gpUniformBuffers[stage].Buffers[index];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the compute engine bindings are visible to the host GPU.
|
||||
/// Note: this actually performs the binding using the host graphics API.
|
||||
|
@ -35,6 +35,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
HostProgram = hostProgram;
|
||||
SpecializationState = specializationState;
|
||||
Shaders = shaders;
|
||||
|
||||
SpecializationState.Prepare(shaders);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -418,7 +418,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa))
|
||||
{
|
||||
return cpShader.SpecializationState.MatchesCompute(channel, poolState);
|
||||
return cpShader.SpecializationState.MatchesCompute(channel, poolState, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -454,7 +454,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
}
|
||||
|
||||
return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState);
|
||||
return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState))
|
||||
if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState, true))
|
||||
{
|
||||
program = entry;
|
||||
return true;
|
||||
@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
if (entry.SpecializationState.MatchesCompute(channel, poolState))
|
||||
if (entry.SpecializationState.MatchesCompute(channel, poolState, true))
|
||||
{
|
||||
program = entry;
|
||||
return true;
|
||||
|
@ -1,9 +1,14 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
@ -158,6 +163,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
|
||||
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
|
||||
private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
|
||||
private Box<TextureSpecializationState>[][] _textureByBinding;
|
||||
private Box<TextureSpecializationState>[][] _imageByBinding;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the shader specialization state.
|
||||
@ -194,6 +202,48 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepare the shader specialization state for quick binding lookups.
|
||||
/// </summary>
|
||||
/// <param name="stages">The shader stages</param>
|
||||
public void Prepare(CachedShaderStage[] stages)
|
||||
{
|
||||
_allTextures = _textureSpecialization.ToArray();
|
||||
|
||||
_textureByBinding = new Box<TextureSpecializationState>[stages.Length][];
|
||||
_imageByBinding = new Box<TextureSpecializationState>[stages.Length][];
|
||||
|
||||
for (int i = 0; i < stages.Length; i++)
|
||||
{
|
||||
CachedShaderStage stage = stages[i];
|
||||
if (stage?.Info != null)
|
||||
{
|
||||
var textures = stage.Info.Textures;
|
||||
var images = stage.Info.Images;
|
||||
|
||||
var texBindings = new Box<TextureSpecializationState>[textures.Count];
|
||||
var imageBindings = new Box<TextureSpecializationState>[images.Count];
|
||||
|
||||
int stageIndex = Math.Max(i - 1, 0); // Don't count VertexA for looking up spec state. No-Op for compute.
|
||||
|
||||
for (int j = 0; j < textures.Count; j++)
|
||||
{
|
||||
var texture = textures[j];
|
||||
texBindings[j] = GetTextureSpecState(stageIndex, texture.HandleIndex, texture.CbufSlot);
|
||||
}
|
||||
|
||||
for (int j = 0; j < images.Count; j++)
|
||||
{
|
||||
var image = images[j];
|
||||
imageBindings[j] = GetTextureSpecState(stageIndex, image.HandleIndex, image.CbufSlot);
|
||||
}
|
||||
|
||||
_textureByBinding[i] = texBindings;
|
||||
_imageByBinding[i] = imageBindings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the shader accesses the early Z force state.
|
||||
/// </summary>
|
||||
@ -396,15 +446,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="graphicsState">Graphics state</param>
|
||||
/// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
|
||||
/// <returns>True if the state matches, false otherwise</returns>
|
||||
public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState)
|
||||
public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState, bool checkTextures)
|
||||
{
|
||||
if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Matches(channel, poolState, isCompute: false);
|
||||
return Matches(channel, poolState, checkTextures, isCompute: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -412,10 +463,64 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
|
||||
/// <returns>True if the state matches, false otherwise</returns>
|
||||
public bool MatchesCompute(GpuChannel channel, GpuChannelPoolState poolState)
|
||||
public bool MatchesCompute(GpuChannel channel, GpuChannelPoolState poolState, bool checkTextures)
|
||||
{
|
||||
return Matches(channel, poolState, isCompute: true);
|
||||
return Matches(channel, poolState, checkTextures, isCompute: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch the constant buffers used for a texture to cache.
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
|
||||
/// <param name="cachedTextureBufferIndex">The currently cached texture buffer index</param>
|
||||
/// <param name="cachedSamplerBufferIndex">The currently cached sampler buffer index</param>
|
||||
/// <param name="cachedTextureBuffer">The currently cached texture buffer data</param>
|
||||
/// <param name="cachedSamplerBuffer">The currently cached sampler buffer data</param>
|
||||
/// <param name="cachedStageIndex">The currently cached stage</param>
|
||||
/// <param name="textureBufferIndex">The new texture buffer index</param>
|
||||
/// <param name="samplerBufferIndex">The new sampler buffer index</param>
|
||||
/// <param name="stageIndex">Stage index of the constant buffer</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void UpdateCachedBuffer(
|
||||
GpuChannel channel,
|
||||
bool isCompute,
|
||||
ref int cachedTextureBufferIndex,
|
||||
ref int cachedSamplerBufferIndex,
|
||||
ref ReadOnlySpan<int> cachedTextureBuffer,
|
||||
ref ReadOnlySpan<int> cachedSamplerBuffer,
|
||||
ref int cachedStageIndex,
|
||||
int textureBufferIndex,
|
||||
int samplerBufferIndex,
|
||||
int stageIndex)
|
||||
{
|
||||
bool stageChange = stageIndex != cachedStageIndex;
|
||||
|
||||
if (stageChange || textureBufferIndex != cachedTextureBufferIndex)
|
||||
{
|
||||
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
|
||||
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
|
||||
cachedTextureBufferIndex = textureBufferIndex;
|
||||
|
||||
if (samplerBufferIndex == textureBufferIndex)
|
||||
{
|
||||
cachedSamplerBuffer = cachedTextureBuffer;
|
||||
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (stageChange || samplerBufferIndex != cachedSamplerBufferIndex)
|
||||
{
|
||||
ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
|
||||
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
|
||||
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||
}
|
||||
|
||||
cachedStageIndex = stageIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -423,9 +528,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
|
||||
/// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
|
||||
/// <returns>True if the state matches, false otherwise</returns>
|
||||
private bool Matches(GpuChannel channel, GpuChannelPoolState poolState, bool isCompute)
|
||||
private bool Matches(GpuChannel channel, GpuChannelPoolState poolState, bool checkTextures, bool isCompute)
|
||||
{
|
||||
int constantBufferUsePerStageMask = _constantBufferUsePerStage;
|
||||
|
||||
@ -445,55 +551,62 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
constantBufferUsePerStageMask &= ~(1 << index);
|
||||
}
|
||||
|
||||
foreach (var kv in _textureSpecialization)
|
||||
if (checkTextures)
|
||||
{
|
||||
TextureKey textureKey = kv.Key;
|
||||
TexturePool pool = channel.TextureManager.GetTexturePool(poolState.TexturePoolGpuVa, poolState.TexturePoolMaximumId);
|
||||
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
|
||||
int cachedTextureBufferIndex = -1;
|
||||
int cachedSamplerBufferIndex = -1;
|
||||
int cachedStageIndex = -1;
|
||||
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
|
||||
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
|
||||
|
||||
ulong textureCbAddress;
|
||||
ulong samplerCbAddress;
|
||||
|
||||
if (isCompute)
|
||||
foreach (var kv in _allTextures)
|
||||
{
|
||||
textureCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex);
|
||||
samplerCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
textureCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, textureBufferIndex);
|
||||
samplerCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, samplerBufferIndex);
|
||||
}
|
||||
TextureKey textureKey = kv.Key;
|
||||
|
||||
if (!channel.MemoryManager.Physical.IsMapped(textureCbAddress) || !channel.MemoryManager.Physical.IsMapped(samplerCbAddress))
|
||||
{
|
||||
continue;
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
|
||||
|
||||
UpdateCachedBuffer(channel,
|
||||
isCompute,
|
||||
ref cachedTextureBufferIndex,
|
||||
ref cachedSamplerBufferIndex,
|
||||
ref cachedTextureBuffer,
|
||||
ref cachedSamplerBuffer,
|
||||
ref cachedStageIndex,
|
||||
textureBufferIndex,
|
||||
samplerBufferIndex,
|
||||
textureKey.StageIndex);
|
||||
|
||||
int packedId = TextureHandle.ReadPackedId(textureKey.Handle, cachedTextureBuffer, cachedSamplerBuffer);
|
||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||
|
||||
if (pool.IsValidId(textureId))
|
||||
{
|
||||
ref readonly Image.TextureDescriptor descriptor = ref pool.GetDescriptorRef(textureId);
|
||||
|
||||
if (!MatchesTexture(kv.Value, descriptor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image.TextureDescriptor descriptor;
|
||||
|
||||
if (isCompute)
|
||||
{
|
||||
descriptor = channel.TextureManager.GetComputeTextureDescriptor(
|
||||
poolState.TexturePoolGpuVa,
|
||||
poolState.TextureBufferIndex,
|
||||
poolState.TexturePoolMaximumId,
|
||||
textureKey.Handle,
|
||||
textureKey.CbufSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
descriptor = channel.TextureManager.GetGraphicsTextureDescriptor(
|
||||
poolState.TexturePoolGpuVa,
|
||||
poolState.TextureBufferIndex,
|
||||
poolState.TexturePoolMaximumId,
|
||||
textureKey.StageIndex,
|
||||
textureKey.Handle,
|
||||
textureKey.CbufSlot);
|
||||
}
|
||||
|
||||
Box<TextureSpecializationState> specializationState = kv.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the recorded texture state matches the given texture descriptor.
|
||||
/// </summary>
|
||||
/// <param name="specializationState">Texture specialization state</param>
|
||||
/// <param name="descriptor">Texture descriptor</param>
|
||||
/// <returns>True if the state matches, false otherwise</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool MatchesTexture(Box<TextureSpecializationState> specializationState, in Image.TextureDescriptor descriptor)
|
||||
{
|
||||
if (specializationState != null)
|
||||
{
|
||||
if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) &&
|
||||
specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized())
|
||||
{
|
||||
@ -504,6 +617,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the recorded texture state for a given texture binding matches a texture descriptor.
|
||||
/// </summary>
|
||||
/// <param name="stage">The shader stage</param>
|
||||
/// <param name="index">The texture index</param>
|
||||
/// <param name="descriptor">Texture descriptor</param>
|
||||
/// <returns>True if the state matches, false otherwise</returns>
|
||||
public bool MatchesTexture(ShaderStage stage, int index, in Image.TextureDescriptor descriptor)
|
||||
{
|
||||
Box<TextureSpecializationState> specializationState = _textureByBinding[(int)stage][index];
|
||||
|
||||
return MatchesTexture(specializationState, descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the recorded texture state for a given image binding matches a texture descriptor.
|
||||
/// </summary>
|
||||
/// <param name="stage">The shader stage</param>
|
||||
/// <param name="index">The texture index</param>
|
||||
/// <param name="descriptor">Texture descriptor</param>
|
||||
/// <returns>True if the state matches, false otherwise</returns>
|
||||
public bool MatchesImage(ShaderStage stage, int index, in Image.TextureDescriptor descriptor)
|
||||
{
|
||||
Box<TextureSpecializationState> specializationState = _imageByBinding[(int)stage][index];
|
||||
|
||||
return MatchesTexture(specializationState, descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads shader specialization state that has been serialized.
|
||||
/// </summary>
|
||||
|
@ -9,10 +9,13 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
class Framebuffer : IDisposable
|
||||
{
|
||||
public int Handle { get; private set; }
|
||||
private int _clearFbHandle;
|
||||
private bool _clearFbInitialized;
|
||||
|
||||
private FramebufferAttachment _lastDsAttachment;
|
||||
|
||||
private readonly TextureView[] _colors;
|
||||
private TextureView _depthStencil;
|
||||
|
||||
private int _colorsCount;
|
||||
private bool _dualSourceBlend;
|
||||
@ -20,6 +23,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
public Framebuffer()
|
||||
{
|
||||
Handle = GL.GenFramebuffer();
|
||||
_clearFbHandle = GL.GenFramebuffer();
|
||||
|
||||
_colors = new TextureView[8];
|
||||
}
|
||||
@ -55,20 +59,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
if (depthStencil != null)
|
||||
{
|
||||
FramebufferAttachment attachment;
|
||||
|
||||
if (IsPackedDepthStencilFormat(depthStencil.Format))
|
||||
{
|
||||
attachment = FramebufferAttachment.DepthStencilAttachment;
|
||||
}
|
||||
else if (IsDepthOnlyFormat(depthStencil.Format))
|
||||
{
|
||||
attachment = FramebufferAttachment.DepthAttachment;
|
||||
}
|
||||
else
|
||||
{
|
||||
attachment = FramebufferAttachment.StencilAttachment;
|
||||
}
|
||||
FramebufferAttachment attachment = GetAttachment(depthStencil.Format);
|
||||
|
||||
GL.FramebufferTexture(
|
||||
FramebufferTarget.Framebuffer,
|
||||
@ -82,6 +73,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
_lastDsAttachment = 0;
|
||||
}
|
||||
|
||||
_depthStencil = depthStencil;
|
||||
}
|
||||
|
||||
public void SetDualSourceBlend(bool enable)
|
||||
@ -124,6 +117,22 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
GL.DrawBuffers(colorsCount, drawBuffers);
|
||||
}
|
||||
|
||||
private static FramebufferAttachment GetAttachment(Format format)
|
||||
{
|
||||
if (IsPackedDepthStencilFormat(format))
|
||||
{
|
||||
return FramebufferAttachment.DepthStencilAttachment;
|
||||
}
|
||||
else if (IsDepthOnlyFormat(format))
|
||||
{
|
||||
return FramebufferAttachment.DepthAttachment;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FramebufferAttachment.StencilAttachment;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPackedDepthStencilFormat(Format format)
|
||||
{
|
||||
return format == Format.D24UnormS8Uint ||
|
||||
@ -136,6 +145,78 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
return format == Format.D16Unorm || format == Format.D32Float;
|
||||
}
|
||||
|
||||
public void AttachColorLayerForClear(int index, int layer)
|
||||
{
|
||||
TextureView color = _colors[index];
|
||||
|
||||
if (!IsLayered(color))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BindClearFb();
|
||||
GL.FramebufferTextureLayer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0 + index, color.Handle, 0, layer);
|
||||
}
|
||||
|
||||
public void DetachColorLayerForClear(int index)
|
||||
{
|
||||
TextureView color = _colors[index];
|
||||
|
||||
if (!IsLayered(color))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GL.FramebufferTexture(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0 + index, 0, 0);
|
||||
Bind();
|
||||
}
|
||||
|
||||
public void AttachDepthStencilLayerForClear(int layer)
|
||||
{
|
||||
TextureView depthStencil = _depthStencil;
|
||||
|
||||
if (!IsLayered(depthStencil))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BindClearFb();
|
||||
GL.FramebufferTextureLayer(FramebufferTarget.Framebuffer, GetAttachment(depthStencil.Format), depthStencil.Handle, 0, layer);
|
||||
}
|
||||
|
||||
public void DetachDepthStencilLayerForClear()
|
||||
{
|
||||
TextureView depthStencil = _depthStencil;
|
||||
|
||||
if (!IsLayered(depthStencil))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GL.FramebufferTexture(FramebufferTarget.Framebuffer, GetAttachment(depthStencil.Format), 0, 0);
|
||||
Bind();
|
||||
}
|
||||
|
||||
private void BindClearFb()
|
||||
{
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, _clearFbHandle);
|
||||
|
||||
if (!_clearFbInitialized)
|
||||
{
|
||||
SetDrawBuffersImpl(Constants.MaxRenderTargets);
|
||||
_clearFbInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsLayered(TextureView view)
|
||||
{
|
||||
return view != null &&
|
||||
view.Target != Target.Texture1D &&
|
||||
view.Target != Target.Texture2D &&
|
||||
view.Target != Target.Texture2DMultisample &&
|
||||
view.Target != Target.TextureBuffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != 0)
|
||||
@ -144,6 +225,13 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
Handle = 0;
|
||||
}
|
||||
|
||||
if (_clearFbHandle != 0)
|
||||
{
|
||||
GL.DeleteFramebuffer(_clearFbHandle);
|
||||
|
||||
_clearFbHandle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
98
Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs
Normal file
98
Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
class IntermmediatePool : IDisposable
|
||||
{
|
||||
private readonly Renderer _renderer;
|
||||
private readonly List<TextureView> _entries;
|
||||
|
||||
public IntermmediatePool(Renderer renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
_entries = new List<TextureView>();
|
||||
}
|
||||
|
||||
public TextureView GetOrCreateWithAtLeast(
|
||||
Target target,
|
||||
int blockWidth,
|
||||
int blockHeight,
|
||||
int bytesPerPixel,
|
||||
Format format,
|
||||
int width,
|
||||
int height,
|
||||
int depth,
|
||||
int levels)
|
||||
{
|
||||
TextureView entry;
|
||||
|
||||
for (int i = 0; i < _entries.Count; i++)
|
||||
{
|
||||
entry = _entries[i];
|
||||
|
||||
if (entry.Target == target && entry.Format == format)
|
||||
{
|
||||
if (entry.Width < width || entry.Height < height || entry.Info.Depth < depth || entry.Info.Levels < levels)
|
||||
{
|
||||
width = Math.Max(width, entry.Width);
|
||||
height = Math.Max(height, entry.Height);
|
||||
depth = Math.Max(depth, entry.Info.Depth);
|
||||
levels = Math.Max(levels, entry.Info.Levels);
|
||||
|
||||
entry.Dispose();
|
||||
entry = CreateNew(target, blockWidth, blockHeight, bytesPerPixel, format, width, height, depth, levels);
|
||||
_entries[i] = entry;
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
entry = CreateNew(target, blockWidth, blockHeight, bytesPerPixel, format, width, height, depth, levels);
|
||||
_entries.Add(entry);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
private TextureView CreateNew(
|
||||
Target target,
|
||||
int blockWidth,
|
||||
int blockHeight,
|
||||
int bytesPerPixel,
|
||||
Format format,
|
||||
int width,
|
||||
int height,
|
||||
int depth,
|
||||
int levels)
|
||||
{
|
||||
return (TextureView)_renderer.CreateTexture(new TextureCreateInfo(
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
levels,
|
||||
1,
|
||||
blockWidth,
|
||||
blockHeight,
|
||||
bytesPerPixel,
|
||||
format,
|
||||
DepthStencilMode.Depth,
|
||||
target,
|
||||
SwizzleComponent.Red,
|
||||
SwizzleComponent.Green,
|
||||
SwizzleComponent.Blue,
|
||||
SwizzleComponent.Alpha), 1f);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (TextureView entry in _entries)
|
||||
{
|
||||
entry.Dispose();
|
||||
}
|
||||
|
||||
_entries.Clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
private readonly Renderer _renderer;
|
||||
|
||||
public IntermmediatePool IntermmediatePool { get; }
|
||||
|
||||
private int _srcFramebuffer;
|
||||
private int _dstFramebuffer;
|
||||
|
||||
@ -18,6 +20,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
public TextureCopy(Renderer renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
IntermmediatePool = new IntermmediatePool(renderer);
|
||||
}
|
||||
|
||||
public void Copy(
|
||||
@ -25,7 +28,30 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
TextureView dst,
|
||||
Extents2D srcRegion,
|
||||
Extents2D dstRegion,
|
||||
bool linearFilter)
|
||||
bool linearFilter,
|
||||
int srcLayer = 0,
|
||||
int dstLayer = 0,
|
||||
int srcLevel = 0,
|
||||
int dstLevel = 0)
|
||||
{
|
||||
int levels = Math.Min(src.Info.Levels - srcLevel, dst.Info.Levels - dstLevel);
|
||||
int layers = Math.Min(src.Info.GetLayers() - srcLayer, dst.Info.GetLayers() - dstLayer);
|
||||
|
||||
Copy(src, dst, srcRegion, dstRegion, linearFilter, srcLayer, dstLayer, srcLevel, dstLevel, layers, levels);
|
||||
}
|
||||
|
||||
public void Copy(
|
||||
TextureView src,
|
||||
TextureView dst,
|
||||
Extents2D srcRegion,
|
||||
Extents2D dstRegion,
|
||||
bool linearFilter,
|
||||
int srcLayer,
|
||||
int dstLayer,
|
||||
int srcLevel,
|
||||
int dstLevel,
|
||||
int layers,
|
||||
int levels)
|
||||
{
|
||||
TextureView srcConverted = src.Format.IsBgr() != dst.Format.IsBgr() ? BgraSwap(src) : src;
|
||||
|
||||
@ -34,22 +60,29 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetSrcFramebufferLazy());
|
||||
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy());
|
||||
|
||||
int levels = Math.Min(src.Info.Levels, dst.Info.Levels);
|
||||
int layers = Math.Min(src.Info.GetLayers(), dst.Info.GetLayers());
|
||||
if (srcLevel != 0)
|
||||
{
|
||||
srcRegion = srcRegion.Reduce(srcLevel);
|
||||
}
|
||||
|
||||
if (dstLevel != 0)
|
||||
{
|
||||
dstRegion = dstRegion.Reduce(dstLevel);
|
||||
}
|
||||
|
||||
for (int level = 0; level < levels; level++)
|
||||
{
|
||||
for (int layer = 0; layer < layers; layer++)
|
||||
{
|
||||
if (layers > 1)
|
||||
if ((srcLayer | dstLayer) != 0 || layers > 1)
|
||||
{
|
||||
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, level, layer);
|
||||
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, level, layer);
|
||||
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, srcLevel + level, srcLayer + layer);
|
||||
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, dstLevel + level, dstLayer + layer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, level);
|
||||
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, level);
|
||||
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, srcLevel + level);
|
||||
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, dstLevel + level);
|
||||
}
|
||||
|
||||
ClearBufferMask mask = GetMask(src.Format);
|
||||
@ -484,6 +517,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
|
||||
_copyPboHandle = 0;
|
||||
}
|
||||
|
||||
IntermmediatePool.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -115,14 +115,77 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
TextureView destinationView = (TextureView)destination;
|
||||
|
||||
_renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
|
||||
if (destinationView.Target.IsMultisample() || Target.IsMultisample())
|
||||
{
|
||||
Extents2D srcRegion = new Extents2D(0, 0, Width, Height);
|
||||
Extents2D dstRegion = new Extents2D(0, 0, destinationView.Width, destinationView.Height);
|
||||
|
||||
TextureView intermmediate = _renderer.TextureCopy.IntermmediatePool.GetOrCreateWithAtLeast(
|
||||
GetIntermmediateTarget(Target),
|
||||
Info.BlockWidth,
|
||||
Info.BlockHeight,
|
||||
Info.BytesPerPixel,
|
||||
Format,
|
||||
Width,
|
||||
Height,
|
||||
Info.Depth,
|
||||
Info.Levels);
|
||||
|
||||
GL.Disable(EnableCap.FramebufferSrgb);
|
||||
|
||||
_renderer.TextureCopy.Copy(this, intermmediate, srcRegion, srcRegion, true);
|
||||
_renderer.TextureCopy.Copy(intermmediate, destinationView, srcRegion, dstRegion, true, 0, firstLayer, 0, firstLevel);
|
||||
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
|
||||
{
|
||||
TextureView destinationView = (TextureView)destination;
|
||||
TextureView destinationView = (TextureView)destination;
|
||||
|
||||
_renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
||||
if (destinationView.Target.IsMultisample() || Target.IsMultisample())
|
||||
{
|
||||
Extents2D srcRegion = new Extents2D(0, 0, Width, Height);
|
||||
Extents2D dstRegion = new Extents2D(0, 0, destinationView.Width, destinationView.Height);
|
||||
|
||||
TextureView intermmediate = _renderer.TextureCopy.IntermmediatePool.GetOrCreateWithAtLeast(
|
||||
GetIntermmediateTarget(Target),
|
||||
Info.BlockWidth,
|
||||
Info.BlockHeight,
|
||||
Info.BytesPerPixel,
|
||||
Format,
|
||||
Math.Max(1, Width >> srcLevel),
|
||||
Math.Max(1, Height >> srcLevel),
|
||||
1,
|
||||
1);
|
||||
|
||||
GL.Disable(EnableCap.FramebufferSrgb);
|
||||
|
||||
_renderer.TextureCopy.Copy(this, intermmediate, srcRegion, srcRegion, true, srcLayer, 0, srcLevel, 0, 1, 1);
|
||||
_renderer.TextureCopy.Copy(intermmediate, destinationView, srcRegion, dstRegion, true, 0, dstLayer, 0, dstLevel, 1, 1);
|
||||
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static Target GetIntermmediateTarget(Target srcTarget)
|
||||
{
|
||||
return srcTarget switch
|
||||
{
|
||||
Target.Texture2D => Target.Texture2DMultisample,
|
||||
Target.Texture2DArray => Target.Texture2DMultisampleArray,
|
||||
Target.Texture2DMultisampleArray => Target.Texture2DArray,
|
||||
_ => Target.Texture2D
|
||||
};
|
||||
}
|
||||
|
||||
public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
|
||||
|
@ -110,7 +110,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
Buffer.Clear(destination, offset, size, value);
|
||||
}
|
||||
|
||||
public void ClearRenderTargetColor(int index, uint componentMask, ColorF color)
|
||||
public void ClearRenderTargetColor(int index, int layer, uint componentMask, ColorF color)
|
||||
{
|
||||
GL.ColorMask(
|
||||
index,
|
||||
@ -119,14 +119,18 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
(componentMask & 4) != 0,
|
||||
(componentMask & 8) != 0);
|
||||
|
||||
_framebuffer.AttachColorLayerForClear(index, layer);
|
||||
|
||||
float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha };
|
||||
|
||||
GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors);
|
||||
|
||||
_framebuffer.DetachColorLayerForClear(index);
|
||||
|
||||
RestoreComponentMask(index);
|
||||
}
|
||||
|
||||
public void ClearRenderTargetDepthStencil(float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
||||
public void ClearRenderTargetDepthStencil(int layer, float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
||||
{
|
||||
bool stencilMaskChanged =
|
||||
stencilMask != 0 &&
|
||||
@ -144,6 +148,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
GL.DepthMask(depthMask);
|
||||
}
|
||||
|
||||
_framebuffer.AttachDepthStencilLayerForClear(layer);
|
||||
|
||||
if (depthMask && stencilMask != 0)
|
||||
{
|
||||
GL.ClearBuffer(ClearBufferCombined.DepthStencil, 0, depthValue, stencilValue);
|
||||
@ -157,6 +163,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Stencil, 0, ref stencilValue);
|
||||
}
|
||||
|
||||
_framebuffer.DetachDepthStencilLayerForClear();
|
||||
|
||||
if (stencilMaskChanged)
|
||||
{
|
||||
GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask);
|
||||
@ -597,6 +605,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
GL.EndTransformFeedback();
|
||||
}
|
||||
|
||||
GL.ClipControl(ClipOrigin.UpperLeft, ClipDepthMode.NegativeOneToOne);
|
||||
|
||||
_drawTexture.Draw(
|
||||
view,
|
||||
samp,
|
||||
@ -627,6 +637,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
GL.BeginTransformFeedback(_tfTopology);
|
||||
}
|
||||
|
||||
RestoreClipControl();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader
|
||||
@ -50,5 +51,63 @@ namespace Ryujinx.Graphics.Shader
|
||||
{
|
||||
return (handle & 0x3fff, (handle >> 14) & 0x3fff, (TextureHandleType)((uint)handle >> 28));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the texture ID from the real texture handle.
|
||||
/// </summary>
|
||||
/// <param name="packedId">The real texture handle</param>
|
||||
/// <returns>The texture ID</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int UnpackTextureId(int packedId)
|
||||
{
|
||||
return (packedId >> 0) & 0xfffff;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the sampler ID from the real texture handle.
|
||||
/// </summary>
|
||||
/// <param name="packedId">The real texture handle</param>
|
||||
/// <returns>The sampler ID</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int UnpackSamplerId(int packedId)
|
||||
{
|
||||
return (packedId >> 20) & 0xfff;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a packed texture and sampler ID (basically, the real texture handle)
|
||||
/// from a given texture/sampler constant buffer.
|
||||
/// </summary>
|
||||
/// <param name="wordOffset">A word offset of the handle on the buffer (the "fake" shader handle)</param>
|
||||
/// <param name="cachedTextureBuffer">The constant buffer to fetch texture IDs from</param>
|
||||
/// <param name="cachedSamplerBuffer">The constant buffer to fetch sampler IDs from</param>
|
||||
/// <returns>The packed texture and sampler ID (the real texture handle)</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ReadPackedId(int wordOffset, ReadOnlySpan<int> cachedTextureBuffer, ReadOnlySpan<int> cachedSamplerBuffer)
|
||||
{
|
||||
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = UnpackOffsets(wordOffset);
|
||||
|
||||
int handle = cachedTextureBuffer[textureWordOffset];
|
||||
|
||||
// The "wordOffset" (which is really the immediate value used on texture instructions on the shader)
|
||||
// is a 13-bit value. However, in order to also support separate samplers and textures (which uses
|
||||
// bindless textures on the shader), we extend it with another value on the higher 16 bits with
|
||||
// another offset for the sampler.
|
||||
// The shader translator has code to detect separate texture and sampler uses with a bindless texture,
|
||||
// turn that into a regular texture access and produce those special handles with values on the higher 16 bits.
|
||||
if (handleType != TextureHandleType.CombinedSampler)
|
||||
{
|
||||
int samplerHandle = cachedSamplerBuffer[samplerWordOffset];
|
||||
|
||||
if (handleType == TextureHandleType.SeparateSamplerId)
|
||||
{
|
||||
samplerHandle <<= 20;
|
||||
}
|
||||
|
||||
handle |= samplerHandle;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,17 +10,22 @@ namespace Ryujinx.Graphics.Vic
|
||||
{
|
||||
static class Blender
|
||||
{
|
||||
public static void BlendOne(Surface dst, Surface src, ref SlotStruct slot)
|
||||
public static void BlendOne(Surface dst, Surface src, ref SlotStruct slot, Rectangle targetRect)
|
||||
{
|
||||
if (Sse41.IsSupported && (dst.Width & 3) == 0)
|
||||
int x1 = targetRect.X;
|
||||
int y1 = targetRect.Y;
|
||||
int x2 = Math.Min(src.Width, x1 + targetRect.Width);
|
||||
int y2 = Math.Min(src.Height, y1 + targetRect.Height);
|
||||
|
||||
if (Sse41.IsSupported && ((x1 | x2) & 3) == 0)
|
||||
{
|
||||
BlendOneSse41(dst, src, ref slot);
|
||||
BlendOneSse41(dst, src, ref slot, x1, y1, x2, y2);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int y = 0; y < dst.Height; y++)
|
||||
for (int y = y1; y < y2; y++)
|
||||
{
|
||||
for (int x = 0; x < dst.Width; x++)
|
||||
for (int x = x1; x < x2; x++)
|
||||
{
|
||||
int inR = src.GetR(x, y);
|
||||
int inG = src.GetG(x, y);
|
||||
@ -40,9 +45,9 @@ namespace Ryujinx.Graphics.Vic
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe static void BlendOneSse41(Surface dst, Surface src, ref SlotStruct slot)
|
||||
private unsafe static void BlendOneSse41(Surface dst, Surface src, ref SlotStruct slot, int x1, int y1, int x2, int y2)
|
||||
{
|
||||
Debug.Assert((dst.Width & 3) == 0);
|
||||
Debug.Assert(((x1 | x2) & 3) == 0);
|
||||
|
||||
ref MatrixStruct mtx = ref slot.ColorMatrixStruct;
|
||||
|
||||
@ -62,9 +67,9 @@ namespace Ryujinx.Graphics.Vic
|
||||
Pixel* ip = srcPtr;
|
||||
Pixel* op = dstPtr;
|
||||
|
||||
for (int y = 0; y < dst.Height; y++, ip += src.Width, op += dst.Width)
|
||||
for (int y = y1; y < y2; y++, ip += src.Width, op += dst.Width)
|
||||
{
|
||||
for (int x = 0; x < dst.Width; x += 4)
|
||||
for (int x = x1; x < x2; x += 4)
|
||||
{
|
||||
Vector128<int> pixel1 = Sse41.ConvertToVector128Int32((ushort*)(ip + (uint)x));
|
||||
Vector128<int> pixel2 = Sse41.ConvertToVector128Int32((ushort*)(ip + (uint)x + 1));
|
||||
|
18
Ryujinx.Graphics.Vic/Rectangle.cs
Normal file
18
Ryujinx.Graphics.Vic/Rectangle.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Ryujinx.Graphics.Vic
|
||||
{
|
||||
struct Rectangle
|
||||
{
|
||||
public readonly int X;
|
||||
public readonly int Y;
|
||||
public readonly int Width;
|
||||
public readonly int Height;
|
||||
|
||||
public Rectangle(int x, int y, int width, int height)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Vic.Image;
|
||||
using Ryujinx.Graphics.Vic.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Vic
|
||||
@ -47,7 +48,19 @@ namespace Ryujinx.Graphics.Vic
|
||||
|
||||
using Surface src = SurfaceReader.Read(_rm, ref slot.SlotConfig, ref slot.SlotSurfaceConfig, ref offsets);
|
||||
|
||||
Blender.BlendOne(output, src, ref slot);
|
||||
int x1 = config.OutputConfig.TargetRectLeft;
|
||||
int y1 = config.OutputConfig.TargetRectTop;
|
||||
int x2 = config.OutputConfig.TargetRectRight + 1;
|
||||
int y2 = config.OutputConfig.TargetRectBottom + 1;
|
||||
|
||||
int targetX = Math.Min(x1, x2);
|
||||
int targetY = Math.Min(y1, y2);
|
||||
int targetW = Math.Min(output.Width - targetX, Math.Abs(x2 - x1));
|
||||
int targetH = Math.Min(output.Height - targetY, Math.Abs(y2 - y1));
|
||||
|
||||
Rectangle targetRect = new Rectangle(targetX, targetY, targetW, targetH);
|
||||
|
||||
Blender.BlendOne(output, src, ref slot, targetRect);
|
||||
}
|
||||
|
||||
SurfaceWriter.Write(_rm, output, ref config.OutputSurfaceConfig, ref _state.State.SetOutputSurface);
|
||||
|
@ -280,13 +280,6 @@ namespace Ryujinx.HLE.HOS
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainNca == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Unable to load NSP: Could not find Main NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainNca != null)
|
||||
{
|
||||
_device.Configuration.ContentManager.ClearAocData();
|
||||
@ -298,7 +291,7 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
|
||||
// This is not a normal NSP, it's actually a ExeFS as a NSP
|
||||
LoadExeFs(nsp);
|
||||
LoadExeFs(nsp, null, isHomebrew: true);
|
||||
}
|
||||
|
||||
public void LoadNca(string ncaFile)
|
||||
@ -593,7 +586,7 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadExeFs(IFileSystem codeFs, MetaLoader metaData = null)
|
||||
private void LoadExeFs(IFileSystem codeFs, MetaLoader metaData = null, bool isHomebrew = false)
|
||||
{
|
||||
if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
|
||||
{
|
||||
@ -661,7 +654,7 @@ namespace Ryujinx.HLE.HOS
|
||||
Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, memoryManagerMode);
|
||||
|
||||
// We allow it for nx-hbloader because it can be used to launch homebrew.
|
||||
bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL;
|
||||
bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL || isHomebrew;
|
||||
|
||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit);
|
||||
|
@ -1,5 +1,4 @@
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
@ -11,12 +10,12 @@ namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
private readonly ulong _pid;
|
||||
private readonly GpuContext _gpuContext;
|
||||
private readonly CpuContext _cpuContext;
|
||||
private readonly ICpuContext _cpuContext;
|
||||
private T _memoryManager;
|
||||
|
||||
public IVirtualMemoryManager AddressSpace => _memoryManager;
|
||||
|
||||
public ArmProcessContext(ulong pid, GpuContext gpuContext, T memoryManager, bool for64Bit)
|
||||
public ArmProcessContext(ulong pid, ICpuEngine cpuEngine, GpuContext gpuContext, T memoryManager, bool for64Bit)
|
||||
{
|
||||
if (memoryManager is IRefCounted rc)
|
||||
{
|
||||
@ -27,11 +26,16 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
_pid = pid;
|
||||
_gpuContext = gpuContext;
|
||||
_cpuContext = new CpuContext(memoryManager, for64Bit);
|
||||
_cpuContext = cpuEngine.CreateCpuContext(memoryManager, for64Bit);
|
||||
_memoryManager = memoryManager;
|
||||
}
|
||||
|
||||
public void Execute(ExecutionContext context, ulong codeAddress)
|
||||
public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks)
|
||||
{
|
||||
return _cpuContext.CreateExecutionContext(exceptionCallbacks);
|
||||
}
|
||||
|
||||
public void Execute(IExecutionContext context, ulong codeAddress)
|
||||
{
|
||||
_cpuContext.Execute(context, codeAddress);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.Cpu.Jit;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
@ -10,10 +11,12 @@ namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
class ArmProcessContextFactory : IProcessContextFactory
|
||||
{
|
||||
private readonly ICpuEngine _cpuEngine;
|
||||
private readonly GpuContext _gpu;
|
||||
|
||||
public ArmProcessContextFactory(GpuContext gpu)
|
||||
public ArmProcessContextFactory(ICpuEngine cpuEngine, GpuContext gpu)
|
||||
{
|
||||
_cpuEngine = cpuEngine;
|
||||
_gpu = gpu;
|
||||
}
|
||||
|
||||
@ -29,12 +32,14 @@ namespace Ryujinx.HLE.HOS
|
||||
switch (mode)
|
||||
{
|
||||
case MemoryManagerMode.SoftwarePageTable:
|
||||
return new ArmProcessContext<MemoryManager>(pid, _gpu, new MemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler), for64Bit);
|
||||
var memoryManager = new MemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler);
|
||||
return new ArmProcessContext<MemoryManager>(pid, _cpuEngine, _gpu, memoryManager, for64Bit);
|
||||
|
||||
case MemoryManagerMode.HostMapped:
|
||||
case MemoryManagerMode.HostMappedUnsafe:
|
||||
bool unsafeMode = mode == MemoryManagerMode.HostMappedUnsafe;
|
||||
return new ArmProcessContext<MemoryManagerHostMapped>(pid, _gpu, new MemoryManagerHostMapped(context.Memory, addressSpaceSize, unsafeMode, invalidAccessHandler), for64Bit);
|
||||
var memoryManagerHostMapped = new MemoryManagerHostMapped(context.Memory, addressSpaceSize, unsafeMode, invalidAccessHandler);
|
||||
return new ArmProcessContext<MemoryManagerHostMapped>(pid, _cpuEngine, _gpu, memoryManagerHostMapped, for64Bit);
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
@ -10,6 +10,8 @@ using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Audio.Output;
|
||||
using Ryujinx.Audio.Renderer.Device;
|
||||
using Ryujinx.Audio.Renderer.Server;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.Cpu.Jit;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
@ -57,6 +59,9 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
internal Switch Device { get; private set; }
|
||||
|
||||
internal ITickSource TickSource { get; }
|
||||
internal ICpuEngine CpuEngine { get; }
|
||||
|
||||
internal SurfaceFlinger SurfaceFlinger { get; private set; }
|
||||
internal AudioManager AudioManager { get; private set; }
|
||||
internal AudioOutputManager AudioOutputManager { get; private set; }
|
||||
@ -121,7 +126,11 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
public Horizon(Switch device)
|
||||
{
|
||||
TickSource = new TickSource(KernelConstants.CounterFrequency);
|
||||
CpuEngine = new JitEngine(TickSource);
|
||||
|
||||
KernelContext = new KernelContext(
|
||||
TickSource,
|
||||
device,
|
||||
device.Memory,
|
||||
device.Configuration.MemoryConfiguration.ToKernelMemorySize(),
|
||||
@ -215,40 +224,40 @@ namespace Ryujinx.HLE.HOS
|
||||
internalOffset = new TimeSpanType(-internalOffset.NanoSeconds);
|
||||
|
||||
// First init the standard steady clock
|
||||
TimeServiceManager.Instance.SetupStandardSteadyClock(null, clockSourceId, systemTime, internalOffset, TimeSpanType.Zero, false);
|
||||
TimeServiceManager.Instance.SetupStandardLocalSystemClock(null, new SystemClockContext(), systemTime.ToSeconds());
|
||||
TimeServiceManager.Instance.SetupStandardSteadyClock(TickSource, clockSourceId, systemTime, internalOffset, TimeSpanType.Zero, false);
|
||||
TimeServiceManager.Instance.SetupStandardLocalSystemClock(TickSource, new SystemClockContext(), systemTime.ToSeconds());
|
||||
|
||||
if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes))
|
||||
{
|
||||
TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000);
|
||||
|
||||
// The network system clock needs a valid system clock, as such we setup this system clock using the local system clock.
|
||||
TimeServiceManager.Instance.StandardLocalSystemClock.GetClockContext(null, out SystemClockContext localSytemClockContext);
|
||||
TimeServiceManager.Instance.StandardLocalSystemClock.GetClockContext(TickSource, out SystemClockContext localSytemClockContext);
|
||||
TimeServiceManager.Instance.SetupStandardNetworkSystemClock(localSytemClockContext, standardNetworkClockSufficientAccuracy);
|
||||
}
|
||||
|
||||
TimeServiceManager.Instance.SetupStandardUserSystemClock(null, false, SteadyClockTimePoint.GetRandom());
|
||||
TimeServiceManager.Instance.SetupStandardUserSystemClock(TickSource, false, SteadyClockTimePoint.GetRandom());
|
||||
|
||||
// FIXME: TimeZone should be init here but it's actually done in ContentManager
|
||||
|
||||
TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock();
|
||||
|
||||
DatabaseImpl.Instance.InitializeDatabase(LibHacHorizonManager.SdbClient);
|
||||
DatabaseImpl.Instance.InitializeDatabase(TickSource, LibHacHorizonManager.SdbClient);
|
||||
|
||||
HostSyncpoint = new NvHostSyncpt(device);
|
||||
|
||||
SurfaceFlinger = new SurfaceFlinger(device);
|
||||
|
||||
InitializeAudioRenderer();
|
||||
InitializeAudioRenderer(TickSource);
|
||||
InitializeServices();
|
||||
}
|
||||
|
||||
private void InitializeAudioRenderer()
|
||||
private void InitializeAudioRenderer(ITickSource tickSource)
|
||||
{
|
||||
AudioManager = new AudioManager();
|
||||
AudioOutputManager = new AudioOutputManager();
|
||||
AudioInputManager = new AudioInputManager();
|
||||
AudioRendererManager = new AudioRendererManager();
|
||||
AudioRendererManager = new AudioRendererManager(tickSource);
|
||||
AudioRendererManager.SetVolume(Device.Configuration.AudioVolume);
|
||||
AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry();
|
||||
|
||||
@ -492,12 +501,12 @@ namespace Ryujinx.HLE.HOS
|
||||
if (pause && !IsPaused)
|
||||
{
|
||||
Device.AudioDeviceDriver.GetPauseEvent().Reset();
|
||||
ARMeilleure.State.ExecutionContext.SuspendCounter();
|
||||
TickSource.Suspend();
|
||||
}
|
||||
else if (!pause && IsPaused)
|
||||
{
|
||||
Device.AudioDeviceDriver.GetPauseEvent().Set();
|
||||
ARMeilleure.State.ExecutionContext.ResumeCounter();
|
||||
TickSource.Resume();
|
||||
}
|
||||
}
|
||||
IsPaused = pause;
|
||||
|
@ -12,5 +12,7 @@ namespace Ryujinx.HLE.HOS.Kernel
|
||||
public const ulong UserSlabHeapBase = DramMemoryMap.SlabHeapBase;
|
||||
public const ulong UserSlabHeapItemSize = KPageTableBase.PageSize;
|
||||
public const ulong UserSlabHeapSize = 0x3de000;
|
||||
|
||||
public const ulong CounterFrequency = 19200000;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
|
||||
@ -23,6 +24,7 @@ namespace Ryujinx.HLE.HOS.Kernel
|
||||
|
||||
public Switch Device { get; }
|
||||
public MemoryBlock Memory { get; }
|
||||
public ITickSource TickSource { get; }
|
||||
public Syscall Syscall { get; }
|
||||
public SyscallHandler SyscallHandler { get; }
|
||||
|
||||
@ -52,11 +54,13 @@ namespace Ryujinx.HLE.HOS.Kernel
|
||||
private ulong _threadUid;
|
||||
|
||||
public KernelContext(
|
||||
ITickSource tickSource,
|
||||
Switch device,
|
||||
MemoryBlock memory,
|
||||
MemorySize memorySize,
|
||||
MemoryArrange memoryArrange)
|
||||
{
|
||||
TickSource = tickSource;
|
||||
Device = device;
|
||||
Memory = memory;
|
||||
|
||||
|
@ -1,87 +0,0 @@
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
class KMemoryRegionBlock
|
||||
{
|
||||
public long[][] Masks;
|
||||
|
||||
public ulong FreeCount;
|
||||
public int MaxLevel;
|
||||
public ulong StartAligned;
|
||||
public ulong SizeInBlocksTruncated;
|
||||
public ulong SizeInBlocksRounded;
|
||||
public int Order;
|
||||
public int NextOrder;
|
||||
|
||||
public bool TryCoalesce(int index, int count)
|
||||
{
|
||||
long mask = ((1L << count) - 1) << (index & 63);
|
||||
|
||||
index /= 64;
|
||||
|
||||
if (count >= 64)
|
||||
{
|
||||
int remaining = count;
|
||||
int tempIdx = index;
|
||||
|
||||
do
|
||||
{
|
||||
if (Masks[MaxLevel - 1][tempIdx++] != -1L)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
remaining -= 64;
|
||||
}
|
||||
while (remaining != 0);
|
||||
|
||||
remaining = count;
|
||||
tempIdx = index;
|
||||
|
||||
do
|
||||
{
|
||||
Masks[MaxLevel - 1][tempIdx] = 0;
|
||||
|
||||
ClearMaskBit(MaxLevel - 2, tempIdx++);
|
||||
|
||||
remaining -= 64;
|
||||
}
|
||||
while (remaining != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
long value = Masks[MaxLevel - 1][index];
|
||||
|
||||
if ((mask & ~value) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value &= ~mask;
|
||||
|
||||
Masks[MaxLevel - 1][index] = value;
|
||||
|
||||
if (value == 0)
|
||||
{
|
||||
ClearMaskBit(MaxLevel - 2, index);
|
||||
}
|
||||
}
|
||||
|
||||
FreeCount -= (ulong)count;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ClearMaskBit(int startLevel, int index)
|
||||
{
|
||||
for (int level = startLevel; level >= 0; level--, index /= 64)
|
||||
{
|
||||
Masks[level][index / 64] &= ~(1L << (index & 63));
|
||||
|
||||
if (Masks[level][index / 64] != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,102 +1,42 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
class KMemoryRegionManager
|
||||
{
|
||||
private static readonly int[] BlockOrders = new int[] { 12, 16, 21, 22, 25, 29, 30 };
|
||||
private readonly KPageHeap _pageHeap;
|
||||
|
||||
public ulong Address { get; private set; }
|
||||
public ulong EndAddr { get; private set; }
|
||||
public ulong Size { get; private set; }
|
||||
|
||||
private int _blockOrdersCount;
|
||||
|
||||
private readonly KMemoryRegionBlock[] _blocks;
|
||||
public ulong Address { get; }
|
||||
public ulong Size { get; }
|
||||
public ulong EndAddr => Address + Size;
|
||||
|
||||
private readonly ushort[] _pageReferenceCounts;
|
||||
|
||||
public KMemoryRegionManager(ulong address, ulong size, ulong endAddr)
|
||||
{
|
||||
_blocks = new KMemoryRegionBlock[BlockOrders.Length];
|
||||
|
||||
Address = address;
|
||||
Size = size;
|
||||
EndAddr = endAddr;
|
||||
|
||||
_blockOrdersCount = BlockOrders.Length;
|
||||
|
||||
for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++)
|
||||
{
|
||||
_blocks[blockIndex] = new KMemoryRegionBlock();
|
||||
|
||||
_blocks[blockIndex].Order = BlockOrders[blockIndex];
|
||||
|
||||
int nextOrder = blockIndex == _blockOrdersCount - 1 ? 0 : BlockOrders[blockIndex + 1];
|
||||
|
||||
_blocks[blockIndex].NextOrder = nextOrder;
|
||||
|
||||
int currBlockSize = 1 << BlockOrders[blockIndex];
|
||||
int nextBlockSize = currBlockSize;
|
||||
|
||||
if (nextOrder != 0)
|
||||
{
|
||||
nextBlockSize = 1 << nextOrder;
|
||||
}
|
||||
|
||||
ulong startAligned = BitUtils.AlignDown(address, nextBlockSize);
|
||||
ulong endAddrAligned = BitUtils.AlignDown(endAddr, currBlockSize);
|
||||
|
||||
ulong sizeInBlocksTruncated = (endAddrAligned - startAligned) >> BlockOrders[blockIndex];
|
||||
|
||||
ulong endAddrRounded = BitUtils.AlignUp(address + size, nextBlockSize);
|
||||
|
||||
ulong sizeInBlocksRounded = (endAddrRounded - startAligned) >> BlockOrders[blockIndex];
|
||||
|
||||
_blocks[blockIndex].StartAligned = startAligned;
|
||||
_blocks[blockIndex].SizeInBlocksTruncated = sizeInBlocksTruncated;
|
||||
_blocks[blockIndex].SizeInBlocksRounded = sizeInBlocksRounded;
|
||||
|
||||
ulong currSizeInBlocks = sizeInBlocksRounded;
|
||||
|
||||
int maxLevel = 0;
|
||||
|
||||
do
|
||||
{
|
||||
maxLevel++;
|
||||
}
|
||||
while ((currSizeInBlocks /= 64) != 0);
|
||||
|
||||
_blocks[blockIndex].MaxLevel = maxLevel;
|
||||
|
||||
_blocks[blockIndex].Masks = new long[maxLevel][];
|
||||
|
||||
currSizeInBlocks = sizeInBlocksRounded;
|
||||
|
||||
for (int level = maxLevel - 1; level >= 0; level--)
|
||||
{
|
||||
currSizeInBlocks = (currSizeInBlocks + 63) / 64;
|
||||
|
||||
_blocks[blockIndex].Masks[level] = new long[currSizeInBlocks];
|
||||
}
|
||||
}
|
||||
Size = size;
|
||||
|
||||
_pageReferenceCounts = new ushort[size / KPageTableBase.PageSize];
|
||||
|
||||
if (size != 0)
|
||||
{
|
||||
FreePages(address, size / KPageTableBase.PageSize);
|
||||
}
|
||||
_pageHeap = new KPageHeap(address, size);
|
||||
_pageHeap.Free(address, size / KPageTableBase.PageSize);
|
||||
_pageHeap.UpdateUsedSize();
|
||||
}
|
||||
|
||||
public KernelResult AllocatePages(ulong pagesCount, bool backwards, out KPageList pageList)
|
||||
public KernelResult AllocatePages(out KPageList pageList, ulong pagesCount)
|
||||
{
|
||||
lock (_blocks)
|
||||
if (pagesCount == 0)
|
||||
{
|
||||
KernelResult result = AllocatePagesImpl(pagesCount, backwards, out pageList);
|
||||
pageList = new KPageList();
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
lock (_pageHeap)
|
||||
{
|
||||
KernelResult result = AllocatePagesImpl(out pageList, pagesCount, false);
|
||||
|
||||
if (result == KernelResult.Success)
|
||||
{
|
||||
@ -112,9 +52,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
public ulong AllocatePagesContiguous(KernelContext context, ulong pagesCount, bool backwards)
|
||||
{
|
||||
lock (_blocks)
|
||||
if (pagesCount == 0)
|
||||
{
|
||||
ulong address = AllocatePagesContiguousImpl(pagesCount, backwards);
|
||||
return 0;
|
||||
}
|
||||
|
||||
lock (_pageHeap)
|
||||
{
|
||||
ulong address = AllocatePagesContiguousImpl(pagesCount, 1, backwards);
|
||||
|
||||
if (address != 0)
|
||||
{
|
||||
@ -126,373 +71,110 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
}
|
||||
}
|
||||
|
||||
private KernelResult AllocatePagesImpl(ulong pagesCount, bool backwards, out KPageList pageList)
|
||||
private KernelResult AllocatePagesImpl(out KPageList pageList, ulong pagesCount, bool random)
|
||||
{
|
||||
pageList = new KPageList();
|
||||
|
||||
if (_blockOrdersCount > 0)
|
||||
{
|
||||
if (GetFreePagesImpl() < pagesCount)
|
||||
{
|
||||
return KernelResult.OutOfMemory;
|
||||
}
|
||||
}
|
||||
else if (pagesCount != 0)
|
||||
int heapIndex = KPageHeap.GetBlockIndex(pagesCount);
|
||||
|
||||
if (heapIndex < 0)
|
||||
{
|
||||
return KernelResult.OutOfMemory;
|
||||
}
|
||||
|
||||
for (int blockIndex = _blockOrdersCount - 1; blockIndex >= 0; blockIndex--)
|
||||
for (int index = heapIndex; index >= 0; index--)
|
||||
{
|
||||
KMemoryRegionBlock block = _blocks[blockIndex];
|
||||
ulong pagesPerAlloc = KPageHeap.GetBlockPagesCount(index);
|
||||
|
||||
ulong bestFitBlockSize = 1UL << block.Order;
|
||||
|
||||
ulong blockPagesCount = bestFitBlockSize / KPageTableBase.PageSize;
|
||||
|
||||
// Check if this is the best fit for this page size.
|
||||
// If so, try allocating as much requested pages as possible.
|
||||
while (blockPagesCount <= pagesCount)
|
||||
while (pagesCount >= pagesPerAlloc)
|
||||
{
|
||||
ulong address = AllocatePagesForOrder(blockIndex, backwards, bestFitBlockSize);
|
||||
ulong allocatedBlock = _pageHeap.AllocateBlock(index, random);
|
||||
|
||||
// The address being zero means that no free space was found on that order,
|
||||
// just give up and try with the next one.
|
||||
if (address == 0)
|
||||
if (allocatedBlock == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Add new allocated page(s) to the pages list.
|
||||
// If an error occurs, then free all allocated pages and fail.
|
||||
KernelResult result = pageList.AddRange(address, blockPagesCount);
|
||||
KernelResult result = pageList.AddRange(allocatedBlock, pagesPerAlloc);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
FreePages(address, blockPagesCount);
|
||||
|
||||
foreach (KPageNode pageNode in pageList)
|
||||
{
|
||||
FreePages(pageNode.Address, pageNode.PagesCount);
|
||||
}
|
||||
FreePages(pageList);
|
||||
_pageHeap.Free(allocatedBlock, pagesPerAlloc);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pagesCount -= blockPagesCount;
|
||||
pagesCount -= pagesPerAlloc;
|
||||
}
|
||||
}
|
||||
|
||||
// Success case, all requested pages were allocated successfully.
|
||||
if (pagesCount == 0)
|
||||
if (pagesCount != 0)
|
||||
{
|
||||
return KernelResult.Success;
|
||||
FreePages(pageList);
|
||||
|
||||
return KernelResult.OutOfMemory;
|
||||
}
|
||||
|
||||
// Error case, free allocated pages and return out of memory.
|
||||
foreach (KPageNode pageNode in pageList)
|
||||
{
|
||||
FreePages(pageNode.Address, pageNode.PagesCount);
|
||||
}
|
||||
|
||||
pageList = null;
|
||||
|
||||
return KernelResult.OutOfMemory;
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
private ulong AllocatePagesContiguousImpl(ulong pagesCount, bool backwards)
|
||||
private ulong AllocatePagesContiguousImpl(ulong pagesCount, ulong alignPages, bool random)
|
||||
{
|
||||
if (pagesCount == 0 || _blocks.Length < 1)
|
||||
int heapIndex = KPageHeap.GetAlignedBlockIndex(pagesCount, alignPages);
|
||||
|
||||
ulong allocatedBlock = _pageHeap.AllocateBlock(heapIndex, random);
|
||||
|
||||
if (allocatedBlock == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int blockIndex = 0;
|
||||
ulong allocatedPages = KPageHeap.GetBlockPagesCount(heapIndex);
|
||||
|
||||
while ((1UL << _blocks[blockIndex].Order) / KPageTableBase.PageSize < pagesCount)
|
||||
if (allocatedPages > pagesCount)
|
||||
{
|
||||
if (++blockIndex >= _blocks.Length)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
_pageHeap.Free(allocatedBlock + pagesCount * KPageTableBase.PageSize, allocatedPages - pagesCount);
|
||||
}
|
||||
|
||||
ulong tightestFitBlockSize = 1UL << _blocks[blockIndex].Order;
|
||||
|
||||
ulong address = AllocatePagesForOrder(blockIndex, backwards, tightestFitBlockSize);
|
||||
|
||||
ulong requiredSize = pagesCount * KPageTableBase.PageSize;
|
||||
|
||||
if (address != 0 && tightestFitBlockSize > requiredSize)
|
||||
{
|
||||
FreePages(address + requiredSize, (tightestFitBlockSize - requiredSize) / KPageTableBase.PageSize);
|
||||
}
|
||||
|
||||
return address;
|
||||
return allocatedBlock;
|
||||
}
|
||||
|
||||
private ulong AllocatePagesForOrder(int blockIndex, bool backwards, ulong bestFitBlockSize)
|
||||
public void FreePage(ulong address)
|
||||
{
|
||||
ulong address = 0;
|
||||
|
||||
KMemoryRegionBlock block = null;
|
||||
|
||||
for (int currBlockIndex = blockIndex;
|
||||
currBlockIndex < _blockOrdersCount && address == 0;
|
||||
currBlockIndex++)
|
||||
lock (_pageHeap)
|
||||
{
|
||||
block = _blocks[currBlockIndex];
|
||||
|
||||
int index = 0;
|
||||
|
||||
bool zeroMask = false;
|
||||
|
||||
for (int level = 0; level < block.MaxLevel; level++)
|
||||
{
|
||||
long mask = block.Masks[level][index];
|
||||
|
||||
if (mask == 0)
|
||||
{
|
||||
zeroMask = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (backwards)
|
||||
{
|
||||
index = (index * 64 + 63) - BitOperations.LeadingZeroCount((ulong)mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
index = index * 64 + BitOperations.LeadingZeroCount((ulong)BitUtils.ReverseBits64(mask));
|
||||
}
|
||||
}
|
||||
|
||||
if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
block.FreeCount--;
|
||||
|
||||
int tempIdx = index;
|
||||
|
||||
for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64)
|
||||
{
|
||||
block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63));
|
||||
|
||||
if (block.Masks[level][tempIdx / 64] != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
address = block.StartAligned + ((ulong)index << block.Order);
|
||||
_pageHeap.Free(address, 1);
|
||||
}
|
||||
|
||||
for (int currBlockIndex = blockIndex;
|
||||
currBlockIndex < _blockOrdersCount && address == 0;
|
||||
currBlockIndex++)
|
||||
{
|
||||
block = _blocks[currBlockIndex];
|
||||
|
||||
int index = 0;
|
||||
|
||||
bool zeroMask = false;
|
||||
|
||||
for (int level = 0; level < block.MaxLevel; level++)
|
||||
{
|
||||
long mask = block.Masks[level][index];
|
||||
|
||||
if (mask == 0)
|
||||
{
|
||||
zeroMask = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (backwards)
|
||||
{
|
||||
index = index * 64 + BitOperations.LeadingZeroCount((ulong)BitUtils.ReverseBits64(mask));
|
||||
}
|
||||
else
|
||||
{
|
||||
index = (index * 64 + 63) - BitOperations.LeadingZeroCount((ulong)mask);
|
||||
}
|
||||
}
|
||||
|
||||
if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
block.FreeCount--;
|
||||
|
||||
int tempIdx = index;
|
||||
|
||||
for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64)
|
||||
{
|
||||
block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63));
|
||||
|
||||
if (block.Masks[level][tempIdx / 64] != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
address = block.StartAligned + ((ulong)index << block.Order);
|
||||
}
|
||||
|
||||
if (address != 0)
|
||||
{
|
||||
// If we are using a larger order than best fit, then we should
|
||||
// split it into smaller blocks.
|
||||
ulong firstFreeBlockSize = 1UL << block.Order;
|
||||
|
||||
if (firstFreeBlockSize > bestFitBlockSize)
|
||||
{
|
||||
FreePages(address + bestFitBlockSize, (firstFreeBlockSize - bestFitBlockSize) / KPageTableBase.PageSize);
|
||||
}
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
private void FreePages(ulong address, ulong pagesCount)
|
||||
public void FreePages(KPageList pageList)
|
||||
{
|
||||
lock (_blocks)
|
||||
lock (_pageHeap)
|
||||
{
|
||||
ulong endAddr = address + pagesCount * KPageTableBase.PageSize;
|
||||
|
||||
int blockIndex = _blockOrdersCount - 1;
|
||||
|
||||
ulong addressRounded = 0;
|
||||
ulong endAddrTruncated = 0;
|
||||
|
||||
for (; blockIndex >= 0; blockIndex--)
|
||||
foreach (KPageNode pageNode in pageList)
|
||||
{
|
||||
KMemoryRegionBlock allocInfo = _blocks[blockIndex];
|
||||
|
||||
int blockSize = 1 << allocInfo.Order;
|
||||
|
||||
addressRounded = BitUtils.AlignUp (address, blockSize);
|
||||
endAddrTruncated = BitUtils.AlignDown(endAddr, blockSize);
|
||||
|
||||
if (addressRounded < endAddrTruncated)
|
||||
{
|
||||
break;
|
||||
}
|
||||
_pageHeap.Free(pageNode.Address, pageNode.PagesCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FreeRegion(ulong currAddress)
|
||||
{
|
||||
for (int currBlockIndex = blockIndex;
|
||||
currBlockIndex < _blockOrdersCount && currAddress != 0;
|
||||
currBlockIndex++)
|
||||
{
|
||||
KMemoryRegionBlock block = _blocks[currBlockIndex];
|
||||
|
||||
block.FreeCount++;
|
||||
|
||||
ulong freedBlocks = (currAddress - block.StartAligned) >> block.Order;
|
||||
|
||||
int index = (int)freedBlocks;
|
||||
|
||||
for (int level = block.MaxLevel - 1; level >= 0; level--, index /= 64)
|
||||
{
|
||||
long mask = block.Masks[level][index / 64];
|
||||
|
||||
block.Masks[level][index / 64] = mask | (1L << (index & 63));
|
||||
|
||||
if (mask != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int blockSizeDelta = 1 << (block.NextOrder - block.Order);
|
||||
|
||||
int freedBlocksTruncated = BitUtils.AlignDown((int)freedBlocks, blockSizeDelta);
|
||||
|
||||
if (!block.TryCoalesce(freedBlocksTruncated, blockSizeDelta))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
currAddress = block.StartAligned + ((ulong)freedBlocksTruncated << block.Order);
|
||||
}
|
||||
}
|
||||
|
||||
// Free inside aligned region.
|
||||
ulong baseAddress = addressRounded;
|
||||
|
||||
while (baseAddress < endAddrTruncated)
|
||||
{
|
||||
ulong blockSize = 1UL << _blocks[blockIndex].Order;
|
||||
|
||||
FreeRegion(baseAddress);
|
||||
|
||||
baseAddress += blockSize;
|
||||
}
|
||||
|
||||
int nextBlockIndex = blockIndex - 1;
|
||||
|
||||
// Free region between Address and aligned region start.
|
||||
baseAddress = addressRounded;
|
||||
|
||||
for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--)
|
||||
{
|
||||
ulong blockSize = 1UL << _blocks[blockIndex].Order;
|
||||
|
||||
while (baseAddress - blockSize >= address)
|
||||
{
|
||||
baseAddress -= blockSize;
|
||||
|
||||
FreeRegion(baseAddress);
|
||||
}
|
||||
}
|
||||
|
||||
// Free region between aligned region end and End Address.
|
||||
baseAddress = endAddrTruncated;
|
||||
|
||||
for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--)
|
||||
{
|
||||
ulong blockSize = 1UL << _blocks[blockIndex].Order;
|
||||
|
||||
while (baseAddress + blockSize <= endAddr)
|
||||
{
|
||||
FreeRegion(baseAddress);
|
||||
|
||||
baseAddress += blockSize;
|
||||
}
|
||||
}
|
||||
public void FreePages(ulong address, ulong pagesCount)
|
||||
{
|
||||
lock (_pageHeap)
|
||||
{
|
||||
_pageHeap.Free(address, pagesCount);
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetFreePages()
|
||||
{
|
||||
lock (_blocks)
|
||||
lock (_pageHeap)
|
||||
{
|
||||
return GetFreePagesImpl();
|
||||
return _pageHeap.GetFreePagesCount();
|
||||
}
|
||||
}
|
||||
|
||||
private ulong GetFreePagesImpl()
|
||||
{
|
||||
ulong availablePages = 0;
|
||||
|
||||
for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++)
|
||||
{
|
||||
KMemoryRegionBlock block = _blocks[blockIndex];
|
||||
|
||||
ulong blockPagesCount = (1UL << block.Order) / KPageTableBase.PageSize;
|
||||
|
||||
availablePages += blockPagesCount * block.FreeCount;
|
||||
}
|
||||
|
||||
return availablePages;
|
||||
}
|
||||
|
||||
public void IncrementPagesReferenceCount(ulong address, ulong pagesCount)
|
||||
{
|
||||
ulong index = GetPageOffset(address);
|
||||
|
298
Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs
Normal file
298
Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs
Normal file
@ -0,0 +1,298 @@
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
class KPageBitmap
|
||||
{
|
||||
private struct RandomNumberGenerator
|
||||
{
|
||||
private uint _entropy;
|
||||
private uint _bitsAvailable;
|
||||
|
||||
private void RefreshEntropy()
|
||||
{
|
||||
_entropy = 0;
|
||||
_bitsAvailable = sizeof(uint) * 8;
|
||||
}
|
||||
|
||||
private bool GenerateRandomBit()
|
||||
{
|
||||
if (_bitsAvailable == 0)
|
||||
{
|
||||
RefreshEntropy();
|
||||
}
|
||||
|
||||
bool bit = (_entropy & 1) != 0;
|
||||
|
||||
_entropy >>= 1;
|
||||
_bitsAvailable--;
|
||||
|
||||
return bit;
|
||||
}
|
||||
|
||||
public int SelectRandomBit(ulong bitmap)
|
||||
{
|
||||
int selected = 0;
|
||||
|
||||
int bitsCount = UInt64BitSize / 2;
|
||||
ulong mask = (1UL << bitsCount) - 1;
|
||||
|
||||
while (bitsCount != 0)
|
||||
{
|
||||
ulong low = bitmap & mask;
|
||||
ulong high = (bitmap >> bitsCount) & mask;
|
||||
|
||||
bool chooseLow;
|
||||
|
||||
if (high == 0)
|
||||
{
|
||||
chooseLow = true;
|
||||
}
|
||||
else if (low == 0)
|
||||
{
|
||||
chooseLow = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
chooseLow = GenerateRandomBit();
|
||||
}
|
||||
|
||||
if (chooseLow)
|
||||
{
|
||||
bitmap = low;
|
||||
}
|
||||
else
|
||||
{
|
||||
bitmap = high;
|
||||
selected += bitsCount;
|
||||
}
|
||||
|
||||
bitsCount /= 2;
|
||||
mask >>= bitsCount;
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
|
||||
private const int UInt64BitSize = sizeof(ulong) * 8;
|
||||
private const int MaxDepth = 4;
|
||||
|
||||
private readonly RandomNumberGenerator _rng;
|
||||
private readonly ArraySegment<ulong>[] _bitStorages;
|
||||
private int _usedDepths;
|
||||
|
||||
public int BitsCount { get; private set; }
|
||||
|
||||
public int HighestDepthIndex => _usedDepths - 1;
|
||||
|
||||
public KPageBitmap()
|
||||
{
|
||||
_rng = new RandomNumberGenerator();
|
||||
_bitStorages = new ArraySegment<ulong>[MaxDepth];
|
||||
}
|
||||
|
||||
public ArraySegment<ulong> Initialize(ArraySegment<ulong> storage, ulong size)
|
||||
{
|
||||
_usedDepths = GetRequiredDepth(size);
|
||||
|
||||
for (int depth = HighestDepthIndex; depth >= 0; depth--)
|
||||
{
|
||||
_bitStorages[depth] = storage;
|
||||
size = BitUtils.DivRoundUp(size, UInt64BitSize);
|
||||
storage = storage.Slice((int)size);
|
||||
}
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
public ulong FindFreeBlock(bool random)
|
||||
{
|
||||
ulong offset = 0;
|
||||
int depth = 0;
|
||||
|
||||
if (random)
|
||||
{
|
||||
do
|
||||
{
|
||||
ulong v = _bitStorages[depth][(int)offset];
|
||||
|
||||
if (v == 0)
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
offset = offset * UInt64BitSize + (ulong)_rng.SelectRandomBit(v);
|
||||
}
|
||||
while (++depth < _usedDepths);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
ulong v = _bitStorages[depth][(int)offset];
|
||||
|
||||
if (v == 0)
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
offset = offset * UInt64BitSize + (ulong)BitOperations.TrailingZeroCount(v);
|
||||
}
|
||||
while (++depth < _usedDepths);
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void SetBit(ulong offset)
|
||||
{
|
||||
SetBit(HighestDepthIndex, offset);
|
||||
BitsCount++;
|
||||
}
|
||||
|
||||
public void ClearBit(ulong offset)
|
||||
{
|
||||
ClearBit(HighestDepthIndex, offset);
|
||||
BitsCount--;
|
||||
}
|
||||
|
||||
public bool ClearRange(ulong offset, int count)
|
||||
{
|
||||
int depth = HighestDepthIndex;
|
||||
var bits = _bitStorages[depth];
|
||||
|
||||
int bitInd = (int)(offset / UInt64BitSize);
|
||||
|
||||
if (count < UInt64BitSize)
|
||||
{
|
||||
int shift = (int)(offset % UInt64BitSize);
|
||||
|
||||
ulong mask = ((1UL << count) - 1) << shift;
|
||||
|
||||
ulong v = bits[bitInd];
|
||||
|
||||
if ((v & mask) != mask)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
v &= ~mask;
|
||||
bits[bitInd] = v;
|
||||
|
||||
if (v == 0)
|
||||
{
|
||||
ClearBit(depth - 1, (ulong)bitInd);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int remaining = count;
|
||||
int i = 0;
|
||||
|
||||
do
|
||||
{
|
||||
if (bits[bitInd + i++] != ulong.MaxValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
remaining -= UInt64BitSize;
|
||||
}
|
||||
while (remaining > 0);
|
||||
|
||||
remaining = count;
|
||||
i = 0;
|
||||
|
||||
do
|
||||
{
|
||||
bits[bitInd + i] = 0;
|
||||
ClearBit(depth - 1, (ulong)(bitInd + i));
|
||||
i++;
|
||||
remaining -= UInt64BitSize;
|
||||
}
|
||||
while (remaining > 0);
|
||||
}
|
||||
|
||||
BitsCount -= count;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetBit(int depth, ulong offset)
|
||||
{
|
||||
while (depth >= 0)
|
||||
{
|
||||
int ind = (int)(offset / UInt64BitSize);
|
||||
int which = (int)(offset % UInt64BitSize);
|
||||
|
||||
ulong mask = 1UL << which;
|
||||
|
||||
ulong v = _bitStorages[depth][ind];
|
||||
|
||||
_bitStorages[depth][ind] = v | mask;
|
||||
|
||||
if (v != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
offset = (ulong)ind;
|
||||
depth--;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearBit(int depth, ulong offset)
|
||||
{
|
||||
while (depth >= 0)
|
||||
{
|
||||
int ind = (int)(offset / UInt64BitSize);
|
||||
int which = (int)(offset % UInt64BitSize);
|
||||
|
||||
ulong mask = 1UL << which;
|
||||
|
||||
ulong v = _bitStorages[depth][ind];
|
||||
|
||||
v &= ~mask;
|
||||
|
||||
_bitStorages[depth][ind] = v;
|
||||
|
||||
if (v != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
offset = (ulong)ind;
|
||||
depth--;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetRequiredDepth(ulong regionSize)
|
||||
{
|
||||
int depth = 0;
|
||||
|
||||
do
|
||||
{
|
||||
regionSize /= UInt64BitSize;
|
||||
depth++;
|
||||
}
|
||||
while (regionSize != 0);
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
public static int CalculateManagementOverheadSize(ulong regionSize)
|
||||
{
|
||||
int overheadBits = 0;
|
||||
|
||||
for (int depth = GetRequiredDepth(regionSize) - 1; depth >= 0; depth--)
|
||||
{
|
||||
regionSize = BitUtils.DivRoundUp(regionSize, UInt64BitSize);
|
||||
overheadBits += (int)regionSize;
|
||||
}
|
||||
|
||||
return overheadBits * sizeof(ulong);
|
||||
}
|
||||
}
|
||||
}
|
283
Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs
Normal file
283
Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs
Normal file
@ -0,0 +1,283 @@
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
class KPageHeap
|
||||
{
|
||||
private class Block
|
||||
{
|
||||
private KPageBitmap _bitmap = new KPageBitmap();
|
||||
private ulong _heapAddress;
|
||||
private ulong _endOffset;
|
||||
|
||||
public int Shift { get; private set; }
|
||||
public int NextShift { get; private set; }
|
||||
public ulong Size => 1UL << Shift;
|
||||
public int PagesCount => (int)(Size / KPageTableBase.PageSize);
|
||||
public int FreeBlocksCount => _bitmap.BitsCount;
|
||||
public int FreePagesCount => FreeBlocksCount * PagesCount;
|
||||
|
||||
public ArraySegment<ulong> Initialize(ulong address, ulong size, int blockShift, int nextBlockShift, ArraySegment<ulong> bitStorage)
|
||||
{
|
||||
Shift = blockShift;
|
||||
NextShift = nextBlockShift;
|
||||
|
||||
ulong endAddress = address + size;
|
||||
|
||||
ulong align = nextBlockShift != 0
|
||||
? 1UL << nextBlockShift
|
||||
: 1UL << blockShift;
|
||||
|
||||
address = BitUtils.AlignDown(address, align);
|
||||
endAddress = BitUtils.AlignUp (endAddress, align);
|
||||
|
||||
_heapAddress = address;
|
||||
_endOffset = (endAddress - address) / (1UL << blockShift);
|
||||
|
||||
return _bitmap.Initialize(bitStorage, _endOffset);
|
||||
}
|
||||
|
||||
public ulong PushBlock(ulong address)
|
||||
{
|
||||
ulong offset = (address - _heapAddress) >> Shift;
|
||||
|
||||
_bitmap.SetBit(offset);
|
||||
|
||||
if (NextShift != 0)
|
||||
{
|
||||
int diff = 1 << (NextShift - Shift);
|
||||
|
||||
offset = BitUtils.AlignDown(offset, diff);
|
||||
|
||||
if (_bitmap.ClearRange(offset, diff))
|
||||
{
|
||||
return _heapAddress + (offset << Shift);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public ulong PopBlock(bool random)
|
||||
{
|
||||
long sOffset = (long)_bitmap.FindFreeBlock(random);
|
||||
|
||||
if (sOffset < 0L)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ulong offset = (ulong)sOffset;
|
||||
|
||||
_bitmap.ClearBit(offset);
|
||||
|
||||
return _heapAddress + (offset << Shift);
|
||||
}
|
||||
|
||||
public static int CalculateManagementOverheadSize(ulong regionSize, int currBlockShift, int nextBlockShift)
|
||||
{
|
||||
ulong currBlockSize = 1UL << currBlockShift;
|
||||
ulong nextBlockSize = 1UL << nextBlockShift;
|
||||
ulong align = nextBlockShift != 0 ? nextBlockSize : currBlockSize;
|
||||
return KPageBitmap.CalculateManagementOverheadSize((align * 2 + BitUtils.AlignUp(regionSize, align)) / currBlockSize);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly int[] _memoryBlockPageShifts = new int[] { 12, 16, 21, 22, 25, 29, 30 };
|
||||
|
||||
private readonly ulong _heapAddress;
|
||||
private readonly ulong _heapSize;
|
||||
private ulong _usedSize;
|
||||
private readonly int _blocksCount;
|
||||
private readonly Block[] _blocks;
|
||||
|
||||
public KPageHeap(ulong address, ulong size) : this(address, size, _memoryBlockPageShifts)
|
||||
{
|
||||
}
|
||||
|
||||
public KPageHeap(ulong address, ulong size, int[] blockShifts)
|
||||
{
|
||||
_heapAddress = address;
|
||||
_heapSize = size;
|
||||
_blocksCount = blockShifts.Length;
|
||||
_blocks = new Block[_memoryBlockPageShifts.Length];
|
||||
|
||||
var currBitmapStorage = new ArraySegment<ulong>(new ulong[CalculateManagementOverheadSize(size, blockShifts)]);
|
||||
|
||||
for (int i = 0; i < blockShifts.Length; i++)
|
||||
{
|
||||
int currBlockShift = blockShifts[i];
|
||||
int nextBlockShift = i != blockShifts.Length - 1 ? blockShifts[i + 1] : 0;
|
||||
|
||||
_blocks[i] = new Block();
|
||||
|
||||
currBitmapStorage = _blocks[i].Initialize(address, size, currBlockShift, nextBlockShift, currBitmapStorage);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateUsedSize()
|
||||
{
|
||||
_usedSize = _heapSize - (GetFreePagesCount() * KPageTableBase.PageSize);
|
||||
}
|
||||
|
||||
public ulong GetFreePagesCount()
|
||||
{
|
||||
ulong freeCount = 0;
|
||||
|
||||
for (int i = 0; i < _blocksCount; i++)
|
||||
{
|
||||
freeCount += (ulong)_blocks[i].FreePagesCount;
|
||||
}
|
||||
|
||||
return freeCount;
|
||||
}
|
||||
|
||||
public ulong AllocateBlock(int index, bool random)
|
||||
{
|
||||
ulong neededSize = _blocks[index].Size;
|
||||
|
||||
for (int i = index; i < _blocksCount; i++)
|
||||
{
|
||||
ulong address = _blocks[i].PopBlock(random);
|
||||
|
||||
if (address != 0)
|
||||
{
|
||||
ulong allocatedSize = _blocks[i].Size;
|
||||
|
||||
if (allocatedSize > neededSize)
|
||||
{
|
||||
Free(address + neededSize, (allocatedSize - neededSize) / KPageTableBase.PageSize);
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void FreeBlock(ulong block, int index)
|
||||
{
|
||||
do
|
||||
{
|
||||
block = _blocks[index++].PushBlock(block);
|
||||
}
|
||||
while (block != 0);
|
||||
}
|
||||
|
||||
public void Free(ulong address, ulong pagesCount)
|
||||
{
|
||||
if (pagesCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int bigIndex = _blocksCount - 1;
|
||||
|
||||
ulong start = address;
|
||||
ulong end = address + pagesCount * KPageTableBase.PageSize;
|
||||
ulong beforeStart = start;
|
||||
ulong beforeEnd = start;
|
||||
ulong afterStart = end;
|
||||
ulong afterEnd = end;
|
||||
|
||||
while (bigIndex >= 0)
|
||||
{
|
||||
ulong blockSize = _blocks[bigIndex].Size;
|
||||
|
||||
ulong bigStart = BitUtils.AlignUp (start, blockSize);
|
||||
ulong bigEnd = BitUtils.AlignDown(end, blockSize);
|
||||
|
||||
if (bigStart < bigEnd)
|
||||
{
|
||||
for (ulong block = bigStart; block < bigEnd; block += blockSize)
|
||||
{
|
||||
FreeBlock(block, bigIndex);
|
||||
}
|
||||
|
||||
beforeEnd = bigStart;
|
||||
afterStart = bigEnd;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
bigIndex--;
|
||||
}
|
||||
|
||||
for (int i = bigIndex - 1; i >= 0; i--)
|
||||
{
|
||||
ulong blockSize = _blocks[i].Size;
|
||||
|
||||
while (beforeStart + blockSize <= beforeEnd)
|
||||
{
|
||||
beforeEnd -= blockSize;
|
||||
FreeBlock(beforeEnd, i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = bigIndex - 1; i >= 0; i--)
|
||||
{
|
||||
ulong blockSize = _blocks[i].Size;
|
||||
|
||||
while (afterStart + blockSize <= afterEnd)
|
||||
{
|
||||
FreeBlock(afterStart, i);
|
||||
afterStart += blockSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetAlignedBlockIndex(ulong pagesCount, ulong alignPages)
|
||||
{
|
||||
ulong targetPages = Math.Max(pagesCount, alignPages);
|
||||
|
||||
for (int i = 0; i < _memoryBlockPageShifts.Length; i++)
|
||||
{
|
||||
if (targetPages <= GetBlockPagesCount(i))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int GetBlockIndex(ulong pagesCount)
|
||||
{
|
||||
for (int i = _memoryBlockPageShifts.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (pagesCount >= GetBlockPagesCount(i))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static ulong GetBlockSize(int index)
|
||||
{
|
||||
return 1UL << _memoryBlockPageShifts[index];
|
||||
}
|
||||
|
||||
public static ulong GetBlockPagesCount(int index)
|
||||
{
|
||||
return GetBlockSize(index) / KPageTableBase.PageSize;
|
||||
}
|
||||
|
||||
private static int CalculateManagementOverheadSize(ulong regionSize, int[] blockShifts)
|
||||
{
|
||||
int overheadSize = 0;
|
||||
|
||||
for (int i = 0; i < blockShifts.Length; i++)
|
||||
{
|
||||
int currBlockShift = blockShifts[i];
|
||||
int nextBlockShift = i != blockShifts.Length - 1 ? blockShifts[i + 1] : 0;
|
||||
overheadSize += Block.CalculateManagementOverheadSize(regionSize, currBlockShift, nextBlockShift);
|
||||
}
|
||||
|
||||
return BitUtils.AlignUp(overheadSize, KPageTableBase.PageSize);
|
||||
}
|
||||
}
|
||||
}
|
@ -555,7 +555,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
KMemoryRegionManager region = GetMemoryRegionManager();
|
||||
|
||||
KernelResult result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList);
|
||||
KernelResult result = region.AllocatePages(out KPageList pageList, pagesCount);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
@ -712,7 +712,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
KMemoryRegionManager region = GetMemoryRegionManager();
|
||||
|
||||
KernelResult result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList);
|
||||
KernelResult result = region.AllocatePages(out KPageList pageList, pagesCount);
|
||||
|
||||
using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager));
|
||||
|
||||
@ -1276,7 +1276,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
KMemoryRegionManager region = GetMemoryRegionManager();
|
||||
|
||||
KernelResult result = region.AllocatePages(remainingPages, _aslrDisabled, out KPageList pageList);
|
||||
KernelResult result = region.AllocatePages(out KPageList pageList, remainingPages);
|
||||
|
||||
using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager));
|
||||
|
||||
|
@ -115,7 +115,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
string GetReg(int x)
|
||||
{
|
||||
var v = x == 32 ? (ulong)thread.LastPc : context.GetX(x);
|
||||
var v = x == 32 ? context.Pc : context.GetX(x);
|
||||
if (!AnalyzePointer(out PointerInfo info, v, thread))
|
||||
{
|
||||
return $"0x{v:x16}";
|
||||
|
@ -1,4 +1,4 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
|
||||
@ -8,7 +8,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
IVirtualMemoryManager AddressSpace { get; }
|
||||
|
||||
void Execute(ExecutionContext context, ulong codeAddress);
|
||||
IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks);
|
||||
void Execute(IExecutionContext context, ulong codeAddress);
|
||||
void InvalidateCacheRegion(ulong address, ulong size);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
@ -736,22 +735,25 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
ulong argsPtr,
|
||||
ulong stackTop,
|
||||
int priority,
|
||||
int cpuCore)
|
||||
int cpuCore,
|
||||
ThreadStart customThreadStart = null)
|
||||
{
|
||||
lock (_processLock)
|
||||
{
|
||||
return thread.Initialize(entrypoint, argsPtr, stackTop, priority, cpuCore, this, ThreadType.User, null);
|
||||
return thread.Initialize(entrypoint, argsPtr, stackTop, priority, cpuCore, this, ThreadType.User, customThreadStart);
|
||||
}
|
||||
}
|
||||
|
||||
public void SubscribeThreadEventHandlers(ARMeilleure.State.ExecutionContext context)
|
||||
public IExecutionContext CreateExecutionContext()
|
||||
{
|
||||
context.Interrupt += InterruptHandler;
|
||||
context.SupervisorCall += KernelContext.SyscallHandler.SvcCall;
|
||||
context.Undefined += UndefinedInstructionHandler;
|
||||
return Context?.CreateExecutionContext(new ExceptionCallbacks(
|
||||
InterruptHandler,
|
||||
null,
|
||||
KernelContext.SyscallHandler.SvcCall,
|
||||
UndefinedInstructionHandler));
|
||||
}
|
||||
|
||||
private void InterruptHandler(object sender, EventArgs e)
|
||||
private void InterruptHandler(IExecutionContext context)
|
||||
{
|
||||
KThread currentThread = KernelStatic.GetCurrentThread();
|
||||
|
||||
@ -964,6 +966,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
SignalExitToDebugExited();
|
||||
SignalExit();
|
||||
}
|
||||
|
||||
KernelStatic.GetCurrentThread().Exit();
|
||||
}
|
||||
|
||||
private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread)
|
||||
@ -979,7 +983,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
foreach (KThread thread in _threads)
|
||||
{
|
||||
if ((thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
|
||||
if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
|
||||
{
|
||||
thread.PrepareForTermination();
|
||||
}
|
||||
@ -1093,12 +1097,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UndefinedInstructionHandler(object sender, InstUndefinedEventArgs e)
|
||||
private void UndefinedInstructionHandler(IExecutionContext context, ulong address, int opCode)
|
||||
{
|
||||
KernelStatic.GetCurrentThread().PrintGuestStackTrace();
|
||||
KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout();
|
||||
|
||||
throw new UndefinedInstructionException(e.Address, e.OpCode);
|
||||
throw new UndefinedInstructionException(address, opCode);
|
||||
}
|
||||
|
||||
protected override void Destroy() => Context.Dispose();
|
||||
|
@ -1,4 +1,4 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
|
||||
@ -13,7 +13,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
AddressSpace = asManager;
|
||||
}
|
||||
|
||||
public void Execute(ExecutionContext context, ulong codeAddress)
|
||||
public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks)
|
||||
{
|
||||
return new ProcessExecutionContext();
|
||||
}
|
||||
|
||||
public void Execute(IExecutionContext context, ulong codeAddress)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
44
Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs
Normal file
44
Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Cpu;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
class ProcessExecutionContext : IExecutionContext
|
||||
{
|
||||
public ulong Pc => 0UL;
|
||||
|
||||
public ulong CntfrqEl0 { get => 0; set { } }
|
||||
public ulong CntpctEl0 => 0UL;
|
||||
|
||||
public long TpidrEl0 { get => 0; set { } }
|
||||
public long TpidrroEl0 { get => 0; set { } }
|
||||
|
||||
public uint Pstate { get => 0; set { } }
|
||||
|
||||
public uint Fpcr { get => 0; set { } }
|
||||
public uint Fpsr { get => 0; set { } }
|
||||
|
||||
public bool IsAarch32 { get => false; set { } }
|
||||
|
||||
public bool Running { get; private set; } = true;
|
||||
|
||||
public ulong GetX(int index) => 0UL;
|
||||
public void SetX(int index, ulong value) { }
|
||||
|
||||
public V128 GetV(int index) => default;
|
||||
public void SetV(int index, V128 value) { }
|
||||
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
}
|
||||
|
||||
public void StopRunning()
|
||||
{
|
||||
Running = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
{
|
||||
class InvalidSvcException : Exception
|
||||
{
|
||||
public InvalidSvcException(string message) : base(message) { }
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
class PointerSizedAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user