Compare commits

..

28 Commits

Author SHA1 Message Date
2382717600 timezone: Fix regression caused by #3361 (#3418)
Because of that PR, TimeZoneRule was bigger than 0x4000 thanks to a
misuse of a constant.

This commit address this issue and add a new unit test to ensure the size of
TimeZoneRule is 0x4000 bytes.

Also address suggestions that were lost on the original PR.
2022-06-24 21:11:56 +02:00
30ee70a9bc time: Make TimeZoneRule blittable and avoid copies (#3361)
* time: Make TimeZoneRule blittable and avoid copies

This drastically reduce overhead of using TimeZoneRule around the
codebase.

Effect on games is unknown

* Add missing Box type

* Ensure we clean the structure still

This doesn't perform any copies

* Address gdkchan's comments

* Simplify Box
2022-06-24 19:04:57 +02:00
232b1012b0 Fix ThreadingLock deadlock on invalid access and TerminateProcess (#3407) 2022-06-24 02:53:16 +02:00
e747f5cd83 Ensure texture ID is valid before getting texture descriptor (#3406) 2022-06-24 02:41:57 +02:00
8aff17a93c UI: Some Avalonia cleanup (#3358) 2022-06-23 15:59:02 -03:00
f2a41b7a1c Rewrite kernel memory allocator (#3316)
* Rewrite kernel memory allocator

* Remove unused using

* Adjust private static field naming

* Change UlongBitSize to UInt64BitSize

* Fix unused argument, change argument order to be inline with official code and disable random allocation
2022-06-22 12:28:14 -03:00
c881cd2d14 Fix doubling of detected gamepads on program start (#3398)
* Fix doubling of detected gamepads (sometimes the connected event is fired when the app starts even though the pad was connected for some time now).

The fix rejects the gamepad if one with the same ID is already present.

* Fixed review findings
2022-06-20 19:01:55 +02:00
68f9091870 Account for res scale changes when updating bindings (#3403)
Fixes a regression introduced by the texture bindings PR.

Also renames TextureStatePerStage, as it's no longer per stage.
2022-06-17 17:41:38 -03:00
99ffc061d3 Optimize Texture Binding and Shader Specialization Checks (#3399)
* Changes 1

* Changes 2

* Better ModifiedSequence handling

This should handle PreciseEvents properly, and simplifies a few things.

* Minor changes, remove debug log

* Handle stage.Info being null

Hopefully fixes Catherine crash

* Fix shader specialization fast texture lookup

* Fix some things.

* Address Feedback Part 1

* Make method static.
2022-06-17 13:09:14 -03:00
d987cacfb7 Fix VIC out of bounds copy (#3386)
* Fix VIC out of bounds copy

* Update the assert
2022-06-17 12:01:52 -03:00
851f56b08a Support Array/3D depth-stencil render target, and single layer clears (#3400)
* Support Array/3D depth-stencil render target, and single layer clears

* Alignment
2022-06-14 13:30:39 -03:00
b1bd6a50b5 Less invasive fix for EventFd blocking operations (#3394) 2022-06-12 09:29:12 +02:00
70895bdb04 Allow concurrent BSD EventFd read/write (#3385) 2022-06-11 14:58:30 -03:00
830cbf91bb Ignore ClipControl on draw texture fallback (#3388) 2022-06-11 14:31:17 -03:00
9a9349f0f4 Fix instanced indexed inline draw index count (#3389) 2022-06-10 23:44:49 -03:00
46cc7b55f0 Fix instanced indexed inline draws (#3383) 2022-06-05 21:24:28 -03:00
dd8f97ab9e Remove freed memory range from tree on memory block disposal (#3347)
* Remove freed memory range from tree on memory block disposal

* PR feedback
2022-06-05 15:12:42 -03:00
633c5ec330 Extend uses count from ushort to uint on Operand Data structure (#3374) 2022-06-05 14:15:27 -03:00
a3e7bb8eb4 Copy dependency for multisample and non-multisample textures (#3382)
* Use copy dependency for textures that differs in multisample but are otherwise compatible

* Remove allowMs flag as it's no longer required for correctness, it's just an optimization now

* Dispose intermmediate pool
2022-06-05 14:06:47 -03:00
2073ba2919 Fix a potential GPFIFO submission race (#3378)
The syncpoint maximum value represents the maximum possible syncpt value at a given time, however due to PBs being submitted before max was incremented, for a brief moment of time this is not the case which could lead to invalid behaviour if a game waits on the fence at that specific time.
2022-06-04 21:36:36 +02:00
d03124a992 Fix 3D semaphore counter type 0 handling (#3380)
Counter type 0 actually releases the semaphore payload rather than a constant zero as was previously thought. This is required by Skyrim.
2022-06-02 19:51:36 -03:00
59490d54b5 infra: Switch to win10-x64 RID and fix PR comment for Avalonia and SDL2 artifact rename (#3375)
* infra: Switch to win10-x64 RID and fix PR comment for Avalonia and SDL2 artifact rename

* Address gdkchan's comments
2022-06-01 02:01:16 +02:00
e546e5933f Rewrite SVC handler using source generators rather than IL emit (#3371)
* Implement syscall handlers using a source generator

* Copy FlushProcessDataCache implementation to Syscall since it was only implemented on Syscall32

* Fix wrong argument order in some syscalls

* Delete old Reflection.Emit based syscall handling code

* Improvements to the code generation

* ControlCodeMemory address and size is always 64-bit
2022-05-31 17:12:46 -03:00
0c87bf9ea4 Refactor CPU interface to allow the implementation of other CPU emulators (#3362)
* Refactor CPU interface

* Use IExecutionContext interface on SVC handler, change how CPU interrupts invokes the handlers

* Make CpuEngine take a ITickSource rather than returning one

The previous implementation had the scenario where the CPU engine had to implement the tick source in mind, like for example, when we have a hypervisor and the game can read CNTPCT on the host directly. However given that we need to do conversion due to different frequencies anyway, it's not worth it. It's better to just let the user pass the tick source and redirect any reads to CNTPCT to the user tick source

* XML docs for the public interfaces

* PPTC invalidation due to NativeInterface function name changes

* Fix build of the CPU tests

* PR feedback
2022-05-31 16:29:35 -03:00
9827dc35e1 Allow loading NSPs without a NCA inside (#3364)
* Allow loading NSPs without a NCA inside

* Set isHomebrew as true
2022-05-31 16:16:59 -03:00
448723d3b3 Don't force DPI aware on Avalonia - it already has it covered. (#3354) 2022-05-21 23:32:50 +02:00
89294b7772 Fix audio renderer error message result code base (#3348) 2022-05-19 00:59:27 +02:00
7b9c4757dd UI - Scale end framebuffer blit (#3342)
* Scale end framebuffer blit

* fix

* fix

* apply changes to avalonia
2022-05-16 18:10:29 -03:00
168 changed files with 5221 additions and 3585 deletions

View File

@ -39,7 +39,7 @@ jobs:
- os: windows-latest - os: windows-latest
OS_NAME: Windows x64 OS_NAME: Windows x64
DOTNET_RUNTIME_IDENTIFIER: win-x64 DOTNET_RUNTIME_IDENTIFIER: win10-x64
RELEASE_ZIP_OS_NAME: win_x64 RELEASE_ZIP_OS_NAME: win_x64
fail-fast: false fail-fast: false

View File

@ -36,19 +36,24 @@ jobs:
return core.error(`No artifacts found`); return core.error(`No artifacts found`);
} }
let body = `Download the artifacts for this pull request:\n`; 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_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`; let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
for (const art of artifacts) { for (const art of artifacts) {
if(art.name.includes('Debug')) { if(art.name.includes('Debug')) {
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; 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)`; hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else { } else {
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; 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_headless_artifacts += `\n</details>`;
hidden_debug_artifacts += `\n</details>`; hidden_debug_artifacts += `\n</details>`;
body += hidden_avalonia_artifacts;
body += hidden_headless_artifacts; body += hidden_headless_artifacts;
body += hidden_debug_artifacts; body += hidden_debug_artifacts;

View File

@ -51,9 +51,9 @@ jobs:
run: "mkdir release_output" run: "mkdir release_output"
- name: Publish Windows - name: Publish Windows
run: | 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 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 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 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 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_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 - name: Packing Windows builds
run: | run: |
pushd publish_windows pushd publish_windows

View File

@ -33,13 +33,13 @@ namespace ARMeilleure.Instructions
switch (GetPackedId(op)) switch (GetPackedId(op))
{ {
case 0b11_011_0000_0000_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCtrEl0)); 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_0000_0000_111: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0)); break;
case 0b11_011_0100_0010_000: EmitGetNzcv(context); return; 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_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_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_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_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_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_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); break;
case 0b11_011_1110_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0)); break; case 0b11_011_1110_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0)); break;

View File

@ -107,14 +107,14 @@ namespace ARMeilleure.Instructions
return (uint)GetContext().TpidrEl0; return (uint)GetContext().TpidrEl0;
} }
public static ulong GetTpidr() public static ulong GetTpidrroEl0()
{ {
return (ulong)GetContext().Tpidr; return (ulong)GetContext().TpidrroEl0;
} }
public static uint GetTpidr32() public static uint GetTpidr32()
{ {
return (uint)GetContext().Tpidr; return (uint)GetContext().TpidrroEl0;
} }
public static ulong GetCntfrqEl0() public static ulong GetCntfrqEl0()

View File

@ -14,10 +14,11 @@ namespace ARMeilleure.IntermediateRepresentation
public byte Kind; public byte Kind;
public byte Type; public byte Type;
public byte SymbolType; public byte SymbolType;
public byte Padding; // Unused space.
public ushort AssignmentsCount; public ushort AssignmentsCount;
public ushort AssignmentsCapacity; public ushort AssignmentsCapacity;
public ushort UsesCount; public uint UsesCount;
public ushort UsesCapacity; public uint UsesCapacity;
public Operation* Assignments; public Operation* Assignments;
public Operation* Uses; public Operation* Uses;
public ulong Value; public ulong Value;
@ -84,11 +85,11 @@ namespace ARMeilleure.IntermediateRepresentation
{ {
Debug.Assert(Kind != OperandKind.Memory); 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 int AssignmentsCount => _data->AssignmentsCount;
public bool Relocatable => Symbol.Type != SymbolType.None; 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); Add(operation, ref addr._data->Assignments, ref addr._data->AssignmentsCount, ref addr._data->AssignmentsCapacity);
} }
if (index != default) if (index != default)
{ {
Add(operation, ref index._data->Assignments, ref index._data->AssignmentsCount, ref index._data->AssignmentsCapacity); 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); 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 private static void Add<T>(T item, ref T* data, ref ushort count, ref ushort capacity) where T : unmanaged
{ {
if (count < capacity) 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 private static void Remove<T>(in T item, ref T* data, ref ushort count) where T : unmanaged
{ {
var span = new Span<T>(data, count); 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() public override int GetHashCode()
{ {
if (Kind == OperandKind.LocalVariable) if (Kind == OperandKind.LocalVariable)

View File

@ -0,0 +1,5 @@
namespace ARMeilleure.State
{
public delegate void ExceptionCallbackNoArgs(ExecutionContext context);
public delegate void ExceptionCallback(ExecutionContext context, ulong address, int id);
}

View File

@ -1,6 +1,5 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using System; using System;
using System.Diagnostics;
namespace ARMeilleure.State namespace ARMeilleure.State
{ {
@ -14,34 +13,22 @@ namespace ARMeilleure.State
private bool _interrupted; 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 uint DczidEl0 => 0x00000004;
public ulong CntfrqEl0 { get; set; } public ulong CntfrqEl0 => _counter.Frequency;
public ulong CntpctEl0 public ulong CntpctEl0 => _counter.Counter;
{
get
{
double ticks = _tickCounter.ElapsedTicks * _hostTickFreq;
return (ulong)(ticks * CntfrqEl0);
}
}
// CNTVCT_EL0 = CNTPCT_EL0 - CNTVOFF_EL2 // CNTVCT_EL0 = CNTPCT_EL0 - CNTVOFF_EL2
// Since EL2 isn't implemented, CNTVOFF_EL2 = 0 // Since EL2 isn't implemented, CNTVOFF_EL2 = 0
public ulong CntvctEl0 => CntpctEl0; 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 TpidrEl0 { get; set; }
public long Tpidr { get; set; } public long TpidrroEl0 { get; set; }
public uint Pstate public uint Pstate
{ {
@ -78,35 +65,38 @@ namespace ARMeilleure.State
private set => _nativeContext.SetRunning(value); private set => _nativeContext.SetRunning(value);
} }
public event EventHandler<EventArgs> Interrupt; private readonly ExceptionCallbackNoArgs _interruptCallback;
public event EventHandler<InstExceptionEventArgs> Break; private readonly ExceptionCallback _breakCallback;
public event EventHandler<InstExceptionEventArgs> SupervisorCall; private readonly ExceptionCallback _supervisorCallback;
public event EventHandler<InstUndefinedEventArgs> Undefined; private readonly ExceptionCallback _undefinedCallback;
static ExecutionContext() public ExecutionContext(
{ IJitMemoryAllocator allocator,
_hostTickFreq = 1.0 / Stopwatch.Frequency; ICounter counter,
ExceptionCallbackNoArgs interruptCallback = null,
_tickCounter = new Stopwatch(); ExceptionCallback breakCallback = null,
_tickCounter.Start(); ExceptionCallback supervisorCallback = null,
} ExceptionCallback undefinedCallback = null)
public ExecutionContext(IJitMemoryAllocator allocator)
{ {
_nativeContext = new NativeContext(allocator); _nativeContext = new NativeContext(allocator);
_counter = counter;
_interruptCallback = interruptCallback;
_breakCallback = breakCallback;
_supervisorCallback = supervisorCallback;
_undefinedCallback = undefinedCallback;
Running = true; Running = true;
_nativeContext.SetCounter(MinCountForCheck); _nativeContext.SetCounter(MinCountForCheck);
} }
public ulong GetX(int index) => _nativeContext.GetX(index); public ulong GetX(int index) => _nativeContext.GetX(index);
public void SetX(int index, ulong value) => _nativeContext.SetX(index, value); 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 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 void SetPstateFlag(PState flag, bool value) => _nativeContext.SetPstateFlag(flag, value);
public bool GetFPstateFlag(FPState flag) => _nativeContext.GetFPStateFlag(flag); public bool GetFPstateFlag(FPState flag) => _nativeContext.GetFPStateFlag(flag);
@ -118,7 +108,7 @@ namespace ARMeilleure.State
{ {
_interrupted = false; _interrupted = false;
Interrupt?.Invoke(this, EventArgs.Empty); _interruptCallback?.Invoke(this);
} }
_nativeContext.SetCounter(MinCountForCheck); _nativeContext.SetCounter(MinCountForCheck);
@ -131,17 +121,17 @@ namespace ARMeilleure.State
internal void OnBreak(ulong address, int imm) 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) 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) internal void OnUndefined(ulong address, int opCode)
{ {
Undefined?.Invoke(this, new InstUndefinedEventArgs(address, opCode)); _undefinedCallback?.Invoke(this, address, opCode);
} }
public void StopRunning() public void StopRunning()
@ -151,16 +141,6 @@ namespace ARMeilleure.State
_nativeContext.SetCounter(0); _nativeContext.SetCounter(0);
} }
public static void SuspendCounter()
{
_tickCounter.Stop();
}
public static void ResumeCounter()
{
_tickCounter.Start();
}
public void Dispose() public void Dispose()
{ {
_nativeContext.Dispose(); _nativeContext.Dispose();

View 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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -34,6 +34,12 @@ namespace ARMeilleure.State
GetStorage().ExclusiveAddress = ulong.MaxValue; GetStorage().ExclusiveAddress = ulong.MaxValue;
} }
public ulong GetPc()
{
// TODO: More precise tracking of PC value.
return GetStorage().DispatchAddress;
}
public unsafe ulong GetX(int index) public unsafe ulong GetX(int index)
{ {
if ((uint)index >= RegisterConsts.IntRegsCount) if ((uint)index >= RegisterConsts.IntRegsCount)

View File

@ -115,7 +115,7 @@ namespace ARMeilleure.Translation
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpsr))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpsr)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine))); 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.GetTpidr32))); // A32 only.
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032))); // A32 only. SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032))); // A32 only.

View File

@ -27,7 +27,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\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 ActualDir = "0";
private const string BackupDir = "1"; private const string BackupDir = "1";

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64;linux-x64;osx-x64</RuntimeIdentifiers>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -15,11 +15,11 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dll</TargetPath> <TargetPath>libsoundio.dll</TargetPath>
</ContentWithTargetPath> </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> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dylib</TargetPath> <TargetPath>libsoundio.dylib</TargetPath>
</ContentWithTargetPath> </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> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.so</TargetPath> <TargetPath>libsoundio.so</TargetPath>
</ContentWithTargetPath> </ContentWithTargetPath>

View File

@ -523,9 +523,7 @@ namespace Ryujinx.Audio.Renderer.Server
private ulong GetSystemTicks() private ulong GetSystemTicks()
{ {
double ticks = ARMeilleure.State.ExecutionContext.ElapsedTicks * ARMeilleure.State.ExecutionContext.TickFrequency; return (ulong)(_manager.TickSource.ElapsedSeconds * Constants.TargetTimerFrequency);
return (ulong)(ticks * Constants.TargetTimerFrequency);
} }
private uint ComputeVoiceDrop(CommandBuffer commandBuffer, long voicesEstimatedTime, long deltaTimeDsp) private uint ComputeVoiceDrop(CommandBuffer commandBuffer, long voicesEstimatedTime, long deltaTimeDsp)

View File

@ -19,6 +19,7 @@ using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Renderer.Dsp; using Ryujinx.Audio.Renderer.Dsp;
using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
@ -77,6 +78,11 @@ namespace Ryujinx.Audio.Renderer.Server
/// </summary> /// </summary>
private IHardwareDeviceDriver _deviceDriver; private IHardwareDeviceDriver _deviceDriver;
/// <summary>
/// Tick source used to measure elapsed time.
/// </summary>
public ITickSource TickSource { get; }
/// <summary> /// <summary>
/// The <see cref="AudioProcessor"/> instance associated to this manager. /// The <see cref="AudioProcessor"/> instance associated to this manager.
/// </summary> /// </summary>
@ -90,9 +96,11 @@ namespace Ryujinx.Audio.Renderer.Server
/// <summary> /// <summary>
/// Create a new <see cref="AudioRendererManager"/>. /// Create a new <see cref="AudioRendererManager"/>.
/// </summary> /// </summary>
public AudioRendererManager() /// <param name="tickSource">Tick source used to measure elapsed time.</param>
public AudioRendererManager(ITickSource tickSource)
{ {
Processor = new AudioProcessor(); Processor = new AudioProcessor();
TickSource = tickSource;
_sessionIds = new int[Constants.AudioRendererSessionCountMax]; _sessionIds = new int[Constants.AudioRendererSessionCountMax];
_sessions = new AudioRenderSystem[Constants.AudioRendererSessionCountMax]; _sessions = new AudioRenderSystem[Constants.AudioRendererSessionCountMax];
_activeSessionCount = 0; _activeSessionCount = 0;

View File

@ -1,9 +1,9 @@
<Application <Application
x:Class="Ryujinx.Ava.App" x:Class="Ryujinx.Ava.App"
xmlns="https://github.com/avaloniaui" 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> <Application.Styles>
<sty:FluentAvaloniaTheme UseSystemThemeOnWindows="False"/> <sty:FluentAvaloniaTheme UseSystemThemeOnWindows="False" />
</Application.Styles> </Application.Styles>
</Application> </Application>

View File

@ -2,7 +2,6 @@ using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading;
using FluentAvalonia.Styling; using FluentAvalonia.Styling;
using Ryujinx.Ava.Ui.Windows; using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common; using Ryujinx.Common;
@ -13,7 +12,7 @@ using System.IO;
namespace Ryujinx.Ava namespace Ryujinx.Ava
{ {
public class App : Avalonia.Application public class App : Application
{ {
public override void Initialize() public override void Initialize()
{ {
@ -46,7 +45,7 @@ namespace Ryujinx.Ava
private void ShowRestartDialog() 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) private void ThemeChanged_Event(object sender, ReactiveEventArgs<string> e)

View File

@ -57,7 +57,7 @@ namespace Ryujinx.Ava
private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None); private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None);
private readonly AccountManager _accountManager; private readonly AccountManager _accountManager;
private UserChannelPersistence _userChannelPersistence; private readonly UserChannelPersistence _userChannelPersistence;
private readonly InputManager _inputManager; private readonly InputManager _inputManager;
@ -82,7 +82,6 @@ namespace Ryujinx.Ava
private bool _dialogShown; private bool _dialogShown;
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
private KeyboardStateSnapshot _lastKeyboardSnapshot;
private readonly CancellationTokenSource _gpuCancellationTokenSource; private readonly CancellationTokenSource _gpuCancellationTokenSource;
@ -126,7 +125,6 @@ namespace Ryujinx.Ava
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel; _glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
_inputManager.SetMouseDriver(new AvaloniaMouseDriver(renderer)); _inputManager.SetMouseDriver(new AvaloniaMouseDriver(renderer));
_keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
_lastKeyboardSnapshot = _keyboardInterface.GetKeyboardStateSnapshot();
NpadManager = _inputManager.CreateNpadManager(); NpadManager = _inputManager.CreateNpadManager();
TouchScreenManager = _inputManager.CreateTouchScreenManager(); TouchScreenManager = _inputManager.CreateTouchScreenManager();
@ -177,7 +175,7 @@ namespace Ryujinx.Ava
{ {
if (_renderer != null) if (_renderer != null)
{ {
double scale = Program.WindowScaleFactor; double scale = _parent.PlatformImpl.RenderScaling;
_renderer.Window.SetSize((int)(size.Width * scale), (int)(size.Height * scale)); _renderer.Window.SetSize((int)(size.Width * scale), (int)(size.Height * scale));
} }
} }
@ -722,9 +720,7 @@ namespace Ryujinx.Ava
} }
} }
var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? HLE.MemoryConfiguration.MemoryConfiguration6GB : HLE.MemoryConfiguration.MemoryConfiguration4GB;
? HLE.MemoryConfiguration.MemoryConfiguration6GB
: HLE.MemoryConfiguration.MemoryConfiguration4GB;
IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
@ -809,7 +805,7 @@ namespace Ryujinx.Ava
Width = (int)Renderer.Bounds.Width; Width = (int)Renderer.Bounds.Width;
Height = (int)Renderer.Bounds.Height; Height = (int)Renderer.Bounds.Height;
_renderer.Window.SetSize((int)(Width * Program.WindowScaleFactor), (int)(Height * Program.WindowScaleFactor)); _renderer.Window.SetSize((int)(Width * _parent.PlatformImpl.RenderScaling), (int)(Height * _parent.PlatformImpl.RenderScaling));
Device.Gpu.Renderer.RunLoop(() => Device.Gpu.Renderer.RunLoop(() =>
{ {
@ -898,7 +894,7 @@ namespace Ryujinx.Ava
} }
} }
private void HandleScreenState(KeyboardStateSnapshot keyboard, KeyboardStateSnapshot lastKeyboard) private void HandleScreenState()
{ {
if (ConfigurationState.Instance.Hid.EnableMouse) if (ConfigurationState.Instance.Hid.EnableMouse)
{ {
@ -935,19 +931,12 @@ namespace Ryujinx.Ava
{ {
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
KeyboardStateSnapshot keyboard = _keyboardInterface.GetKeyboardStateSnapshot(); HandleScreenState();
HandleScreenState(keyboard, _lastKeyboardSnapshot); if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _parent.WindowState != WindowState.FullScreen)
if (keyboard.IsPressed(Key.Delete))
{ {
if (_parent.WindowState != WindowState.FullScreen) Ptc.Continue();
{
Ptc.Continue();
}
} }
_lastKeyboardSnapshot = keyboard;
}); });
} }

View File

@ -1,9 +1,7 @@
<Styles <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" /> <StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" />
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="20" Height="2000"> <Border Height="2000" Padding="20">
<StackPanel Spacing="5"> <StackPanel Spacing="5">
<TextBlock Text="Code Font Family" /> <TextBlock Text="Code Font Family" />
<Grid RowDefinitions="*,Auto"> <Grid RowDefinitions="*,Auto">
@ -27,8 +25,12 @@
Name="btnRem" Name="btnRem"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Content="Add" /> Content="Add" />
<TextBox Width="100" VerticalAlignment="Center" Text="Rrrrr" Watermark="Hello" <TextBox
UseFloatingWatermark="True" /> Width="100"
VerticalAlignment="Center"
Text="Rrrrr"
UseFloatingWatermark="True"
Watermark="Hello" />
<CheckBox>Test Check</CheckBox> <CheckBox>Test Check</CheckBox>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@ -1,9 +1,7 @@
<Styles <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" /> <StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" />
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="20" Height="2000"> <Border Height="2000" Padding="20">
<StackPanel Spacing="5"> <StackPanel Spacing="5">
<TextBlock Text="Code Font Family" /> <TextBlock Text="Code Font Family" />
<Grid RowDefinitions="*,Auto"> <Grid RowDefinitions="*,Auto">
@ -27,8 +25,12 @@
Name="btnRem" Name="btnRem"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Content="Add" /> Content="Add" />
<TextBox Width="100" VerticalAlignment="Center" Text="Rrrrr" Watermark="Hello" <TextBox
UseFloatingWatermark="True" /> Width="100"
VerticalAlignment="Center"
Text="Rrrrr"
UseFloatingWatermark="True"
Watermark="Hello" />
<CheckBox>Test Check</CheckBox> <CheckBox>Test Check</CheckBox>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@ -1,10 +1,10 @@
<Styles <Styles
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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> <Design.PreviewWith>
<Border Padding="20" Height="2000"> <Border Height="2000" Padding="20">
<StackPanel Spacing="5"> <StackPanel Spacing="5">
<TextBlock Text="Code Font Family" /> <TextBlock Text="Code Font Family" />
<Grid RowDefinitions="*,Auto"> <Grid RowDefinitions="*,Auto">
@ -22,15 +22,19 @@
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<ToggleButton <ToggleButton
Name="btnAdd" Name="btnAdd"
HorizontalAlignment="Right"
Height="28" Height="28"
HorizontalAlignment="Right"
Content="Addy" /> Content="Addy" />
<Button <Button
Name="btnRem" Name="btnRem"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Content="Add" /> Content="Add" />
<TextBox Width="100" VerticalAlignment="Center" Text="Rrrrr" Watermark="Hello" <TextBox
UseFloatingWatermark="True" /> Width="100"
VerticalAlignment="Center"
Text="Rrrrr"
UseFloatingWatermark="True"
Watermark="Hello" />
<CheckBox>Test Check</CheckBox> <CheckBox>Test Check</CheckBox>
</StackPanel> </StackPanel>
</Grid> </Grid>
@ -62,13 +66,10 @@
<Style Selector="Image.huge"> <Style Selector="Image.huge">
<Setter Property="Width" Value="120" /> <Setter Property="Width" Value="120" />
</Style> </Style>
<Style Selector="RadioButton"> <Style Selector="#TitleBarHost &gt; Image">
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
<Style Selector="#TitleBarHost > Image">
<Setter Property="Margin" Value="10" /> <Setter Property="Margin" Value="10" />
</Style> </Style>
<Style Selector="#TitleBarHost > Label"> <Style Selector="#TitleBarHost &gt; Label">
<Setter Property="Margin" Value="5" /> <Setter Property="Margin" Value="5" />
<Setter Property="FontSize" Value="14" /> <Setter Property="FontSize" Value="14" />
</Style> </Style>
@ -225,12 +226,12 @@
<StaticResource x:Key="ListViewItemBackgroundPointerOver" ResourceKey="SystemAccentColorDark2" /> <StaticResource x:Key="ListViewItemBackgroundPointerOver" ResourceKey="SystemAccentColorDark2" />
<StaticResource x:Key="ListViewItemBackgroundSelectedPressed" ResourceKey="ThemeAccentColorBrush" /> <StaticResource x:Key="ListViewItemBackgroundSelectedPressed" ResourceKey="ThemeAccentColorBrush" />
<StaticResource x:Key="ListViewItemBackgroundSelectedPointerOver" ResourceKey="SystemAccentColorDark2" /> <StaticResource x:Key="ListViewItemBackgroundSelectedPointerOver" ResourceKey="SystemAccentColorDark2" />
<SolidColorBrush x:Key="DataGridGridLinesBrush" <SolidColorBrush
Color="{DynamicResource SystemBaseMediumLowColor}" x:Key="DataGridGridLinesBrush"
Opacity="0.4" /> Opacity="0.4"
Color="{DynamicResource SystemBaseMediumLowColor}" />
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" /> <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="MenuFlyoutPresenterBorderBrush" <SolidColorBrush x:Key="MenuFlyoutPresenterBorderBrush" Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" /> <SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="ListBoxBackground" Color="{DynamicResource ThemeContentBackgroundColor}" /> <SolidColorBrush x:Key="ListBoxBackground" Color="{DynamicResource ThemeContentBackgroundColor}" />
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="{DynamicResource ThemeForegroundColor}" /> <SolidColorBrush x:Key="ThemeForegroundBrush" Color="{DynamicResource ThemeForegroundColor}" />
@ -241,7 +242,6 @@
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedDisabled" Color="#00E81123" /> <SolidColorBrush x:Key="SplitButtonBackgroundCheckedDisabled" Color="#00E81123" />
<Thickness x:Key="PageMargin">40 0 40 0</Thickness> <Thickness x:Key="PageMargin">40 0 40 0</Thickness>
<Thickness x:Key="Margin">0 5 0 5</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> <Thickness x:Key="MenuItemPadding">5 0 5 0</Thickness>
<Color x:Key="MenuFlyoutPresenterBorderColor">#00000000</Color> <Color x:Key="MenuFlyoutPresenterBorderColor">#00000000</Color>
<Color x:Key="SystemAccentColor">#FF00C3E3</Color> <Color x:Key="SystemAccentColor">#FF00C3E3</Color>

View File

@ -137,7 +137,6 @@ namespace Ryujinx.Ava
} }
// Make process DPI aware for proper window sizing on high-res screens. // Make process DPI aware for proper window sizing on high-res screens.
ForceDpiAware.Windows();
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor(); WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
// Delete backup files after updating. // Delete backup files after updating.

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version> <Version>1.0.0-dirty</Version>

View File

@ -1,17 +1,21 @@
<Window xmlns="https://github.com/avaloniaui" <Window
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Applet.ErrorAppletWindow"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
x:Class="Ryujinx.Ava.Ui.Applet.ErrorAppletWindow" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
CanResize="False" Title="{locale:Locale ErrorWindowTitle}"
SizeToContent="Height" Width="450"
Width="450" Height="340"
Height="340" CanResize="False"
Title="{locale:Locale ErrorWindowTitle}"> SizeToContent="Height"
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20"> mc:Ignorable="d">
<Grid
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
@ -21,11 +25,28 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition /> <ColumnDefinition />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Image Grid.Row="1" Grid.RowSpan="2" Margin="5, 10, 20 , 10" Grid.Column="0" <Image
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="80" MinWidth="50" /> Grid.Row="1"
<TextBlock Grid.Row="1" Margin="10" Grid.Column="1" VerticalAlignment="Stretch" TextWrapping="Wrap" Grid.RowSpan="2"
Text="{Binding Message}" /> Grid.Column="0"
<StackPanel Name="ButtonStack" Margin="10" Spacing="10" Grid.Row="2" Grid.Column="1" Height="80"
HorizontalAlignment="Right" Orientation="Horizontal" /> 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> </Grid>
</Window> </Window>

View File

@ -1,12 +1,16 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Controls.SwkbdAppletDialog"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="Ryujinx.Ava.Ui.Controls.SwkbdAppletDialog" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
Width="400"> Width="400"
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20"> mc:Ignorable="d">
<Grid
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@ -18,15 +22,43 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition /> <ColumnDefinition />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Image Grid.Row="1" VerticalAlignment="Center" Grid.RowSpan="5" Margin="5, 10, 20 , 10" <Image
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="80" Grid.Row="1"
MinWidth="50" /> Grid.RowSpan="5"
<TextBlock Grid.Row="1" Margin="5" Grid.Column="1" Text="{Binding MainText}" TextWrapping="Wrap" /> Height="80"
<TextBlock Grid.Row="2" Margin="5" Grid.Column="1" Text="{Binding SecondaryText}" TextWrapping="Wrap" /> MinWidth="50"
<TextBox Name="Input" KeyUp="Message_KeyUp" UseFloatingWatermark="True" TextInput="Message_TextInput" Margin="5,10,20,10"
Text="{Binding Message}" Grid.Row="2" VerticalAlignment="Center"
Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Stretch" TextWrapping="Wrap" /> Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
<TextBlock Name="Error" Margin="5" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Stretch" <TextBlock
TextWrapping="Wrap" /> 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> </Grid>
</UserControl> </UserControl>

View File

@ -1,188 +1,219 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Controls.GameGridView"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls" xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="Ryujinx.Ava.Ui.Controls.GameGridView"> xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
<UserControl.Resources> d:DesignHeight="450"
<controls:BitmapArrayValueConverter x:Key="ByteImage" /> d:DesignWidth="800"
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened"> mc:Ignorable="d">
<MenuItem <UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
<MenuItem
Command="{Binding ToggleFavorite}" Command="{Binding ToggleFavorite}"
Header="{locale:Locale GameListContextMenuToggleFavorite}" Header="{locale:Locale GameListContextMenuToggleFavorite}"
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Command="{Binding OpenUserSaveDirectory}" Command="{Binding OpenUserSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}" Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenDeviceSaveDirectory}" Command="{Binding OpenDeviceSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserDeviceDirectory}" Header="{locale:Locale GameListContextMenuOpenUserDeviceDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserDeviceDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserDeviceDirectoryToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenBcatSaveDirectory}" Command="{Binding OpenBcatSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserBcatDirectory}" Header="{locale:Locale GameListContextMenuOpenUserBcatDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserBcatDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserBcatDirectoryToolTip}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Command="{Binding OpenTitleUpdateManager}" Command="{Binding OpenTitleUpdateManager}"
Header="{locale:Locale GameListContextMenuManageTitleUpdates}" Header="{locale:Locale GameListContextMenuManageTitleUpdates}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenDlcManager}" Command="{Binding OpenDlcManager}"
Header="{locale:Locale GameListContextMenuManageDlc}" Header="{locale:Locale GameListContextMenuManageDlc}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenCheatManager}" Command="{Binding OpenCheatManager}"
Header="{locale:Locale GameListContextMenuManageCheat}" Header="{locale:Locale GameListContextMenuManageCheat}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenModsDirectory}" Command="{Binding OpenModsDirectory}"
Header="{locale:Locale GameListContextMenuOpenModsDirectory}" Header="{locale:Locale GameListContextMenuOpenModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenSdModsDirectory}" Command="{Binding OpenSdModsDirectory}"
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}" Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
<Separator /> <Separator />
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}"> <MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
<MenuItem <MenuItem
Command="{Binding PurgePtcCache}" Command="{Binding PurgePtcCache}"
Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}" Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
<MenuItem <MenuItem
Command="{Binding PurgeShaderCache}" Command="{Binding PurgeShaderCache}"
Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}" Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenPtcDirectory}" Command="{Binding OpenPtcDirectory}"
Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}" Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenShaderCacheDirectory}" Command="{Binding OpenShaderCacheDirectory}"
Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}" Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" />
</MenuItem> </MenuItem>
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}"> <MenuItem Header="{locale:Locale GameListContextMenuExtractData}">
<MenuItem <MenuItem
Command="{Binding ExtractExeFs}" Command="{Binding ExtractExeFs}"
Header="{locale:Locale GameListContextMenuExtractDataExeFS}" Header="{locale:Locale GameListContextMenuExtractDataExeFS}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" />
<MenuItem <MenuItem
Command="{Binding ExtractRomFs}" Command="{Binding ExtractRomFs}"
Header="{locale:Locale GameListContextMenuExtractDataRomFS}" Header="{locale:Locale GameListContextMenuExtractDataRomFS}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" />
<MenuItem <MenuItem
Command="{Binding ExtractLogo}" Command="{Binding ExtractLogo}"
Header="{locale:Locale GameListContextMenuExtractDataLogo}" Header="{locale:Locale GameListContextMenuExtractDataLogo}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
</MenuItem> </MenuItem>
</MenuFlyout> </MenuFlyout>
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ListBox Grid.Row="0" <ListBox
Padding="8" Grid.Row="0"
HorizontalAlignment="Stretch" Padding="8"
DoubleTapped="GameList_DoubleTapped" HorizontalAlignment="Stretch"
SelectionChanged="GameList_SelectionChanged" VerticalAlignment="Stretch"
ContextFlyout="{StaticResource GameContextMenu}" ContextFlyout="{StaticResource GameContextMenu}"
VerticalAlignment="Stretch" DoubleTapped="GameList_DoubleTapped"
Items="{Binding AppsObservableList}"> Items="{Binding AppsObservableList}"
<ListBox.ItemsPanel> SelectionChanged="GameList_SelectionChanged">
<ItemsPanelTemplate> <ListBox.ItemsPanel>
<flex:FlexPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" JustifyContent="Center" <ItemsPanelTemplate>
AlignContent="FlexStart" /> <flex:FlexPanel
</ItemsPanelTemplate> HorizontalAlignment="Stretch"
</ListBox.ItemsPanel> VerticalAlignment="Stretch"
<ListBox.Styles> AlignContent="FlexStart"
<Style Selector="ListBoxItem"> JustifyContent="Center" />
<Setter Property="Padding" Value="0" /> </ItemsPanelTemplate>
<Setter Property="Margin" Value="5" /> </ListBox.ItemsPanel>
<Setter Property="CornerRadius" Value="5" /> <ListBox.Styles>
<Setter Property="Background" Value="{DynamicResource SystemAccentColorDark3}" /> <Style Selector="ListBoxItem">
<Style.Animations> <Setter Property="Padding" Value="0" />
<Animation Duration="0:0:0.7"> <Setter Property="Margin" Value="5" />
<KeyFrame Cue="0%"> <Setter Property="CornerRadius" Value="5" />
<Setter Property="MaxWidth" Value="0"/> <Setter Property="Background" Value="{DynamicResource SystemAccentColorDark3}" />
<Setter Property="Opacity" Value="0.0"/> <Style.Animations>
</KeyFrame> <Animation Duration="0:0:0.7">
<KeyFrame Cue="50%"> <KeyFrame Cue="0%">
<Setter Property="MaxWidth" Value="1000"/> <Setter Property="MaxWidth" Value="0" />
<Setter Property="Opacity" Value="0.3"/> <Setter Property="Opacity" Value="0.0" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame Cue="50%">
<Setter Property="MaxWidth" Value="1000"/> <Setter Property="MaxWidth" Value="1000" />
<Setter Property="Opacity" Value="1.0"/> <Setter Property="Opacity" Value="0.3" />
</KeyFrame> </KeyFrame>
</Animation> <KeyFrame Cue="100%">
</Style.Animations> <Setter Property="MaxWidth" Value="1000" />
</Style> <Setter Property="Opacity" Value="1.0" />
</ListBox.Styles> </KeyFrame>
<ListBox.ItemTemplate> </Animation>
<DataTemplate> </Style.Animations>
<Grid> </Style>
<Grid.Styles> </ListBox.Styles>
<Style Selector="ui|SymbolIcon.small.icon"> <ListBox.ItemTemplate>
<Setter Property="FontSize" Value="15" /> <DataTemplate>
</Style> <Grid>
<Style Selector="ui|SymbolIcon.normal.icon"> <Grid.Styles>
<Setter Property="FontSize" Value="19" /> <Style Selector="ui|SymbolIcon.small.icon">
</Style> <Setter Property="FontSize" Value="15" />
<Style Selector="ui|SymbolIcon.large.icon"> </Style>
<Setter Property="FontSize" Value="23" /> <Style Selector="ui|SymbolIcon.normal.icon">
</Style> <Setter Property="FontSize" Value="19" />
<Style Selector="ui|SymbolIcon.huge.icon"> </Style>
<Setter Property="FontSize" Value="26" /> <Style Selector="ui|SymbolIcon.large.icon">
</Style> <Setter Property="FontSize" Value="23" />
</Grid.Styles> </Style>
<Border <Style Selector="ui|SymbolIcon.huge.icon">
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}" <Setter Property="FontSize" Value="26" />
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}" </Style>
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}" </Grid.Styles>
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}" <Border
Margin="0"
Padding="{Binding $parent[UserControl].DataContext.GridItemPadding}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Padding="{Binding $parent[UserControl].DataContext.GridItemPadding}" CornerRadius="5" VerticalAlignment="Stretch"
VerticalAlignment="Stretch" Margin="0" ClipToBounds="True"> Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
<Grid Margin="0"> Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
<Grid.RowDefinitions> Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
<RowDefinition Height="Auto" /> Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
<RowDefinition Height="Auto" /> ClipToBounds="True"
</Grid.RowDefinitions> CornerRadius="5">
<Image HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="0" Grid.Row="0" <Grid Margin="0">
Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> <Grid.RowDefinitions>
<StackPanel IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}" <RowDefinition Height="Auto" />
Height="50" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" <RowDefinition Height="Auto" />
Margin="5" Grid.Row="1"> </Grid.RowDefinitions>
<TextBlock Text="{Binding TitleName}" TextAlignment="Center" TextWrapping="Wrap" <Image
HorizontalAlignment="Stretch" /> Grid.Row="0"
</StackPanel> Margin="0"
</Grid> HorizontalAlignment="Stretch"
</Border> VerticalAlignment="Top"
<ui:SymbolIcon Classes.icon="true" Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}" Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}" <StackPanel
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}" Grid.Row="1"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}" Height="50"
Foreground="Yellow" Symbol="StarFilled" Margin="5"
IsVisible="{Binding Favorite}" Margin="5" VerticalAlignment="Top" HorizontalAlignment="Stretch"
HorizontalAlignment="Left" /> VerticalAlignment="Stretch"
<ui:SymbolIcon Classes.icon="true" Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}" IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}">
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}" <TextBlock
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}" HorizontalAlignment="Stretch"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}" Text="{Binding TitleName}"
Foreground="Black" Symbol="Star" TextAlignment="Center"
IsVisible="{Binding Favorite}" Margin="5" VerticalAlignment="Top" TextWrapping="Wrap" />
HorizontalAlignment="Left" /> </StackPanel>
</Grid> </Grid>
</DataTemplate> </Border>
</ListBox.ItemTemplate> <ui:SymbolIcon
</ListBox> Margin="5"
</Grid> 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> </UserControl>

View File

@ -1,13 +1,16 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Controls.GameListView"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls" xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="Ryujinx.Ava.Ui.Controls.GameListView"> xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" /> <controls:BitmapArrayValueConverter x:Key="ByteImage" />
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened"> <MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
@ -88,18 +91,23 @@
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ListBox Grid.Row="0" <ListBox
Padding="8" Name="GameListBox"
HorizontalAlignment="Stretch" Grid.Row="0"
DoubleTapped="GameList_DoubleTapped" Padding="8"
SelectionChanged="GameList_SelectionChanged" HorizontalAlignment="Stretch"
ContextFlyout="{StaticResource GameContextMenu}" VerticalAlignment="Stretch"
VerticalAlignment="Stretch" ContextFlyout="{StaticResource GameContextMenu}"
Name="GameListBox" DoubleTapped="GameList_DoubleTapped"
Items="{Binding AppsObservableList}"> Items="{Binding AppsObservableList}"
SelectionChanged="GameList_SelectionChanged">
<ListBox.ItemsPanel> <ListBox.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Orientation="Vertical" Spacing="2" /> <StackPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Vertical"
Spacing="2" />
</ItemsPanelTemplate> </ItemsPanelTemplate>
</ListBox.ItemsPanel> </ListBox.ItemsPanel>
<ListBox.Styles> <ListBox.Styles>
@ -112,16 +120,16 @@
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:0.7"> <Animation Duration="0:0:0.7">
<KeyFrame Cue="0%"> <KeyFrame Cue="0%">
<Setter Property="MaxHeight" Value="0"/> <Setter Property="MaxHeight" Value="0" />
<Setter Property="Opacity" Value="0.0"/> <Setter Property="Opacity" Value="0.0" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="50%"> <KeyFrame Cue="50%">
<Setter Property="MaxHeight" Value="1000"/> <Setter Property="MaxHeight" Value="1000" />
<Setter Property="Opacity" Value="0.3"/> <Setter Property="Opacity" Value="0.3" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame Cue="100%">
<Setter Property="MaxHeight" Value="1000"/> <Setter Property="MaxHeight" Value="1000" />
<Setter Property="Opacity" Value="1.0"/> <Setter Property="Opacity" Value="1.0" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>
@ -130,54 +138,96 @@
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid> <Grid>
<Border HorizontalAlignment="Stretch" <Border
Padding="10" CornerRadius="5" Margin="0"
VerticalAlignment="Stretch" Margin="0" ClipToBounds="True"> Padding="10"
<Grid > HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
CornerRadius="5">
<Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10"/> <ColumnDefinition Width="10" />
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition/> <RowDefinition />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image <Image
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}" Grid.RowSpan="3"
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}" Grid.Column="0"
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}" Margin="0"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}" Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
Grid.RowSpan="3" Grid.Column="0" Margin="0" Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
<StackPanel Orientation="Vertical" Spacing="5" VerticalAlignment="Top" HorizontalAlignment="Left" Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
Grid.Column="2"> Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
<TextBlock Text="{Binding TitleName}" TextAlignment="Left" TextWrapping="Wrap" <StackPanel
HorizontalAlignment="Stretch" /> Grid.Column="2"
<TextBlock Text="{Binding Developer}" TextAlignment="Left" TextWrapping="Wrap" HorizontalAlignment="Left"
HorizontalAlignment="Stretch" /> VerticalAlignment="Top"
<TextBlock Text="{Binding Version}" TextAlignment="Left" TextWrapping="Wrap" Orientation="Vertical"
HorizontalAlignment="Stretch" /> Spacing="5">
</StackPanel> <TextBlock
<StackPanel Orientation="Vertical" Spacing="5" VerticalAlignment="Top" HorizontalAlignment="Right" HorizontalAlignment="Stretch"
Grid.Column="3"> Text="{Binding TitleName}"
<TextBlock Text="{Binding TimePlayed}" TextAlignment="Right" TextWrapping="Wrap" TextAlignment="Left"
HorizontalAlignment="Stretch" /> TextWrapping="Wrap" />
<TextBlock Text="{Binding LastPlayed}" TextAlignment="Right" TextWrapping="Wrap" <TextBlock
HorizontalAlignment="Stretch" /> HorizontalAlignment="Stretch"
<TextBlock Text="{Binding FileSize}" TextAlignment="Right" TextWrapping="Wrap" Text="{Binding Developer}"
HorizontalAlignment="Stretch" /> TextAlignment="Left"
</StackPanel> TextWrapping="Wrap" />
<ui:SymbolIcon Grid.Row="0" Grid.Column="0" FontSize="20" <TextBlock
Foreground="Yellow" HorizontalAlignment="Stretch"
Symbol="StarFilled" Text="{Binding Version}"
IsVisible="{Binding Favorite}" Margin="-5, -5, 0, 0" VerticalAlignment="Top" TextAlignment="Left"
HorizontalAlignment="Left" /> TextWrapping="Wrap" />
<ui:SymbolIcon Grid.Row="0" Grid.Column="0" FontSize="20" </StackPanel>
Foreground="Black" <StackPanel
Symbol="Star" Grid.Column="3"
IsVisible="{Binding Favorite}" Margin="-5, -5, 0, 0" VerticalAlignment="Top" HorizontalAlignment="Right"
HorizontalAlignment="Left" /> 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> </Grid>
</Border> </Border>
</Grid> </Grid>

View File

@ -1,18 +1,31 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Controls.InputDialog"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Class="Ryujinx.Ava.Ui.Controls.InputDialog"> xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,10,5, 5"> mc:Ignorable="d">
<Grid
Margin="5,10,5,5"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Center" Text="{Binding Message}" /> <TextBlock HorizontalAlignment="Center" Text="{Binding Message}" />
<TextBox MaxLength="{Binding MaxLength}" Grid.Row="1" Margin="10" Width="300" HorizontalAlignment="Center" <TextBox
Text="{Binding Input, Mode=TwoWay}" /> Grid.Row="1"
<TextBlock Grid.Row="2" Margin="5, 5, 5, 10" HorizontalAlignment="Center" Text="{Binding SubMessage}" /> 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> </Grid>
</UserControl> </UserControl>

View File

@ -73,10 +73,13 @@ namespace Ryujinx.Ava.Ui.Controls
{ {
SizeChanged?.Invoke(this, rect.Size); SizeChanged?.Invoke(this, rect.Size);
RenderSize = rect.Size * Program.WindowScaleFactor; if (!rect.IsEmpty)
{
RenderSize = rect.Size * VisualRoot.RenderScaling;
_glDrawOperation?.Dispose(); _glDrawOperation?.Dispose();
_glDrawOperation = new GlDrawOperation(this); _glDrawOperation = new GlDrawOperation(this);
}
} }
public override void Render(DrawingContext context) public override void Render(DrawingContext context)

View File

@ -1,14 +1,18 @@
<Window xmlns="https://github.com/avaloniaui" <Window
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Controls.UpdateWaitWindow"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="Ryujinx.Ava.Ui.Controls.UpdateWaitWindow" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
WindowStartupLocation="CenterOwner" Title="Ryujinx - Waiting"
SizeToContent="WidthAndHeight" SizeToContent="WidthAndHeight"
Title="Ryujinx - Waiting"> WindowStartupLocation="CenterOwner"
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20"> mc:Ignorable="d">
<Grid
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@ -17,12 +21,22 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition /> <ColumnDefinition />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Image Grid.Row="1" Margin="5, 10, 20 , 10" Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" <Image
Height="70" Grid.Row="1"
MinWidth="50" /> Height="70"
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Orientation="Vertical"> MinWidth="50"
<TextBlock Margin="5" Name="PrimaryText" /> Margin="5,10,20,10"
<TextBlock VerticalAlignment="Center" Name="SecondaryText" Margin="5" /> 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> </StackPanel>
</Grid> </Grid>
</Window> </Window>

View File

@ -1,28 +1,41 @@
<window:StyleableWindow xmlns="https://github.com/avaloniaui" <window:StyleableWindow
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Windows.AboutWindow"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Class="Ryujinx.Ava.Ui.Windows.AboutWindow" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
CanResize="False" Title="Ryujinx - About"
WindowStartupLocation="CenterOwner" Width="850"
Width="850" MinHeight="550" Height="550" Height="550"
SizeToContent="Width" MinWidth="500"
MinWidth="500" MinHeight="550"
Title="Ryujinx - About"> d:DesignHeight="350"
<Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> d:DesignWidth="400"
CanResize="False"
SizeToContent="Width"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<Grid
Margin="15"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto" />
<RowDefinition Height="*"/> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </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> <Grid.RowDefinitions>
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
@ -40,93 +53,168 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Margin="5, 10, 20 , 10" <Image
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="110" MinWidth="50" /> Grid.Row="0"
<TextBlock FontSize="35" TextAlignment="Center" Grid.Row="0" Grid.Column="1" Text="Ryujinx" Grid.RowSpan="3"
Margin="0,20,0,0" /> Grid.Column="0"
<TextBlock FontSize="16" TextAlignment="Center" Grid.Row="1" Grid.Column="1" Text="(REE-YOU-JINX)" Height="110"
Margin="0,0,0,0" /> MinWidth="50"
<Button Grid.Column="1" Background="Transparent" HorizontalAlignment="Center" Margin="0" Grid.Row="2" Margin="5,10,20,10"
Tag="https://www.ryujinx.org/" Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
Click="Button_OnClick"> <TextBlock
<TextBlock ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" Grid.Row="0"
TextAlignment="Center" TextDecorations="Underline" Text="www.ryujinx.org" /> 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> </Button>
</Grid> </Grid>
<TextBlock TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" <TextBlock
Text="{Binding Version}" Grid.Row="1" /> Grid.Row="1"
<TextBlock Grid.Row="2" TextAlignment="Center" HorizontalAlignment="Center" Margin="20" HorizontalAlignment="Center"
Text="{locale:Locale AboutDisclaimerMessage}" VerticalAlignment="Center"
MaxLines="2" /> Text="{Binding Version}"
<TextBlock Grid.Row="3" TextAlignment="Center" HorizontalAlignment="Center" Margin="20" TextAlignment="Center" />
Text="{locale:Locale AboutAmiiboDisclaimerMessage}" <TextBlock
Name="AmiiboLabel" Grid.Row="2"
PointerPressed="AmiiboLabel_OnPointerPressed" Margin="20"
MaxLines="2" /> HorizontalAlignment="Center"
<StackPanel Spacing="10" Orientation="Horizontal" Grid.Row="4" HorizontalAlignment="Center"> MaxLines="2"
<StackPanel Orientation="Vertical" Text="{locale:Locale AboutDisclaimerMessage}"
ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}"> TextAlignment="Center" />
<Button Height="65" Background="Transparent" Tag="https://www.patreon.com/ryujinx" <TextBlock
Click="Button_OnClick"> 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>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" /> <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> </Grid>
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}"> <Button
<Button Height="65" Background="Transparent" Tag="https://github.com/Ryujinx/Ryujinx" Height="65"
Click="Button_OnClick"> Background="Transparent"
Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" /> <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> </Grid>
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}"> <Button
<Button Height="65" Background="Transparent" Tag="https://discordapp.com/invite/N2FmfVc" Height="65"
Click="Button_OnClick"> Background="Transparent"
Click="Button_OnClick"
Tag="https://discordapp.com/invite/N2FmfVc">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" /> <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> </Grid>
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}"> <Button
<Button Height="65" Background="Transparent" Tag="https://twitter.com/RyujinxEmu" Height="65"
Click="Button_OnClick"> Background="Transparent"
Click="Button_OnClick"
Tag="https://twitter.com/RyujinxEmu">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" /> <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> </Grid>
</Button> </Button>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>
<Border Grid.Row="1" Grid.Column="1" VerticalAlignment="Stretch" Margin="5" Width="2" BorderBrush="White" <Border
BorderThickness="1,0,0,0"> Grid.Row="1"
Grid.Column="1"
Width="2"
Margin="5"
VerticalAlignment="Stretch"
BorderBrush="White"
BorderThickness="1,0,0,0">
<Separator Width="0" /> <Separator Width="0" />
</Border> </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> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@ -136,27 +224,58 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Text="{locale:Locale AboutRyujinxAboutTitle}" FontWeight="Bold" TextDecorations="Underline" /> <TextBlock
<TextBlock LineHeight="20" Grid.Row="1" Margin="20,5,5,5" FontWeight="Bold"
Text="{locale:Locale AboutRyujinxAboutContent}" /> Text="{locale:Locale AboutRyujinxAboutTitle}"
<TextBlock Grid.Row="2" Margin="0,10,0,0" Text="{locale:Locale AboutRyujinxMaintainersTitle}" TextDecorations="Underline" />
FontWeight="Bold" <TextBlock
TextDecorations="Underline" /> Grid.Row="1"
<TextBlock LineHeight="20" Grid.Row="3" Margin="20,5,5,5" Margin="20,5,5,5"
Text="{Binding Developers}" /> LineHeight="20"
<Button Background="Transparent" HorizontalAlignment="Right" Grid.Row="4" Text="{locale:Locale AboutRyujinxAboutContent}" />
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a" Click="Button_OnClick"> <TextBlock
<TextBlock ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" Grid.Row="2"
TextAlignment="Right" TextDecorations="Underline" Margin="0,10,0,0"
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}" /> 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> </Button>
<TextBlock Grid.Row="5" Margin="0,0,0,0" Text="{locale:Locale AboutRyujinxSupprtersTitle}" <TextBlock
FontWeight="Bold" Grid.Row="5"
TextDecorations="Underline" /> Margin="0,0,0,0"
<Border Width="460" Grid.Row="6" VerticalAlignment="Stretch" Height="200" BorderThickness="1" Margin="20,5" FontWeight="Bold"
BorderBrush="White" Padding="5"> Text="{locale:Locale AboutRyujinxSupprtersTitle}"
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top" Name="SupportersTextBlock" TextDecorations="Underline" />
Text="{Binding Supporters}" /> <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> </Border>
</Grid> </Grid>
</Grid> </Grid>

View File

@ -1,25 +1,25 @@
<window:StyleableWindow <window:StyleableWindow
x:Class="Ryujinx.Ava.Ui.Windows.MainWindow" x:Class="Ryujinx.Ava.Ui.Windows.MainWindow"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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: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: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: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:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
Title="Ryujinx" Title="Ryujinx"
Height="785"
Width="1280" Width="1280"
d:DesignHeight="720" Height="785"
d:DesignWidth="1280"
MinWidth="1024" MinWidth="1024"
MinHeight="680" MinHeight="680"
WindowStartupLocation="CenterScreen" d:DesignHeight="720"
d:DesignWidth="1280"
x:CompileBindings="True" x:CompileBindings="True"
x:DataType="viewModels:MainWindowViewModel" x:DataType="viewModels:MainWindowViewModel"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d"> mc:Ignorable="d">
<Window.Styles> <Window.Styles>
<Style Selector="TitleBar:fullscreen"> <Style Selector="TitleBar:fullscreen">
@ -38,23 +38,28 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<controls:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" /> <controls:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" />
<ContentControl Grid.Row="1" <ContentControl
Focusable="False" Grid.Row="1"
IsVisible="False" Focusable="False"
KeyboardNavigation.IsTabStop="False"> IsVisible="False"
<ui:ContentDialog Name="ContentDialog" KeyboardNavigation.IsTabStop="False">
KeyboardNavigation.IsTabStop="False" <ui:ContentDialog
IsPrimaryButtonEnabled="True" Name="ContentDialog"
IsSecondaryButtonEnabled="True" IsPrimaryButtonEnabled="True"
IsVisible="True" /> IsSecondaryButtonEnabled="True"
IsVisible="True"
KeyboardNavigation.IsTabStop="False" />
</ContentControl> </ContentControl>
<StackPanel IsVisible="False" Grid.Row="0"> <StackPanel Grid.Row="0" IsVisible="False">
<controls:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" /> <controls:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" />
<controls:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" /> <controls:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" />
<controls:HotKeyControl Name="DockToggleHotKey" Command="{ReflectionBinding ToggleDockMode}" /> <controls:HotKeyControl Name="DockToggleHotKey" Command="{ReflectionBinding ToggleDockMode}" />
<controls:HotKeyControl Name="ExitHotKey" Command="{ReflectionBinding ExitCurrentState}" /> <controls:HotKeyControl Name="ExitHotKey" Command="{ReflectionBinding ExitCurrentState}" />
</StackPanel> </StackPanel>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1"> <Grid
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@ -73,47 +78,51 @@
<DockPanel HorizontalAlignment="Stretch"> <DockPanel HorizontalAlignment="Stretch">
<Menu <Menu
Name="Menu" Name="Menu"
Margin="0"
Height="35" Height="35"
Margin="0"
HorizontalAlignment="Left"> HorizontalAlignment="Left">
<Menu.ItemsPanel> <Menu.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<DockPanel HorizontalAlignment="Stretch" Margin="0" /> <DockPanel Margin="0" HorizontalAlignment="Stretch" />
</ItemsPanelTemplate> </ItemsPanelTemplate>
</Menu.ItemsPanel> </Menu.ItemsPanel>
<MenuItem <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarFile}">
VerticalAlignment="Center" <MenuItem
Header="{locale:Locale MenuBarFile}"> Command="{ReflectionBinding OpenFile}"
<MenuItem IsEnabled="{Binding EnableNonGameRunningControls}" Header="{locale:Locale MenuBarFileOpenFromFile}"
Command="{ReflectionBinding OpenFile}" IsEnabled="{Binding EnableNonGameRunningControls}"
Header="{locale:Locale MenuBarFileOpenFromFile}" ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" /> <MenuItem
<MenuItem IsEnabled="{Binding EnableNonGameRunningControls}" Command="{ReflectionBinding OpenFolder}"
Command="{ReflectionBinding OpenFolder}" Header="{locale:Locale MenuBarFileOpenUnpacked}"
Header="{locale:Locale MenuBarFileOpenUnpacked}" IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" /> ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" <MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}">
IsEnabled="{Binding IsAppletMenuActive}"> <MenuItem
<MenuItem Command="{ReflectionBinding OpenMiiApplet}" Header="Mii Edit Applet" Command="{ReflectionBinding OpenMiiApplet}"
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" /> Header="Mii Edit Applet"
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Command="{ReflectionBinding OpenRyujinxFolder}" <MenuItem
Header="{locale:Locale MenuBarFileOpenEmuFolder}" Command="{ReflectionBinding OpenRyujinxFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" /> Header="{locale:Locale MenuBarFileOpenEmuFolder}"
<MenuItem Command="{ReflectionBinding OpenLogsFolder}" ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" />
Header="{locale:Locale MenuBarFileOpenLogsFolder}" <MenuItem
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" /> Command="{ReflectionBinding OpenLogsFolder}"
Header="{locale:Locale MenuBarFileOpenLogsFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" />
<Separator /> <Separator />
<MenuItem Command="{ReflectionBinding CloseWindow}" <MenuItem
Header="{locale:Locale MenuBarFileExit}" Command="{ReflectionBinding CloseWindow}"
ToolTip.Tip="{locale:Locale ExitTooltip}" /> Header="{locale:Locale MenuBarFileExit}"
ToolTip.Tip="{locale:Locale ExitTooltip}" />
</MenuItem> </MenuItem>
<MenuItem <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}">
VerticalAlignment="Center" <MenuItem
Header="{locale:Locale MenuBarOptions}"> Command="{ReflectionBinding ToggleFullscreen}"
<MenuItem Command="{ReflectionBinding ToggleFullscreen}" Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
Header="{locale:Locale MenuBarOptionsToggleFullscreen}" InputGesture="F11" /> InputGesture="F11" />
<MenuItem Header="{locale:Locale MenuBarOptionsStartGamesInFullscreen}"> <MenuItem Header="{locale:Locale MenuBarOptionsStartGamesInFullscreen}">
<MenuItem.Icon> <MenuItem.Icon>
<CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}" /> <CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}" />
@ -126,60 +135,82 @@
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Header="{locale:Locale MenuBarOptionsChangeLanguage}"> <MenuItem Header="{locale:Locale MenuBarOptionsChangeLanguage}">
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="en_US" <MenuItem
Header="American English" /> Command="{ReflectionBinding ChangeLanguage}"
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="pt_BR" CommandParameter="en_US"
Header="Brazilian Portuguese" /> Header="American English" />
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="es_ES" <MenuItem
Header="Castilian Spanish" /> Command="{ReflectionBinding ChangeLanguage}"
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="fr_FR" CommandParameter="pt_BR"
Header="French" /> Header="Brazilian Portuguese" />
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="de_DE" <MenuItem
Header="German" /> Command="{ReflectionBinding ChangeLanguage}"
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="el_GR" CommandParameter="es_ES"
Header="Greek" /> Header="Castilian Spanish" />
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="it_IT" <MenuItem
Header="Italian" /> Command="{ReflectionBinding ChangeLanguage}"
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="ko_KR" CommandParameter="fr_FR"
Header="Korean" /> Header="French" />
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="ru_RU" <MenuItem
Header="Russian" /> Command="{ReflectionBinding ChangeLanguage}"
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="tr_TR" CommandParameter="de_DE"
Header="Turkish" /> 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> </MenuItem>
<Separator /> <Separator />
<MenuItem Command="{ReflectionBinding OpenSettings}" <MenuItem
Header="{locale:Locale MenuBarOptionsSettings}" Command="{ReflectionBinding OpenSettings}"
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" /> Header="{locale:Locale MenuBarOptionsSettings}"
<MenuItem Command="{ReflectionBinding ManageProfiles}" ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
IsEnabled="{Binding EnableNonGameRunningControls}" <MenuItem
Header="{locale:Locale MenuBarOptionsManageUserProfiles}" Command="{ReflectionBinding ManageProfiles}"
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" /> Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
</MenuItem> </MenuItem>
<MenuItem <MenuItem
Name="ActionsMenuItem"
VerticalAlignment="Center" VerticalAlignment="Center"
Header="{locale:Locale MenuBarActions}" Header="{locale:Locale MenuBarActions}"
Name="ActionsMenuItem"
IsEnabled="{Binding IsGameRunning}"> IsEnabled="{Binding IsGameRunning}">
<MenuItem <MenuItem
Click="PauseEmulation_Click" Click="PauseEmulation_Click"
Header="{locale:Locale MenuBarOptionsPauseEmulation}" Header="{locale:Locale MenuBarOptionsPauseEmulation}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding !IsPaused}" IsEnabled="{Binding !IsPaused}"
IsVisible="{Binding !IsPaused}" IsVisible="{Binding !IsPaused}" />
InputGesture="{Binding PauseKey}" />
<MenuItem <MenuItem
Click="ResumeEmulation_Click" Click="ResumeEmulation_Click"
Header="{locale:Locale MenuBarOptionsResumeEmulation}" Header="{locale:Locale MenuBarOptionsResumeEmulation}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding IsPaused}" IsEnabled="{Binding IsPaused}"
IsVisible="{Binding IsPaused}" IsVisible="{Binding IsPaused}" />
InputGesture="{Binding PauseKey}" />
<MenuItem <MenuItem
Click="StopEmulation_Click" Click="StopEmulation_Click"
Header="{locale:Locale MenuBarOptionsStopEmulation}" Header="{locale:Locale MenuBarOptionsStopEmulation}"
ToolTip.Tip="{locale:Locale StopEmulationTooltip}" InputGesture="Escape"
IsEnabled="{Binding IsGameRunning}" InputGesture="Escape" /> IsEnabled="{Binding IsGameRunning}"
<MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" ToolTip.Tip="{locale:Locale StopEmulationTooltip}" />
Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" /> <MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Name="ScanAmiiboMenuItem" Name="ScanAmiiboMenuItem"
@ -187,41 +218,38 @@
Command="{ReflectionBinding OpenAmiiboWindow}" Command="{ReflectionBinding OpenAmiiboWindow}"
Header="{locale:Locale MenuBarActionsScanAmiibo}" Header="{locale:Locale MenuBarActionsScanAmiibo}"
IsEnabled="{Binding IsAmiiboRequested}" /> IsEnabled="{Binding IsAmiiboRequested}" />
<MenuItem Command="{ReflectionBinding TakeScreenshot}" <MenuItem
IsEnabled="{Binding IsGameRunning}" Command="{ReflectionBinding TakeScreenshot}"
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}" Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
InputGesture="{Binding ScreenshotKey}" /> InputGesture="{Binding ScreenshotKey}"
<MenuItem Command="{ReflectionBinding HideUi}" IsEnabled="{Binding IsGameRunning}" />
IsEnabled="{Binding IsGameRunning}" <MenuItem
Header="{locale:Locale MenuBarFileToolsHideUi}" Command="{ReflectionBinding HideUi}"
InputGesture="{Binding ShowUiKey}" /> Header="{locale:Locale MenuBarFileToolsHideUi}"
<MenuItem Command="{ReflectionBinding OpenCheatManagerForCurrentApp}" InputGesture="{Binding ShowUiKey}"
IsEnabled="{Binding IsGameRunning}" IsEnabled="{Binding IsGameRunning}" />
Header="{locale:Locale GameListContextMenuManageCheat}" /> <MenuItem
Command="{ReflectionBinding OpenCheatManagerForCurrentApp}"
Header="{locale:Locale GameListContextMenuManageCheat}"
IsEnabled="{Binding IsGameRunning}" />
</MenuItem> </MenuItem>
<MenuItem <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
VerticalAlignment="Center" <MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}">
Header="{locale:Locale MenuBarTools}"> <MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" <MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
IsEnabled="{Binding EnableNonGameRunningControls}">
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}"
Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}"
Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
VerticalAlignment="Center"
Header="{locale:Locale MenuBarHelp}">
<MenuItem <MenuItem
Name="UpdateMenuItem" Name="UpdateMenuItem"
Command="{ReflectionBinding CheckForUpdates}" Command="{ReflectionBinding CheckForUpdates}"
Header="{locale:Locale MenuBarHelpCheckForUpdates}" Header="{locale:Locale MenuBarHelpCheckForUpdates}"
ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" /> ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" />
<Separator /> <Separator />
<MenuItem Command="{ReflectionBinding OpenAboutWindow}" <MenuItem
Header="{locale:Locale MenuBarHelpAbout}" Command="{ReflectionBinding OpenAboutWindow}"
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" /> Header="{locale:Locale MenuBarHelpAbout}"
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
</MenuItem> </MenuItem>
</Menu> </Menu>
</DockPanel> </DockPanel>
@ -230,152 +258,213 @@
Name="Content" Name="Content"
Grid.Row="1" Grid.Row="1"
Padding="0" Padding="0"
IsVisible="{Binding ShowContent}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
BorderBrush="{DynamicResource ThemeControlBorderColor}" BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="0,0,0,0" BorderThickness="0,0,0,0"
DockPanel.Dock="Top"> DockPanel.Dock="Top"
IsVisible="{Binding ShowContent}">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </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 <Button
IsEnabled="{Binding IsGrid}" VerticalAlignment="Stretch" MinWidth="40" Width="40" Width="40"
Margin="5,2,0,2" Command="{ReflectionBinding SetListMode}"> MinWidth="40"
<ui:FontIcon FontFamily="avares://FluentAvalonia/Fonts#Symbols" Margin="5,2,0,2"
VerticalAlignment="Center" VerticalAlignment="Stretch"
Margin="0" Command="{ReflectionBinding SetListMode}"
Glyph="{controls:GlyphValueConverter List}" IsEnabled="{Binding IsGrid}">
HorizontalAlignment="Stretch" /> <ui:FontIcon
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
Glyph="{controls:GlyphValueConverter List}" />
</Button> </Button>
<Button <Button
IsEnabled="{Binding IsList}" VerticalAlignment="Stretch" MinWidth="40" Width="40" Width="40"
Margin="5,2,5,2" Command="{ReflectionBinding SetGridMode}"> MinWidth="40"
<ui:FontIcon FontFamily="avares://FluentAvalonia/Fonts#Symbols" Margin="5,2,5,2"
VerticalAlignment="Center" VerticalAlignment="Stretch"
Margin="0" Command="{ReflectionBinding SetGridMode}"
Glyph="{controls:GlyphValueConverter Grid}" IsEnabled="{Binding IsList}">
HorizontalAlignment="Stretch" /> <ui:FontIcon
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
Glyph="{controls:GlyphValueConverter Grid}" />
</Button> </Button>
<TextBlock Text="{locale:Locale IconSize}" <TextBlock
VerticalAlignment="Center" Margin="10,0" Margin="10,0"
ToolTip.Tip="{locale:Locale IconSizeTooltip}" /> VerticalAlignment="Center"
<Slider Width="150" Margin="5,-10,5 ,0" Height="35" Text="{locale:Locale IconSize}"
ToolTip.Tip="{locale:Locale IconSizeTooltip}" ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
VerticalAlignment="Center" Minimum="1" Maximum="4" IsSnapToTickEnabled="True" <Slider
TickFrequency="1" Value="{Binding GridSizeScale}" /> Width="150"
<CheckBox Margin="0" IsChecked="{Binding ShowNames, Mode=TwoWay}" VerticalAlignment="Center" Height="35"
IsVisible="{Binding IsGrid}"> Margin="5,-10,5,0"
<TextBlock Text="{locale:Locale CommonShowNames}" Margin="5,3,0,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> </CheckBox>
<TextBox <TextBox
Name="SearchBox" Name="SearchBox"
DockPanel.Dock="Right"
VerticalAlignment="Center"
MinWidth="200" MinWidth="200"
Margin="5,0,5,0" Margin="5,0,5,0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
KeyUp="SearchBox_OnKeyUp" KeyUp="SearchBox_OnKeyUp"
Text="{Binding SearchText}" Text="{Binding SearchText}"
Watermark="{locale:Locale MenuSearch}" /> Watermark="{locale:Locale MenuSearch}" />
<ui:DropDownButton DockPanel.Dock="Right" <ui:DropDownButton
HorizontalAlignment="Right" Width="150" VerticalAlignment="Center" Width="150"
Content="{Binding SortName}"> HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{Binding SortName}"
DockPanel.Dock="Right">
<ui:DropDownButton.Flyout> <ui:DropDownButton.Flyout>
<Flyout Placement="Bottom"> <Flyout Placement="Bottom">
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Margin="0"> <StackPanel
Margin="0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<StackPanel> <StackPanel>
<RadioButton Tag="Favorite" <RadioButton
IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}" Checked="Sort_Checked"
GroupName="Sort" Content="{locale:Locale CommonFavorite}"
Content="{locale:Locale CommonFavorite}" GroupName="Sort"
Checked="Sort_Checked" /> IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}"
<RadioButton Tag="Title" GroupName="Sort" Tag="Favorite" />
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}" <RadioButton
Content="{locale:Locale GameListHeaderApplication}" Checked="Sort_Checked"
Checked="Sort_Checked" /> Content="{locale:Locale GameListHeaderApplication}"
<RadioButton Tag="Developer" GroupName="Sort" GroupName="Sort"
IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}" IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
Content="{locale:Locale GameListHeaderDeveloper}" Tag="Title" />
Checked="Sort_Checked" /> <RadioButton
<RadioButton Tag="TotalTimePlayed" GroupName="Sort" Checked="Sort_Checked"
IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}" Content="{locale:Locale GameListHeaderDeveloper}"
Content="{locale:Locale GameListHeaderTimePlayed}" GroupName="Sort"
Checked="Sort_Checked" /> IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
<RadioButton Tag="LastPlayed" GroupName="Sort" Tag="Developer" />
IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}" <RadioButton
Content="{locale:Locale GameListHeaderLastPlayed}" Checked="Sort_Checked"
Checked="Sort_Checked" /> Content="{locale:Locale GameListHeaderTimePlayed}"
<RadioButton Tag="FileType" GroupName="Sort" GroupName="Sort"
IsChecked="{Binding IsSortedByType, Mode=OneTime}" IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
Content="{locale:Locale GameListHeaderFileExtension}" Tag="TotalTimePlayed" />
Checked="Sort_Checked" /> <RadioButton
<RadioButton Tag="FileSize" GroupName="Sort" Checked="Sort_Checked"
IsChecked="{Binding IsSortedBySize, Mode=OneTime}" Content="{locale:Locale GameListHeaderLastPlayed}"
Content="{locale:Locale GameListHeaderFileSize}" GroupName="Sort"
Checked="Sort_Checked" /> IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
<RadioButton Tag="Path" GroupName="Sort" Tag="LastPlayed" />
IsChecked="{Binding IsSortedByPath, Mode=OneTime}" <RadioButton
Content="{locale:Locale GameListHeaderPath}" Checked="Sort_Checked"
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> </StackPanel>
<Border HorizontalAlignment="Stretch" Margin="5" Height="2" BorderBrush="White" <Border
Width="60" BorderThickness="0,1,0,0"> Width="60"
<Separator HorizontalAlignment="Stretch" Height="0" /> Height="2"
Margin="5"
HorizontalAlignment="Stretch"
BorderBrush="White"
BorderThickness="0,1,0,0">
<Separator Height="0" HorizontalAlignment="Stretch" />
</Border> </Border>
<RadioButton Tag="Ascending" IsChecked="{Binding IsAscending, Mode=OneTime}" <RadioButton
GroupName="Order" Checked="Order_Checked"
Content="{locale:Locale OrderAscending}" Checked="Order_Checked" /> Content="{locale:Locale OrderAscending}"
<RadioButton Tag="Descending" GroupName="Order" GroupName="Order"
IsChecked="{Binding !IsAscending, Mode=OneTime}" IsChecked="{Binding IsAscending, Mode=OneTime}"
Content="{locale:Locale OrderDescending}" Checked="Order_Checked" /> Tag="Ascending" />
<RadioButton
Checked="Order_Checked"
Content="{locale:Locale OrderDescending}"
GroupName="Order"
IsChecked="{Binding !IsAscending, Mode=OneTime}"
Tag="Descending" />
</StackPanel> </StackPanel>
</Flyout> </Flyout>
</ui:DropDownButton.Flyout> </ui:DropDownButton.Flyout>
</ui:DropDownButton> </ui:DropDownButton>
<TextBlock DockPanel.Dock="Right" HorizontalAlignment="Right" <TextBlock
Text="{locale:Locale CommonSort}" VerticalAlignment="Center" Margin="10,0" /> Margin="10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
Text="{locale:Locale CommonSort}" />
</DockPanel> </DockPanel>
<controls:GameListView <controls:GameListView
x:Name="GameList" x:Name="GameList"
Grid.Row="1" Grid.Row="1"
HorizontalContentAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalContentAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
IsVisible="{Binding IsList}" /> IsVisible="{Binding IsList}" />
<controls:GameGridView <controls:GameGridView
x:Name="GameGrid" x:Name="GameGrid"
Grid.Row="1" Grid.Row="1"
HorizontalContentAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalContentAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
IsVisible="{Binding IsGrid}" /> IsVisible="{Binding IsGrid}" />
</Grid> </Grid>
</ContentControl> </ContentControl>
<Grid Grid.Row="1" <Grid
VerticalAlignment="Stretch" Grid.Row="1"
Background="{DynamicResource ThemeContentBackgroundColor}" HorizontalAlignment="Stretch"
IsVisible="{Binding ShowLoadProgress}" VerticalAlignment="Stretch"
ZIndex="1000" Background="{DynamicResource ThemeContentBackgroundColor}"
HorizontalAlignment="Stretch"> IsVisible="{Binding ShowLoadProgress}"
ZIndex="1000">
<Grid <Grid
HorizontalAlignment="Center"
IsVisible="{Binding ShowLoadProgress}"
Margin="40" Margin="40"
VerticalAlignment="Center"> HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding ShowLoadProgress}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Border <Border
Grid.Column="0"
Grid.RowSpan="2" Grid.RowSpan="2"
IsVisible="{Binding ShowLoadProgress}" Grid.Column="0"
Width="256" Width="256"
Height="256" Height="256"
Margin="10" Margin="10"
@ -383,62 +472,64 @@
BorderBrush="Black" BorderBrush="Black"
BorderThickness="2" BorderThickness="2"
BoxShadow="4 4 32 8 #40000000" BoxShadow="4 4 32 8 #40000000"
CornerRadius="3"> CornerRadius="3"
IsVisible="{Binding ShowLoadProgress}">
<Image <Image
IsVisible="{Binding ShowLoadProgress}"
Width="256" Width="256"
Height="256" Height="256"
IsVisible="{Binding ShowLoadProgress}"
Source="{Binding SelectedIcon, Converter={StaticResource ByteImage}}" /> Source="{Binding SelectedIcon, Converter={StaticResource ByteImage}}" />
</Border> </Border>
<Grid Grid.Column="1" <Grid
IsVisible="{Binding ShowLoadProgress}" Grid.Column="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center"> VerticalAlignment="Center"
IsVisible="{Binding ShowLoadProgress}">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock <TextBlock
IsVisible="{Binding ShowLoadProgress}"
Grid.Row="0" Grid.Row="0"
Margin="10" Margin="10"
FontSize="30" FontSize="30"
FontWeight="Bold" FontWeight="Bold"
TextWrapping="Wrap"
Text="{Binding LoadHeading}"
TextAlignment="Left" />
<Border
IsVisible="{Binding ShowLoadProgress}" IsVisible="{Binding ShowLoadProgress}"
Text="{Binding LoadHeading}"
TextAlignment="Left"
TextWrapping="Wrap" />
<Border
Grid.Row="1" Grid.Row="1"
CornerRadius="5" Margin="10"
ClipToBounds="True"
BorderBrush="{Binding ProgressBarBackgroundColor}"
Padding="0" Padding="0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Margin="10" BorderBrush="{Binding ProgressBarBackgroundColor}"
BorderThickness="1"> BorderThickness="1"
ClipToBounds="True"
CornerRadius="5"
IsVisible="{Binding ShowLoadProgress}">
<ProgressBar <ProgressBar
IsVisible="{Binding ShowLoadProgress}"
Height="10" Height="10"
MinWidth="500"
Margin="0" Margin="0"
Padding="0" Padding="0"
CornerRadius="5"
ClipToBounds="True"
MinWidth="500"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Background="{Binding ProgressBarBackgroundColor}" Background="{Binding ProgressBarBackgroundColor}"
ClipToBounds="True"
CornerRadius="5"
Foreground="{Binding ProgressBarForegroundColor}" Foreground="{Binding ProgressBarForegroundColor}"
IsIndeterminate="{Binding IsLoadingIndeterminate}"
IsVisible="{Binding ShowLoadProgress}"
Maximum="{Binding ProgressMaximum}" Maximum="{Binding ProgressMaximum}"
Minimum="0" Minimum="0"
IsIndeterminate="{Binding IsLoadingIndeterminate}"
Value="{Binding ProgressValue}" /> Value="{Binding ProgressValue}" />
</Border> </Border>
<TextBlock <TextBlock
IsVisible="{Binding ShowLoadProgress}"
Grid.Row="2" Grid.Row="2"
Margin="10" Margin="10"
FontSize="18" FontSize="18"
IsVisible="{Binding ShowLoadProgress}"
Text="{Binding CacheLoadStatus}" Text="{Binding CacheLoadStatus}"
TextAlignment="Left" /> TextAlignment="Left" />
</Grid> </Grid>
@ -450,8 +541,8 @@
Height="30" Height="30"
Margin="0,0" Margin="0,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Background="{DynamicResource ThemeContentBackgroundColor}"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
Background="{DynamicResource ThemeContentBackgroundColor}"
DockPanel.Dock="Bottom" DockPanel.Dock="Bottom"
IsVisible="{Binding ShowMenuAndStatusBar}"> IsVisible="{Binding ShowMenuAndStatusBar}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@ -460,8 +551,11 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" IsVisible="{Binding EnableNonGameRunningControls}" <StackPanel
VerticalAlignment="Center" Margin="10,0"> Grid.Column="0"
Margin="10,0"
VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}">
<Grid Margin="0"> <Grid Margin="0">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@ -476,7 +570,10 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Background="Transparent" Background="Transparent"
Command="{ReflectionBinding LoadApplications}"> Command="{ReflectionBinding LoadApplications}">
<ui:SymbolIcon Symbol="Refresh" Height="100" Width="50" /> <ui:SymbolIcon
Width="50"
Height="100"
Symbol="Refresh" />
</Button> </Button>
<TextBlock <TextBlock
Name="LoadStatus" Name="LoadStatus"
@ -489,11 +586,11 @@
Name="LoadProgressBar" Name="LoadProgressBar"
Grid.Column="2" Grid.Column="2"
Height="6" Height="6"
Maximum="{Binding StatusBarProgressMaximum}"
Value="{Binding StatusBarProgressValue}"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="{DynamicResource HighlightColor}" Foreground="{DynamicResource HighlightColor}"
IsVisible="{Binding EnableNonGameRunningControls}" /> IsVisible="{Binding EnableNonGameRunningControls}"
Maximum="{Binding StatusBarProgressMaximum}"
Value="{Binding StatusBarProgressValue}" />
</Grid> </Grid>
</StackPanel> </StackPanel>
<StackPanel <StackPanel
@ -505,132 +602,137 @@
Orientation="Horizontal"> Orientation="Horizontal">
<TextBlock <TextBlock
Name="VsyncStatus" Name="VsyncStatus"
Margin="0,0,5,0"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="{Binding VsyncColor}" Foreground="{Binding VsyncColor}"
PointerReleased="VsyncStatus_PointerReleased"
IsVisible="{Binding !ShowLoadProgress}" IsVisible="{Binding !ShowLoadProgress}"
Margin="0,0,5,0" PointerReleased="VsyncStatus_PointerReleased"
Text="VSync" Text="VSync"
TextAlignment="Left" /> TextAlignment="Left" />
<Border <Border
Width="2" Width="2"
Margin="2,0"
IsVisible="{Binding !ShowLoadProgress}"
BorderThickness="1"
Height="12" Height="12"
BorderBrush="Gray" /> Margin="2,0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock <TextBlock
Margin="5,0,5,0"
Name="DockedStatus" Name="DockedStatus"
IsVisible="{Binding !ShowLoadProgress}" Margin="5,0,5,0"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="DockedStatus_PointerReleased" PointerReleased="DockedStatus_PointerReleased"
Text="{Binding DockedStatusText}" Text="{Binding DockedStatusText}"
TextAlignment="Left" /> TextAlignment="Left" />
<Border <Border
Width="2" Width="2"
Margin="2,0"
IsVisible="{Binding !ShowLoadProgress}"
BorderThickness="1"
Height="12" Height="12"
BorderBrush="Gray" /> Margin="2,0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock <TextBlock
Margin="5,0,5,0"
Name="AspectRatioStatus" Name="AspectRatioStatus"
IsVisible="{Binding !ShowLoadProgress}" Margin="5,0,5,0"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="AspectRatioStatus_PointerReleased" PointerReleased="AspectRatioStatus_PointerReleased"
Text="{Binding AspectRatioStatusText}" Text="{Binding AspectRatioStatusText}"
TextAlignment="Left" /> TextAlignment="Left" />
<Border <Border
Width="2" Width="2"
Margin="2,0"
IsVisible="{Binding !ShowLoadProgress}"
BorderThickness="1"
Height="12" Height="12"
BorderBrush="Gray" /> Margin="2,0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<ui:ToggleSplitButton <ui:ToggleSplitButton
Name="VolumeStatus"
Margin="-2,0,-3,0" Margin="-2,0,-3,0"
Padding="5,0,0,5" Padding="5,0,0,5"
Name="VolumeStatus"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
BorderBrush="{DynamicResource ThemeContentBackgroundColor}"
Background="{DynamicResource ThemeContentBackgroundColor}" Background="{DynamicResource ThemeContentBackgroundColor}"
BorderBrush="{DynamicResource ThemeContentBackgroundColor}"
Content="{Binding VolumeStatusText}"
IsChecked="{Binding VolumeMuted}" IsChecked="{Binding VolumeMuted}"
Content="{Binding VolumeStatusText}"> IsVisible="{Binding !ShowLoadProgress}">
<ui:ToggleSplitButton.Flyout> <ui:ToggleSplitButton.Flyout>
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway"> <Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
<Grid Margin="0"> <Grid Margin="0">
<Slider Value="{Binding Volume}" <Slider
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}" Width="150"
Minimum="0" Margin="0"
Maximum="1" Padding="0"
TickFrequency="0.05" IsSnapToTickEnabled="True"
IsSnapToTickEnabled="True" LargeChange="0.05"
Padding="0" Maximum="1"
Margin="0" Minimum="0"
SmallChange="0.01" SmallChange="0.01"
LargeChange="0.05" TickFrequency="0.05"
Width="150" /> ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Value="{Binding Volume}" />
</Grid> </Grid>
</Flyout> </Flyout>
</ui:ToggleSplitButton.Flyout> </ui:ToggleSplitButton.Flyout>
</ui:ToggleSplitButton> </ui:ToggleSplitButton>
<Border <Border
Width="2" Width="2"
Margin="2,0"
IsVisible="{Binding !ShowLoadProgress}"
BorderThickness="1"
Height="12" Height="12"
BorderBrush="Gray" /> Margin="2,0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock <TextBlock
Margin="5,0,5,0" Margin="5,0,5,0"
IsVisible="{Binding !ShowLoadProgress}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GameStatusText}" Text="{Binding GameStatusText}"
TextAlignment="Left" /> TextAlignment="Left" />
<Border <Border
Width="2" Width="2"
IsVisible="{Binding !ShowLoadProgress}"
Margin="2,0"
BorderThickness="1"
Height="12" Height="12"
BorderBrush="Gray" /> Margin="2,0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock <TextBlock
Margin="5,0,5,0" Margin="5,0,5,0"
IsVisible="{Binding !ShowLoadProgress}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding FifoStatusText}" Text="{Binding FifoStatusText}"
TextAlignment="Left" /> TextAlignment="Left" />
<Border <Border
Width="2" Width="2"
Margin="2,0"
IsVisible="{Binding !ShowLoadProgress}"
BorderThickness="1"
Height="12" Height="12"
BorderBrush="Gray" /> Margin="2,0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock <TextBlock
Margin="5,0,5,0" Margin="5,0,5,0"
IsVisible="{Binding !ShowLoadProgress}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GpuStatusText}" Text="{Binding GpuStatusText}"
TextAlignment="Left" /> TextAlignment="Left" />
</StackPanel> </StackPanel>
<StackPanel VerticalAlignment="Center" IsVisible="{Binding ShowFirmwareStatus}" Grid.Column="3" <StackPanel
Orientation="Horizontal" Margin="10, 0"> Grid.Column="3"
Margin="10,0"
VerticalAlignment="Center"
IsVisible="{Binding ShowFirmwareStatus}"
Orientation="Horizontal">
<TextBlock <TextBlock
Name="FirmwareStatus" Name="FirmwareStatus"
Margin="0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="0"
Text="{locale:Locale StatusBarSystemVersion}" /> Text="{locale:Locale StatusBarSystemVersion}" />
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@ -123,10 +123,6 @@ namespace Ryujinx.Ava.Ui.Windows
CheckLaunchState(); CheckLaunchState();
} }
if (OperatingSystem.IsLinux())
{
Program.WindowScaleFactor = this.PlatformImpl.RenderScaling;
}
_rendererWaitEvent = new AutoResetEvent(false); _rendererWaitEvent = new AutoResetEvent(false);
} }

View File

@ -1,18 +1,26 @@
<window:StyleableWindow xmlns="https://github.com/avaloniaui" <window:StyleableWindow
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ryujinx.Ava.Ui.Windows.UpdaterWindow"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
x:Class="Ryujinx.Ava.Ui.Windows.UpdaterWindow" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
CanResize="False" Title="Ryujinx Updater"
SizeToContent="Height" Width="500"
Width="500" MinHeight="500" Height="500" Height="500"
WindowStartupLocation="CenterOwner" MinWidth="500"
MinWidth="500" MinHeight="500"
Title="Ryujinx Updater"> d:DesignHeight="350"
<Grid Margin="20" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> d:DesignWidth="400"
CanResize="False"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<Grid
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition /> <RowDefinition />
@ -20,17 +28,38 @@
<RowDefinition /> <RowDefinition />
<RowDefinition /> <RowDefinition />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Row="1" HorizontalAlignment="Stretch" TextAlignment="Center" Height="20" Name="MainText" /> <TextBlock
<TextBlock Height="20" HorizontalAlignment="Stretch" TextAlignment="Center" Name="SecondaryText" Grid.Row="2" /> Name="MainText"
<ProgressBar IsVisible="False" HorizontalAlignment="Stretch" Name="ProgressBar" Maximum="100" Minimum="0" Grid.Row="1"
Margin="20" Grid.Row="3" /> Height="20"
<StackPanel IsVisible="False" Name="ButtonBox" Orientation="Horizontal" Spacing="20" Grid.Row="4" HorizontalAlignment="Stretch"
HorizontalAlignment="Right"> TextAlignment="Center" />
<Button Command="{Binding YesPressed}" MinWidth="50"> <TextBlock
<TextBlock TextAlignment="Center" Text="{locale:Locale InputDialogYes}" /> 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>
<Button Command="{Binding NoPressed}" MinWidth="50"> <Button MinWidth="50" Command="{Binding NoPressed}">
<TextBlock TextAlignment="Center" Text="{locale:Locale InputDialogNo}" /> <TextBlock Text="{locale:Locale InputDialogNo}" TextAlignment="Center" />
</Button> </Button>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@ -0,0 +1,12 @@
namespace Ryujinx.Common.Memory
{
public class Box<T> where T : unmanaged
{
public T Data;
public Box()
{
Data = new T();
}
}
}

View File

@ -22,7 +22,17 @@ namespace Ryujinx.Common
public static long AlignUp(long value, int size) 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) public static uint AlignDown(uint value, int size)
@ -42,7 +52,17 @@ namespace Ryujinx.Common
public static long AlignDown(long value, int size) 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) public static int DivRoundUp(int value, int dividend)

View 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;
}
}
}

View 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
View 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);
}
}

View 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();
}
}

View 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();
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View File

@ -1,9 +1,9 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using Ryujinx.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 Allocate(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.None);
public IJitMemoryBlock Reserve(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.Reserve); public IJitMemoryBlock Reserve(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.Reserve);

View File

@ -2,9 +2,9 @@
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
namespace Ryujinx.Cpu namespace Ryujinx.Cpu.Jit
{ {
class JitMemoryBlock : IJitMemoryBlock public class JitMemoryBlock : IJitMemoryBlock
{ {
private readonly MemoryBlock _impl; private readonly MemoryBlock _impl;

View File

@ -10,7 +10,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
namespace Ryujinx.Cpu namespace Ryujinx.Cpu.Jit
{ {
/// <summary> /// <summary>
/// Represents a CPU memory manager. /// Represents a CPU memory manager.

View File

@ -8,12 +8,12 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
namespace Ryujinx.Cpu namespace Ryujinx.Cpu.Jit
{ {
/// <summary> /// <summary>
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
/// </summary> /// </summary>
public class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock public sealed class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{ {
public const int PageBits = 12; public const int PageBits = 12;
public const int PageSize = 1 << PageBits; public const int PageSize = 1 << PageBits;

45
Ryujinx.Cpu/TickSource.cs Normal file
View 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();
}
}
}

View File

@ -10,9 +10,10 @@ namespace Ryujinx.Graphics.GAL
void ClearBuffer(BufferHandle destination, int offset, int size, uint value); 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( void ClearRenderTargetDepthStencil(
int layer,
float depthValue, float depthValue,
bool depthMask, bool depthMask,
int stencilValue, int stencilValue,

View File

@ -4,19 +4,21 @@
{ {
public CommandType CommandType => CommandType.ClearRenderTargetColor; public CommandType CommandType => CommandType.ClearRenderTargetColor;
private int _index; private int _index;
private int _layer;
private uint _componentMask; private uint _componentMask;
private ColorF _color; 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; _index = index;
_layer = layer;
_componentMask = componentMask; _componentMask = componentMask;
_color = color; _color = color;
} }
public static void Run(ref ClearRenderTargetColorCommand command, ThreadedRenderer threaded, IRenderer renderer) 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);
} }
} }
} }

View File

@ -3,13 +3,15 @@
struct ClearRenderTargetDepthStencilCommand : IGALCommand struct ClearRenderTargetDepthStencilCommand : IGALCommand
{ {
public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil; public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil;
private int _layer;
private float _depthValue; private float _depthValue;
private bool _depthMask; private bool _depthMask;
private int _stencilValue; private int _stencilValue;
private int _stencilMask; 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; _depthValue = depthValue;
_depthMask = depthMask; _depthMask = depthMask;
_stencilValue = stencilValue; _stencilValue = stencilValue;
@ -18,7 +20,7 @@
public static void Run(ref ClearRenderTargetDepthStencilCommand command, ThreadedRenderer threaded, IRenderer renderer) 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);
} }
} }
} }

View File

@ -40,15 +40,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _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(); _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(); _renderer.QueueCommand();
} }

View File

@ -13,4 +13,12 @@ namespace Ryujinx.Graphics.GAL
CubemapArray, CubemapArray,
TextureBuffer TextureBuffer
} }
public static class TargetExtensions
{
public static bool IsMultisample(this Target target)
{
return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray;
}
}
} }

View File

@ -188,6 +188,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
_channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers); _channel.BufferManager.SetComputeStorageBufferBindings(info.SBuffers);
_channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers); _channel.BufferManager.SetComputeUniformBufferBindings(info.CBuffers);
int maxTextureBinding = -1;
int maxImageBinding = -1;
TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count); TextureBindingInfo[] textureBindings = _channel.TextureManager.RentComputeTextureBindings(info.Textures.Count);
for (int index = 0; index < info.Textures.Count; index++) for (int index = 0; index < info.Textures.Count; index++)
@ -202,6 +205,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags);
if (descriptor.Binding > maxTextureBinding)
{
maxTextureBinding = descriptor.Binding;
}
} }
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count); TextureBindingInfo[] imageBindings = _channel.TextureManager.RentComputeImageBindings(info.Images.Count);
@ -220,9 +228,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); 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(); _channel.BufferManager.CommitComputeBindings();
_context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth); _context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth);

View File

@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private bool _instancedDrawPending; private bool _instancedDrawPending;
private bool _instancedIndexed; private bool _instancedIndexed;
private bool _instancedIndexedInline;
private int _instancedFirstIndex; private int _instancedFirstIndex;
private int _instancedFirstVertex; private int _instancedFirstVertex;
@ -134,13 +135,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{ {
_instancedDrawPending = true; _instancedDrawPending = true;
int ibCount = _drawState.IbStreamer.InlineIndexCount;
_instancedIndexed = _drawState.DrawIndexed; _instancedIndexed = _drawState.DrawIndexed;
_instancedIndexedInline = ibCount != 0;
_instancedFirstIndex = firstIndex; _instancedFirstIndex = firstIndex;
_instancedFirstVertex = (int)_state.State.FirstVertex; _instancedFirstVertex = (int)_state.State.FirstVertex;
_instancedFirstInstance = (int)_state.State.FirstInstance; _instancedFirstInstance = (int)_state.State.FirstInstance;
_instancedIndexCount = indexCount; _instancedIndexCount = ibCount != 0 ? ibCount : indexCount;
var drawState = _state.State.VertexBufferDrawState; var drawState = _state.State.VertexBufferDrawState;
@ -451,8 +455,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{ {
_instancedDrawPending = false; _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( _context.Renderer.Pipeline.DrawIndexed(
_instancedIndexCount, _instancedIndexCount,
_instanceIndex + 1, _instanceIndex + 1,
@ -491,8 +505,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
} }
int index = (argument >> 6) & 0xf; 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 // 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 // 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); 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) if (clearDepth || clearStencil)
@ -588,6 +603,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
} }
_context.Renderer.Pipeline.ClearRenderTargetDepthStencil( _context.Renderer.Pipeline.ClearRenderTargetDepthStencil(
layer,
depthValue, depthValue,
clearDepth, clearDepth,
stencilValue, stencilValue,

View File

@ -20,6 +20,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary> /// </summary>
public bool HasInlineIndexData => _inlineIndexCount != 0; public bool HasInlineIndexData => _inlineIndexCount != 0;
/// <summary>
/// Total numbers of indices that have been pushed.
/// </summary>
public int InlineIndexCount => _inlineIndexCount;
/// <summary> /// <summary>
/// Gets the handle for the host buffer currently holding the inline index buffer data. /// Gets the handle for the host buffer currently holding the inline index buffer data.
/// </summary> /// </summary>

View File

@ -44,7 +44,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary> /// </summary>
private enum ReportCounterType private enum ReportCounterType
{ {
Zero = 0, Payload = 0,
InputVertices = 1, InputVertices = 1,
InputPrimitives = 3, InputPrimitives = 3,
VertexShaderInvocations = 5, VertexShaderInvocations = 5,
@ -169,8 +169,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
switch (type) switch (type)
{ {
case ReportCounterType.Zero: case ReportCounterType.Payload:
resultHandler(null, 0); resultHandler(null, (ulong)_state.State.SemaphorePayload);
break; break;
case ReportCounterType.SamplesPassed: case ReportCounterType.SamplesPassed:
counter = _context.Renderer.ReportCounter(CounterType.SamplesPassed, resultHandler, false); counter = _context.Renderer.ReportCounter(CounterType.SamplesPassed, resultHandler, false);

View File

@ -201,7 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
// of the shader for the new state. // of the shader for the new state.
if (_shaderSpecState != null) if (_shaderSpecState != null)
{ {
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState())) if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState(), false))
{ {
ForceShaderUpdate(); ForceShaderUpdate();
} }
@ -275,7 +275,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{ {
UpdateStorageBuffers(); UpdateStorageBuffers();
_channel.TextureManager.CommitGraphicsBindings(); if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState))
{
// Shader must be reloaded.
UpdateShaderState();
}
_channel.BufferManager.CommitGraphicsBindings(); _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. /// Updates render targets (color and depth-stencil buffers) based on current render target state.
/// </summary> /// </summary>
/// <param name="useControl">Use draw buffers information from render target control register</param> /// <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> /// <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 memoryManager = _channel.MemoryManager;
var rtControl = _state.State.RtControl; var rtControl = _state.State.RtControl;
@ -399,7 +405,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture( Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture(
memoryManager, memoryManager,
colorState, colorState,
_vtgWritesRtLayer, _vtgWritesRtLayer || layered,
samplesInX, samplesInX,
samplesInY, samplesInY,
sizeHint); sizeHint);
@ -433,6 +439,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
memoryManager, memoryManager,
dsState, dsState,
dsSize, dsSize,
_vtgWritesRtLayer || layered,
samplesInX, samplesInX,
samplesInY, samplesInY,
sizeHint); sizeHint);
@ -1148,6 +1155,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
return; return;
} }
int maxTextureBinding = -1;
int maxImageBinding = -1;
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count); Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
if (info.UsesRtLayer) if (info.UsesRtLayer)
@ -1167,6 +1177,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags);
if (descriptor.Binding > maxTextureBinding)
{
maxTextureBinding = descriptor.Binding;
}
} }
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count); TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
@ -1185,8 +1200,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
descriptor.CbufSlot, descriptor.CbufSlot,
descriptor.HandleIndex, descriptor.HandleIndex,
descriptor.Flags); descriptor.Flags);
if (descriptor.Binding > maxImageBinding)
{
maxImageBinding = descriptor.Binding;
}
} }
_channel.TextureManager.SetGraphicsMaxBindings(maxTextureBinding, maxImageBinding);
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers); _channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers); _channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
} }

View File

@ -131,10 +131,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// Updates render targets (color and depth-stencil buffers) based on current render target state. /// Updates render targets (color and depth-stencil buffers) based on current render target state.
/// </summary> /// </summary>
/// <param name="useControl">Use draw buffers information from render target control register</param> /// <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> /// <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> /// <summary>

View File

@ -1,6 +1,7 @@
using Ryujinx.Cpu.Tracking; using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using System; using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
{ {
@ -16,6 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Image
protected GpuContext Context; protected GpuContext Context;
protected PhysicalMemory PhysicalMemory; protected PhysicalMemory PhysicalMemory;
protected int SequenceNumber; protected int SequenceNumber;
protected int ModifiedSequenceNumber;
protected T1[] Items; protected T1[] Items;
protected T2[] DescriptorCache; protected T2[] DescriptorCache;
@ -41,6 +43,9 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly CpuMultiRegionHandle _memoryTracking; private readonly CpuMultiRegionHandle _memoryTracking;
private readonly Action<ulong, ulong> _modifiedDelegate; private readonly Action<ulong, ulong> _modifiedDelegate;
private int _modifiedSequenceOffset;
private bool _modified;
/// <summary> /// <summary>
/// Creates a new instance of the GPU resource pool. /// Creates a new instance of the GPU resource pool.
/// </summary> /// </summary>
@ -79,6 +84,16 @@ namespace Ryujinx.Graphics.Gpu.Image
return PhysicalMemory.Read<T2>(Address + (ulong)id * DescriptorSize); 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> /// <summary>
/// Gets the GPU resource with the given ID. /// Gets the GPU resource with the given ID.
/// </summary> /// </summary>
@ -86,6 +101,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>The GPU resource with the given ID</returns> /// <returns>The GPU resource with the given ID</returns>
public abstract T1 Get(int id); 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> /// <summary>
/// Synchronizes host memory with guest memory. /// Synchronizes host memory with guest memory.
/// This causes invalidation of pool entries, /// This causes invalidation of pool entries,
@ -93,7 +118,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public void SynchronizeMemory() public void SynchronizeMemory()
{ {
_modified = false;
_memoryTracking.QueryModified(_modifiedDelegate); _memoryTracking.QueryModified(_modifiedDelegate);
if (_modified)
{
UpdateModifiedSequence();
}
} }
/// <summary> /// <summary>
@ -103,6 +134,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="mSize">Size of the modified region</param> /// <param name="mSize">Size of the modified region</param>
private void RegionModified(ulong mAddress, ulong mSize) private void RegionModified(ulong mAddress, ulong mSize)
{ {
_modified = true;
if (mAddress < Address) if (mAddress < Address)
{ {
mAddress = Address; mAddress = Address;
@ -118,6 +151,15 @@ namespace Ryujinx.Graphics.Gpu.Image
InvalidateRangeImpl(mAddress, mSize); 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> /// <summary>
/// An action to be performed when a precise memory access occurs to this resource. /// An action to be performed when a precise memory access occurs to this resource.
/// Makes sure that the dirty flags are checked. /// Makes sure that the dirty flags are checked.
@ -129,6 +171,16 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
if (write && Context.SequenceNumber == SequenceNumber) 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--; SequenceNumber--;
} }

View File

@ -8,6 +8,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
class Sampler : IDisposable class Sampler : IDisposable
{ {
/// <summary>
/// True if the sampler is disposed, false otherwise.
/// </summary>
public bool IsDisposed { get; private set; }
/// <summary> /// <summary>
/// Host sampler object. /// Host sampler object.
/// </summary> /// </summary>
@ -101,6 +106,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
IsDisposed = true;
_hostSampler.Dispose(); _hostSampler.Dispose();
_anisoSampler?.Dispose(); _anisoSampler?.Dispose();
} }

View File

@ -48,6 +48,8 @@ namespace Ryujinx.Graphics.Gpu.Image
Items[i] = null; Items[i] = null;
} }
} }
UpdateModifiedSequence();
} }
SequenceNumber = Context.SequenceNumber; SequenceNumber = Context.SequenceNumber;
@ -71,6 +73,39 @@ namespace Ryujinx.Graphics.Gpu.Image
return sampler; 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> /// <summary>
/// Implementation of the sampler pool range invalidation. /// Implementation of the sampler pool range invalidation.
/// </summary> /// </summary>

View File

@ -100,6 +100,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public bool AlwaysFlushOnOverlap { get; private set; } 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 _depth;
private int _layers; private int _layers;
public int FirstLayer { get; private set; } 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="range">Texture view physical memory ranges</param>
/// <param name="layerSize">Layer size on the given texture</param> /// <param name="layerSize">Layer size on the given texture</param>
/// <param name="caps">Host GPU capabilities</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="firstLayer">Texture view initial layer on this texture</param>
/// <param name="firstLevel">Texture view first mipmap level 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> /// <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; TextureViewCompatibility result = TextureViewCompatibility.Full;
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps)); result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps));
if (result != TextureViewCompatibility.Incompatible) 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; result = TextureViewCompatibility.Incompatible;
}
if (!msTargetCompatible)
{
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
if (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY)
{
result = TextureViewCompatibility.Incompatible;
}
} }
if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat) if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat)
@ -1417,6 +1412,7 @@ namespace Ryujinx.Graphics.Gpu.Image
DisposeTextures(); DisposeTextures();
HostTexture = hostTexture; HostTexture = hostTexture;
InvalidatedSequence++;
} }
/// <summary> /// <summary>
@ -1545,6 +1541,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_poolOwners.Clear(); _poolOwners.Clear();
} }
InvalidatedSequence++;
} }
/// <summary> /// <summary>

View File

@ -1,8 +1,12 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
{ {
@ -31,21 +35,30 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly TextureBindingInfo[][] _textureBindings; private readonly TextureBindingInfo[][] _textureBindings;
private readonly TextureBindingInfo[][] _imageBindings; private readonly TextureBindingInfo[][] _imageBindings;
private struct TextureStatePerStage private struct TextureState
{ {
public ITexture Texture; public ITexture Texture;
public ISampler Sampler; 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 TextureState[] _textureState;
private readonly TextureStatePerStage[][] _imageState; private TextureState[] _imageState;
private int[] _textureBindingsCount; private int[] _textureBindingsCount;
private int[] _imageBindingsCount; private int[] _imageBindingsCount;
private int _textureBufferIndex; private int _texturePoolSequence;
private int _samplerPoolSequence;
private bool _rebind; private int _textureBufferIndex;
private readonly float[] _scales; private readonly float[] _scales;
private bool _scaleChanged; private bool _scaleChanged;
@ -72,8 +85,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_textureBindings = new TextureBindingInfo[stages][]; _textureBindings = new TextureBindingInfo[stages][];
_imageBindings = new TextureBindingInfo[stages][]; _imageBindings = new TextureBindingInfo[stages][];
_textureState = new TextureStatePerStage[stages][]; _textureState = new TextureState[InitialTextureStateSize];
_imageState = new TextureStatePerStage[stages][]; _imageState = new TextureState[InitialImageStateSize];
_textureBindingsCount = new int[stages]; _textureBindingsCount = new int[stages];
_imageBindingsCount = new int[stages]; _imageBindingsCount = new int[stages];
@ -82,9 +95,6 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize]; _textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
_imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize]; _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) if (count > _textureBindings[stage].Length)
{ {
Array.Resize(ref _textureBindings[stage], count); 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; _textureBindingsCount[stage] = count;
@ -126,15 +127,6 @@ namespace Ryujinx.Graphics.Gpu.Image
if (count > _imageBindings[stage].Length) if (count > _imageBindings[stage].Length)
{ {
Array.Resize(ref _imageBindings[stage], count); 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; _imageBindingsCount[stage] = count;
@ -142,6 +134,24 @@ namespace Ryujinx.Graphics.Gpu.Image
return _imageBindings[stage]; 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> /// <summary>
/// Sets the textures constant buffer index. /// Sets the textures constant buffer index.
/// The constant buffer specified holds the texture handles. /// 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. /// Updates the texture scale for a given texture or image.
/// </summary> /// </summary>
/// <param name="texture">Start GPU virtual address of the pool</param> /// <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="index">The texture/image binding index</param>
/// <param name="stage">The active shader stage</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> /// <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; float result = 1f;
bool changed = false; 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; changed = texture.ScaleMode != TextureScaleMode.Blacklisted;
texture.BlacklistScale(); texture.BlacklistScale();
@ -323,7 +333,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Ensures that the bindings are visible to the host GPU. /// Ensures that the bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API. /// Note: this actually performs the binding using the host graphics API.
/// </summary> /// </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; ulong texturePoolAddress = _texturePoolAddress;
@ -331,10 +343,38 @@ namespace Ryujinx.Graphics.Gpu.Image
? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId) ? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId)
: null; : 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) if (_isCompute)
{ {
CommitTextureBindings(texturePool, ShaderStage.Compute, 0); specStateMatches &= CommitTextureBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
CommitImageBindings (texturePool, ShaderStage.Compute, 0); specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
} }
else else
{ {
@ -342,14 +382,57 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
int stageIndex = (int)stage - 1; int stageIndex = (int)stage - 1;
CommitTextureBindings(texturePool, stage, stageIndex); specStateMatches &= CommitTextureBindings(texturePool, stage, stageIndex, poolModified, specState);
CommitImageBindings (texturePool, stage, stageIndex); specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
} }
} }
CommitRenderScale(); 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> /// <summary>
@ -358,13 +441,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="pool">The current texture pool</param> /// <param name="pool">The current texture pool</param>
/// <param name="stage">The shader stage using the textures to be bound</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> /// <param name="stageIndex">The stage number of the specified shader stage</param
private void CommitTextureBindings(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 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]; int textureCount = _textureBindingsCount[stageIndex];
if (textureCount == 0) if (textureCount == 0)
{ {
return; return true;
} }
var samplerPool = _samplerPool; var samplerPool = _samplerPool;
@ -372,17 +458,27 @@ namespace Ryujinx.Graphics.Gpu.Image
if (pool == null) if (pool == null)
{ {
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set."); 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++) for (int index = 0; index < textureCount; index++)
{ {
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index]; TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags;
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex); UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
int textureId = UnpackTextureId(packedId);
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
int textureId = TextureHandle.UnpackTextureId(packedId);
int samplerId; int samplerId;
if (_samplerIndex == SamplerIndex.ViaHeaderIndex) if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
@ -391,10 +487,42 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
else 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); ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
@ -407,30 +535,38 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
else 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); 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); _context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
} }
Sampler sampler = samplerPool?.Get(samplerId); Sampler sampler = samplerPool?.Get(samplerId);
state.CachedSampler = sampler;
ISampler hostSampler = sampler?.GetHostSampler(texture); 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); _context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler);
} }
state.CachedTexture = texture;
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
} }
} }
return specStateMatches;
} }
/// <summary> /// <summary>
@ -440,38 +576,90 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="pool">The current texture pool</param> /// <param name="pool">The current texture pool</param>
/// <param name="stage">The shader stage using the textures to be bound</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> /// <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]; int imageCount = _imageBindingsCount[stageIndex];
if (imageCount == 0) if (imageCount == 0)
{ {
return; return true;
} }
if (pool == null) if (pool == null)
{ {
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses images, but texture pool was not set."); 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. // Scales for images appear after the texture ones.
int baseScaleIndex = _textureBindingsCount[stageIndex]; 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++) for (int index = 0; index < imageCount; index++)
{ {
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index]; TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
TextureUsageFlags usageFlags = bindingInfo.Flags;
int scaleIndex = baseScaleIndex + index;
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex, samplerBufferIndex); UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
int textureId = UnpackTextureId(packedId);
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); 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) if (hostTexture != null && texture.Target == Target.TextureBuffer)
{ {
// Ensure that the buffer texture is using the correct buffer as storage. // Ensure that the buffer texture is using the correct buffer as storage.
@ -494,14 +682,16 @@ namespace Ryujinx.Graphics.Gpu.Image
texture?.SignalModified(); 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); hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
} }
_imageState[stageIndex][index].Texture = hostTexture; state.Texture = hostTexture;
state.ScaleIndex = scaleIndex;
state.UsageFlags = usageFlags;
Format format = bindingInfo.Format; Format format = bindingInfo.Format;
@ -512,8 +702,13 @@ namespace Ryujinx.Graphics.Gpu.Image
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format); _context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
} }
state.CachedTexture = texture;
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
} }
} }
return specStateMatches;
} }
/// <summary> /// <summary>
@ -537,13 +732,28 @@ namespace Ryujinx.Graphics.Gpu.Image
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex); (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex);
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex); int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
int textureId = UnpackTextureId(packedId); int textureId = TextureHandle.UnpackTextureId(packedId);
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId); 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> /// <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="textureBufferIndex">Index of the constant buffer holding the texture handles</param>
/// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler 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> /// <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) private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex)
{ {
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset); (int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
@ -590,32 +801,13 @@ namespace Ryujinx.Graphics.Gpu.Image
return handle; 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> /// <summary>
/// Force all bound textures and images to be rebound the next time CommitBindings is called. /// Force all bound textures and images to be rebound the next time CommitBindings is called.
/// </summary> /// </summary>
public void Rebind() public void Rebind()
{ {
_rebind = true; Array.Clear(_textureState);
Array.Clear(_imageState);
} }
/// <summary> /// <summary>

View File

@ -349,6 +349,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="memoryManager">GPU memory manager where the texture is mapped</param> /// <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="dsState">Depth-stencil buffer texture to find or create</param>
/// <param name="size">Size of the depth-stencil texture</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="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="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> /// <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, MemoryManager memoryManager,
RtDepthStencilState dsState, RtDepthStencilState dsState,
Size3D size, Size3D size,
bool layered,
int samplesInX, int samplesInX,
int samplesInY, int samplesInY,
Size sizeHint) Size sizeHint)
@ -364,9 +366,24 @@ namespace Ryujinx.Graphics.Gpu.Image
int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY(); int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ(); int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
Target target = (samplesInX | samplesInY) != 1 Target target;
? Target.Texture2DMultisample
: Target.Texture2D; 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(); FormatInfo formatInfo = dsState.Format.Convert();
@ -547,7 +564,6 @@ namespace Ryujinx.Graphics.Gpu.Image
range.Value, range.Value,
sizeInfo.LayerSize, sizeInfo.LayerSize,
_context.Capabilities, _context.Capabilities,
flags.HasFlag(TextureSearchFlags.ForCopy),
out int firstLayer, out int firstLayer,
out int firstLevel); out int firstLevel);
@ -662,7 +678,6 @@ namespace Ryujinx.Graphics.Gpu.Image
overlap.Range, overlap.Range,
overlap.LayerSize, overlap.LayerSize,
_context.Capabilities, _context.Capabilities,
false,
out int firstLayer, out int firstLayer,
out int firstLevel); out int firstLevel);

View File

@ -2,7 +2,6 @@ using Ryujinx.Common;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
using System; using System;
using System.Numerics;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
{ {
@ -657,6 +656,11 @@ namespace Ryujinx.Graphics.Gpu.Image
case Target.Texture2DMultisample: case Target.Texture2DMultisample:
case Target.Texture2DMultisampleArray: case Target.Texture2DMultisampleArray:
if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
{
return TextureViewCompatibility.CopyOnly;
}
result = rhs.Target == Target.Texture2DMultisample || result = rhs.Target == Target.Texture2DMultisample ||
rhs.Target == Target.Texture2DMultisampleArray; rhs.Target == Target.Texture2DMultisampleArray;
break; break;

View File

@ -241,25 +241,6 @@ namespace Ryujinx.Graphics.Gpu.Image
return (TextureMsaaMode)((Word7 >> 8) & 0xf); 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> /// <summary>
/// Check if two descriptors are equal. /// Check if two descriptors are equal.
/// </summary> /// </summary>

View File

@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Shader;
using System; using System;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
@ -10,9 +11,11 @@ namespace Ryujinx.Graphics.Gpu.Image
class TextureManager : IDisposable class TextureManager : IDisposable
{ {
private readonly GpuContext _context; private readonly GpuContext _context;
private readonly GpuChannel _channel;
private readonly TextureBindingsManager _cpBindingsManager; private readonly TextureBindingsManager _cpBindingsManager;
private readonly TextureBindingsManager _gpBindingsManager; private readonly TextureBindingsManager _gpBindingsManager;
private readonly TexturePoolCache _texturePoolCache;
private readonly Texture[] _rtColors; private readonly Texture[] _rtColors;
private readonly ITexture[] _rtHostColors; private readonly ITexture[] _rtHostColors;
@ -35,6 +38,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public TextureManager(GpuContext context, GpuChannel channel) public TextureManager(GpuContext context, GpuChannel channel)
{ {
_context = context; _context = context;
_channel = channel;
TexturePoolCache texturePoolCache = new TexturePoolCache(context); TexturePoolCache texturePoolCache = new TexturePoolCache(context);
@ -43,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: true); _cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: true);
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: false); _gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: false);
_texturePoolCache = texturePoolCache;
_rtColors = new Texture[Constants.TotalRenderTargets]; _rtColors = new Texture[Constants.TotalRenderTargets];
_rtHostColors = new ITexture[Constants.TotalRenderTargets]; _rtHostColors = new ITexture[Constants.TotalRenderTargets];
@ -99,6 +104,16 @@ namespace Ryujinx.Graphics.Gpu.Image
_cpBindingsManager.SetTextureBufferIndex(index); _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> /// <summary>
/// Sets the texture constant buffer index on the graphics pipeline. /// Sets the texture constant buffer index on the graphics pipeline.
/// </summary> /// </summary>
@ -108,6 +123,16 @@ namespace Ryujinx.Graphics.Gpu.Image
_gpBindingsManager.SetTextureBufferIndex(index); _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> /// <summary>
/// Sets the current sampler pool on the compute pipeline. /// Sets the current sampler pool on the compute pipeline.
/// </summary> /// </summary>
@ -335,25 +360,48 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary> /// <summary>
/// Commits bindings on the compute pipeline. /// Commits bindings on the compute pipeline.
/// </summary> /// </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, // Every time we switch between graphics and compute work,
// we must rebind everything. // we must rebind everything.
// Since compute work happens less often, we always do that // Since compute work happens less often, we always do that
// before and after the compute dispatch. // before and after the compute dispatch.
_cpBindingsManager.Rebind(); _cpBindingsManager.Rebind();
_cpBindingsManager.CommitBindings(); bool result = _cpBindingsManager.CommitBindings(specState);
_gpBindingsManager.Rebind(); _gpBindingsManager.Rebind();
return result;
} }
/// <summary> /// <summary>
/// Commits bindings on the graphics pipeline. /// Commits bindings on the graphics pipeline.
/// </summary> /// </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(); 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> /// <summary>

View File

@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
private readonly GpuChannel _channel; private readonly GpuChannel _channel;
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>(); private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
private TextureDescriptor _defaultDescriptor;
/// <summary> /// <summary>
/// Intrusive linked list node used on the texture pool cache. /// Intrusive linked list node used on the texture pool cache.
@ -32,6 +33,62 @@ namespace Ryujinx.Graphics.Gpu.Image
_channel = channel; _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> /// <summary>
/// Gets the texture with the given ID. /// Gets the texture with the given ID.
/// </summary> /// </summary>
@ -51,56 +108,49 @@ namespace Ryujinx.Graphics.Gpu.Image
SynchronizeMemory(); SynchronizeMemory();
} }
Texture texture = Items[id]; GetInternal(id, out Texture texture);
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();
}
return 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> /// <summary>
/// Forcibly remove a texture from this pool's items. /// Forcibly remove a texture from this pool's items.
/// If deferred, the dereference will be queued to occur on the render thread. /// 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="descriptor">The texture descriptor</param>
/// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param> /// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param>
/// <returns>The texture information</returns> /// <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 depthOrLayers = descriptor.UnpackDepth();
int levels = descriptor.UnpackLevels(); int levels = descriptor.UnpackLevels();

View File

@ -378,6 +378,25 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _gpUniformBuffers[stage].Buffers[index].Address; 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> /// <summary>
/// Ensures that the compute engine bindings are visible to the host GPU. /// Ensures that the compute engine bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API. /// Note: this actually performs the binding using the host graphics API.

View File

@ -35,6 +35,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
HostProgram = hostProgram; HostProgram = hostProgram;
SpecializationState = specializationState; SpecializationState = specializationState;
Shaders = shaders; Shaders = shaders;
SpecializationState.Prepare(shaders);
} }
/// <summary> /// <summary>

View File

@ -418,7 +418,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa)) if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa))
{ {
return cpShader.SpecializationState.MatchesCompute(channel, poolState); return cpShader.SpecializationState.MatchesCompute(channel, poolState, true);
} }
return false; 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> /// <summary>

View File

@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
foreach (var entry in _entries) foreach (var entry in _entries)
{ {
if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState)) if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState, true))
{ {
program = entry; program = entry;
return true; return true;
@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
foreach (var entry in _entries) foreach (var entry in _entries)
{ {
if (entry.SpecializationState.MatchesCompute(channel, poolState)) if (entry.SpecializationState.MatchesCompute(channel, poolState, true))
{ {
program = entry; program = entry;
return true; return true;

View File

@ -1,9 +1,14 @@
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader.DiskCache; using Ryujinx.Graphics.Gpu.Shader.DiskCache;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Shader namespace Ryujinx.Graphics.Gpu.Shader
{ {
@ -158,6 +163,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization; private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
private Box<TextureSpecializationState>[][] _textureByBinding;
private Box<TextureSpecializationState>[][] _imageByBinding;
/// <summary> /// <summary>
/// Creates a new instance of the shader specialization state. /// 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> /// <summary>
/// Indicates that the shader accesses the early Z force state. /// Indicates that the shader accesses the early Z force state.
/// </summary> /// </summary>
@ -396,15 +446,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param> /// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">Graphics 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> /// <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) if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
{ {
return false; return false;
} }
return Matches(channel, poolState, isCompute: false); return Matches(channel, poolState, checkTextures, isCompute: false);
} }
/// <summary> /// <summary>
@ -412,10 +463,64 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</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> /// <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> /// <summary>
@ -423,9 +528,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</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> /// <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> /// <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; int constantBufferUsePerStageMask = _constantBufferUsePerStage;
@ -445,55 +551,62 @@ namespace Ryujinx.Graphics.Gpu.Shader
constantBufferUsePerStageMask &= ~(1 << index); 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; foreach (var kv in _allTextures)
ulong samplerCbAddress;
if (isCompute)
{ {
textureCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex); TextureKey textureKey = kv.Key;
samplerCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex);
}
else
{
textureCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, textureBufferIndex);
samplerCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, samplerBufferIndex);
}
if (!channel.MemoryManager.Physical.IsMapped(textureCbAddress) || !channel.MemoryManager.Physical.IsMapped(samplerCbAddress)) (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
{
continue; 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; return true;
}
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;
/// <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) && if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) &&
specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized()) specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized())
{ {
@ -504,6 +617,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
return true; 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> /// <summary>
/// Reads shader specialization state that has been serialized. /// Reads shader specialization state that has been serialized.
/// </summary> /// </summary>

View File

@ -9,10 +9,13 @@ namespace Ryujinx.Graphics.OpenGL
class Framebuffer : IDisposable class Framebuffer : IDisposable
{ {
public int Handle { get; private set; } public int Handle { get; private set; }
private int _clearFbHandle;
private bool _clearFbInitialized;
private FramebufferAttachment _lastDsAttachment; private FramebufferAttachment _lastDsAttachment;
private readonly TextureView[] _colors; private readonly TextureView[] _colors;
private TextureView _depthStencil;
private int _colorsCount; private int _colorsCount;
private bool _dualSourceBlend; private bool _dualSourceBlend;
@ -20,6 +23,7 @@ namespace Ryujinx.Graphics.OpenGL
public Framebuffer() public Framebuffer()
{ {
Handle = GL.GenFramebuffer(); Handle = GL.GenFramebuffer();
_clearFbHandle = GL.GenFramebuffer();
_colors = new TextureView[8]; _colors = new TextureView[8];
} }
@ -55,20 +59,7 @@ namespace Ryujinx.Graphics.OpenGL
if (depthStencil != null) if (depthStencil != null)
{ {
FramebufferAttachment attachment; FramebufferAttachment attachment = GetAttachment(depthStencil.Format);
if (IsPackedDepthStencilFormat(depthStencil.Format))
{
attachment = FramebufferAttachment.DepthStencilAttachment;
}
else if (IsDepthOnlyFormat(depthStencil.Format))
{
attachment = FramebufferAttachment.DepthAttachment;
}
else
{
attachment = FramebufferAttachment.StencilAttachment;
}
GL.FramebufferTexture( GL.FramebufferTexture(
FramebufferTarget.Framebuffer, FramebufferTarget.Framebuffer,
@ -82,6 +73,8 @@ namespace Ryujinx.Graphics.OpenGL
{ {
_lastDsAttachment = 0; _lastDsAttachment = 0;
} }
_depthStencil = depthStencil;
} }
public void SetDualSourceBlend(bool enable) public void SetDualSourceBlend(bool enable)
@ -124,6 +117,22 @@ namespace Ryujinx.Graphics.OpenGL
GL.DrawBuffers(colorsCount, drawBuffers); 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) private static bool IsPackedDepthStencilFormat(Format format)
{ {
return format == Format.D24UnormS8Uint || return format == Format.D24UnormS8Uint ||
@ -136,6 +145,78 @@ namespace Ryujinx.Graphics.OpenGL
return format == Format.D16Unorm || format == Format.D32Float; 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() public void Dispose()
{ {
if (Handle != 0) if (Handle != 0)
@ -144,6 +225,13 @@ namespace Ryujinx.Graphics.OpenGL
Handle = 0; Handle = 0;
} }
if (_clearFbHandle != 0)
{
GL.DeleteFramebuffer(_clearFbHandle);
_clearFbHandle = 0;
}
} }
} }
} }

View 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();
}
}
}

View File

@ -9,6 +9,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
{ {
private readonly Renderer _renderer; private readonly Renderer _renderer;
public IntermmediatePool IntermmediatePool { get; }
private int _srcFramebuffer; private int _srcFramebuffer;
private int _dstFramebuffer; private int _dstFramebuffer;
@ -18,6 +20,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
public TextureCopy(Renderer renderer) public TextureCopy(Renderer renderer)
{ {
_renderer = renderer; _renderer = renderer;
IntermmediatePool = new IntermmediatePool(renderer);
} }
public void Copy( public void Copy(
@ -25,7 +28,30 @@ namespace Ryujinx.Graphics.OpenGL.Image
TextureView dst, TextureView dst,
Extents2D srcRegion, Extents2D srcRegion,
Extents2D dstRegion, 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; 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.ReadFramebuffer, GetSrcFramebufferLazy());
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy()); GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy());
int levels = Math.Min(src.Info.Levels, dst.Info.Levels); if (srcLevel != 0)
int layers = Math.Min(src.Info.GetLayers(), dst.Info.GetLayers()); {
srcRegion = srcRegion.Reduce(srcLevel);
}
if (dstLevel != 0)
{
dstRegion = dstRegion.Reduce(dstLevel);
}
for (int level = 0; level < levels; level++) for (int level = 0; level < levels; level++)
{ {
for (int layer = 0; layer < layers; layer++) 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.ReadFramebuffer, src.Format, srcConverted.Handle, srcLevel + level, srcLayer + layer);
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, level, layer); Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, dstLevel + level, dstLayer + layer);
} }
else else
{ {
Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, level); Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, srcLevel + level);
Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, level); Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, dstLevel + level);
} }
ClearBufferMask mask = GetMask(src.Format); ClearBufferMask mask = GetMask(src.Format);
@ -484,6 +517,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
_copyPboHandle = 0; _copyPboHandle = 0;
} }
IntermmediatePool.Dispose();
} }
} }
} }

View File

@ -115,14 +115,77 @@ namespace Ryujinx.Graphics.OpenGL.Image
{ {
TextureView destinationView = (TextureView)destination; 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) 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) public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)

View File

@ -110,7 +110,7 @@ namespace Ryujinx.Graphics.OpenGL
Buffer.Clear(destination, offset, size, value); 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( GL.ColorMask(
index, index,
@ -119,14 +119,18 @@ namespace Ryujinx.Graphics.OpenGL
(componentMask & 4) != 0, (componentMask & 4) != 0,
(componentMask & 8) != 0); (componentMask & 8) != 0);
_framebuffer.AttachColorLayerForClear(index, layer);
float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha }; float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha };
GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors); GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors);
_framebuffer.DetachColorLayerForClear(index);
RestoreComponentMask(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 = bool stencilMaskChanged =
stencilMask != 0 && stencilMask != 0 &&
@ -144,6 +148,8 @@ namespace Ryujinx.Graphics.OpenGL
GL.DepthMask(depthMask); GL.DepthMask(depthMask);
} }
_framebuffer.AttachDepthStencilLayerForClear(layer);
if (depthMask && stencilMask != 0) if (depthMask && stencilMask != 0)
{ {
GL.ClearBuffer(ClearBufferCombined.DepthStencil, 0, depthValue, stencilValue); 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); GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Stencil, 0, ref stencilValue);
} }
_framebuffer.DetachDepthStencilLayerForClear();
if (stencilMaskChanged) if (stencilMaskChanged)
{ {
GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask); GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask);
@ -597,6 +605,8 @@ namespace Ryujinx.Graphics.OpenGL
GL.EndTransformFeedback(); GL.EndTransformFeedback();
} }
GL.ClipControl(ClipOrigin.UpperLeft, ClipDepthMode.NegativeOneToOne);
_drawTexture.Draw( _drawTexture.Draw(
view, view,
samp, samp,
@ -627,6 +637,8 @@ namespace Ryujinx.Graphics.OpenGL
{ {
GL.BeginTransformFeedback(_tfTopology); GL.BeginTransformFeedback(_tfTopology);
} }
RestoreClipControl();
} }
} }
} }

View File

@ -1,3 +1,4 @@
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Shader namespace Ryujinx.Graphics.Shader
@ -50,5 +51,63 @@ namespace Ryujinx.Graphics.Shader
{ {
return (handle & 0x3fff, (handle >> 14) & 0x3fff, (TextureHandleType)((uint)handle >> 28)); 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;
}
} }
} }

View File

@ -10,17 +10,22 @@ namespace Ryujinx.Graphics.Vic
{ {
static class Blender 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; 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 inR = src.GetR(x, y);
int inG = src.GetG(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; ref MatrixStruct mtx = ref slot.ColorMatrixStruct;
@ -62,9 +67,9 @@ namespace Ryujinx.Graphics.Vic
Pixel* ip = srcPtr; Pixel* ip = srcPtr;
Pixel* op = dstPtr; 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> pixel1 = Sse41.ConvertToVector128Int32((ushort*)(ip + (uint)x));
Vector128<int> pixel2 = Sse41.ConvertToVector128Int32((ushort*)(ip + (uint)x + 1)); Vector128<int> pixel2 = Sse41.ConvertToVector128Int32((ushort*)(ip + (uint)x + 1));

View 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;
}
}
}

View File

@ -2,6 +2,7 @@
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Vic.Image; using Ryujinx.Graphics.Vic.Image;
using Ryujinx.Graphics.Vic.Types; using Ryujinx.Graphics.Vic.Types;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Ryujinx.Graphics.Vic 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); 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); SurfaceWriter.Write(_rm, output, ref config.OutputSurfaceConfig, ref _state.State.SetOutputSurface);

View File

@ -280,13 +280,6 @@ namespace Ryujinx.HLE.HOS
return; return;
} }
if (mainNca == null)
{
Logger.Error?.Print(LogClass.Loader, "Unable to load NSP: Could not find Main NCA");
return;
}
if (mainNca != null) if (mainNca != null)
{ {
_device.Configuration.ContentManager.ClearAocData(); _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 // 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) 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)) if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
{ {
@ -661,7 +654,7 @@ namespace Ryujinx.HLE.HOS
Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, memoryManagerMode); Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, memoryManagerMode);
// We allow it for nx-hbloader because it can be used to launch homebrew. // 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(); metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit); ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit);

View File

@ -1,5 +1,4 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using ARMeilleure.State;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
@ -11,12 +10,12 @@ namespace Ryujinx.HLE.HOS
{ {
private readonly ulong _pid; private readonly ulong _pid;
private readonly GpuContext _gpuContext; private readonly GpuContext _gpuContext;
private readonly CpuContext _cpuContext; private readonly ICpuContext _cpuContext;
private T _memoryManager; private T _memoryManager;
public IVirtualMemoryManager AddressSpace => _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) if (memoryManager is IRefCounted rc)
{ {
@ -27,11 +26,16 @@ namespace Ryujinx.HLE.HOS
_pid = pid; _pid = pid;
_gpuContext = gpuContext; _gpuContext = gpuContext;
_cpuContext = new CpuContext(memoryManager, for64Bit); _cpuContext = cpuEngine.CreateCpuContext(memoryManager, for64Bit);
_memoryManager = memoryManager; _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); _cpuContext.Execute(context, codeAddress);
} }

View File

@ -1,5 +1,6 @@
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.Cpu.Jit;
using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
@ -10,10 +11,12 @@ namespace Ryujinx.HLE.HOS
{ {
class ArmProcessContextFactory : IProcessContextFactory class ArmProcessContextFactory : IProcessContextFactory
{ {
private readonly ICpuEngine _cpuEngine;
private readonly GpuContext _gpu; private readonly GpuContext _gpu;
public ArmProcessContextFactory(GpuContext gpu) public ArmProcessContextFactory(ICpuEngine cpuEngine, GpuContext gpu)
{ {
_cpuEngine = cpuEngine;
_gpu = gpu; _gpu = gpu;
} }
@ -29,12 +32,14 @@ namespace Ryujinx.HLE.HOS
switch (mode) switch (mode)
{ {
case MemoryManagerMode.SoftwarePageTable: 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.HostMapped:
case MemoryManagerMode.HostMappedUnsafe: case MemoryManagerMode.HostMappedUnsafe:
bool unsafeMode = mode == 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: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();

View File

@ -10,6 +10,8 @@ using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Output; using Ryujinx.Audio.Output;
using Ryujinx.Audio.Renderer.Device; using Ryujinx.Audio.Renderer.Device;
using Ryujinx.Audio.Renderer.Server; using Ryujinx.Audio.Renderer.Server;
using Ryujinx.Cpu;
using Ryujinx.Cpu.Jit;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Memory;
@ -57,6 +59,9 @@ namespace Ryujinx.HLE.HOS
internal Switch Device { get; private set; } internal Switch Device { get; private set; }
internal ITickSource TickSource { get; }
internal ICpuEngine CpuEngine { get; }
internal SurfaceFlinger SurfaceFlinger { get; private set; } internal SurfaceFlinger SurfaceFlinger { get; private set; }
internal AudioManager AudioManager { get; private set; } internal AudioManager AudioManager { get; private set; }
internal AudioOutputManager AudioOutputManager { get; private set; } internal AudioOutputManager AudioOutputManager { get; private set; }
@ -121,7 +126,11 @@ namespace Ryujinx.HLE.HOS
public Horizon(Switch device) public Horizon(Switch device)
{ {
TickSource = new TickSource(KernelConstants.CounterFrequency);
CpuEngine = new JitEngine(TickSource);
KernelContext = new KernelContext( KernelContext = new KernelContext(
TickSource,
device, device,
device.Memory, device.Memory,
device.Configuration.MemoryConfiguration.ToKernelMemorySize(), device.Configuration.MemoryConfiguration.ToKernelMemorySize(),
@ -215,40 +224,40 @@ namespace Ryujinx.HLE.HOS
internalOffset = new TimeSpanType(-internalOffset.NanoSeconds); internalOffset = new TimeSpanType(-internalOffset.NanoSeconds);
// First init the standard steady clock // First init the standard steady clock
TimeServiceManager.Instance.SetupStandardSteadyClock(null, clockSourceId, systemTime, internalOffset, TimeSpanType.Zero, false); TimeServiceManager.Instance.SetupStandardSteadyClock(TickSource, clockSourceId, systemTime, internalOffset, TimeSpanType.Zero, false);
TimeServiceManager.Instance.SetupStandardLocalSystemClock(null, new SystemClockContext(), systemTime.ToSeconds()); TimeServiceManager.Instance.SetupStandardLocalSystemClock(TickSource, new SystemClockContext(), systemTime.ToSeconds());
if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes)) if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes))
{ {
TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000); 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. // 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.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 // FIXME: TimeZone should be init here but it's actually done in ContentManager
TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock(); TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock();
DatabaseImpl.Instance.InitializeDatabase(LibHacHorizonManager.SdbClient); DatabaseImpl.Instance.InitializeDatabase(TickSource, LibHacHorizonManager.SdbClient);
HostSyncpoint = new NvHostSyncpt(device); HostSyncpoint = new NvHostSyncpt(device);
SurfaceFlinger = new SurfaceFlinger(device); SurfaceFlinger = new SurfaceFlinger(device);
InitializeAudioRenderer(); InitializeAudioRenderer(TickSource);
InitializeServices(); InitializeServices();
} }
private void InitializeAudioRenderer() private void InitializeAudioRenderer(ITickSource tickSource)
{ {
AudioManager = new AudioManager(); AudioManager = new AudioManager();
AudioOutputManager = new AudioOutputManager(); AudioOutputManager = new AudioOutputManager();
AudioInputManager = new AudioInputManager(); AudioInputManager = new AudioInputManager();
AudioRendererManager = new AudioRendererManager(); AudioRendererManager = new AudioRendererManager(tickSource);
AudioRendererManager.SetVolume(Device.Configuration.AudioVolume); AudioRendererManager.SetVolume(Device.Configuration.AudioVolume);
AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(); AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry();
@ -492,12 +501,12 @@ namespace Ryujinx.HLE.HOS
if (pause && !IsPaused) if (pause && !IsPaused)
{ {
Device.AudioDeviceDriver.GetPauseEvent().Reset(); Device.AudioDeviceDriver.GetPauseEvent().Reset();
ARMeilleure.State.ExecutionContext.SuspendCounter(); TickSource.Suspend();
} }
else if (!pause && IsPaused) else if (!pause && IsPaused)
{ {
Device.AudioDeviceDriver.GetPauseEvent().Set(); Device.AudioDeviceDriver.GetPauseEvent().Set();
ARMeilleure.State.ExecutionContext.ResumeCounter(); TickSource.Resume();
} }
} }
IsPaused = pause; IsPaused = pause;

View File

@ -12,5 +12,7 @@ namespace Ryujinx.HLE.HOS.Kernel
public const ulong UserSlabHeapBase = DramMemoryMap.SlabHeapBase; public const ulong UserSlabHeapBase = DramMemoryMap.SlabHeapBase;
public const ulong UserSlabHeapItemSize = KPageTableBase.PageSize; public const ulong UserSlabHeapItemSize = KPageTableBase.PageSize;
public const ulong UserSlabHeapSize = 0x3de000; public const ulong UserSlabHeapSize = 0x3de000;
public const ulong CounterFrequency = 19200000;
} }
} }

View File

@ -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.Memory;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.SupervisorCall; using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
@ -23,6 +24,7 @@ namespace Ryujinx.HLE.HOS.Kernel
public Switch Device { get; } public Switch Device { get; }
public MemoryBlock Memory { get; } public MemoryBlock Memory { get; }
public ITickSource TickSource { get; }
public Syscall Syscall { get; } public Syscall Syscall { get; }
public SyscallHandler SyscallHandler { get; } public SyscallHandler SyscallHandler { get; }
@ -52,11 +54,13 @@ namespace Ryujinx.HLE.HOS.Kernel
private ulong _threadUid; private ulong _threadUid;
public KernelContext( public KernelContext(
ITickSource tickSource,
Switch device, Switch device,
MemoryBlock memory, MemoryBlock memory,
MemorySize memorySize, MemorySize memorySize,
MemoryArrange memoryArrange) MemoryArrange memoryArrange)
{ {
TickSource = tickSource;
Device = device; Device = device;
Memory = memory; Memory = memory;

View File

@ -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;
}
}
}
}
}

View File

@ -1,102 +1,42 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics;
namespace Ryujinx.HLE.HOS.Kernel.Memory namespace Ryujinx.HLE.HOS.Kernel.Memory
{ {
class KMemoryRegionManager 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 Address { get; }
public ulong EndAddr { get; private set; } public ulong Size { get; }
public ulong Size { get; private set; } public ulong EndAddr => Address + Size;
private int _blockOrdersCount;
private readonly KMemoryRegionBlock[] _blocks;
private readonly ushort[] _pageReferenceCounts; private readonly ushort[] _pageReferenceCounts;
public KMemoryRegionManager(ulong address, ulong size, ulong endAddr) public KMemoryRegionManager(ulong address, ulong size, ulong endAddr)
{ {
_blocks = new KMemoryRegionBlock[BlockOrders.Length];
Address = address; Address = address;
Size = size; 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];
}
}
_pageReferenceCounts = new ushort[size / KPageTableBase.PageSize]; _pageReferenceCounts = new ushort[size / KPageTableBase.PageSize];
if (size != 0) _pageHeap = new KPageHeap(address, size);
{ _pageHeap.Free(address, size / KPageTableBase.PageSize);
FreePages(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) if (result == KernelResult.Success)
{ {
@ -112,9 +52,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
public ulong AllocatePagesContiguous(KernelContext context, ulong pagesCount, bool backwards) 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) 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(); pageList = new KPageList();
if (_blockOrdersCount > 0) int heapIndex = KPageHeap.GetBlockIndex(pagesCount);
{
if (GetFreePagesImpl() < pagesCount) if (heapIndex < 0)
{
return KernelResult.OutOfMemory;
}
}
else if (pagesCount != 0)
{ {
return KernelResult.OutOfMemory; 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; while (pagesCount >= pagesPerAlloc)
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)
{ {
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, if (allocatedBlock == 0)
// just give up and try with the next one.
if (address == 0)
{ {
break; break;
} }
// Add new allocated page(s) to the pages list. KernelResult result = pageList.AddRange(allocatedBlock, pagesPerAlloc);
// If an error occurs, then free all allocated pages and fail.
KernelResult result = pageList.AddRange(address, blockPagesCount);
if (result != KernelResult.Success) if (result != KernelResult.Success)
{ {
FreePages(address, blockPagesCount); FreePages(pageList);
_pageHeap.Free(allocatedBlock, pagesPerAlloc);
foreach (KPageNode pageNode in pageList)
{
FreePages(pageNode.Address, pageNode.PagesCount);
}
return result; 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. return KernelResult.Success;
foreach (KPageNode pageNode in pageList)
{
FreePages(pageNode.Address, pageNode.PagesCount);
}
pageList = null;
return KernelResult.OutOfMemory;
} }
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; 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) _pageHeap.Free(allocatedBlock + pagesCount * KPageTableBase.PageSize, allocatedPages - pagesCount);
{
return 0;
}
} }
ulong tightestFitBlockSize = 1UL << _blocks[blockIndex].Order; return allocatedBlock;
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;
} }
private ulong AllocatePagesForOrder(int blockIndex, bool backwards, ulong bestFitBlockSize) public void FreePage(ulong address)
{ {
ulong address = 0; lock (_pageHeap)
KMemoryRegionBlock block = null;
for (int currBlockIndex = blockIndex;
currBlockIndex < _blockOrdersCount && address == 0;
currBlockIndex++)
{ {
block = _blocks[currBlockIndex]; _pageHeap.Free(address, 1);
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);
} }
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; foreach (KPageNode pageNode in pageList)
int blockIndex = _blockOrdersCount - 1;
ulong addressRounded = 0;
ulong endAddrTruncated = 0;
for (; blockIndex >= 0; blockIndex--)
{ {
KMemoryRegionBlock allocInfo = _blocks[blockIndex]; _pageHeap.Free(pageNode.Address, pageNode.PagesCount);
int blockSize = 1 << allocInfo.Order;
addressRounded = BitUtils.AlignUp (address, blockSize);
endAddrTruncated = BitUtils.AlignDown(endAddr, blockSize);
if (addressRounded < endAddrTruncated)
{
break;
}
} }
}
}
void FreeRegion(ulong currAddress) public void FreePages(ulong address, ulong pagesCount)
{ {
for (int currBlockIndex = blockIndex; lock (_pageHeap)
currBlockIndex < _blockOrdersCount && currAddress != 0; {
currBlockIndex++) _pageHeap.Free(address, pagesCount);
{
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 ulong GetFreePages() 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) public void IncrementPagesReferenceCount(ulong address, ulong pagesCount)
{ {
ulong index = GetPageOffset(address); ulong index = GetPageOffset(address);

View 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);
}
}
}

View 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);
}
}
}

View File

@ -555,7 +555,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
{ {
KMemoryRegionManager region = GetMemoryRegionManager(); KMemoryRegionManager region = GetMemoryRegionManager();
KernelResult result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList); KernelResult result = region.AllocatePages(out KPageList pageList, pagesCount);
if (result != KernelResult.Success) if (result != KernelResult.Success)
{ {
@ -712,7 +712,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
KMemoryRegionManager region = GetMemoryRegionManager(); 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)); using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager));
@ -1276,7 +1276,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
KMemoryRegionManager region = GetMemoryRegionManager(); 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)); using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager));

View File

@ -115,7 +115,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
string GetReg(int x) 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)) if (!AnalyzePointer(out PointerInfo info, v, thread))
{ {
return $"0x{v:x16}"; return $"0x{v:x16}";

View File

@ -1,4 +1,4 @@
using ARMeilleure.State; using Ryujinx.Cpu;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
@ -8,7 +8,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
IVirtualMemoryManager AddressSpace { get; } 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); void InvalidateCacheRegion(ulong address, ulong size);
} }
} }

View File

@ -1,4 +1,3 @@
using ARMeilleure.State;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Cpu; using Ryujinx.Cpu;
@ -736,22 +735,25 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
ulong argsPtr, ulong argsPtr,
ulong stackTop, ulong stackTop,
int priority, int priority,
int cpuCore) int cpuCore,
ThreadStart customThreadStart = null)
{ {
lock (_processLock) 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; return Context?.CreateExecutionContext(new ExceptionCallbacks(
context.SupervisorCall += KernelContext.SyscallHandler.SvcCall; InterruptHandler,
context.Undefined += UndefinedInstructionHandler; null,
KernelContext.SyscallHandler.SvcCall,
UndefinedInstructionHandler));
} }
private void InterruptHandler(object sender, EventArgs e) private void InterruptHandler(IExecutionContext context)
{ {
KThread currentThread = KernelStatic.GetCurrentThread(); KThread currentThread = KernelStatic.GetCurrentThread();
@ -964,6 +966,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
SignalExitToDebugExited(); SignalExitToDebugExited();
SignalExit(); SignalExit();
} }
KernelStatic.GetCurrentThread().Exit();
} }
private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread) private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread)
@ -979,7 +983,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
foreach (KThread thread in _threads) foreach (KThread thread in _threads)
{ {
if ((thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending) if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
{ {
thread.PrepareForTermination(); thread.PrepareForTermination();
} }
@ -1093,12 +1097,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return false; return false;
} }
private void UndefinedInstructionHandler(object sender, InstUndefinedEventArgs e) private void UndefinedInstructionHandler(IExecutionContext context, ulong address, int opCode)
{ {
KernelStatic.GetCurrentThread().PrintGuestStackTrace(); KernelStatic.GetCurrentThread().PrintGuestStackTrace();
KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout(); KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout();
throw new UndefinedInstructionException(e.Address, e.OpCode); throw new UndefinedInstructionException(address, opCode);
} }
protected override void Destroy() => Context.Dispose(); protected override void Destroy() => Context.Dispose();

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