Compare commits

..

9 Commits

Author SHA1 Message Date
riperiperi
33f544fd92 GPU: Track basic buffer copies that modify texture memory (#5554)
This branch changes the buffer copy fast path to notify memory tracking for all resources that aren't buffers. This fixes cases where games would copy buffer data directly into texture memory, which before would only work if the texture did not already exist. I imagine this happens when the guest driver is moving data between allocations or uploading it.

Since this only affects the fast path, cases where the source data has been modified from GPU (fast path copy destination doesn't count) will still fail to notify the texture, though I don't imagine games will do this. This should be resolved in future.

This should fix some texture issues with guest OpenGL games on switch, such as Dragon Quest Builders.

This may also be useful in future for games that move shader data around memory, if we end up using memory tracking for those.
2023-08-14 08:41:11 +02:00
gdkchan
b423197619 Delete ShaderConfig and organize shader resources/definitions better (#5509)
* Move some properties out of ShaderConfig

* Stop using ShaderConfig on backends

* Replace ShaderConfig usages on Translator and passes

* Move remaining properties out of ShaderConfig and delete ShaderConfig

* Remove ResourceManager property from TranslatorContext

* Move Rewriter passes to separate transform pass files

* Fix TransformPasses.RunPass on cases where a node is removed

* Move remaining ClipDistancePrimitivesWritten and UsedFeatures updates to decode stage

* Reduce excessive parameter passing a bit by using structs more

* Remove binding parameter from ShaderProperties methods since it is redundant

* Replace decoder instruction checks with switch statement

* Put GLSL on the same plan as SPIR-V for input/output declaration

* Stop mutating TranslatorContext state when Translate is called

* Pass most of the graphics state using a struct instead of individual query methods

* Auto-format

* Auto-format

* Add backend logging interface

* Auto-format

* Remove unnecessary use of interpolated strings

* Remove more modifications of AttributeUsage after decode

* PR feedback

* gl_Layer is not supported on compute
2023-08-13 22:26:42 -03:00
Marco Carvalho
8edfb2bc7b "static readonly" constants should be "const" instead (#5560)
* "static readonly" constants should be "const" instead

* change fields to PascalCase
2023-08-13 19:07:57 -03:00
MutantAura
ddefb4fff4 Remove animations on listbox items (#5563) 2023-08-13 22:40:40 +02:00
MutantAura
2efd74b9cb Ava UI: Make some settings methods async (#5332)
* Ava: Asynchronously load Vulkan device settings items.

* Sound checks, timezones and network interface async

* Refresh UI items once awaited tasks complete

* Remove unused dep

* Timezone UI update

* Use UIThread dispatcher for thread-unsafe collections + simplify GPU collection.

* Remove empty lines

* Remove unused string

* Dispatch property changes

* format changes

* format 2

* Use Tasks instead of async void

* Make NetworkInterfaceIndex access thread safe.
2023-08-12 22:43:03 +02:00
riperiperi
8c61ddd49d Ava UI: Allow DPI switching (#5558)
ForceDpiAware.Windows has a side effect of forcing the application DPI to be the same as the primary monitor. This isn't good if you have multiple monitors with different DPI.

On Avalonia, I don't think there are any downsides to disabling this. When it's disabled, `ForceDpiAware.GetWindowScaleFactor` always returns 1.
2023-08-12 13:02:22 -03:00
Isaac Marovitz
7b2225c6b0 Ava UI: Avalonia 11 & FluentAvalonia 2 Support (#4362)
* It builds

(Doesn’t run waiting on FluentAvalonia Preview 5 Release)

* Enable CompiledBindings by default

* Ignore `PointerPressedEventArgs` Init warning

* Define MIME and UTI Types

* Update `UserProfileImageSelectorView` to StorageProvider API

* PFS0 Magic

* Update `MainWindowViewModel` to StorageProvider API

* Update `SettingsUIView` to StorageProvider API

* Update `ApplicationHelper` to StorageProvider API

* Use `IsCheckChanged`

* Rename events

* Update Fluent Avalonia to Preivew 5

* More package updates

* Fix long selection bar

* return glyph value directly, instead of using a binding

* fix menu item checkboxes

* Fix build

* Update to Preview 6

Unicorn conflict

Fix remaining package oopsie

* Fix issues from merge

* Fix some warnings

* Warnings

* Squashed commit of the following:

commit 79d1c190db
Author: Mary <mary@mary.zone>
Date:   Sun Apr 16 11:38:07 2023 +0200

    chore: Update Silk.NET to 2.17.1 (#4686)

commit 2bc88467eb
Author: Ac_K <Acoustik666@gmail.com>
Date:   Sun Apr 16 09:37:31 2023 +0000

    Update README.md

commit baf8752e74
Author: Vincenzo Nizza <vincenzonizzaufficio@gmail.com>
Date:   Sun Apr 16 11:19:33 2023 +0200

    Ensure the updater doesn't delete hidden or system files (#4626)

    * Copy desktop.ini to update directory if it exists in HomeDir

    * EnumerateFilesToDelete() exclude files with "Hidden" and "System" attributes

commit d5e4378aea
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Sun Apr 16 09:02:06 2023 +0000

    nuget: bump DynamicData from 7.13.1 to 7.13.5 (#4654)

    Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 7.13.1 to 7.13.5.
    - [Release notes](https://github.com/reactiveui/DynamicData/releases)
    - [Changelog](https://github.com/reactivemarbles/DynamicData/blob/main/ReleaseNotes.md)
    - [Commits](https://github.com/reactiveui/DynamicData/compare/7.13.1...7.13.5)

    ---
    updated-dependencies:
    - dependency-name: DynamicData
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit 6dbcdfea47
Author: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
Date:   Sun Apr 16 09:09:02 2023 +0200

    Ava: Fix nca extraction window never closing & minor cleanup (#4569)

    * ava: Remove unused doWhileDeferred parameters

    * ava: Minimally improve swkbd dialog

    It's currently impossible to get the dialog to redirect focus to the InputBox.

    * ava: Fix nca extraction dialog never closing

    Also contains some minor cleanup

commit c5258cf082
Author: NitroTears <73270647+NitroTears@users.noreply.github.com>
Date:   Sun Apr 16 11:03:35 2023 +1000

    Ability to hide file types in Game List (#4555)

    * Added HiddenFileTypes to config state, and check to file enumeration

    * Added hiddenfiletypes checkboxes to the UI

    * Added Ava version of HiddenFileTypes

    * Inverted Hide to Show with file types, minor formatting

    * all variables with a reference to 'hidden' is now 'shown'

    * one more variable name changed

    * review feedback

    * added FileTypes extension methof to get the correlating config value

    * moved extension method to new folder and file in Ryujinx.Ui.Common

    * added default case for ToggleFileType

    * changed exception type to OutOfRangeException

commit 5c89e22bb9
Author: Daniel Shala <daniel.shala08@gmail.com>
Date:   Sat Apr 15 18:11:24 2023 +0200

    Added check for eventual symlink when displaying game files. (#4526)

    * Added check for eventual symlink when displaying game files.

    * Moved symlink check logic

    * Moved symlink check logic

    * Fixed prev commit

    ---------

    Co-authored-by: Daniel Shala <danielshala00@gmail.com>

commit 11ecff2ff0
Author: Alex Barney <thealexbarney@gmail.com>
Date:   Fri Apr 14 16:00:34 2023 -0700

    Rename Hipc to Cmif where appropriate (#3880)

commit 4c3f09644a
Author: MutantAura <44103205+MutantAura@users.noreply.github.com>
Date:   Wed Apr 12 20:18:40 2023 +0100

    Move swkbd message null check into constructor (#4671)

commit e187a8870a
Author: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
Date:   Wed Apr 12 03:09:47 2023 +0200

    HLE: Deal with empty title names properly (#4643)

    * hle: Deal with empty titleNames in some languages

    * gui: Fix displaying the wrong title name

    * Remove unnecessary bounds check

    * Fix a NRE when getting the version string

    * Restore empty string logic

commit a64fee29dc
Author: riperiperi <rhy3756547@hotmail.com>
Date:   Tue Apr 11 08:23:41 2023 +0100

    Vulkan: add situational "Fast Flush" mode (#4667)

    * Flush in the middle of long command buffers.

    * Vulkan: add situational "Fast Flush" mode

    The AutoFlushCounter class was added to periodically flush Vulkan command buffers throughout a frame, which reduces latency to the GPU as commands are submitted and processed much sooner. This was done by allowing command buffers to flush when framebuffer attachments changed.

    However, some games have incredibly long render passes with a large number of draws, and really aggressive data access that forces GPU sync.

    The Vulkan backend could potentially end up building a single command buffer for 4-5ms if a pass has enough draws, such as in BOTW. In the scenario where sync is waited on immediately after submission, this would have to wait for the completion of a much longer command buffer than usual.

    The solution is to force command buffer submission periodically in a "fast flush" mode. This will end up splitting render passes, but it will only enable if sync is aggressive enough.

    This should improve performance in GPU limited scenarios, or in games that aggressively wait on synchronization. In some games, it may only kick in when res scaling. It won't trigger in games like SMO where sync is not an issue.

    Improves performance in Pokemon Scarlet/Violet (res scaled) and BOTW (in general).

    * Add conversions in milliseconds next to flush timers.

commit 9ef94c8292
Author: riperiperi <rhy3756547@hotmail.com>
Date:   Tue Apr 11 07:55:04 2023 +0100

    ARMeilleure: Move TPIDR_EL0 and TPIDRRO_EL0 to NativeContext (#4661)

    * ARMeilleure: Move TPIDR_EL0 and TPIDRRO_EL0 to NativeContext

    Some games access these system registers several tens of thousands of times in a second from many different threads. While this isn't really crippling, it is a lot of wasted time spent in a reverse pinvoke transition.

    Example games are Pokemon Scarlet/Violet and BOTW. These games have a lot of different potential bottlenecks so it's unlikely you will see a consistent improvement, but it definitely disappears from the cpu profile.

    * Remove unreachable code.

    * Add ulong conversion for offsets

    * Nit

commit 915d6d044c
Author: riperiperi <rhy3756547@hotmail.com>
Date:   Tue Apr 11 07:32:31 2023 +0100

    OpenGL: Fix OBS/Overlays again by binding FB before present (#4668)

    This seems to have been removed by the Post-Processing PR, but it is required for the display in OBS to be the right way up and properly scaled.

    I've tested this with AA and FSR on MK8D and it seems to behave properly. Testing is welcome.

commit a4780ab33b
Author: MutantAura <44103205+MutantAura@users.noreply.github.com>
Date:   Mon Apr 10 23:04:31 2023 +0100

    Force activate parent window before dialog is shown (#4663)

* Fix build

Extraction dialogue not working

* Avalonia Preview 7

Needs Fluent Avalonia update still…

* Fix Render Scaling

* Update Fluent Avalonia

* Remove `pfs0` as runnable file type

* Restore Info.plist formatting

* Plist Format

* Update Avalonia.Svg.Skia

* Update theme code (TODO)

* swtich to using theme variants  for light dark

* Fix crashes

* Text centering issues

* Update `TitleUpdateViewModel` to StorageProvider API

* Fixed for new PR

(Will crash on launch)

* Fixes…

* UI: Fix sections extraction (#4820)

* UI: Fix sections extraction

There is currently an issue when the update NCA doesn't contains the section we want to extract, this is fixed by adding a check.
I have fixed the inverted handler of ExeFs/Logo introduced in #4755.

Fixes #4521

* Addresses feedback

* Fix issues…

* Preview 8

* Fix fuck ups

* Fixes

* More cleanup

* Ava 11 RC

Maybe there is a god

* Update FluentAvalonia

* update svg

* Second RC (kill me)

* It builds

* Ava 11

* Remove unnecessary usings

* Fix build

* Formatting

* GAS GAS GAS!!!!

* Fix DLC Window Crash

* Linux runner try not to crash challenge (impossible)

* Add app.manifest

* Fix accidental Silk.NET.Vulkan bump

* Try fix truncation

* Linux fix popup Windows

* Fix cutoff text on windows

* Status bar styling fixes

* Volume Toggle Split Button Fixes

* Fix load bar color

* Fix shortcuts

* Best we're gonna get

* Fix spacing

Co-authored-by: Exhigh <exhigh01@gmail.com>

* Formatting

* Fix Profile Dropdown

* Fix Window Startup Position

* Format Fixes

* Fix stupid mistake

* Fix accidental change

* Scaling Handler (peri pls make sure is working)

* Remove Locale Reflection Binding Use + Unsued Usings

* Fix formatting

Code styling

Ughhhh

Fix interface

Make TimeZoneConverter internal

* Remove bell workaround (no longer needed)

* Disable accent menu

* Update to Ava 11.0.2

* Peri suggestions

* Formatting

* Cleanup a bunch of jank

* Dependency update

* Berry fixes and suggestions

* Final suggestions

* Rename assemblyIdentity to Ryujinx.Emulator.Avalonia

---------

Co-authored-by: Emmanuel Hansen <emmausssss@gmail.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: Exhigh <exhigh01@gmail.com>
Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
2023-08-12 02:47:22 +02:00
TSRBerry
fe15c77d30 [Hotfix] hid: Prevent out of bounds array access (#5547)
* hid: Prevent out of bounds array access

* Cast player to uint

* Replace lambda function with local function
2023-08-10 00:29:15 -03:00
TSRBerry
5e9678c8fa Allow access to code memory for exefs mods (#5518)
* Allow access to code memory for exefs mods

* Add ASLR workaround for Skyline

* Hardcode allowCodeMemoryForJit to true
2023-08-09 18:27:45 -03:00
159 changed files with 4060 additions and 3577 deletions

View File

@@ -3,21 +3,21 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="0.10.21" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.21" />
<PackageVersion Include="Avalonia.Desktop" Version="0.10.21" />
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.21" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.21" />
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
<PackageVersion Include="Avalonia" Version="11.0.3" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.3" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.3" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.3" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.3" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="DynamicData" Version="7.14.2" />
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.1" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
<PackageVersion Include="LibHac" Version="0.18.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
@@ -48,6 +48,5 @@
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
<PackageVersion Include="System.Management" Version="7.0.2" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.6.1" />
</ItemGroup>
</Project>

View File

@@ -44,10 +44,115 @@
<string>public.app-category.games</string>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeDescription</key>
<string>Extensible Application Markup Language</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.xml</string>
</array>
<key>UTTypeIdentifier</key>
<string>com.ryujinx.xaml</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>xaml</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeDescription</key>
<string>Nintendo Submission Package</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeIdentifier</key>
<string>com.ryujinx.nsp</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>nsp</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeDescription</key>
<string>Nintendo Switch Cartridge</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeIdentifier</key>
<string>com.ryujinx.xci</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>xci</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeDescription</key>
<string>Nintendo Content Archive</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeIdentifier</key>
<string>com.ryujinx.nca</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>nca</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeDescription</key>
<string>Nintendo Relocatable Object</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeIdentifier</key>
<string>com.ryujinx.nro</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>nro</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeDescription</key>
<string>Nintendo Shared Object</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeIdentifier</key>
<string>com.ryujinx.nso</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>nso</string>
</array>
</dict>
</dict>
</array>
<key>LSEnvironment</key>
<dict>
<key>COMPlus_DefaultStackSize</key>
<key>DOTNET_DefaultStackSize</key>
<string>200000</string>
</dict>
</dict>
</plist>
</plist>

View File

@@ -32,7 +32,7 @@ namespace ARMeilleure.Instructions
15L << 56 | 14L << 48 | 13L << 40 | 12L << 32 | 07L << 24 | 06L << 16 | 05L << 8 | 04L << 0, // S
};
public static readonly long ZeroMask = 128L << 56 | 128L << 48 | 128L << 40 | 128L << 32 | 128L << 24 | 128L << 16 | 128L << 8 | 128L << 0;
public const long ZeroMask = 128L << 56 | 128L << 48 | 128L << 40 | 128L << 32 | 128L << 24 | 128L << 16 | 128L << 8 | 128L << 0;
public static ulong X86GetGf2p8LogicalShiftLeft(int shift)
{

View File

@@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 5502; //! To be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 5518; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";

View File

@@ -27,6 +27,26 @@ namespace ARMeilleure.Translation.PTC
return dictionary;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Dictionary<TKey, TValue> DeserializeAndUpdateDictionary<TKey, TValue>(Stream stream, Func<Stream, TValue> valueFunc, Func<TKey, TValue, (TKey, TValue)> updateFunc) where TKey : struct
{
Dictionary<TKey, TValue> dictionary = new();
int count = DeserializeStructure<int>(stream);
for (int i = 0; i < count; i++)
{
TKey key = DeserializeStructure<TKey>(stream);
TValue value = valueFunc(stream);
(key, value) = updateFunc(key, value);
dictionary.Add(key, value);
}
return dictionary;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<T> DeserializeList<T>(Stream stream) where T : struct
{

View File

@@ -9,10 +9,13 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Timers;
using static ARMeilleure.Translation.PTC.PtcFormatter;
using Timer = System.Timers.Timer;
namespace ARMeilleure.Translation.PTC
{
@@ -20,7 +23,11 @@ namespace ARMeilleure.Translation.PTC
{
private const string OuterHeaderMagicString = "Pohd\0\0\0\0";
private const uint InternalVersion = 1866; //! Not to be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 5518; //! Not to be incremented manually for each change to the ARMeilleure project.
private static readonly uint[] _migrateInternalVersions = {
1866,
};
private const int SaveInterval = 30; // Seconds.
@@ -28,7 +35,7 @@ namespace ARMeilleure.Translation.PTC
private readonly Ptc _ptc;
private readonly System.Timers.Timer _timer;
private readonly Timer _timer;
private readonly ulong _outerHeaderMagic;
@@ -51,7 +58,7 @@ namespace ARMeilleure.Translation.PTC
{
_ptc = ptc;
_timer = new System.Timers.Timer((double)SaveInterval * 1000d);
_timer = new Timer(SaveInterval * 1000d);
_timer.Elapsed += PreSave;
_outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan());
@@ -168,7 +175,7 @@ namespace ARMeilleure.Translation.PTC
return false;
}
if (outerHeader.InfoFileVersion != InternalVersion)
if (outerHeader.InfoFileVersion != InternalVersion && !_migrateInternalVersions.Contains(outerHeader.InfoFileVersion))
{
InvalidateCompressedStream(compressedStream);
@@ -211,7 +218,19 @@ namespace ARMeilleure.Translation.PTC
return false;
}
ProfiledFuncs = Deserialize(stream);
switch (outerHeader.InfoFileVersion)
{
case InternalVersion:
ProfiledFuncs = Deserialize(stream);
break;
case 1866:
ProfiledFuncs = Deserialize(stream, (address, profile) => (address + 0x500000UL, profile));
break;
default:
Logger.Error?.Print(LogClass.Ptc, $"No migration path for {nameof(outerHeader.InfoFileVersion)} '{outerHeader.InfoFileVersion}'. Discarding cache.");
InvalidateCompressedStream(compressedStream);
return false;
}
Debug.Assert(stream.Position == stream.Length);
@@ -225,9 +244,14 @@ namespace ARMeilleure.Translation.PTC
return true;
}
private static Dictionary<ulong, FuncProfile> Deserialize(Stream stream)
private static Dictionary<ulong, FuncProfile> Deserialize(Stream stream, Func<ulong, FuncProfile, (ulong, FuncProfile)> migrateEntryFunc = null)
{
return DeserializeDictionary<ulong, FuncProfile>(stream, (stream) => DeserializeStructure<FuncProfile>(stream));
if (migrateEntryFunc != null)
{
return DeserializeAndUpdateDictionary(stream, DeserializeStructure<FuncProfile>, migrateEntryFunc);
}
return DeserializeDictionary<ulong, FuncProfile>(stream, DeserializeStructure<FuncProfile>);
}
private static ReadOnlySpan<byte> GetReadOnlySpan(MemoryStream memoryStream)
@@ -240,7 +264,7 @@ namespace ARMeilleure.Translation.PTC
compressedStream.SetLength(0L);
}
private void PreSave(object source, System.Timers.ElapsedEventArgs e)
private void PreSave(object source, ElapsedEventArgs e)
{
_waitEvent.Reset();
@@ -277,7 +301,7 @@ namespace ARMeilleure.Translation.PTC
{
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
stream.Seek((long)Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
stream.Seek(Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
lock (_lock)
{
@@ -288,7 +312,7 @@ namespace ARMeilleure.Translation.PTC
Debug.Assert(stream.Position == stream.Length);
stream.Seek((long)Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
stream.Seek(Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
Hash128 hash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
stream.Seek(0L, SeekOrigin.Begin);
@@ -332,7 +356,7 @@ namespace ARMeilleure.Translation.PTC
private static void Serialize(Stream stream, Dictionary<ulong, FuncProfile> profiledFuncs)
{
SerializeDictionary(stream, profiledFuncs, (stream, structure) => SerializeStructure(stream, structure));
SerializeDictionary(stream, profiledFuncs, SerializeStructure);
}
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 29*/)]

View File

@@ -3,7 +3,15 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sty="using:FluentAvalonia.Styling">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<MergeResourceInclude Source="/Assets/Styles/Themes.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
<Application.Styles>
<sty:FluentAvaloniaTheme PreferSystemTheme="False" />
<StyleInclude Source="/Assets/Styles/Styles.xaml"/>
</Application.Styles>
</Application>

View File

@@ -3,7 +3,6 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
using Avalonia.Threading;
using FluentAvalonia.Styling;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
@@ -24,6 +23,11 @@ namespace Ryujinx.Ava
Name = $"Ryujinx {Program.Version}";
AvaloniaXamlLoader.Load(this);
if (OperatingSystem.IsMacOS())
{
Process.Start("/usr/bin/defaults", "write org.ryujinx.Ryujinx ApplePressAndHoldEnabled -bool false");
}
}
public override void OnFrameworkInitializationCompleted()
@@ -89,8 +93,6 @@ namespace Ryujinx.Ava
string themePath = ConfigurationState.Instance.Ui.CustomThemePath;
bool enableCustomTheme = ConfigurationState.Instance.Ui.EnableCustomTheme;
const string BaseStyleUrl = "avares://Ryujinx.Ava/Assets/Styles/Base{0}.xaml";
if (string.IsNullOrWhiteSpace(baseStyle))
{
ConfigurationState.Instance.Ui.BaseStyle.Value = "Dark";
@@ -98,31 +100,12 @@ namespace Ryujinx.Ava
baseStyle = ConfigurationState.Instance.Ui.BaseStyle;
}
var theme = AvaloniaLocator.Current.GetService<FluentAvaloniaTheme>();
theme.RequestedTheme = baseStyle;
var currentStyles = this.Styles;
// Remove all styles except the base style.
if (currentStyles.Count > 1)
RequestedThemeVariant = baseStyle switch
{
currentStyles.RemoveRange(1, currentStyles.Count - 1);
}
IStyle newStyles = null;
// Load requested style, and fallback to Dark theme if loading failed.
try
{
newStyles = (Styles)AvaloniaXamlLoader.Load(new Uri(string.Format(BaseStyleUrl, baseStyle), UriKind.Absolute));
}
catch (XamlLoadException)
{
newStyles = (Styles)AvaloniaXamlLoader.Load(new Uri(string.Format(BaseStyleUrl, "Dark"), UriKind.Absolute));
}
currentStyles.Add(newStyles);
"Light" => ThemeVariant.Light,
"Dark" => ThemeVariant.Dark,
_ => ThemeVariant.Default
};
if (enableCustomTheme)
{
@@ -133,7 +116,7 @@ namespace Ryujinx.Ava
var themeContent = File.ReadAllText(themePath);
var customStyle = AvaloniaRuntimeXamlLoader.Parse<IStyle>(themeContent);
currentStyles.Add(customStyle);
Styles.Add(customStyle);
}
catch (Exception ex)
{

View File

@@ -3,6 +3,7 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Rendering;
using Avalonia.Threading;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.Dummy;
@@ -54,6 +55,7 @@ using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using Image = SixLabors.ImageSharp.Image;
using InputManager = Ryujinx.Input.HLE.InputManager;
using IRenderer = Ryujinx.Graphics.GAL.IRenderer;
using Key = Ryujinx.Input.Key;
using MouseButton = Ryujinx.Input.MouseButton;
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
@@ -167,9 +169,9 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.HideCursor.Event += HideCursorState_Changed;
_topLevel.PointerMoved += TopLevel_PointerEnterOrMoved;
_topLevel.PointerEnter += TopLevel_PointerEnterOrMoved;
_topLevel.PointerLeave += TopLevel_PointerLeave;
_topLevel.PointerMoved += TopLevel_PointerEnteredOrMoved;
_topLevel.PointerEntered += TopLevel_PointerEnteredOrMoved;
_topLevel.PointerExited += TopLevel_PointerExited;
if (OperatingSystem.IsWindows())
{
@@ -194,26 +196,23 @@ namespace Ryujinx.Ava
_gpuDoneEvent = new ManualResetEvent(false);
}
private void TopLevel_PointerEnterOrMoved(object sender, PointerEventArgs e)
private void TopLevel_PointerEnteredOrMoved(object sender, PointerEventArgs e)
{
if (sender is MainWindow window)
{
_lastCursorMoveTime = Stopwatch.GetTimestamp();
if (RendererHost.EmbeddedWindow.TransformedBounds != null)
{
var point = e.GetCurrentPoint(window).Position;
var bounds = RendererHost.EmbeddedWindow.TransformedBounds.Value.Clip;
var point = e.GetCurrentPoint(window).Position;
var bounds = RendererHost.EmbeddedWindow.Bounds;
_isCursorInRenderer = point.X >= bounds.X &&
point.X <= bounds.Width + bounds.X &&
point.Y >= bounds.Y &&
point.Y <= bounds.Height + bounds.Y;
}
_isCursorInRenderer = point.X >= bounds.X &&
point.X <= bounds.Width + bounds.X &&
point.Y >= bounds.Y &&
point.Y <= bounds.Height + bounds.Y;
}
}
private void TopLevel_PointerLeave(object sender, PointerEventArgs e)
private void TopLevel_PointerExited(object sender, PointerEventArgs e)
{
_isCursorInRenderer = false;
}
@@ -265,7 +264,7 @@ namespace Ryujinx.Ava
{
if (_renderer != null)
{
double scale = _topLevel.PlatformImpl.RenderScaling;
double scale = _topLevel.RenderScaling;
_renderer.Window?.SetSize((int)(size.Width * scale), (int)(size.Height * scale));
}
@@ -359,7 +358,7 @@ namespace Ryujinx.Ava
_viewModel.SetUiProgressHandlers(Device);
RendererHost.SizeChanged += Window_SizeChanged;
RendererHost.BoundsChanged += Window_BoundsChanged;
_isActive = true;
@@ -469,9 +468,9 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing;
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event -= UpdateColorSpacePassthrough;
_topLevel.PointerMoved -= TopLevel_PointerEnterOrMoved;
_topLevel.PointerEnter -= TopLevel_PointerEnterOrMoved;
_topLevel.PointerLeave -= TopLevel_PointerLeave;
_topLevel.PointerMoved -= TopLevel_PointerEnteredOrMoved;
_topLevel.PointerEntered -= TopLevel_PointerEnteredOrMoved;
_topLevel.PointerExited -= TopLevel_PointerExited;
_gpuCancellationTokenSource.Cancel();
_gpuCancellationTokenSource.Dispose();
@@ -849,7 +848,7 @@ namespace Ryujinx.Ava
return deviceDriver;
}
private void Window_SizeChanged(object sender, Size e)
private void Window_BoundsChanged(object sender, Size e)
{
Width = (int)e.Width;
Height = (int)e.Height;
@@ -899,7 +898,7 @@ namespace Ryujinx.Ava
Width = (int)RendererHost.Bounds.Width;
Height = (int)RendererHost.Bounds.Height;
_renderer.Window.SetSize((int)(Width * _topLevel.PlatformImpl.RenderScaling), (int)(Height * _topLevel.PlatformImpl.RenderScaling));
_renderer.Window.SetSize((int)(Width * _topLevel.RenderScaling), (int)(Height * _topLevel.RenderScaling));
_chrono.Start();

View File

@@ -1,65 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" />
<Design.PreviewWith>
<Border Height="2000" Padding="20">
<StackPanel Spacing="5">
<TextBlock Text="Code Font Family" />
<Grid RowDefinitions="*,Auto">
<Menu Grid.Row="1" Width="100">
<MenuItem Header="File">
<MenuItem Header="Test 1" />
<MenuItem Header="Test 2" />
<MenuItem Header="Test 3">
<MenuItem.Icon>
<CheckBox Margin="0" IsChecked="{Binding Checkbox, Mode=TwoWay}" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
<StackPanel Orientation="Horizontal">
<Button
Name="btnAdd"
HorizontalAlignment="Right"
Content="Add" />
<Button
Name="btnRem"
HorizontalAlignment="Right"
Content="Add" />
<TextBox
Width="100"
VerticalAlignment="Center"
Text="Rrrrr"
UseFloatingWatermark="True"
Watermark="Hello" />
<CheckBox>Test Check</CheckBox>
</StackPanel>
</Grid>
</StackPanel>
</Border>
</Design.PreviewWith>
<Styles.Resources>
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" />
<Color x:Key="ControlFillColorSecondary">#008AA8</Color>
<SolidColorBrush x:Key="ControlFillColorSecondaryBrush" Color="{StaticResource ControlFillColorSecondary}" />
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="ControlFillColorSecondaryBrush" />
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark1">#FF99b000</Color>
<Color x:Key="SystemAccentColorDark2">#FF006d7d</Color>
<Color x:Key="SystemAccentColorDark3">#FF00525E</Color>
<Color x:Key="SystemAccentColorLight1">#FF00dbff</Color>
<Color x:Key="SystemAccentColorLight2">#FF19dfff</Color>
<Color x:Key="SystemAccentColorLight3">#FF33e3ff</Color>
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
<Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="ThemeForegroundColor">#FFFFFFFF</Color>
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
</Styles.Resources>
</Styles>

View File

@@ -1,57 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" />
<Design.PreviewWith>
<Border Height="2000" Padding="20">
<StackPanel Spacing="5">
<TextBlock Text="Code Font Family" />
<Grid RowDefinitions="*,Auto">
<Menu Grid.Row="1" Width="100">
<MenuItem Header="File">
<MenuItem Header="Test 1" />
<MenuItem Header="Test 2" />
<MenuItem Header="Test 3">
<MenuItem.Icon>
<CheckBox Margin="0" IsChecked="{Binding Checkbox, Mode=TwoWay}" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
<StackPanel Orientation="Horizontal">
<Button
Name="btnAdd"
HorizontalAlignment="Right"
Content="Add" />
<Button
Name="btnRem"
HorizontalAlignment="Right"
Content="Add" />
<TextBox
Width="100"
VerticalAlignment="Center"
Text="Rrrrr"
UseFloatingWatermark="True"
Watermark="Hello" />
<CheckBox>Test Check</CheckBox>
</StackPanel>
</Grid>
</StackPanel>
</Border>
</Design.PreviewWith>
<Styles.Resources>
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" />
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
<Color x:Key="ThemeAccentColor4">#FFe8e8e8</Color>
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FFF0F0F0</Color>
<Color x:Key="ThemeControlBorderColor">#FFd6d6d6</Color>
<Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="ThemeForegroundColor">#FF000000</Color>
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color>
</Styles.Resources>
</Styles>

View File

@@ -3,17 +3,20 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
<Design.PreviewWith>
<Border Height="2000" Padding="20">
<Border Height="2000"
Padding="20">
<StackPanel Spacing="5">
<TextBlock Text="Code Font Family" />
<Grid RowDefinitions="*,Auto">
<Menu Grid.Row="1" Width="100">
<Menu Grid.Row="1"
Width="100">
<MenuItem Header="File">
<MenuItem Header="Test 1" />
<MenuItem Header="Test 2" />
<MenuItem Header="Test 3">
<MenuItem.Icon>
<CheckBox Margin="0" IsChecked="{Binding Checkbox, Mode=TwoWay}" />
<CheckBox Margin="0"
IsChecked="{ReflectionBinding Checkbox, Mode=TwoWay}" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
@@ -42,57 +45,80 @@
</Border>
</Design.PreviewWith>
<Style Selector="Border.small">
<Setter Property="Width" Value="100" />
<Setter Property="Width"
Value="100" />
</Style>
<Style Selector="Border.normal">
<Setter Property="Width" Value="130" />
<Setter Property="Width"
Value="130" />
</Style>
<Style Selector="Border.large">
<Setter Property="Width" Value="160" />
<Setter Property="Width"
Value="160" />
</Style>
<Style Selector="Border.huge">
<Setter Property="Width" Value="200" />
<Setter Property="Width"
Value="200" />
</Style>
<Style Selector="Border.settings">
<Setter Property="Background" Value="{DynamicResource ThemeDarkColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource MenuFlyoutPresenterBorderColor}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="5" />
<Setter Property="Background"
Value="{DynamicResource ThemeDarkColor}" />
<Setter Property="BorderBrush"
Value="{DynamicResource MenuFlyoutPresenterBorderColor}" />
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="CornerRadius"
Value="5" />
</Style>
<Style Selector="Image.small">
<Setter Property="Width" Value="50" />
<Setter Property="Width"
Value="50" />
</Style>
<Style Selector="Image.normal">
<Setter Property="Width" Value="80" />
<Setter Property="Width"
Value="80" />
</Style>
<Style Selector="Image.large">
<Setter Property="Width" Value="100" />
<Setter Property="Width"
Value="100" />
</Style>
<Style Selector="Image.huge">
<Setter Property="Width" Value="120" />
<Setter Property="Width"
Value="120" />
</Style>
<Style Selector="#TitleBarHost &gt; Image">
<Setter Property="Margin" Value="10" />
<Setter Property="Margin"
Value="10" />
</Style>
<Style Selector="#TitleBarHost &gt; Label">
<Setter Property="Margin" Value="5" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Margin"
Value="5" />
<Setter Property="FontSize"
Value="14" />
</Style>
<Style Selector="Button.SystemCaption">
<Setter Property="MinWidth" Value="10" />
<Setter Property="MinWidth"
Value="10" />
</Style>
<Style Selector="DataGridColumnHeader">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Background" Value="{DynamicResource ThemeControlBorderColor}" />
<Setter Property="SeparatorBrush" Value="{DynamicResource ThemeControlBorderColor}" />
<Setter Property="Padding" Value="5" />
<Setter Property="Background" Value="{DynamicResource ThemeContentBackgroundColor}" />
<Setter Property="Foreground"
Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="VerticalContentAlignment"
Value="Center" />
<Setter Property="SeparatorBrush"
Value="{DynamicResource ThemeControlBorderColor}" />
<Setter Property="Padding"
Value="5" />
<Setter Property="Background"
Value="{DynamicResource ThemeContentBackgroundColor}" />
<Setter Property="Template">
<ControlTemplate>
<Grid Background="{TemplateBinding Background}" ColumnDefinitions="*,Auto">
<Grid Background="{TemplateBinding Background}"
ColumnDefinitions="*,Auto">
<Grid
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
@@ -122,193 +148,240 @@
</Setter>
</Style>
<Style Selector="DataGrid">
<Setter Property="RowBackground" Value="{DynamicResource ThemeAccentBrush4}" />
<Setter Property="AlternatingRowBackground" Value="#00FFFFFF" />
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderLowColor}" />
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}" />
<Setter Property="RowBackground"
Value="{DynamicResource ThemeAccentBrush4}" />
<Setter Property="Background"
Value="{DynamicResource ThemeBackgroundBrush}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeBorderLowColor}" />
<Setter Property="BorderThickness"
Value="{DynamicResource ThemeBorderThickness}" />
</Style>
<Style Selector="DataGridRow:selected:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemAccentColor}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
<Setter Property="Fill"
Value="{DynamicResource SystemAccentColor}" />
<Setter Property="Opacity"
Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemListLowColor}" />
<Setter Property="Fill"
Value="{DynamicResource SystemListLowColor}" />
</Style>
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemAccentColor}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
<Setter Property="Fill"
Value="{DynamicResource SystemAccentColor}" />
<Setter Property="Opacity"
Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemAccentColor}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
<Setter Property="Fill"
Value="{DynamicResource SystemAccentColor}" />
<Setter Property="Opacity"
Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:pointerover:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemAccentColor}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
<Setter Property="Fill"
Value="{DynamicResource SystemAccentColor}" />
<Setter Property="Opacity"
Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
</Style>
<Style Selector="DataGridCell">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="HorizontalAlignment"
Value="Center" />
<Setter Property="HorizontalContentAlignment"
Value="Center" />
</Style>
<Style Selector="DataGridCell.Left">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="HorizontalAlignment"
Value="Left" />
</Style>
<Style Selector="CheckBox">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderThickness"
Value="1" />
</Style>
<Style Selector="MenuItem">
<Setter Property="Height" Value="{DynamicResource MenuItemHeight}" />
<Setter Property="Padding" Value="{DynamicResource MenuItemPadding}" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Height"
Value="{DynamicResource MenuItemHeight}" />
<Setter Property="Padding"
Value="{DynamicResource MenuItemPadding}" />
<Setter Property="FontSize"
Value="12" />
</Style>
<Style Selector="MenuItem:selected /template/ Border#root">
<Setter Property="Background" Value="{DynamicResource ThemeControlBorderColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeControlBorderColor}" />
<Setter Property="Background"
Value="{DynamicResource ThemeControlBorderColor}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeControlBorderColor}" />
</Style>
<Style Selector="TabItem > ScrollViewer">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundColor}" />
<Setter Property="Margin" Value="0,-5,0,0" />
<Setter Property="Background"
Value="{DynamicResource ThemeBackgroundColor}" />
<Setter Property="Margin"
Value="0,-5,0,0" />
</Style>
<Style Selector="TabItem > ScrollViewer > Border">
<Setter Property="BorderThickness" Value="0,1,0,0" />
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource HighlightBrush}" />
<Setter Property="BorderThickness"
Value="0,1,0,0" />
<Setter Property="Background"
Value="{DynamicResource ThemeBackgroundColor}" />
<Setter Property="BorderBrush"
Value="{DynamicResource HighlightBrush}" />
</Style>
<Style Selector="Button">
<Setter Property="MinWidth" Value="80" />
<Setter Property="MinWidth"
Value="80" />
</Style>
<Style Selector="ProgressBar /template/ Border#ProgressBarTrack">
<Setter Property="IsVisible" Value="False" />
<Setter Property="IsVisible"
Value="False" />
</Style>
<Style Selector="ToggleButton">
<Setter Property="Padding" Value="0,-5,0,0" />
<Setter Property="Padding"
Value="0,-5,0,0" />
</Style>
<Style Selector="TabItem">
<Setter Property="FontSize" Value="14" />
<Setter Property="BorderThickness" Value="0,0,1,0" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeButtonForegroundColor}" />
<Setter Property="Background" Value="{DynamicResource HighlightColor}" />
<Setter Property="FontSize"
Value="14" />
<Setter Property="BorderThickness"
Value="0,0,1,0" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeButtonForegroundColor}" />
<Setter Property="Background"
Value="{DynamicResource SystemAccentColorLight2}" />
</Style>
<Style Selector="TabItem:pointerover">
<Setter Property="Foreground" Value="{DynamicResource ThemeButtonForegroundColor}" />
<Setter Property="Foreground"
Value="{DynamicResource ThemeButtonForegroundColor}" />
</Style>
<Style Selector="TabItem:selected">
<Setter Property="Background" Value="{DynamicResource HighlightColor}" />
<Setter Property="Foreground" Value="{DynamicResource ThemeBackgroundColor}" />
<Setter Property="Background"
Value="{DynamicResource SystemAccentColorLight2}" />
<Setter Property="Foreground"
Value="{DynamicResource ThemeBackgroundColor}" />
</Style>
<Style Selector="TextBlock">
<Setter Property="Margin" Value="{DynamicResource TextMargin}" />
<Setter Property="FontSize" Value="{DynamicResource FontSize}" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="TextWrapping" Value="WrapWithOverflow" />
<Setter Property="Margin"
Value="{DynamicResource TextMargin}" />
<Setter Property="FontSize"
Value="{DynamicResource FontSize}" />
<Setter Property="VerticalAlignment"
Value="Center" />
<Setter Property="TextWrapping"
Value="WrapWithOverflow" />
</Style>
<Style Selector="TextBlock.h1">
<Setter Property="Margin" Value="{DynamicResource TextMargin}" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontSize" Value="16" />
<Setter Property="TextWrapping" Value="WrapWithOverflow" />
<Setter Property="Margin"
Value="{DynamicResource TextMargin}" />
<Setter Property="VerticalAlignment"
Value="Center" />
<Setter Property="FontWeight"
Value="Bold" />
<Setter Property="FontSize"
Value="16" />
<Setter Property="TextWrapping"
Value="WrapWithOverflow" />
</Style>
<Style Selector="Separator">
<Setter Property="Background" Value="{DynamicResource ThemeControlBorderColor}" />
<Setter Property="Foreground" Value="{DynamicResource ThemeControlBorderColor}" />
<Setter Property="MinHeight" Value="1" />
<Setter Property="Background"
Value="{DynamicResource ThemeControlBorderColor}" />
<Setter Property="Foreground"
Value="{DynamicResource ThemeControlBorderColor}" />
<Setter Property="MinHeight"
Value="1" />
</Style>
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle">
<Setter Property="Background" Value="{DynamicResource HighlightColor}" />
<Setter Property="Foreground" Value="{DynamicResource ThemeBackgroundColor}" />
<Setter Property="Background"
Value="{DynamicResource SystemAccentColorLight2}" />
<Setter Property="Foreground"
Value="{DynamicResource ThemeBackgroundColor}" />
</Style>
<Style Selector="DatePickerPresenter">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeButtonForegroundColor}" />
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeButtonForegroundColor}" />
</Style>
<Style Selector="DataGridCell">
<Setter Property="FontSize" Value="14" />
<Setter Property="FontSize"
Value="14" />
</Style>
<Style Selector="CheckBox TextBlock">
<Setter Property="Margin" Value="0,5,0,0" />
<Setter Property="Margin"
Value="0,5,0,0" />
</Style>
<Style Selector="ContextMenu">
<Setter Property="BorderBrush" Value="{DynamicResource MenuFlyoutPresenterBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}" />
<Setter Property="BorderBrush"
Value="{DynamicResource MenuFlyoutPresenterBorderBrush}" />
<Setter Property="BorderThickness"
Value="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}" />
</Style>
<Style Selector="TextBox">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment"
Value="Center" />
</Style>
<Style Selector="TextBox.NumberBoxTextBoxStyle">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" />
<Setter Property="Foreground"
Value="{DynamicResource ThemeForegroundColor}" />
</Style>
<Style Selector="ListBox ListBoxItem">
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="CornerRadius" Value="5" />
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
<Setter Property="BorderThickness" Value="2"/>
<Style.Animations>
<Animation Duration="0:0:0.7">
<KeyFrame Cue="0%">
<Setter Property="MaxHeight" Value="0" />
<Setter Property="Opacity" Value="0.0" />
</KeyFrame>
<KeyFrame Cue="50%">
<Setter Property="MaxHeight" Value="1000" />
<Setter Property="Opacity" Value="0.3" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="MaxHeight" Value="1000" />
<Setter Property="Opacity" Value="1.0" />
</KeyFrame>
</Animation>
</Style.Animations>
<Setter Property="Padding"
Value="0" />
<Setter Property="Margin"
Value="0" />
<Setter Property="CornerRadius"
Value="5" />
<Setter Property="Background"
Value="{DynamicResource AppListBackgroundColor}" />
<Setter Property="BorderThickness"
Value="2"/>
</Style>
<Style Selector="ListBox ListBoxItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
<Setter Property="Background"
Value="{DynamicResource AppListBackgroundColor}" />
</Style>
<Style Selector="ListBox">
<Setter Property="Background"
Value="{DynamicResource ThemeContentBackgroundColor}" />
</Style>
<Style Selector="FlyoutPresenter, ContextMenu, MenuFlyoutPresenter">
<Setter Property="BorderBrush"
Value="{DynamicResource MenuFlyoutPresenterBorderColor}" />
</Style>
<Style Selector="ListBox ListBoxItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
<Setter Property="Background"
Value="{DynamicResource AppListHoverBackgroundColor}" />
</Style>
<Styles.Resources>
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="ListViewItemBackgroundSelected" ResourceKey="ThemeAccentColorBrush" />
<StaticResource x:Key="ListViewItemBackgroundPressed" ResourceKey="SystemAccentColorDark1" />
<StaticResource x:Key="ListViewItemBackgroundPointerOver" ResourceKey="SystemAccentColorDark2" />
<StaticResource x:Key="ListViewItemBackgroundSelectedPressed" ResourceKey="ThemeAccentColorBrush" />
<StaticResource x:Key="ListViewItemBackgroundSelectedPointerOver" ResourceKey="SystemAccentColorDark2" />
<SolidColorBrush x:Key="ThemeAccentColorBrush"
Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="ListViewItemBackgroundSelected"
ResourceKey="ThemeAccentColorBrush" />
<StaticResource x:Key="ListViewItemBackgroundPressed"
ResourceKey="SystemAccentColorDark1" />
<StaticResource x:Key="ListViewItemBackgroundPointerOver"
ResourceKey="SystemAccentColorDark2" />
<StaticResource x:Key="ListViewItemBackgroundSelectedPressed"
ResourceKey="ThemeAccentColorBrush" />
<StaticResource x:Key="ListViewItemBackgroundSelectedPointerOver"
ResourceKey="SystemAccentColorDark2" />
<SolidColorBrush
x:Key="DataGridGridLinesBrush"
Opacity="0.4"
Color="{DynamicResource SystemBaseMediumLowColor}" />
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="MenuFlyoutPresenterBorderBrush" Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
<SolidColorBrush x:Key="FlyoutBorderThemeBrush" Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
<SolidColorBrush x:Key="ListBoxBackground" Color="{DynamicResource ThemeContentBackgroundColor}" />
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="{DynamicResource ThemeForegroundColor}" />
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" />
<SolidColorBrush x:Key="SplitButtonBackgroundChecked" Color="#00E81123" />
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedPointerOver" Color="#00E81123" />
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedPressed" Color="#00E81123" />
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedDisabled" Color="#00E81123" />
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="SplitButtonBackgroundChecked"
Color="#00E81123" />
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedPointerOver"
Color="#00E81123" />
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedPressed"
Color="#00E81123" />
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedDisabled"
Color="#00E81123" />
<Thickness x:Key="PageMargin">40 0 40 0</Thickness>
<Thickness x:Key="Margin">0 5 0 5</Thickness>
<Thickness x:Key="MenuItemPadding">5 0 5 0</Thickness>
<Color x:Key="MenuFlyoutPresenterBorderColor">#00000000</Color>
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark1">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark2">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark3">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight1">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight2">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight3">#FF00C3E3</Color>
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
<Color x:Key="VsyncEnabled">#FF2EEAC9</Color>
<Color x:Key="VsyncDisabled">#FFFF4554</Color>
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
<x:Double x:Key="ScrollBarThickness">15</x:Double>
<x:Double x:Key="FontSizeSmall">8</x:Double>
<x:Double x:Key="FontSizeNormal">10</x:Double>

View File

@@ -0,0 +1,85 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush"
Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="ThemeAccentBrush4"
Color="{DynamicResource ThemeAccentColor4}" />
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark1">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark2">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark3">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight1">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight2">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight3">#FF00C3E3</Color>
<Color x:Key="ThemeAccentColor4">#FFe8e8e8</Color>
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FFF0F0F0</Color>
<Color x:Key="ThemeControlBorderColor">#FFd6d6d6</Color>
<Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="ThemeForegroundColor">#FF000000</Color>
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color>
<Color x:Key="VsyncEnabled">#FF2EEAC9</Color>
<Color x:Key="VsyncDisabled">#FFFF4554</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush"
Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="ThemeAccentBrush4"
Color="{DynamicResource ThemeAccentColor4}" />
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark1">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark2">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark3">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight1">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight2">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight3">#FF00C3E3</Color>
<Color x:Key="ThemeAccentColor4">#FFe8e8e8</Color>
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FFF0F0F0</Color>
<Color x:Key="ThemeControlBorderColor">#FFd6d6d6</Color>
<Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="ThemeForegroundColor">#FF000000</Color>
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush"
Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="ThemeAccentBrush4"
Color="{DynamicResource ThemeAccentColor4}" />
<Color x:Key="ControlFillColorSecondary">#008AA8</Color>
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark1">#FF99b000</Color>
<Color x:Key="SystemAccentColorDark2">#FF006d7d</Color>
<Color x:Key="SystemAccentColorDark3">#FF00525E</Color>
<Color x:Key="SystemAccentColorLight1">#FF00dbff</Color>
<Color x:Key="SystemAccentColorLight2">#FF19dfff</Color>
<Color x:Key="SystemAccentColorLight3">#FF33e3ff</Color>
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
<Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="ThemeForegroundColor">#FFFFFFFF</Color>
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

View File

@@ -1,5 +1,5 @@
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using LibHac;
using LibHac.Account;
@@ -143,14 +143,20 @@ namespace Ryujinx.Ava.Common
}
}
public static async Task ExtractSection(NcaSectionType ncaSectionType, string titleFilePath, string titleName, int programIndex = 0)
public static async Task ExtractSection(IStorageProvider storageProvider, NcaSectionType ncaSectionType, string titleFilePath, string titleName, int programIndex = 0)
{
OpenFolderDialog folderDialog = new()
var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
};
AllowMultiple = false
});
string destination = await folderDialog.ShowAsync(_owner);
if (result.Count == 0)
{
return;
}
var destination = result[0].Path.LocalPath;
var cancellationToken = new CancellationTokenSource();
UpdateWaitWindow waitingDialog = new(
@@ -158,148 +164,145 @@ namespace Ryujinx.Ava.Common
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
cancellationToken);
if (!string.IsNullOrWhiteSpace(destination))
Thread extractorThread = new(() =>
{
Thread extractorThread = new(() =>
Dispatcher.UIThread.Post(waitingDialog.Show);
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
Nca mainNca = null;
Nca patchNca = null;
string extension = Path.GetExtension(titleFilePath).ToLower();
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
{
Dispatcher.UIThread.Post(waitingDialog.Show);
PartitionFileSystem pfs;
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
Nca mainNca = null;
Nca patchNca = null;
string extension = Path.GetExtension(titleFilePath).ToLower();
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
if (extension == ".xci")
{
PartitionFileSystem pfs;
pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
}
else
{
pfs = new PartitionFileSystem(file.AsStorage());
}
if (extension == ".xci")
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
{
using var ncaFile = new UniqueRef<IFile>();
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
if (nca.Header.ContentType == NcaContentType.Program)
{
pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
}
else
{
pfs = new PartitionFileSystem(file.AsStorage());
}
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
{
using var ncaFile = new UniqueRef<IFile>();
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
if (nca.Header.ContentType == NcaContentType.Program)
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
{
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
{
patchNca = nca;
}
else
{
mainNca = nca;
}
patchNca = nca;
}
else
{
mainNca = nca;
}
}
}
else if (extension == ".nca")
{
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
}
if (mainNca == null)
{
Logger.Error?.Print(LogClass.Application, "Extraction failure. The main NCA was not present in the selected file");
Dispatcher.UIThread.InvokeAsync(async () =>
{
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
});
return;
}
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
if (updatePatchNca != null)
{
patchNca = updatePatchNca;
}
int index = Nca.GetSectionIndexFromType(ncaSectionType, mainNca.Header.ContentType);
try
{
bool sectionExistsInPatch = false;
if (patchNca != null)
{
sectionExistsInPatch = patchNca.CanOpenSection(index);
}
IFileSystem ncaFileSystem = sectionExistsInPatch ? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid)
: mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid);
FileSystemClient fsClient = _horizonClient.Fs;
string source = DateTime.Now.ToFileTime().ToString()[10..];
string output = DateTime.Now.ToFileTime().ToString()[10..];
using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem);
using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination));
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref);
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref);
(Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token);
if (!canceled)
{
if (resultCode.Value.IsFailure())
{
Logger.Error?.Print(LogClass.Application, $"LibHac returned error code: {resultCode.Value.ErrorCode}");
Dispatcher.UIThread.InvokeAsync(async () =>
{
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
});
}
else if (resultCode.Value.IsSuccess())
{
Dispatcher.UIThread.Post(waitingDialog.Close);
NotificationHelper.Show(
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}",
NotificationType.Information);
}
}
fsClient.Unmount(source.ToU8Span());
fsClient.Unmount(output.ToU8Span());
}
catch (ArgumentException ex)
{
Logger.Error?.Print(LogClass.Application, $"{ex.Message}");
Dispatcher.UIThread.InvokeAsync(async () =>
{
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(ex.Message);
});
}
})
}
else if (extension == ".nca")
{
Name = "GUI.NcaSectionExtractorThread",
IsBackground = true,
};
extractorThread.Start();
}
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
}
if (mainNca == null)
{
Logger.Error?.Print(LogClass.Application, "Extraction failure. The main NCA was not present in the selected file");
Dispatcher.UIThread.InvokeAsync(async () =>
{
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
});
return;
}
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
if (updatePatchNca != null)
{
patchNca = updatePatchNca;
}
int index = Nca.GetSectionIndexFromType(ncaSectionType, mainNca.Header.ContentType);
try
{
bool sectionExistsInPatch = false;
if (patchNca != null)
{
sectionExistsInPatch = patchNca.CanOpenSection(index);
}
IFileSystem ncaFileSystem = sectionExistsInPatch ? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid)
: mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid);
FileSystemClient fsClient = _horizonClient.Fs;
string source = DateTime.Now.ToFileTime().ToString()[10..];
string output = DateTime.Now.ToFileTime().ToString()[10..];
using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem);
using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination));
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref);
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref);
(Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token);
if (!canceled)
{
if (resultCode.Value.IsFailure())
{
Logger.Error?.Print(LogClass.Application, $"LibHac returned error code: {resultCode.Value.ErrorCode}");
Dispatcher.UIThread.InvokeAsync(async () =>
{
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
});
}
else if (resultCode.Value.IsSuccess())
{
Dispatcher.UIThread.Post(waitingDialog.Close);
NotificationHelper.Show(
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}",
NotificationType.Information);
}
}
fsClient.Unmount(source.ToU8Span());
fsClient.Unmount(output.ToU8Span());
}
catch (ArgumentException ex)
{
Logger.Error?.Print(LogClass.Application, $"{ex.Message}");
Dispatcher.UIThread.InvokeAsync(async () =>
{
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(ex.Message);
});
}
})
{
Name = "GUI.NcaSectionExtractorThread",
IsBackground = true,
};
extractorThread.Start();
}
public static (Result? result, bool canceled) CopyDirectory(FileSystemClient fs, string sourcePath, string destPath, CancellationToken token)

View File

@@ -31,7 +31,6 @@ namespace Ryujinx.Ava.Input
_control.KeyDown += OnKeyPress;
_control.KeyUp += OnKeyRelease;
_control.TextInput += Control_TextInput;
_control.AddHandler(InputElement.TextInputEvent, Control_LastChanceTextInput, RoutingStrategies.Bubble);
}
private void Control_TextInput(object sender, TextInputEventArgs e)
@@ -39,12 +38,6 @@ namespace Ryujinx.Ava.Input
TextInput?.Invoke(this, e.Text);
}
private void Control_LastChanceTextInput(object sender, TextInputEventArgs e)
{
// Swallow event
e.Handled = true;
}
public event Action<string> OnGamepadConnected
{
add { }

View File

@@ -37,7 +37,7 @@ namespace Ryujinx.Modules
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
private static readonly int _connectionCount = 4;
private const int ConnectionCount = 4;
private static string _buildVer;
private static string _platformExt;
@@ -344,22 +344,22 @@ namespace Ryujinx.Modules
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
{
// Multi-Threaded Updater
long chunkSize = _buildSize / _connectionCount;
long remainderChunk = _buildSize % _connectionCount;
long chunkSize = _buildSize / ConnectionCount;
long remainderChunk = _buildSize % ConnectionCount;
int completedRequests = 0;
int totalProgressPercentage = 0;
int[] progressPercentage = new int[_connectionCount];
int[] progressPercentage = new int[ConnectionCount];
List<byte[]> list = new(_connectionCount);
List<WebClient> webClients = new(_connectionCount);
List<byte[]> list = new(ConnectionCount);
List<WebClient> webClients = new(ConnectionCount);
for (int i = 0; i < _connectionCount; i++)
for (int i = 0; i < ConnectionCount; i++)
{
list.Add(Array.Empty<byte>());
}
for (int i = 0; i < _connectionCount; i++)
for (int i = 0; i < ConnectionCount; i++)
{
#pragma warning disable SYSLIB0014
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
@@ -368,7 +368,7 @@ namespace Ryujinx.Modules
webClients.Add(client);
if (i == _connectionCount - 1)
if (i == ConnectionCount - 1)
{
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
}
@@ -385,7 +385,7 @@ namespace Ryujinx.Modules
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
taskDialog.SetProgressBarState(totalProgressPercentage / _connectionCount, TaskDialogProgressState.Normal);
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
};
client.DownloadDataCompleted += (_, args) =>
@@ -404,10 +404,10 @@ namespace Ryujinx.Modules
list[index] = args.Result;
Interlocked.Increment(ref completedRequests);
if (Equals(completedRequests, _connectionCount))
if (Equals(completedRequests, ConnectionCount))
{
byte[] mergedFileBytes = new byte[_buildSize];
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < _connectionCount; connectionIndex++)
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
{
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
destinationOffset += list[connectionIndex].Length;

View File

@@ -59,15 +59,12 @@ namespace Ryujinx.Ava
{
EnableMultiTouch = true,
EnableIme = true,
UseEGL = false,
UseGpu = true,
RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software },
})
.With(new Win32PlatformOptions
{
EnableMultitouch = true,
UseWgl = false,
AllowEglInitialization = false,
CompositionBackdropCornerRadius = 8.0f,
WinUICompositionBackdropCornerRadius = 8.0f,
RenderingMode = new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software },
})
.UseSkia();
}
@@ -103,8 +100,6 @@ namespace Ryujinx.Ava
ReloadConfig();
ForceDpiAware.Windows();
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
// Logging system information.

View File

@@ -10,6 +10,8 @@
<RootNamespace>Ryujinx.Ava</RootNamespace>
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
<TieredPGO>true</TieredPGO>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
@@ -26,7 +28,7 @@
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.Desktop" />
<PackageReference Include="Avalonia.Diagnostics" />
<PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)'=='Debug'" />
<PackageReference Include="Avalonia.Controls.DataGrid" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
<PackageReference Include="Avalonia.Svg" />
@@ -34,7 +36,6 @@
<PackageReference Include="jp2masa.Avalonia.Flexbox" />
<PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="XamlNameReferenceGenerator" />
<PackageReference Include="OpenTK.Core" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
@@ -97,10 +98,7 @@
<SubType>Designer</SubType>
</AvaloniaResource>
<AvaloniaResource Include="Assets\Fonts\SegoeFluentIcons.ttf" />
<AvaloniaResource Include="Assets\Styles\BaseLight.xaml">
<Generator>MSBuild:Compile</Generator>
</AvaloniaResource>
<AvaloniaResource Include="Assets\Styles\BaseDark.xaml">
<AvaloniaResource Include="Assets\Styles\Themes.xaml">
<Generator>MSBuild:Compile</Generator>
</AvaloniaResource>
<AvaloniaResource Include="Assets\Styles\Styles.xaml" />
@@ -123,8 +121,7 @@
<None Remove="Assets\Locales\zh_CN.json" />
<None Remove="Assets\Locales\zh_TW.json" />
<None Remove="Assets\Styles\Styles.xaml" />
<None Remove="Assets\Styles\BaseDark.xaml" />
<None Remove="Assets\Styles\BaseLight.xaml" />
<None Remove="Assets\Styles\Themes.xaml" />
</ItemGroup>
<ItemGroup>

View File

@@ -38,7 +38,7 @@
Grid.Column="1"
Margin="10"
VerticalAlignment="Stretch"
Text="{Binding Message}"
Text="{ReflectionBinding Message}"
TextWrapping="Wrap" />
<StackPanel
Name="ButtonStack"

View File

@@ -4,9 +4,6 @@ using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Windows;
using System.Threading.Tasks;
#if DEBUG
using Avalonia;
#endif
namespace Ryujinx.Ava.UI.Applet
{
@@ -21,9 +18,7 @@ namespace Ryujinx.Ava.UI.Applet
Message = message;
DataContext = this;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
int responseId = 0;
if (buttons != null)
@@ -44,9 +39,6 @@ namespace Ryujinx.Ava.UI.Applet
{
DataContext = this;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
public string Message { get; set; }

View File

@@ -34,13 +34,13 @@
Grid.Row="1"
Grid.Column="1"
Margin="5"
Text="{Binding MainText}"
Text="{ReflectionBinding MainText}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="2"
Grid.Column="1"
Margin="5"
Text="{Binding SecondaryText}"
Text="{ReflectionBinding SecondaryText}"
TextWrapping="Wrap" />
<TextBox
Name="Input"
@@ -50,7 +50,7 @@
VerticalAlignment="Center"
Focusable="True"
KeyUp="Message_KeyUp"
Text="{Binding Message}"
Text="{ReflectionBinding Message}"
TextInput="Message_TextInput"
TextWrapping="Wrap"
UseFloatingWatermark="True" />

View File

@@ -2,7 +2,9 @@
x:Class="Ryujinx.Ava.UI.Controls.ApplicationContextMenu"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale">
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel">
<MenuItem
Click="RunApplication_Click"
Header="{locale:Locale GameListContextMenuRunApplication}" />

View File

@@ -299,7 +299,11 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
await ApplicationHelper.ExtractSection(NcaSectionType.Code, viewModel.SelectedApplication.Path, viewModel.SelectedApplication.TitleName);
await ApplicationHelper.ExtractSection(
viewModel.StorageProvider,
NcaSectionType.Code,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.TitleName);
}
}
@@ -309,7 +313,11 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
await ApplicationHelper.ExtractSection(NcaSectionType.Data, viewModel.SelectedApplication.Path, viewModel.SelectedApplication.TitleName);
await ApplicationHelper.ExtractSection(
viewModel.StorageProvider,
NcaSectionType.Data,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.TitleName);
}
}
@@ -319,7 +327,11 @@ namespace Ryujinx.Ava.UI.Controls
if (viewModel?.SelectedApplication != null)
{
await ApplicationHelper.ExtractSection(NcaSectionType.Logo, viewModel.SelectedApplication.Path, viewModel.SelectedApplication.TitleName);
await ApplicationHelper.ExtractSection(
viewModel.StorageProvider,
NcaSectionType.Logo,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.TitleName);
}
}

View File

@@ -11,7 +11,9 @@
d:DesignHeight="450"
d:DesignWidth="800"
Focusable="True"
mc:Ignorable="d">
mc:Ignorable="d"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel">
<UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
<controls:ApplicationContextMenu x:Key="ApplicationContextMenu" />
@@ -27,7 +29,7 @@
VerticalAlignment="Stretch"
ContextFlyout="{StaticResource ApplicationContextMenu}"
DoubleTapped="GameList_DoubleTapped"
Items="{Binding AppsObservableList}"
ItemsSource="{Binding AppsObservableList}"
SelectionChanged="GameList_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
@@ -43,8 +45,8 @@
<Setter Property="Margin" Value="5" />
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].DataContext.GridItemSelectorSize}" />
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
<Setter Property="MinHeight" Value="{ReflectionBinding $parent[UserControl].DataContext.GridItemSelectorSize}" />
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
@@ -54,10 +56,10 @@
Margin="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
Classes.huge="{ReflectionBinding $parent[UserControl].DataContext.IsGridHuge}"
Classes.large="{ReflectionBinding $parent[UserControl].DataContext.IsGridLarge}"
Classes.normal="{ReflectionBinding $parent[UserControl].DataContext.IsGridMedium}"
Classes.small="{ReflectionBinding $parent[UserControl].DataContext.IsGridSmall}"
ClipToBounds="True"
CornerRadius="4">
<Grid>
@@ -76,9 +78,9 @@
Margin="0,10,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}">
IsVisible="{ReflectionBinding $parent[UserControl].DataContext.ShowNames}">
<TextBlock
HorizontalAlignment="Stretch"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding TitleName}"
TextAlignment="Center"

View File

@@ -1,7 +1,6 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ui.App.Common;
@@ -25,12 +24,7 @@ namespace Ryujinx.Ava.UI.Controls
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void GameList_DoubleTapped(object sender, RoutedEventArgs args)
public void GameList_DoubleTapped(object sender, TappedEventArgs args)
{
if (sender is ListBox listBox)
{

View File

@@ -10,7 +10,9 @@
d:DesignHeight="450"
d:DesignWidth="800"
Focusable="True"
mc:Ignorable="d">
mc:Ignorable="d"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel">
<UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
<controls:ApplicationContextMenu x:Key="ApplicationContextMenu" />
@@ -27,7 +29,7 @@
VerticalAlignment="Stretch"
ContextFlyout="{StaticResource ApplicationContextMenu}"
DoubleTapped="GameList_DoubleTapped"
Items="{Binding AppsObservableList}"
ItemsSource="{Binding AppsObservableList}"
SelectionChanged="GameList_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
@@ -39,8 +41,8 @@
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].DataContext.ListItemSelectorSize}" />
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
<Setter Property="MinHeight" Value="{ReflectionBinding $parent[UserControl].DataContext.ListItemSelectorSize}" />
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
@@ -65,10 +67,10 @@
Grid.RowSpan="3"
Grid.Column="0"
Margin="0"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
Classes.huge="{ReflectionBinding $parent[UserControl].DataContext.IsGridHuge}"
Classes.large="{ReflectionBinding $parent[UserControl].DataContext.IsGridLarge}"
Classes.normal="{ReflectionBinding $parent[UserControl].DataContext.IsGridMedium}"
Classes.small="{ReflectionBinding $parent[UserControl].DataContext.IsGridSmall}"
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
<Border
Grid.Column="2"

View File

@@ -1,7 +1,6 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ui.App.Common;
@@ -25,12 +24,7 @@ namespace Ryujinx.Ava.UI.Controls
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void GameList_DoubleTapped(object sender, RoutedEventArgs args)
public void GameList_DoubleTapped(object sender, TappedEventArgs args)
{
if (sender is ListBox listBox)
{

View File

@@ -1,6 +1,4 @@
using Avalonia.Data;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
using FluentAvalonia.UI.Controls;
using System;
using System.Collections.Generic;
@@ -38,13 +36,7 @@ namespace Ryujinx.Ava.UI.Helpers
public override object ProvideValue(IServiceProvider serviceProvider)
{
ReflectionBindingExtension binding = new($"[{_key}]")
{
Mode = BindingMode.OneWay,
Source = this,
};
return binding.ProvideValue(serviceProvider);
return this[_key];
}
}
}

View File

@@ -1,52 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using System;
using System.Windows.Input;
namespace Ryujinx.Ava.UI.Helpers
{
public class HotKeyControl : ContentControl, ICommandSource
{
public static readonly StyledProperty<object> CommandParameterProperty =
AvaloniaProperty.Register<HotKeyControl, object>(nameof(CommandParameter));
public static readonly DirectProperty<HotKeyControl, ICommand> CommandProperty =
AvaloniaProperty.RegisterDirect<HotKeyControl, ICommand>(nameof(Command),
control => control.Command, (control, command) => control.Command = command, enableDataValidation: true);
public static readonly StyledProperty<KeyGesture> HotKeyProperty = HotKeyManager.HotKeyProperty.AddOwner<Button>();
private ICommand _command;
private bool _commandCanExecute;
public ICommand Command
{
get { return _command; }
set { SetAndRaise(CommandProperty, ref _command, value); }
}
public KeyGesture HotKey
{
get { return GetValue(HotKeyProperty); }
set { SetValue(HotKeyProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public void CanExecuteChanged(object sender, EventArgs e)
{
var canExecute = Command == null || Command.CanExecute(CommandParameter);
if (canExecute != _commandCanExecute)
{
_commandCanExecute = canExecute;
UpdateIsEffectivelyEnabled();
}
}
}
}

View File

@@ -42,21 +42,6 @@ namespace Ryujinx.Ava.UI.Helpers
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, null));
}
public void Log<T0>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, new object[] { propertyValue0 }));
}
public void Log<T0, T1>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, new object[] { propertyValue0, propertyValue1 }));
}
public void Log<T0, T1, T2>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, new object[] { propertyValue0, propertyValue1, propertyValue2 }));
}
public void Log(AvaLogLevel level, string area, object source, string messageTemplate, params object[] propertyValues)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(level, area, messageTemplate, source, propertyValues));

View File

@@ -31,7 +31,6 @@ namespace Ryujinx.Ava.UI.Helpers
OnTextInput(new TextInputEventArgs
{
Text = text,
Device = KeyboardDevice.Instance,
Source = this,
RoutedEvent = TextInputEvent,
});

View File

@@ -0,0 +1,28 @@
using Avalonia.Data.Converters;
using System;
using System.Globalization;
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
namespace Ryujinx.Ava.UI.Helpers
{
internal class TimeZoneConverter : IValueConverter
{
public static TimeZoneConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return null;
}
var timeZone = (TimeZone)value;
return string.Format("{0} {1} {2}", timeZone.UtcDifference, timeZone.Location, timeZone.Abbreviation);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,4 +1,3 @@
using Avalonia;
using Avalonia.Media;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.ViewModels;
@@ -88,7 +87,8 @@ namespace Ryujinx.Ava.UI.Models
private void UpdateBackground()
{
Application.Current.Styles.TryGetResource("ControlFillColorSecondary", out object color);
var currentApplication = Avalonia.Application.Current;
currentApplication.Styles.TryGetResource("ControlFillColorSecondary", currentApplication.ActualThemeVariant, out object color);
if (color is not null)
{

View File

@@ -34,7 +34,7 @@ namespace Ryujinx.Ava.UI.Renderer
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
public event EventHandler<IntPtr> WindowCreated;
public event EventHandler<Size> SizeChanged;
public event EventHandler<Size> BoundsChanged;
public EmbeddedWindow()
{
@@ -67,7 +67,7 @@ namespace Ryujinx.Ava.UI.Renderer
private void StateChanged(Rect rect)
{
SizeChanged?.Invoke(this, rect.Size);
BoundsChanged?.Invoke(this, rect.Size);
_updateBoundsCallback?.Invoke(rect);
}
@@ -149,9 +149,10 @@ namespace Ryujinx.Ava.UI.Renderer
msg == WindowsMessages.Rbuttonup ||
msg == WindowsMessages.Mousemove)
{
Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), VisualRoot).Value;
Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), this).Value;
Pointer pointer = new(0, PointerType.Mouse, true);
#pragma warning disable CS0618 // Type or member is obsolete (As of Avalonia 11, the constructors for PointerPressedEventArgs & PointerEventArgs are marked as obsolete)
switch (msg)
{
case WindowsMessages.Lbuttondown:
@@ -164,7 +165,7 @@ namespace Ryujinx.Ava.UI.Renderer
var evnt = new PointerPressedEventArgs(
this,
pointer,
VisualRoot,
this,
rootVisualPosition,
(ulong)Environment.TickCount64,
properties,
@@ -184,7 +185,7 @@ namespace Ryujinx.Ava.UI.Renderer
var evnt = new PointerReleasedEventArgs(
this,
pointer,
VisualRoot,
this,
rootVisualPosition,
(ulong)Environment.TickCount64,
properties,
@@ -201,7 +202,7 @@ namespace Ryujinx.Ava.UI.Renderer
PointerMovedEvent,
this,
pointer,
VisualRoot,
this,
rootVisualPosition,
(ulong)Environment.TickCount64,
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
@@ -212,6 +213,7 @@ namespace Ryujinx.Ava.UI.Renderer
break;
}
}
#pragma warning restore CS0618
}
}

View File

@@ -11,7 +11,7 @@ namespace Ryujinx.Ava.UI.Renderer
public readonly EmbeddedWindow EmbeddedWindow;
public event EventHandler<EventArgs> WindowCreated;
public event Action<object, Size> SizeChanged;
public event Action<object, Size> BoundsChanged;
public RendererHost()
{
@@ -32,7 +32,7 @@ namespace Ryujinx.Ava.UI.Renderer
private void Initialize()
{
EmbeddedWindow.WindowCreated += CurrentWindow_WindowCreated;
EmbeddedWindow.SizeChanged += CurrentWindow_SizeChanged;
EmbeddedWindow.BoundsChanged += CurrentWindow_BoundsChanged;
Content = EmbeddedWindow;
}
@@ -42,7 +42,7 @@ namespace Ryujinx.Ava.UI.Renderer
if (EmbeddedWindow != null)
{
EmbeddedWindow.WindowCreated -= CurrentWindow_WindowCreated;
EmbeddedWindow.SizeChanged -= CurrentWindow_SizeChanged;
EmbeddedWindow.BoundsChanged -= CurrentWindow_BoundsChanged;
}
GC.SuppressFinalize(this);
@@ -55,9 +55,9 @@ namespace Ryujinx.Ava.UI.Renderer
Dispose();
}
private void CurrentWindow_SizeChanged(object sender, Size e)
private void CurrentWindow_BoundsChanged(object sender, Size e)
{
SizeChanged?.Invoke(sender, e);
BoundsChanged?.Invoke(sender, e);
}
private void CurrentWindow_WindowCreated(object sender, IntPtr e)

View File

@@ -1,4 +1,3 @@
using Avalonia;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
@@ -88,21 +87,19 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Version = Program.Version;
var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
if (ConfigurationState.Instance.Ui.BaseStyle.Value == "Light")
{
GithubLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Light.png?assembly=Ryujinx.Ui.Common")));
DiscordLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Light.png?assembly=Ryujinx.Ui.Common")));
PatreonLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Light.png?assembly=Ryujinx.Ui.Common")));
TwitterLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Light.png?assembly=Ryujinx.Ui.Common")));
GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Light.png?assembly=Ryujinx.Ui.Common")));
DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Light.png?assembly=Ryujinx.Ui.Common")));
PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Light.png?assembly=Ryujinx.Ui.Common")));
TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Light.png?assembly=Ryujinx.Ui.Common")));
}
else
{
GithubLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Dark.png?assembly=Ryujinx.Ui.Common")));
DiscordLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Dark.png?assembly=Ryujinx.Ui.Common")));
PatreonLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Dark.png?assembly=Ryujinx.Ui.Common")));
TwitterLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Dark.png?assembly=Ryujinx.Ui.Common")));
GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Dark.png?assembly=Ryujinx.Ui.Common")));
DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Dark.png?assembly=Ryujinx.Ui.Common")));
PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Dark.png?assembly=Ryujinx.Ui.Common")));
TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Dark.png?assembly=Ryujinx.Ui.Common")));
}
Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson);

View File

@@ -1,6 +1,6 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using DynamicData;
using LibHac.Common;
@@ -90,12 +90,19 @@ namespace Ryujinx.Ava.UI.ViewModels
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
}
public IStorageProvider StorageProvider;
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
{
_virtualFileSystem = virtualFileSystem;
_titleId = titleId;
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
StorageProvider = desktop.MainWindow.StorageProvider;
}
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
try
@@ -195,29 +202,24 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void Add()
{
OpenFileDialog dialog = new()
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
AllowMultiple = true,
};
dialog.Filters.Add(new FileDialogFilter
{
Name = "NSP",
Extensions = { "nsp" },
});
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
string[] files = await dialog.ShowAsync(desktop.MainWindow);
if (files != null)
FileTypeFilter = new List<FilePickerFileType>
{
foreach (string file in files)
new("NSP")
{
await AddDownloadableContent(file);
Patterns = new[] { "*.nsp" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
MimeTypes = new[] { "application/x-nx-nsp" }
}
}
});
foreach (var file in result)
{
await AddDownloadableContent(file.Path.LocalPath);
}
}

View File

@@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using DynamicData;
using DynamicData.Binding;
@@ -105,8 +106,6 @@ namespace Ryujinx.Ava.UI.ViewModels
public ApplicationData ListSelectedApplication;
public ApplicationData GridSelectedApplication;
public event Action ReloadGameList;
private string TitleName { get; set; }
internal AppHost AppHost { get; set; }
@@ -131,6 +130,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public void Initialize(
ContentManager contentManager,
IStorageProvider storageProvider,
ApplicationLibrary applicationLibrary,
VirtualFileSystem virtualFileSystem,
AccountManager accountManager,
@@ -144,6 +144,7 @@ namespace Ryujinx.Ava.UI.ViewModels
TopLevel topLevel)
{
ContentManager = contentManager;
StorageProvider = storageProvider;
ApplicationLibrary = applicationLibrary;
VirtualFileSystem = virtualFileSystem;
AccountManager = accountManager;
@@ -891,6 +892,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
public ContentManager ContentManager { get; private set; }
public IStorageProvider StorageProvider { get; private set; }
public ApplicationLibrary ApplicationLibrary { get; private set; }
public VirtualFileSystem VirtualFileSystem { get; private set; }
public AccountManager AccountManager { get; private set; }
@@ -1188,7 +1190,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Application.Current.Styles.TryGetResource(args.VSyncEnabled
? "VsyncEnabled"
: "VsyncDisabled", out object color);
: "VsyncDisabled",
Avalonia.Application.Current.ActualThemeVariant,
out object color);
if (color is not null)
{
@@ -1259,6 +1263,16 @@ namespace Ryujinx.Ava.UI.ViewModels
ShowMenuAndStatusBar = false;
}
public void ToggleStartGamesInFullscreen()
{
StartGamesInFullscreen = !StartGamesInFullscreen;
}
public void ToggleShowConsole()
{
ShowConsole = !ShowConsole;
}
public void SetListMode()
{
Glyph = Glyph.List;
@@ -1271,43 +1285,57 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void InstallFirmwareFromFile()
{
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
OpenFileDialog dialog = new() { AllowMultiple = false };
dialog.Filters.Add(new FileDialogFilter { Name = LocaleManager.Instance[LocaleKeys.FileDialogAllTypes], Extensions = { "xci", "zip" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } });
string[] file = await dialog.ShowAsync(desktop.MainWindow);
if (file != null && file.Length > 0)
AllowMultiple = false,
FileTypeFilter = new List<FilePickerFileType>
{
await HandleFirmwareInstallation(file[0]);
new(LocaleManager.Instance[LocaleKeys.FileDialogAllTypes])
{
Patterns = new[] { "*.xci", "*.zip" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci", "public.zip-archive" },
MimeTypes = new[] { "application/x-nx-xci", "application/zip" }
},
new("XCI")
{
Patterns = new[] { "*.xci" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" },
MimeTypes = new[] { "application/x-nx-xci" }
},
new("ZIP")
{
Patterns = new[] { "*.zip" },
AppleUniformTypeIdentifiers = new[] { "public.zip-archive" },
MimeTypes = new[] { "application/zip" }
},
}
});
if (result.Count > 0)
{
await HandleFirmwareInstallation(result[0].Path.LocalPath);
}
}
public async void InstallFirmwareFromFolder()
{
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
OpenFolderDialog dialog = new();
AllowMultiple = false
});
string folder = await dialog.ShowAsync(desktop.MainWindow);
if (!string.IsNullOrEmpty(folder))
{
await HandleFirmwareInstallation(folder);
}
if (result.Count > 0)
{
await HandleFirmwareInstallation(result[0].Path.LocalPath);
}
}
public static void OpenRyujinxFolder()
public void OpenRyujinxFolder()
{
OpenHelper.OpenFolder(AppDataManager.BaseDirPath);
}
public static void OpenLogsFolder()
public void OpenLogsFolder()
{
string logPath = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "Logs");
@@ -1349,25 +1377,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public void ToggleFileType(string fileType)
{
_ = fileType switch
{
#pragma warning disable IDE0055 // Disable formatting
"NSP" => ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSP,
"PFS0" => ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.PFS0,
"XCI" => ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.XCI,
"NCA" => ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NCA,
"NRO" => ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NRO,
"NSO" => ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSO,
_ => throw new ArgumentOutOfRangeException(fileType),
#pragma warning restore IDE0055
};
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
LoadApplications();
}
public async void ManageProfiles()
{
await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient);
@@ -1378,78 +1387,84 @@ namespace Ryujinx.Ava.UI.ViewModels
AppHost.Device.System.SimulateWakeUpMessage();
}
public async void LoadApplications()
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
Applications.Clear();
StatusBarVisible = true;
StatusBarProgressMaximum = 0;
StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
});
ReloadGameList?.Invoke();
}
public async void OpenFile()
{
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
OpenFileDialog dialog = new()
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
AllowMultiple = false,
FileTypeFilter = new List<FilePickerFileType>
{
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
};
dialog.Filters.Add(new FileDialogFilter
{
Name = LocaleManager.Instance[LocaleKeys.AllSupportedFormats],
Extensions =
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
{
"nsp",
"pfs0",
"xci",
"nca",
"nro",
"nso",
Patterns = new[] { "*.nsp", "*.xci", "*.nca", "*.nro", "*.nso" },
AppleUniformTypeIdentifiers = new[]
{
"com.ryujinx.nsp",
"com.ryujinx.xci",
"com.ryujinx.nca",
"com.ryujinx.nro",
"com.ryujinx.nso"
},
MimeTypes = new[]
{
"application/x-nx-nsp",
"application/x-nx-xci",
"application/x-nx-nca",
"application/x-nx-nro",
"application/x-nx-nso"
}
},
new("NSP")
{
Patterns = new[] { "*.nsp" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
MimeTypes = new[] { "application/x-nx-nsp" }
},
new("XCI")
{
Patterns = new[] { "*.xci" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" },
MimeTypes = new[] { "application/x-nx-xci" }
},
new("NCA")
{
Patterns = new[] { "*.nca" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nca" },
MimeTypes = new[] { "application/x-nx-nca" }
},
new("NRO")
{
Patterns = new[] { "*.nro" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nro" },
MimeTypes = new[] { "application/x-nx-nro" }
},
new("NSO")
{
Patterns = new[] { "*.nso" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nso" },
MimeTypes = new[] { "application/x-nx-nso" }
},
});
#pragma warning disable IDE0055 // Disable formatting
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
dialog.Filters.Add(new FileDialogFilter { Name = "PFS0", Extensions = { "pfs0" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } });
#pragma warning restore IDE0055
string[] files = await dialog.ShowAsync(desktop.MainWindow);
if (files != null && files.Length > 0)
{
LoadApplication(files[0]);
}
});
if (result.Count > 0)
{
LoadApplication(result[0].Path.LocalPath);
}
}
public async void OpenFolder()
{
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
OpenFolderDialog dialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle],
};
Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle],
AllowMultiple = false
});
string folder = await dialog.ShowAsync(desktop.MainWindow);
if (!string.IsNullOrWhiteSpace(folder) && Directory.Exists(folder))
{
LoadApplication(folder);
}
if (result.Count > 0)
{
LoadApplication(result[0].Path.LocalPath);
}
}

View File

@@ -1,7 +1,6 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Threading;
using DynamicData;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL2;
@@ -24,6 +23,7 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
namespace Ryujinx.Ava.UI.ViewModels
@@ -44,7 +44,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private float _volume;
private bool _isVulkanAvailable = true;
private bool _directoryChanged;
private List<string> _gpuIds = new();
private readonly List<string> _gpuIds = new();
private KeyboardHotkeys _keyboardHotkeys;
private int _graphicsBackendIndex;
private string _customThemePath;
@@ -278,7 +278,7 @@ namespace Ryujinx.Ava.UI.ViewModels
_contentManager = contentManager;
if (Program.PreviewerDetached)
{
LoadTimeZones();
Task.Run(LoadTimeZones);
}
}
@@ -290,27 +290,34 @@ namespace Ryujinx.Ava.UI.ViewModels
_validTzRegions = new List<string>();
_networkInterfaces = new Dictionary<string, string>();
CheckSoundBackends();
PopulateNetworkInterfaces();
Task.Run(CheckSoundBackends);
Task.Run(PopulateNetworkInterfaces);
if (Program.PreviewerDetached)
{
LoadAvailableGpus();
Task.Run(LoadAvailableGpus);
LoadCurrentConfiguration();
}
}
public void CheckSoundBackends()
public async Task CheckSoundBackends()
{
IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported;
IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported;
IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported;
await Dispatcher.UIThread.InvokeAsync(() =>
{
OnPropertyChanged(nameof(IsOpenAlEnabled));
OnPropertyChanged(nameof(IsSoundIoEnabled));
OnPropertyChanged(nameof(IsSDL2Enabled));
});
}
private void LoadAvailableGpus()
private async Task LoadAvailableGpus()
{
_gpuIds = new List<string>();
List<string> names = new();
AvailableGpus.Clear();
var devices = VulkanRenderer.GetPhysicalDevices();
if (devices.Length == 0)
@@ -322,16 +329,23 @@ namespace Ryujinx.Ava.UI.ViewModels
{
foreach (var device in devices)
{
_gpuIds.Add(device.Id);
names.Add($"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}");
await Dispatcher.UIThread.InvokeAsync(() =>
{
_gpuIds.Add(device.Id);
AvailableGpus.Add(new ComboBoxItem { Content = $"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}" });
});
}
}
AvailableGpus.Clear();
AvailableGpus.AddRange(names.Select(x => new ComboBoxItem { Content = x }));
// GPU configuration needs to be loaded during the async method or it will always return 0.
PreferredGpuIndex = _gpuIds.Contains(ConfigurationState.Instance.Graphics.PreferredGpu) ?
_gpuIds.IndexOf(ConfigurationState.Instance.Graphics.PreferredGpu) : 0;
Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(PreferredGpuIndex)));
}
public void LoadTimeZones()
public async Task LoadTimeZones()
{
_timeZoneContentManager = new TimeZoneContentManager();
@@ -344,21 +358,34 @@ namespace Ryujinx.Ava.UI.ViewModels
string abbr2 = abbr.StartsWith('+') || abbr.StartsWith('-') ? string.Empty : abbr;
TimeZones.Add(new TimeZone($"UTC{hours:+0#;-0#;+00}:{minutes:D2}", location, abbr2));
await Dispatcher.UIThread.InvokeAsync(() =>
{
TimeZones.Add(new TimeZone($"UTC{hours:+0#;-0#;+00}:{minutes:D2}", location, abbr2));
_validTzRegions.Add(location);
_validTzRegions.Add(location);
});
}
Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(TimeZone)));
}
private void PopulateNetworkInterfaces()
private async Task PopulateNetworkInterfaces()
{
_networkInterfaces.Clear();
_networkInterfaces.Add(LocaleManager.Instance[LocaleKeys.NetworkInterfaceDefault], "0");
foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
{
_networkInterfaces.Add(networkInterface.Name, networkInterface.Id);
await Dispatcher.UIThread.InvokeAsync(() =>
{
_networkInterfaces.Add(networkInterface.Name, networkInterface.Id);
});
}
// Network interface index needs to be loaded during the async method or it will always return 0.
NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(NetworkInterfaceIndex)));
}
public void ValidateAndSetTimeZone(string location)
@@ -416,7 +443,7 @@ namespace Ryujinx.Ava.UI.ViewModels
// Graphics
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
PreferredGpuIndex = _gpuIds.Contains(config.Graphics.PreferredGpu) ? _gpuIds.IndexOf(config.Graphics.PreferredGpu) : 0;
// Physical devices are queried asynchronously hence the prefered index config value is loaded in LoadAvailableGpus().
EnableShaderCache = config.Graphics.EnableShaderCache;
EnableTextureRecompression = config.Graphics.EnableTextureRecompression;
EnableMacroHLE = config.Graphics.EnableMacroHLE;
@@ -437,6 +464,7 @@ namespace Ryujinx.Ava.UI.ViewModels
// Network
EnableInternetAccess = config.System.EnableInternetAccess;
// LAN interface index is loaded asynchronously in PopulateNetworkInterfaces()
// Logging
EnableFileLog = config.Logger.EnableFileLog;
@@ -450,8 +478,6 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(config.Multiplayer.LanInterfaceId.Value);
}
public void SaveSettings()

View File

@@ -1,7 +1,7 @@
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using LibHac.Common;
using LibHac.Fs;
@@ -70,12 +70,19 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public IStorageProvider StorageProvider;
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
{
VirtualFileSystem = virtualFileSystem;
TitleId = titleId;
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
StorageProvider = desktop.MainWindow.StorageProvider;
}
TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
try
@@ -202,29 +209,23 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void Add()
{
OpenFileDialog dialog = new()
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
AllowMultiple = true,
};
dialog.Filters.Add(new FileDialogFilter
{
Name = "NSP",
Extensions = { "nsp" },
});
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
string[] files = await dialog.ShowAsync(desktop.MainWindow);
if (files != null)
FileTypeFilter = new List<FilePickerFileType>
{
foreach (string file in files)
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
{
AddUpdate(file);
Patterns = new[] { "*.nsp" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
MimeTypes = new[] { "application/x-nx-nsp" }
}
}
});
foreach (var file in result)
{
AddUpdate(file.Path.LocalPath);
}
SortUpdates();

View File

@@ -14,7 +14,6 @@
d:DesignWidth="800"
x:Class="Ryujinx.Ava.UI.Views.Input.ControllerInputView"
x:DataType="viewModels:ControllerInputViewModel"
x:CompileBindings="True"
mc:Ignorable="d"
Focusable="True">
<Design.DataContext>
@@ -66,7 +65,7 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
SelectionChanged="PlayerIndexBox_OnSelectionChanged"
Items="{Binding PlayerIndexes}"
ItemsSource="{Binding PlayerIndexes}"
SelectedIndex="{Binding PlayerId}">
<ComboBox.ItemTemplate>
<DataTemplate>
@@ -94,15 +93,15 @@
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsProfile}" />
<ui:ComboBox
<ui:FAComboBox
Grid.Column="1"
IsEditable="True"
Name="ProfileBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
SelectedIndex="0"
Items="{Binding ProfilesList}"
Text="{Binding ProfileName}" />
ItemsSource="{Binding ProfilesList}"
Text="{Binding ProfileName, Mode=TwoWay}" />
<Button
Grid.Column="2"
MinWidth="0"
@@ -170,7 +169,7 @@
Name="DeviceBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Items="{Binding DeviceList}"
ItemsSource="{Binding DeviceList}"
SelectedIndex="{Binding Device}" />
<Button
Grid.Column="2"
@@ -203,8 +202,8 @@
<ComboBox
Grid.Column="1"
HorizontalAlignment="Stretch"
Items="{ReflectionBinding Controllers}"
SelectedIndex="{ReflectionBinding Controller}">
ItemsSource="{Binding Controllers}"
SelectedIndex="{Binding Controller}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="models:ControllerModel">
<TextBlock Text="{Binding Name}" />
@@ -723,7 +722,7 @@
<Button
Margin="10"
Grid.Column="1"
Command="{ReflectionBinding ShowMotionConfig}">
Command="{Binding ShowMotionConfig}">
<TextBlock Text="{locale:Locale ControllerSettingsConfigureGeneral}" />
</Button>
</Grid>
@@ -750,7 +749,7 @@
<Button
Margin="10"
Grid.Column="1"
Command="{ReflectionBinding ShowRumbleConfig}">
Command="{Binding ShowRumbleConfig}">
<TextBlock Text="{locale:Locale ControllerSettingsConfigureGeneral}" />
</Button>
</Grid>

View File

@@ -31,8 +31,7 @@ namespace Ryujinx.Ava.UI.Views.Input
{
if (visual is ToggleButton button && visual is not CheckBox)
{
button.Checked += Button_Checked;
button.Unchecked += Button_Unchecked;
button.IsCheckedChanged += Button_IsCheckedChanged;
}
}
}
@@ -47,48 +46,56 @@ namespace Ryujinx.Ava.UI.Views.Input
}
}
private void Button_Checked(object sender, RoutedEventArgs e)
private void Button_IsCheckedChanged(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton button)
{
if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
if ((bool)button.IsChecked)
{
return;
}
bool isStick = button.Tag != null && button.Tag.ToString() == "stick";
if (_currentAssigner == null && (bool)button.IsChecked)
{
_currentAssigner = new ButtonKeyAssigner(button);
FocusManager.Instance.Focus(this, NavigationMethod.Pointer);
PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner = CreateButtonAssigner(isStick);
_currentAssigner.ButtonAssigned += (sender, e) =>
if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
{
if (e.IsAssigned)
{
ViewModel.IsModified = true;
}
};
return;
}
_currentAssigner.GetInputAndAssign(assigner, keyboard);
bool isStick = button.Tag != null && button.Tag.ToString() == "stick";
if (_currentAssigner == null)
{
_currentAssigner = new ButtonKeyAssigner(button);
this.Focus(NavigationMethod.Pointer);
PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner = CreateButtonAssigner(isStick);
_currentAssigner.ButtonAssigned += (sender, e) =>
{
if (e.IsAssigned)
{
ViewModel.IsModified = true;
}
};
_currentAssigner.GetInputAndAssign(assigner, keyboard);
}
else
{
if (_currentAssigner != null)
{
ToggleButton oldButton = _currentAssigner.ToggledButton;
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
}
}
else
{
if (_currentAssigner != null)
{
ToggleButton oldButton = _currentAssigner.ToggledButton;
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
_currentAssigner?.Cancel();
_currentAssigner = null;
}
}
}
@@ -120,12 +127,6 @@ namespace Ryujinx.Ava.UI.Views.Input
return assigner;
}
private void Button_Unchecked(object sender, RoutedEventArgs e)
{
_currentAssigner?.Cancel();
_currentAssigner = null;
}
private void MouseClick(object sender, PointerPressedEventArgs e)
{
bool shouldUnbind = false;

View File

@@ -8,7 +8,6 @@
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView"
x:CompileBindings="True"
x:DataType="viewModels:MotionInputViewModel"
Focusable="True">
<Grid Margin="10">

View File

@@ -8,7 +8,6 @@
mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView"
x:DataType="viewModels:RumbleInputViewModel"
x:CompileBindings="True"
Focusable="True">
<Grid Margin="10">
<Grid.RowDefinitions>

View File

@@ -7,8 +7,7 @@
mc:Ignorable="d"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel"
x:Class="Ryujinx.Ava.UI.Views.Main.MainMenuBarView"
x:CompileBindings="True">
x:Class="Ryujinx.Ava.UI.Views.Main.MainMenuBarView">
<Design.DataContext>
<viewModels:MainWindowViewModel />
</Design.DataContext>
@@ -25,12 +24,12 @@
</Menu.ItemsPanel>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarFile}">
<MenuItem
Command="{ReflectionBinding OpenFile}"
Command="{Binding OpenFile}"
Header="{locale:Locale MenuBarFileOpenFromFile}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
<MenuItem
Command="{ReflectionBinding OpenFolder}"
Command="{Binding OpenFolder}"
Header="{locale:Locale MenuBarFileOpenUnpacked}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
@@ -42,11 +41,11 @@
</MenuItem>
<Separator />
<MenuItem
Command="{ReflectionBinding OpenRyujinxFolder}"
Command="{Binding OpenRyujinxFolder}"
Header="{locale:Locale MenuBarFileOpenEmuFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" />
<MenuItem
Command="{ReflectionBinding OpenLogsFolder}"
Command="{Binding OpenLogsFolder}"
Header="{locale:Locale MenuBarFileOpenLogsFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" />
<Separator />
@@ -57,35 +56,75 @@
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}">
<MenuItem
Command="{ReflectionBinding ToggleFullscreen}"
Padding="-10,0,0,0"
Command="{Binding ToggleFullscreen}"
Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
InputGesture="F11" />
<MenuItem>
<MenuItem
Padding="0"
Command="{Binding ToggleStartGamesInFullscreen}"
Header="{locale:Locale MenuBarOptionsStartGamesInFullscreen}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}"
MinWidth="250">
<TextBlock Text="{locale:Locale MenuBarOptionsStartGamesInFullscreen}"/>
</CheckBox>
<CheckBox
MinWidth="{DynamicResource CheckBoxSize}"
MinHeight="{DynamicResource CheckBoxSize}"
IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}"
Padding="0" />
</MenuItem.Icon>
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem IsVisible="{Binding ShowConsoleVisible}">
<MenuItem
Padding="0"
IsVisible="{Binding ShowConsoleVisible}"
Command="{Binding ToggleShowConsole}"
Header="{locale:Locale MenuBarOptionsShowConsole}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding ShowConsole, Mode=TwoWay}"
MinWidth="250">
<TextBlock Text="{locale:Locale MenuBarOptionsShowConsole}"/>
</CheckBox>
<CheckBox
MinWidth="{DynamicResource CheckBoxSize}"
MinHeight="{DynamicResource CheckBoxSize}"
IsChecked="{Binding ShowConsole, Mode=TwoWay}"
Padding="0" />
</MenuItem.Icon>
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<Separator />
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
<MenuItem Name="ToggleFileTypesMenuItem" Header="{locale:Locale MenuBarShowFileTypes}" />
<MenuItem
Name="ChangeLanguageMenuItem"
Padding="-10,0,0,0"
Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
<MenuItem
Name="ToggleFileTypesMenuItem"
Padding="-10,0,0,0"
Header="{locale:Locale MenuBarShowFileTypes}" />
<Separator />
<MenuItem
Click="OpenSettings"
Padding="-10,0,0,0"
Header="{locale:Locale MenuBarOptionsSettings}"
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
<MenuItem
Command="{ReflectionBinding ManageProfiles}"
Command="{Binding ManageProfiles}"
Padding="-10,0,0,0"
Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
@@ -113,7 +152,7 @@
InputGesture="Escape"
IsEnabled="{Binding IsGameRunning}"
ToolTip.Tip="{locale:Locale StopEmulationTooltip}" />
<MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
<MenuItem Command="{Binding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
<Separator />
<MenuItem
Name="ScanAmiiboMenuItem"
@@ -122,12 +161,12 @@
Header="{locale:Locale MenuBarActionsScanAmiibo}"
IsEnabled="{Binding IsAmiiboRequested}" />
<MenuItem
Command="{ReflectionBinding TakeScreenshot}"
Command="{Binding TakeScreenshot}"
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
InputGesture="{Binding ScreenshotKey}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Command="{ReflectionBinding HideUi}"
Command="{Binding HideUi}"
Header="{locale:Locale MenuBarFileToolsHideUi}"
InputGesture="{Binding ShowUiKey}"
IsEnabled="{Binding IsGameRunning}" />
@@ -138,8 +177,8 @@
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}">
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
<MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
<MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
</MenuItem>
<MenuItem Header="{locale:Locale MenuBarToolsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}">
<MenuItem Header="{locale:Locale MenuBarToolsInstallFileTypes}" Click="InstallFileTypes_Click"/>

View File

@@ -30,8 +30,8 @@ namespace Ryujinx.Ava.UI.Views.Main
{
InitializeComponent();
ToggleFileTypesMenuItem.Items = GenerateToggleFileTypeItems();
ChangeLanguageMenuItem.Items = GenerateLanguageMenuItems();
ToggleFileTypesMenuItem.ItemsSource = GenerateToggleFileTypeItems();
ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems();
}
private CheckBox[] GenerateToggleFileTypeItems()
@@ -45,7 +45,7 @@ namespace Ryujinx.Ava.UI.Views.Main
{
Content = $".{fileName}",
IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.Ui.ShownFileTypes),
Command = MiniCommand.Create(() => ViewModel.ToggleFileType(fileName)),
Command = MiniCommand.Create(() => Window.ToggleFileType(fileName)),
});
}

View File

@@ -8,7 +8,6 @@
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Views.Main.MainStatusBarView"
x:CompileBindings="True"
x:DataType="viewModels:MainWindowViewModel">
<Design.DataContext>
<viewModels:MainWindowViewModel />
@@ -46,7 +45,7 @@
Margin="0,0,5,0"
VerticalAlignment="Center"
Background="Transparent"
Command="{ReflectionBinding LoadApplications}">
Click="Refresh_OnClick">
<ui:SymbolIcon
Width="50"
Height="100"
@@ -64,7 +63,7 @@
Grid.Column="2"
Height="6"
VerticalAlignment="Center"
Foreground="{DynamicResource HighlightColor}"
Foreground="{DynamicResource SystemAccentColorLight2}"
IsVisible="{Binding StatusBarVisible}"
Maximum="{Binding StatusBarProgressMaximum}"
Value="{Binding StatusBarProgressValue}" />
@@ -93,6 +92,7 @@
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
@@ -109,6 +109,7 @@
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
@@ -125,20 +126,32 @@
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<ui:ToggleSplitButton
<ToggleSplitButton
Name="VolumeStatus"
Padding="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Background="{DynamicResource ThemeContentBackgroundColor}"
BorderThickness="0"
Content="{Binding VolumeStatusText}"
IsChecked="{Binding VolumeMuted}"
IsVisible="{Binding !ShowLoadProgress}">
<ui:ToggleSplitButton.Flyout>
IsVisible="{Binding !ShowLoadProgress}"
Background="Transparent"
BorderThickness="0"
CornerRadius="0">
<ToggleSplitButton.Styles>
<Style Selector=":checked">
<Style Selector="^:checked ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" />
</Style>
</Style>
<Style Selector="Border#SeparatorBorder">
<Setter Property="Opacity" Value="0" />
</Style>
</ToggleSplitButton.Styles>
<ToggleSplitButton.Flyout>
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
<Grid Margin="0">
<Slider
@@ -156,13 +169,14 @@
Value="{Binding Volume}" />
</Grid>
</Flyout>
</ui:ToggleSplitButton.Flyout>
</ui:ToggleSplitButton>
</ToggleSplitButton.Flyout>
</ToggleSplitButton>
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
@@ -177,6 +191,7 @@
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
@@ -191,6 +206,7 @@
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
@@ -205,6 +221,7 @@
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
@@ -229,4 +246,4 @@
Text="{locale:Locale StatusBarSystemVersion}" />
</StackPanel>
</Grid>
</UserControl>
</UserControl>

View File

@@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
@@ -48,5 +49,10 @@ namespace Ryujinx.Ava.UI.Views.Main
ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1;
}
private void Refresh_OnClick(object sender, RoutedEventArgs e)
{
Window.LoadApplications();
}
}
}

View File

@@ -9,7 +9,6 @@
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Views.Main.MainViewControls"
x:CompileBindings="True"
x:DataType="viewModels:MainWindowViewModel">
<Design.DataContext>
<viewModels:MainWindowViewModel />
@@ -23,7 +22,7 @@
MinWidth="40"
Margin="5,2,0,2"
VerticalAlignment="Stretch"
Command="{ReflectionBinding SetListMode}"
Command="{Binding SetListMode}"
IsEnabled="{Binding IsGrid}">
<ui:FontIcon
Margin="0"
@@ -37,7 +36,7 @@
MinWidth="40"
Margin="5,2,5,2"
VerticalAlignment="Stretch"
Command="{ReflectionBinding SetGridMode}"
Command="{Binding SetGridMode}"
IsEnabled="{Binding IsList}">
<ui:FontIcon
Margin="0"
@@ -79,13 +78,13 @@
KeyUp="SearchBox_OnKeyUp"
Text="{Binding SearchText}"
Watermark="{locale:Locale MenuSearch}" />
<ui:DropDownButton
<DropDownButton
Width="150"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{Binding SortName}"
DockPanel.Dock="Right">
<ui:DropDownButton.Flyout>
<DropDownButton.Flyout>
<Flyout Placement="Bottom">
<StackPanel
Margin="0"
@@ -164,8 +163,8 @@
Tag="Descending" />
</StackPanel>
</Flyout>
</ui:DropDownButton.Flyout>
</ui:DropDownButton>
</DropDownButton.Flyout>
</DropDownButton>
<TextBlock
Margin="10,0"
HorizontalAlignment="Right"

View File

@@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsAudioView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -8,7 +8,6 @@
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />

View File

@@ -7,7 +7,6 @@
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />

View File

@@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsGraphicsView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -9,7 +9,6 @@
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
Design.Width="1000"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />
@@ -54,7 +53,7 @@
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale SettingsTabGraphicsPreferredGpuTooltip}"
SelectedIndex="{Binding PreferredGpuIndex}"
Items="{Binding AvailableGpus}"/>
ItemsSource="{Binding AvailableGpus}"/>
</StackPanel>
</StackPanel>
<Separator Height="1" />

View File

@@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsHotkeysView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -8,7 +8,6 @@
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel"
Focusable="True">
<Design.DataContext>
@@ -17,7 +16,7 @@
<UserControl.Resources>
<helpers:KeyValueConverter x:Key="Key" />
</UserControl.Resources>
<ScrollViewer
<ScrollViewer
Name="HotkeysPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"

View File

@@ -42,7 +42,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
{
_currentAssigner = new ButtonKeyAssigner(button);
FocusManager.Instance?.Focus(this, NavigationMethod.Pointer);
this.Focus(NavigationMethod.Pointer);
PointerPressed += MouseClick;

View File

@@ -8,7 +8,6 @@
xmlns:views="clr-namespace:Ryujinx.Ava.UI.Views.Input"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />

View File

@@ -8,7 +8,6 @@
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />

View File

@@ -7,7 +7,6 @@
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />
@@ -37,7 +36,7 @@
<ComboBox SelectedIndex="{Binding NetworkInterfaceIndex}"
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
HorizontalContentAlignment="Left"
Items="{Binding NetworkInterfaceList}"
ItemsSource="{Binding NetworkInterfaceList}"
Width="250" />
</StackPanel>
</StackPanel>

View File

@@ -3,12 +3,15 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel"
mc:Ignorable="d">
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
mc:Ignorable="d"
x:DataType="viewModels:SettingsViewModel">
<UserControl.Resources>
<helpers:TimeZoneConverter x:Key="TimeZone" />
</UserControl.Resources>
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
@@ -24,18 +27,24 @@
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemCore}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock
Classes="h1"
Text="{locale:Locale SettingsTabSystemCore}" />
<StackPanel
Margin="10,0,0,0"
Orientation="Vertical">
<StackPanel
Margin="0,0,0,10"
Orientation="Horizontal">
<TextBlock
Width="250"
VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemRegion}" />
Text="{locale:Locale SettingsTabSystemSystemRegion}"
Width="250" />
<ComboBox
Width="350"
HorizontalContentAlignment="Left"
SelectedIndex="{Binding Region}"
ToolTip.Tip="{locale:Locale RegionTooltip}">
ToolTip.Tip="{locale:Locale RegionTooltip}"
HorizontalContentAlignment="Left"
Width="350">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionJapan}" />
</ComboBoxItem>
@@ -59,17 +68,19 @@
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<StackPanel
Margin="0,0,0,10"
Orientation="Horizontal">
<TextBlock
Width="250"
VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemLanguage}"
ToolTip.Tip="{locale:Locale LanguageTooltip}" />
ToolTip.Tip="{locale:Locale LanguageTooltip}"
Width="250" />
<ComboBox
Width="350"
HorizontalContentAlignment="Left"
SelectedIndex="{Binding Language}"
ToolTip.Tip="{locale:Locale LanguageTooltip}">
ToolTip.Tip="{locale:Locale LanguageTooltip}"
HorizontalContentAlignment="Left"
Width="350">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageJapanese}" />
</ComboBoxItem>
@@ -126,63 +137,84 @@
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<StackPanel
Margin="0,0,0,10"
Orientation="Horizontal">
<TextBlock
Width="250"
VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemTimeZone}"
ToolTip.Tip="{locale:Locale TimezoneTooltip}" />
ToolTip.Tip="{locale:Locale TimezoneTooltip}"
Width="250" />
<AutoCompleteBox
Name="TimeZoneBox"
Width="350"
FilterMode="Contains"
Items="{Binding TimeZones}"
MaxDropDownHeight="500"
FilterMode="Contains"
ItemsSource="{Binding TimeZones}"
SelectionChanged="TimeZoneBox_OnSelectionChanged"
Text="{Binding Path=TimeZone, Mode=OneWay}"
TextChanged="TimeZoneBox_OnTextChanged"
ToolTip.Tip="{locale:Locale TimezoneTooltip}" />
ToolTip.Tip="{locale:Locale TimezoneTooltip}"
ValueMemberBinding="{Binding Mode=OneWay, Converter={StaticResource TimeZone}}" />
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<StackPanel
Margin="0,0,0,10"
Orientation="Horizontal">
<TextBlock
Width="250"
VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemTime}"
ToolTip.Tip="{locale:Locale TimeTooltip}" />
ToolTip.Tip="{locale:Locale TimeTooltip}"
Width="250"/>
<DatePicker
Width="350"
VerticalAlignment="Center"
VerticalAlignment="Center"
SelectedDate="{Binding CurrentDate}"
ToolTip.Tip="{locale:Locale TimeTooltip}" />
ToolTip.Tip="{locale:Locale TimeTooltip}"
Width="350" />
</StackPanel>
<StackPanel Margin="250,0,0,10" Orientation="Horizontal">
<StackPanel
Margin="250,0,0,10"
Orientation="Horizontal">
<TimePicker
Width="350"
VerticalAlignment="Center"
ClockIdentifier="24HourClock"
SelectedTime="{Binding CurrentTime}"
Width="350"
ToolTip.Tip="{locale:Locale TimeTooltip}" />
</StackPanel>
<CheckBox IsChecked="{Binding EnableVsync}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableVsync}" ToolTip.Tip="{locale:Locale VSyncToggleTooltip}" />
<TextBlock
Text="{locale:Locale SettingsTabSystemEnableVsync}"
ToolTip.Tip="{locale:Locale VSyncToggleTooltip}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableFsIntegrityChecks}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableFsIntegrityChecks}" ToolTip.Tip="{locale:Locale FsIntegrityToggleTooltip}" />
<TextBlock
Text="{locale:Locale SettingsTabSystemEnableFsIntegrityChecks}"
ToolTip.Tip="{locale:Locale FsIntegrityToggleTooltip}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<StackPanel Orientation="Vertical" Spacing="2">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemHacks}" />
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{locale:Locale SettingsTabSystemHacksNote}" />
<StackPanel
Orientation="Vertical"
Spacing="2">
<TextBlock
Classes="h1"
Text="{locale:Locale SettingsTabSystemHacks}" />
<TextBlock
Foreground="{DynamicResource SecondaryTextColor}"
Text="{locale:Locale SettingsTabSystemHacksNote}" />
</StackPanel>
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<CheckBox IsChecked="{Binding ExpandDramSize}" ToolTip.Tip="{locale:Locale DRamTooltip}">
<CheckBox
IsChecked="{Binding ExpandDramSize}"
ToolTip.Tip="{locale:Locale DRamTooltip}">
<TextBlock Text="{locale:Locale SettingsTabSystemExpandDramSize}" />
</CheckBox>
<CheckBox IsChecked="{Binding IgnoreMissingServices}" ToolTip.Tip="{locale:Locale IgnoreMissingServicesTooltip}">
<CheckBox
IsChecked="{Binding IgnoreMissingServices}"
ToolTip.Tip="{locale:Locale IgnoreMissingServicesTooltip}">
<TextBlock Text="{locale:Locale SettingsTabSystemIgnoreMissingServices}" />
</CheckBox>
</StackPanel>

View File

@@ -1,9 +1,5 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Ryujinx.Ava.UI.ViewModels;
using System;
using System.Linq;
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
namespace Ryujinx.Ava.UI.Views.Settings
@@ -15,15 +11,6 @@ namespace Ryujinx.Ava.UI.Views.Settings
public SettingsSystemView()
{
InitializeComponent();
FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray()).Trim());
MultiBinding tzMultiBinding = new() { Converter = converter };
tzMultiBinding.Bindings.Add(new Binding("UtcDifference"));
tzMultiBinding.Bindings.Add(new Binding("Location"));
tzMultiBinding.Bindings.Add(new Binding("Abbreviation"));
TimeZoneBox.ValueMemberBinding = tzMultiBinding;
}
private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -39,13 +26,11 @@ namespace Ryujinx.Ava.UI.Views.Settings
}
}
private void TimeZoneBox_OnTextChanged(object sender, EventArgs e)
private void TimeZoneBox_OnTextChanged(object sender, TextChangedEventArgs e)
{
if (sender is AutoCompleteBox box && box.SelectedItem is TimeZone timeZone)
{
{
ViewModel.ValidateAndSetTimeZone(timeZone.Location);
}
ViewModel.ValidateAndSetTimeZone(timeZone.Location);
}
}
}

View File

@@ -7,7 +7,6 @@
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
<Design.DataContext>
<viewModels:SettingsViewModel />
@@ -66,7 +65,7 @@
<ListBox
Name="GameList"
MinHeight="230"
Items="{Binding GameDirectories}">
ItemsSource="{Binding GameDirectories}">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="10" />

View File

@@ -1,7 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Avalonia.VisualTree;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using System.Collections.Generic;
@@ -30,13 +30,16 @@ namespace Ryujinx.Ava.UI.Views.Settings
}
else
{
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
if (this.GetVisualRoot() is Window window)
{
path = await new OpenFolderDialog().ShowAsync(desktop.MainWindow);
if (!string.IsNullOrWhiteSpace(path))
var result = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
ViewModel.GameDirectories.Add(path);
AllowMultiple = false
});
if (result.Count > 0)
{
ViewModel.GameDirectories.Add(result[0].Path.LocalPath);
ViewModel.DirectoryChanged = true;
}
}
@@ -61,22 +64,25 @@ namespace Ryujinx.Ava.UI.Views.Settings
public async void BrowseTheme(object sender, RoutedEventArgs e)
{
var dialog = new OpenFileDialog
var window = this.GetVisualRoot() as Window;
var result = await window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.SettingsSelectThemeFileDialogTitle],
AllowMultiple = false,
};
dialog.Filters.Add(new FileDialogFilter { Extensions = { "xaml" }, Name = LocaleManager.Instance[LocaleKeys.SettingsXamlThemeFile] });
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var file = await dialog.ShowAsync(desktop.MainWindow);
if (file != null && file.Length > 0)
FileTypeFilter = new List<FilePickerFileType>
{
ViewModel.CustomThemePath = file[0];
new("xml")
{
Patterns = new[] { "*.xaml" },
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xaml" },
MimeTypes = new[] { "application/xaml+xml" }
}
}
});
if (result.Count > 0)
{
ViewModel.CustomThemePath = result[0].Path.LocalPath;
}
}
}

View File

@@ -13,7 +13,6 @@
Padding="0"
mc:Ignorable="d"
Focusable="True"
x:CompileBindings="True"
x:DataType="models:TempProfile">
<UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />

View File

@@ -12,7 +12,6 @@
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
x:CompileBindings="True"
x:DataType="viewModels:UserFirmwareAvatarSelectorViewModel"
Focusable="True">
<Design.DataContext>
@@ -36,7 +35,7 @@
BorderThickness="0"
SelectedIndex="{Binding SelectedIndex}"
Height="400"
Items="{Binding Images}"
ItemsSource="{Binding Images}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<ListBox.ItemsPanel>
@@ -54,7 +53,7 @@
<Setter Property="MaxWidth" Value="85" />
<Setter Property="MinWidth" Value="85" />
</Style>
<Style Selector="ListBoxItem /template/ Border#SelectionIndicator">
<Style Selector="ListBoxItem /template/ Rectangle#SelectionIndicator">
<Setter Property="MinHeight" Value="70" />
</Style>
</ListBox.Styles>

View File

@@ -9,7 +9,6 @@
Focusable="True"
mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Views.User.UserProfileImageSelectorView"
x:CompileBindings="True"
x:DataType="viewModles:UserProfileImageSelectorViewModel"
Width="500"
d:DesignWidth="500">

View File

@@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Avalonia.VisualTree;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
@@ -10,6 +11,7 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using System.Collections.Generic;
using System.IO;
using Image = SixLabors.ImageSharp.Image;
@@ -63,33 +65,25 @@ namespace Ryujinx.Ava.UI.Views.User
private async void Import_OnClick(object sender, RoutedEventArgs e)
{
OpenFileDialog dialog = new();
dialog.Filters.Add(new FileDialogFilter
var window = this.GetVisualRoot() as Window;
var result = await window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Name = LocaleManager.Instance[LocaleKeys.AllSupportedFormats],
Extensions = { "jpg", "jpeg", "png", "bmp" },
});
dialog.Filters.Add(new FileDialogFilter { Name = "JPEG", Extensions = { "jpg", "jpeg" } });
dialog.Filters.Add(new FileDialogFilter { Name = "PNG", Extensions = { "png" } });
dialog.Filters.Add(new FileDialogFilter { Name = "BMP", Extensions = { "bmp" } });
dialog.AllowMultiple = false;
string[] image = await dialog.ShowAsync(((TopLevel)_parent.GetVisualRoot()) as Window);
if (image != null)
{
if (image.Length > 0)
AllowMultiple = false,
FileTypeFilter = new List<FilePickerFileType>
{
string imageFile = image[0];
_profile.Image = ProcessProfileImage(File.ReadAllBytes(imageFile));
if (_profile.Image != null)
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
{
_parent.GoBack();
Patterns = new[] { "*.jpg", "*.jpeg", "*.png", "*.bmp" },
AppleUniformTypeIdentifiers = new[] { "public.jpeg", "public.png", "com.microsoft.bmp" },
MimeTypes = new[] { "image/jpeg", "image/png", "image/bmp" }
}
}
});
if (result.Count > 0)
{
_profile.Image = ProcessProfileImage(File.ReadAllBytes(result[0].Path.LocalPath));
_parent.GoBack();
}
}

View File

@@ -12,7 +12,6 @@
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:Class="Ryujinx.Ava.UI.Views.User.UserRecovererView"
x:CompileBindings="True"
x:DataType="viewModels:UserProfileViewModel"
Focusable="True">
<Design.DataContext>
@@ -33,7 +32,7 @@
<ListBox
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Items="{Binding LostProfiles}">
ItemsSource="{Binding LostProfiles}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border

View File

@@ -14,7 +14,6 @@
Height="450"
Width="550"
x:Class="Ryujinx.Ava.UI.Views.User.UserSaveManagerView"
x:CompileBindings="True"
x:DataType="viewModels:UserSaveManagerViewModel"
Focusable="True">
<Design.DataContext>
@@ -107,8 +106,7 @@
VerticalAlignment="Stretch">
<ListBox
Name="SaveList"
VirtualizationMode="None"
Items="{Binding Views}"
ItemsSource="{Binding Views}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ListBox.Styles>
@@ -117,7 +115,7 @@
<Setter Property="Margin" Value="5" />
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
<Setter Property="IsVisible" Value="False" />
</Style>
</ListBox.Styles>

View File

@@ -15,7 +15,6 @@
d:DesignWidth="800"
mc:Ignorable="d"
Focusable="True"
x:CompileBindings="True"
x:DataType="viewModels:UserProfileViewModel">
<UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
@@ -38,7 +37,7 @@
VerticalAlignment="Center"
SelectionChanged="ProfilesList_SelectionChanged"
Background="Transparent"
Items="{Binding Profiles}">
ItemsSource="{Binding Profiles}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<flex:FlexPanel
@@ -53,7 +52,7 @@
<Setter Property="Margin" Value="5 5 0 5" />
<Setter Property="CornerRadius" Value="5" />
</Style>
<Style Selector="Border#SelectionIndicator">
<Style Selector="Rectangle#SelectionIndicator">
<Setter Property="Opacity" Value="0" />
</Style>
</ListBox.Styles>
@@ -61,8 +60,8 @@
<DataTemplate
DataType="models:UserProfile">
<Grid
PointerEnter="Grid_PointerEntered"
PointerLeave="Grid_OnPointerExited">
PointerEntered="Grid_PointerEntered"
PointerExited="Grid_OnPointerExited">
<Border
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"

View File

@@ -13,7 +13,6 @@
Margin="0,-12,0,0"
d:DesignHeight="260"
d:DesignWidth="550"
x:CompileBindings="True"
x:DataType="viewModel:AboutWindowViewModel"
Focusable="True"
mc:Ignorable="d">
@@ -64,14 +63,14 @@
FontWeight="Bold"
Text="Ryujinx"
TextAlignment="Center"
Width="100" />
Width="110" />
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="11"
Text="(REE-YOU-JINX)"
TextAlignment="Center"
Width="100" />
Width="110" />
</flex:FlexPanel>
</Grid>
<TextBlock
@@ -265,4 +264,4 @@
</StackPanel>
</Grid>
</Grid>
</UserControl>
</UserControl>

View File

@@ -1,4 +1,4 @@
<window:StyleableWindow
<window:StyleableWindow
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -6,14 +6,15 @@
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
mc:Ignorable="d"
d:DesignWidth="400"
mc:Ignorable="d"
d:DesignWidth="400"
d:DesignHeight="350"
x:Class="Ryujinx.Ava.UI.Windows.AmiiboWindow"
x:DataType="viewModels:AmiiboWindowViewModel"
CanResize="False"
WindowStartupLocation="CenterOwner"
Width="800"
MinHeight="650"
Width="800"
MinHeight="650"
Height="650"
SizeToContent="Manual"
MinWidth="600"
@@ -35,11 +36,11 @@
</Grid.ColumnDefinitions>
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale AmiiboSeriesLabel}" />
<ComboBox SelectedIndex="{Binding SeriesSelectedIndex}" Items="{Binding AmiiboSeries}" MinWidth="100" />
<ComboBox SelectedIndex="{Binding SeriesSelectedIndex}" ItemsSource="{Binding AmiiboSeries}" MinWidth="100" />
</StackPanel>
<StackPanel Spacing="10" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale AmiiboCharacterLabel}" />
<ComboBox SelectedIndex="{Binding AmiiboSelectedIndex}" MinWidth="100" Items="{Binding AmiiboList}" />
<ComboBox SelectedIndex="{Binding AmiiboSelectedIndex}" MinWidth="100" ItemsSource="{Binding AmiiboList}" />
</StackPanel>
</Grid>
<StackPanel Margin="20" Grid.Row="2">

View File

@@ -40,7 +40,7 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
LineHeight="18"
Text="{Binding Heading}"
Text="{ReflectionBinding Heading}"
TextAlignment="Center"
TextWrapping="Wrap" />
<TextBlock
@@ -61,7 +61,7 @@
MinWidth="160"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding BuildId}"
Text="{ReflectionBinding BuildId}"
IsReadOnly="True" />
<Border
Grid.Row="3"
@@ -77,7 +77,7 @@
MinHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Items="{Binding LoadedCheats}">
ItemsSource="{ReflectionBinding LoadedCheats}">
<TreeView.Styles>
<Styles>
<Style Selector="TreeViewItem:empty /template/ ItemsPresenter">
@@ -120,15 +120,15 @@
Name="SaveButton"
MinWidth="90"
Margin="5"
Command="{Binding Save}"
IsVisible="{Binding !NoCheatsFound}">
Command="{ReflectionBinding Save}"
IsVisible="{ReflectionBinding !NoCheatsFound}">
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
</Button>
<Button
Name="CancelButton"
MinWidth="90"
Margin="5"
Command="{Binding Close}">
Command="{ReflectionBinding Close}">
<TextBlock Text="{locale:Locale InputDialogCancel}" />
</Button>
</DockPanel>

View File

@@ -1,8 +1,5 @@
using Avalonia.Controls;
using Avalonia.Media;
#if DEBUG
using Avalonia;
#endif
namespace Ryujinx.Ava.UI.Windows
{
@@ -11,11 +8,9 @@ namespace Ryujinx.Ava.UI.Windows
public ContentDialogOverlayWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
ExtendClientAreaToDecorationsHint = true;
TransparencyLevelHint = WindowTransparencyLevel.Transparent;
TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent };
WindowStartupLocation = WindowStartupLocation.Manual;
SystemDecorations = SystemDecorations.None;
ExtendClientAreaTitleBarHeightHint = 0;

View File

@@ -11,7 +11,6 @@
Width="500"
Height="380"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:DownloadableContentManagerViewModel"
Focusable="True">
<Grid>
@@ -53,8 +52,8 @@
</StackPanel>
<TextBox
Grid.Column="2"
MinHeight="27"
MaxHeight="27"
MinHeight="29"
MaxHeight="29"
HorizontalAlignment="Stretch"
Watermark="{locale:Locale Search}"
Text="{Binding Search}" />
@@ -71,12 +70,11 @@
Padding="2.5">
<ListBox
AutoScrollToSelectedItem="False"
VirtualizationMode="None"
SelectionMode="Multiple, Toggle"
Background="Transparent"
SelectionChanged="OnSelectionChanged"
SelectedItems="{Binding SelectedDownloadableContents, Mode=TwoWay}"
Items="{Binding Views}">
ItemsSource="{Binding Views}">
<ListBox.DataTemplates>
<DataTemplate
DataType="models:DownloadableContentModel">

View File

@@ -18,9 +18,9 @@
MinHeight="672"
d:DesignHeight="720"
d:DesignWidth="1280"
x:CompileBindings="True"
x:DataType="viewModels:MainWindowViewModel"
mc:Ignorable="d"
WindowStartupLocation="Manual"
Focusable="True">
<Window.Styles>
<Style Selector="TitleBar:fullscreen">
@@ -33,19 +33,19 @@
<Window.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
</Window.Resources>
<Window.KeyBindings>
<KeyBinding Gesture="Alt+Return" Command="{Binding ToggleFullscreen}" />
<KeyBinding Gesture="F11" Command="{Binding ToggleFullscreen}" />
<KeyBinding Gesture="Ctrl+Cmd+F" Command="{Binding ToggleFullscreen}" />
<KeyBinding Gesture="F9" Command="{Binding ToggleDockMode}" />
<KeyBinding Gesture="Escape" Command="{Binding ExitCurrentState}" />
</Window.KeyBindings>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<helpers:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" />
<StackPanel Grid.Row="0" IsVisible="False">
<helpers:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" />
<helpers:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" />
<helpers:HotKeyControl Name="FullscreenHotKeyMacOS" Command="{ReflectionBinding ToggleFullscreen}" />
<helpers:HotKeyControl Name="DockToggleHotKey" Command="{ReflectionBinding ToggleDockMode}" />
<helpers:HotKeyControl Name="ExitHotKey" Command="{ReflectionBinding ExitCurrentState}" />
</StackPanel>
<Grid
Grid.Row="1"
HorizontalAlignment="Stretch"
@@ -67,7 +67,7 @@
VerticalAlignment="Stretch"
IsVisible="{Binding ShowMenuAndStatusBar}"
Orientation="Vertical">
<main:MainMenuBarView
<main:MainMenuBarView
Name="MenuBarView" />
</StackPanel>
<ContentControl
@@ -197,7 +197,7 @@
</Grid>
</Grid>
</Grid>
<main:MainStatusBarView
<main:MainStatusBarView
Name="StatusBarView"
Grid.Row="2" />
</Grid>

View File

@@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common;
@@ -21,7 +22,6 @@ using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.Versioning;
using System.Threading.Tasks;
@@ -85,6 +85,7 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.Initialize(
ContentManager,
StorageProvider,
ApplicationLibrary,
VirtualFileSystem,
AccountManager,
@@ -102,11 +103,16 @@ namespace Ryujinx.Ava.UI.Windows
LoadGameList();
this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged);
this.ScalingChanged += OnScalingChanged;
}
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
ViewModel.ReloadGameList += ReloadGameList;
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
NotificationHelper.SetNotificationManager(this);
}
@@ -130,10 +136,9 @@ namespace Ryujinx.Ava.UI.Windows
_isLoading = false;
}
protected override void HandleScalingChanged(double scale)
private void OnScalingChanged(object sender, EventArgs e)
{
Program.DesktopScaleFactor = scale;
base.HandleScalingChanged(scale);
Program.DesktopScaleFactor = this.RenderScaling;
}
public void AddApplication(ApplicationData applicationData)
@@ -221,16 +226,6 @@ namespace Ryujinx.Ava.UI.Windows
});
}
protected override void HandleWindowStateChanged(WindowState state)
{
ViewModel.WindowState = state;
if (state != WindowState.Minimized)
{
Renderer.Start();
}
}
private void Initialize()
{
_userChannelPersistence = new UserChannelPersistence();
@@ -367,14 +362,12 @@ namespace Ryujinx.Ava.UI.Windows
ApplicationList.ApplicationOpened += Application_Opened;
ApplicationList.DataContext = ViewModel;
LoadHotKeys();
}
private void SetWindowSizePosition()
{
PixelPoint savedPoint = new(ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX,
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY);
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY);
ViewModel.WindowHeight = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
ViewModel.WindowWidth = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor;
@@ -447,18 +440,7 @@ namespace Ryujinx.Ava.UI.Windows
#pragma warning restore IDE0055
}
public void LoadHotKeys()
{
#pragma warning disable IDE0055 // Disable formatting
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
HotKeyManager.SetHotKey(FullscreenHotKeyMacOS, new KeyGesture(Key.F, KeyModifiers.Control | KeyModifiers.Meta));
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
#pragma warning restore IDE0055
}
private void VolumeStatus_CheckedChanged(object sender, SplitButtonClickEventArgs e)
private void VolumeStatus_CheckedChanged(object sender, RoutedEventArgs e)
{
var volumeSplitButton = sender as ToggleSplitButton;
if (ViewModel.IsGameRunning)
@@ -476,7 +458,7 @@ namespace Ryujinx.Ava.UI.Windows
}
}
protected override void OnClosing(CancelEventArgs e)
protected override void OnClosing(WindowClosingEventArgs e)
{
if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit)
{
@@ -548,6 +530,25 @@ namespace Ryujinx.Ava.UI.Windows
ReloadGameList();
}
public void ToggleFileType(string fileType)
{
_ = fileType switch
{
#pragma warning disable IDE0055 // Disable formatting
"NSP" => ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSP,
"PFS0" => ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.PFS0,
"XCI" => ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.XCI,
"NCA" => ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NCA,
"NRO" => ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NRO,
"NSO" => ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSO,
_ => throw new ArgumentOutOfRangeException(fileType),
#pragma warning restore IDE0055
};
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
LoadApplications();
}
private void ReloadGameList()
{
if (_isLoading)

View File

@@ -15,7 +15,6 @@
MinWidth="800"
MinHeight="480"
WindowStartupLocation="CenterOwner"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel"
mc:Ignorable="d"
Focusable="True">
@@ -59,44 +58,44 @@
IsSelected="True"
Content="{locale:Locale SettingsTabGeneral}"
Tag="UiPage"
Icon="New" />
IconSource="New" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabInput}"
Tag="InputPage"
Icon="Games" />
IconSource="Games" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabHotkeys}"
Tag="HotkeysPage"
Icon="Keyboard" />
IconSource="Keyboard" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabSystem}"
Tag="SystemPage"
Icon="Settings" />
IconSource="Settings" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabCpu}"
Tag="CpuPage">
<ui:NavigationViewItem.Icon>
<ui:FontIcon
<ui:NavigationViewItem.IconSource>
<ui:FontIconSource
FontFamily="avares://Ryujinx.Ava/Assets/Fonts#Segoe Fluent Icons"
Glyph="{helpers:GlyphValueConverter Chip}" />
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabGraphics}"
Tag="GraphicsPage"
Icon="Image" />
IconSource="Image" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabAudio}"
Icon="Audio"
IconSource="Audio"
Tag="AudioPage" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabNetwork}"
Tag="NetworkPage"
Icon="Globe" />
IconSource="Globe" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabLogging}"
Tag="LoggingPage"
Icon="Document" />
IconSource="Document" />
</ui:NavigationView.MenuItems>
<ui:NavigationView.Styles>
<Style Selector="Grid#PlaceholderGrid">
@@ -115,14 +114,14 @@
HotKey="Enter"
Classes="accent"
Content="{locale:Locale SettingsButtonOk}"
Command="{ReflectionBinding OkButton}" />
Command="{Binding OkButton}" />
<Button
HotKey="Escape"
Content="{locale:Locale SettingsButtonCancel}"
Command="{ReflectionBinding CancelButton}" />
Command="{Binding CancelButton}" />
<Button
Content="{locale:Locale SettingsButtonApply}"
Command="{ReflectionBinding ApplyButton}" />
Command="{Binding ApplyButton}" />
</ReversibleStackPanel>
</Grid>
</window:StyleableWindow>

View File

@@ -1,10 +1,10 @@
using Avalonia.Controls;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
using System;
using System.ComponentModel;
namespace Ryujinx.Ava.UI.Windows
{
@@ -41,7 +41,7 @@ namespace Ryujinx.Ava.UI.Windows
if (Owner is MainWindow window && ViewModel.DirectoryChanged)
{
window.ViewModel.LoadApplications();
window.LoadApplications();
}
}
@@ -93,7 +93,7 @@ namespace Ryujinx.Ava.UI.Windows
}
}
protected override void OnClosing(CancelEventArgs e)
protected override void OnClosing(WindowClosingEventArgs e)
{
HotkeysPage.Dispose();
InputPage.Dispose();

View File

@@ -11,12 +11,12 @@ namespace Ryujinx.Ava.UI.Windows
{
public class StyleableWindow : Window
{
public IBitmap IconImage { get; set; }
public Bitmap IconImage { get; set; }
public StyleableWindow()
{
WindowStartupLocation = WindowStartupLocation.CenterOwner;
TransparencyLevelHint = WindowTransparencyLevel.None;
TransparencyLevelHint = new[] { WindowTransparencyLevel.None };
using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState)).GetManifestResourceStream("Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");

View File

@@ -11,7 +11,6 @@
Width="500"
Height="300"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:TitleUpdateViewModel"
Focusable="True">
<Grid>
@@ -29,10 +28,9 @@
CornerRadius="5"
Padding="2.5">
<ListBox
VirtualizationMode="None"
Background="Transparent"
SelectedItem="{Binding SelectedUpdate, Mode=TwoWay}"
Items="{Binding Views}">
ItemsSource="{Binding Views}">
<ListBox.DataTemplates>
<DataTemplate
DataType="models:TitleUpdateModel">
@@ -103,7 +101,7 @@
<Button
Name="AddButton"
MinWidth="90"
Command="{ReflectionBinding Add}">
Command="{Binding Add}">
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
</Button>
<Button

View File

@@ -61,7 +61,7 @@ namespace Ryujinx.Ava.UI.Windows
if (VisualRoot is MainWindow window)
{
window.ViewModel.LoadApplications();
window.LoadApplications();
}
((ContentDialog)Parent).Hide();

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="Ryujinx.Emulator.Avalonia"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 & 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View File

@@ -28,8 +28,8 @@ namespace Ryujinx.Common.SystemInterop
[LibraryImport(X11LibraryName)]
private static partial int XCloseDisplay(IntPtr display);
private static readonly double _standardDpiScale = 96.0;
private static readonly double _maxScaleFactor = 1.25;
private const double StandardDpiScale = 96.0;
private const double MaxScaleFactor = 1.25;
/// <summary>
/// Marks the application as DPI-Aware when running on the Windows operating system.
@@ -90,7 +90,7 @@ namespace Ryujinx.Common.SystemInterop
{
double userDpiScale = GetActualScaleFactor();
return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor);
return Math.Min(userDpiScale / StandardDpiScale, MaxScaleFactor);
}
}
}

View File

@@ -344,7 +344,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
// Optimization: If the data being copied is already in memory, then copy it directly instead of flushing from GPU.
dstBuffer.ClearModified(dstAddress, size);
memoryManager.Physical.WriteUntracked(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size));
memoryManager.Physical.WriteTrackedResource(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
}
}

View File

@@ -236,6 +236,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
_cpuMemory.WriteUntracked(address, data);
}
/// <summary>
/// Writes data to the application process, triggering a precise memory tracking event.
/// </summary>
/// <param name="address">Address to write into</param>
/// <param name="data">Data to be written</param>
/// <param name="kind">Kind of the resource being written, which will not be signalled as CPU modified</param>
public void WriteTrackedResource(ulong address, ReadOnlySpan<byte> data, ResourceKind kind)
{
_cpuMemory.SignalMemoryTracking(address, (ulong)data.Length, true, precise: true, exemptId: (int)kind);
_cpuMemory.WriteUntracked(address, data);
}
/// <summary>
/// Writes data to the application process.
/// </summary>

View File

@@ -1,5 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
@@ -44,6 +43,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
_newSpecState = newSpecState;
_stageIndex = stageIndex;
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
if (stageIndex == (int)ShaderStage.Geometry - 1)
{
// Only geometry shaders require the primitive topology.
newSpecState.RecordPrimitiveTopology();
}
}
/// <inheritdoc/>
@@ -69,48 +74,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return MemoryMarshal.Cast<byte, ulong>(_data.Span[(int)address..]);
}
/// <inheritdoc/>
public bool QueryAlphaToCoverageDitherEnable()
{
return _oldSpecState.GraphicsState.AlphaToCoverageEnable && _oldSpecState.GraphicsState.AlphaToCoverageDitherEnable;
}
/// <inheritdoc/>
public AlphaTestOp QueryAlphaTestCompare()
{
if (!_isVulkan || !_oldSpecState.GraphicsState.AlphaTestEnable)
{
return AlphaTestOp.Always;
}
return _oldSpecState.GraphicsState.AlphaTestCompare switch
{
CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
_ => AlphaTestOp.Always,
};
}
/// <inheritdoc/>
public float QueryAlphaTestReference() => _oldSpecState.GraphicsState.AlphaTestReference;
/// <inheritdoc/>
public AttributeType QueryAttributeType(int location)
{
return _oldSpecState.GraphicsState.AttributeTypes[location];
}
/// <inheritdoc/>
public AttributeType QueryFragmentOutputType(int location)
{
return _oldSpecState.GraphicsState.FragmentOutputTypes[location];
}
/// <inheritdoc/>
public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;
@@ -133,55 +96,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.ConstantBufferUse[_stageIndex];
}
/// <inheritdoc/>
public GpuGraphicsState QueryGraphicsState()
{
return _oldSpecState.GraphicsState.CreateShaderGraphicsState(!_isVulkan, _isVulkan || _oldSpecState.GraphicsState.YNegateEnabled);
}
/// <inheritdoc/>
public bool QueryHasConstantBufferDrawParameters()
{
return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters;
}
/// <inheritdoc/>
public bool QueryDualSourceBlendEnable()
{
return _oldSpecState.GraphicsState.DualSourceBlendEnable;
}
/// <inheritdoc/>
public InputTopology QueryPrimitiveTopology()
{
_newSpecState.RecordPrimitiveTopology();
return ConvertToInputTopology(_oldSpecState.GraphicsState.Topology, _oldSpecState.GraphicsState.TessellationMode);
}
/// <inheritdoc/>
public bool QueryProgramPointSize()
{
return _oldSpecState.GraphicsState.ProgramPointSizeEnable;
}
/// <inheritdoc/>
public float QueryPointSize()
{
return _oldSpecState.GraphicsState.PointSize;
}
/// <inheritdoc/>
public bool QueryTessCw()
{
return _oldSpecState.GraphicsState.TessellationMode.UnpackCw();
}
/// <inheritdoc/>
public TessPatchType QueryTessPatchType()
{
return _oldSpecState.GraphicsState.TessellationMode.UnpackPatchType();
}
/// <inheritdoc/>
public TessSpacing QueryTessSpacing()
{
return _oldSpecState.GraphicsState.TessellationMode.UnpackSpacing();
}
/// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{
@@ -204,12 +130,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
}
/// <inheritdoc/>
public bool QueryTransformDepthMinusOneToOne()
{
return _oldSpecState.GraphicsState.DepthMode;
}
/// <inheritdoc/>
public bool QueryTransformFeedbackEnabled()
{
@@ -228,31 +148,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.TransformFeedbackDescriptors[bufferIndex].Stride;
}
/// <inheritdoc/>
public bool QueryEarlyZForce()
{
_newSpecState.RecordEarlyZForce();
return _oldSpecState.GraphicsState.EarlyZForce;
}
/// <inheritdoc/>
public bool QueryHasUnalignedStorageBuffer()
{
return _oldSpecState.GraphicsState.HasUnalignedStorageBuffer || _oldSpecState.ComputeState.HasUnalignedStorageBuffer;
}
/// <inheritdoc/>
public bool QueryViewportTransformDisable()
{
return _oldSpecState.GraphicsState.ViewportTransformDisable;
}
/// <inheritdoc/>
public bool QueryYNegateEnabled()
{
return _oldSpecState.GraphicsState.YNegateEnabled;
}
/// <inheritdoc/>
public void RegisterTexture(int handle, int cbufSlot)
{

View File

@@ -1,5 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
@@ -36,6 +35,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
_channel = channel;
_state = state;
_stageIndex = stageIndex;
if (stageIndex == (int)ShaderStage.Geometry - 1)
{
// Only geometry shaders require the primitive topology.
_state.SpecializationState.RecordPrimitiveTopology();
}
}
/// <summary>
@@ -74,58 +79,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
return MemoryMarshal.Cast<byte, ulong>(_channel.MemoryManager.GetSpan(address, size));
}
/// <inheritdoc/>
public bool QueryAlphaToCoverageDitherEnable()
{
return _state.GraphicsState.AlphaToCoverageEnable && _state.GraphicsState.AlphaToCoverageDitherEnable;
}
/// <inheritdoc/>
public AlphaTestOp QueryAlphaTestCompare()
{
if (!_isVulkan || !_state.GraphicsState.AlphaTestEnable)
{
return AlphaTestOp.Always;
}
return _state.GraphicsState.AlphaTestCompare switch
{
CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
_ => AlphaTestOp.Always,
};
}
/// <inheritdoc/>
public float QueryAlphaTestReference()
{
return _state.GraphicsState.AlphaTestReference;
}
/// <inheritdoc/>
public AttributeType QueryAttributeType(int location)
{
return _state.GraphicsState.AttributeTypes[location];
}
/// <inheritdoc/>
public bool QueryEarlyZForce()
{
_state.SpecializationState?.RecordEarlyZForce();
return _state.GraphicsState.EarlyZForce;
}
/// <inheritdoc/>
public AttributeType QueryFragmentOutputType(int location)
{
return _state.GraphicsState.FragmentOutputTypes[location];
}
/// <inheritdoc/>
public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX;
@@ -152,6 +105,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
return useMask;
}
/// <inheritdoc/>
public GpuGraphicsState QueryGraphicsState()
{
return _state.GraphicsState.CreateShaderGraphicsState(!_isVulkan, _isVulkan || _state.GraphicsState.YNegateEnabled);
}
/// <inheritdoc/>
public bool QueryHasConstantBufferDrawParameters()
{
@@ -164,49 +123,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
}
/// <inheritdoc/>
public bool QueryDualSourceBlendEnable()
{
return _state.GraphicsState.DualSourceBlendEnable;
}
/// <inheritdoc/>
public InputTopology QueryPrimitiveTopology()
{
_state.SpecializationState?.RecordPrimitiveTopology();
return ConvertToInputTopology(_state.GraphicsState.Topology, _state.GraphicsState.TessellationMode);
}
/// <inheritdoc/>
public bool QueryProgramPointSize()
{
return _state.GraphicsState.ProgramPointSizeEnable;
}
/// <inheritdoc/>
public float QueryPointSize()
{
return _state.GraphicsState.PointSize;
}
/// <inheritdoc/>
public bool QueryTessCw()
{
return _state.GraphicsState.TessellationMode.UnpackCw();
}
/// <inheritdoc/>
public TessPatchType QueryTessPatchType()
{
return _state.GraphicsState.TessellationMode.UnpackPatchType();
}
/// <inheritdoc/>
public TessSpacing QueryTessSpacing()
{
return _state.GraphicsState.TessellationMode.UnpackSpacing();
}
//// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{
@@ -258,12 +174,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
/// <inheritdoc/>
public bool QueryTransformDepthMinusOneToOne()
{
return _state.GraphicsState.DepthMode;
}
/// <inheritdoc/>
public bool QueryTransformFeedbackEnabled()
{
@@ -282,18 +192,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.TransformFeedbackDescriptors[bufferIndex].Stride;
}
/// <inheritdoc/>
public bool QueryViewportTransformDisable()
{
return _state.GraphicsState.ViewportTransformDisable;
}
/// <inheritdoc/>
public bool QueryYNegateEnabled()
{
return _state.GraphicsState.YNegateEnabled;
}
/// <inheritdoc/>
public void RegisterTexture(int handle, int cbufSlot)
{

View File

@@ -1,6 +1,5 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
@@ -232,33 +231,5 @@ namespace Ryujinx.Graphics.Gpu.Shader
#pragma warning restore IDE0055
};
}
/// <summary>
/// Converts the Maxwell primitive topology to the shader translator topology.
/// </summary>
/// <param name="topology">Maxwell primitive topology</param>
/// <param name="tessellationMode">Maxwell tessellation mode</param>
/// <returns>Shader translator topology</returns>
protected static InputTopology ConvertToInputTopology(PrimitiveTopology topology, TessMode tessellationMode)
{
return topology switch
{
PrimitiveTopology.Points => InputTopology.Points,
PrimitiveTopology.Lines or
PrimitiveTopology.LineLoop or
PrimitiveTopology.LineStrip => InputTopology.Lines,
PrimitiveTopology.LinesAdjacency or
PrimitiveTopology.LineStripAdjacency => InputTopology.LinesAdjacency,
PrimitiveTopology.Triangles or
PrimitiveTopology.TriangleStrip or
PrimitiveTopology.TriangleFan => InputTopology.Triangles,
PrimitiveTopology.TrianglesAdjacency or
PrimitiveTopology.TriangleStripAdjacency => InputTopology.TrianglesAdjacency,
PrimitiveTopology.Patches => tessellationMode.UnpackPatchType() == TessPatchType.Isolines
? InputTopology.Lines
: InputTopology.Triangles,
_ => InputTopology.Points,
};
}
}
}

View File

@@ -103,64 +103,81 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool YNegateEnabled;
/// <summary>
/// Creates a new GPU graphics state.
/// Creates a new graphics state from this state that can be used for shader generation.
/// </summary>
/// <param name="earlyZForce">Early Z force enable</param>
/// <param name="topology">Primitive topology</param>
/// <param name="tessellationMode">Tessellation mode</param>
/// <param name="alphaToCoverageEnable">Indicates whether alpha-to-coverage is enabled</param>
/// <param name="alphaToCoverageDitherEnable">Indicates whether alpha-to-coverage dithering is enabled</param>
/// <param name="viewportTransformDisable">Indicates whether the viewport transform is disabled</param>
/// <param name="depthMode">Depth mode zero to one or minus one to one</param>
/// <param name="programPointSizeEnable">Indicates if the point size is set on the shader or is fixed</param>
/// <param name="pointSize">Point size if not set from shader</param>
/// <param name="alphaTestEnable">Indicates whether alpha test is enabled</param>
/// <param name="alphaTestCompare">When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded</param>
/// <param name="alphaTestReference">When alpha test is enabled, indicates the value to compare with the fragment output alpha</param>
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
/// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
/// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
/// <param name="dualSourceBlendEnable">Indicates whether dual source blend is enabled</param>
/// <param name="yNegateEnabled">Indicates whether Y negate of the fragment coordinates is enabled</param>
public GpuChannelGraphicsState(
bool earlyZForce,
PrimitiveTopology topology,
TessMode tessellationMode,
bool alphaToCoverageEnable,
bool alphaToCoverageDitherEnable,
bool viewportTransformDisable,
bool depthMode,
bool programPointSizeEnable,
float pointSize,
bool alphaTestEnable,
CompareOp alphaTestCompare,
float alphaTestReference,
ref Array32<AttributeType> attributeTypes,
bool hasConstantBufferDrawParameters,
bool hasUnalignedStorageBuffer,
ref Array8<AttributeType> fragmentOutputTypes,
bool dualSourceBlendEnable,
bool yNegateEnabled)
/// <param name="hostSupportsAlphaTest">Indicates if the host API supports alpha test operations</param>
/// <returns>GPU graphics state that can be used for shader translation</returns>
public readonly GpuGraphicsState CreateShaderGraphicsState(bool hostSupportsAlphaTest, bool originUpperLeft)
{
EarlyZForce = earlyZForce;
Topology = topology;
TessellationMode = tessellationMode;
AlphaToCoverageEnable = alphaToCoverageEnable;
AlphaToCoverageDitherEnable = alphaToCoverageDitherEnable;
ViewportTransformDisable = viewportTransformDisable;
DepthMode = depthMode;
ProgramPointSizeEnable = programPointSizeEnable;
PointSize = pointSize;
AlphaTestEnable = alphaTestEnable;
AlphaTestCompare = alphaTestCompare;
AlphaTestReference = alphaTestReference;
AttributeTypes = attributeTypes;
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
FragmentOutputTypes = fragmentOutputTypes;
DualSourceBlendEnable = dualSourceBlendEnable;
YNegateEnabled = yNegateEnabled;
AlphaTestOp alphaTestOp;
if (hostSupportsAlphaTest || !AlphaTestEnable)
{
alphaTestOp = AlphaTestOp.Always;
}
else
{
alphaTestOp = AlphaTestCompare switch
{
CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
_ => AlphaTestOp.Always,
};
}
return new GpuGraphicsState(
EarlyZForce,
ConvertToInputTopology(Topology, TessellationMode),
TessellationMode.UnpackCw(),
TessellationMode.UnpackPatchType(),
TessellationMode.UnpackSpacing(),
AlphaToCoverageEnable,
AlphaToCoverageDitherEnable,
ViewportTransformDisable,
DepthMode,
ProgramPointSizeEnable,
PointSize,
alphaTestOp,
AlphaTestReference,
in AttributeTypes,
HasConstantBufferDrawParameters,
in FragmentOutputTypes,
DualSourceBlendEnable,
YNegateEnabled,
originUpperLeft);
}
/// <summary>
/// Converts the Maxwell primitive topology to the shader translator topology.
/// </summary>
/// <param name="topology">Maxwell primitive topology</param>
/// <param name="tessellationMode">Maxwell tessellation mode</param>
/// <returns>Shader translator topology</returns>
private static InputTopology ConvertToInputTopology(PrimitiveTopology topology, TessMode tessellationMode)
{
return topology switch
{
PrimitiveTopology.Points => InputTopology.Points,
PrimitiveTopology.Lines or
PrimitiveTopology.LineLoop or
PrimitiveTopology.LineStrip => InputTopology.Lines,
PrimitiveTopology.LinesAdjacency or
PrimitiveTopology.LineStripAdjacency => InputTopology.LinesAdjacency,
PrimitiveTopology.Triangles or
PrimitiveTopology.TriangleStrip or
PrimitiveTopology.TriangleFan => InputTopology.Triangles,
PrimitiveTopology.TrianglesAdjacency or
PrimitiveTopology.TriangleStripAdjacency => InputTopology.TrianglesAdjacency,
PrimitiveTopology.Patches => tessellationMode.UnpackPatchType() == TessPatchType.Isolines
? InputTopology.Lines
: InputTopology.Triangles,
_ => InputTopology.Points,
};
}
}
}

View File

@@ -28,9 +28,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
[Flags]
private enum QueriedStateFlags
{
EarlyZForce = 1 << 0,
PrimitiveTopology = 1 << 1,
TessellationMode = 1 << 2,
TransformFeedback = 1 << 3,
}
@@ -264,14 +262,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
PipelineState = pipelineState;
}
/// <summary>
/// Indicates that the shader accesses the early Z force state.
/// </summary>
public void RecordEarlyZForce()
{
_queriedState |= QueriedStateFlags.EarlyZForce;
}
/// <summary>
/// Indicates that the shader accesses the primitive topology state.
/// </summary>
@@ -280,14 +270,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
_queriedState |= QueriedStateFlags.PrimitiveTopology;
}
/// <summary>
/// Indicates that the shader accesses the tessellation mode state.
/// </summary>
public void RecordTessellationMode()
{
_queriedState |= QueriedStateFlags.TessellationMode;
}
/// <summary>
/// Indicates that the shader accesses the constant buffer use state.
/// </summary>

View File

@@ -13,17 +13,6 @@ namespace Ryujinx.Graphics.Shader
static class AttributeTypeExtensions
{
public static string ToVec4Type(this AttributeType type)
{
return type switch
{
AttributeType.Float => "vec4",
AttributeType.Sint => "ivec4",
AttributeType.Uint => "uvec4",
_ => throw new ArgumentException($"Invalid attribute type \"{type}\"."),
};
}
public static AggregateType ToAggregateType(this AttributeType type)
{
return type switch

View File

@@ -0,0 +1,31 @@
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
namespace Ryujinx.Graphics.Shader.CodeGen
{
readonly struct CodeGenParameters
{
public readonly AttributeUsage AttributeUsage;
public readonly ShaderDefinitions Definitions;
public readonly ShaderProperties Properties;
public readonly HostCapabilities HostCapabilities;
public readonly ILogger Logger;
public readonly TargetApi TargetApi;
public CodeGenParameters(
AttributeUsage attributeUsage,
ShaderDefinitions definitions,
ShaderProperties properties,
HostCapabilities hostCapabilities,
ILogger logger,
TargetApi targetApi)
{
AttributeUsage = attributeUsage;
Definitions = definitions;
Properties = properties;
HostCapabilities = hostCapabilities;
Logger = logger;
TargetApi = targetApi;
}
}
}

View File

@@ -12,7 +12,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public StructuredProgramInfo Info { get; }
public ShaderConfig Config { get; }
public AttributeUsage AttributeUsage { get; }
public ShaderDefinitions Definitions { get; }
public ShaderProperties Properties { get; }
public HostCapabilities HostCapabilities { get; }
public ILogger Logger { get; }
public TargetApi TargetApi { get; }
public OperandManager OperandManager { get; }
@@ -22,10 +27,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private string _indentation;
public CodeGenContext(StructuredProgramInfo info, ShaderConfig config)
public CodeGenContext(StructuredProgramInfo info, CodeGenParameters parameters)
{
Info = info;
Config = config;
AttributeUsage = parameters.AttributeUsage;
Definitions = parameters.Definitions;
Properties = parameters.Properties;
HostCapabilities = parameters.HostCapabilities;
Logger = parameters.Logger;
TargetApi = parameters.TargetApi;
OperandManager = new OperandManager();

View File

@@ -1,4 +1,5 @@
using Ryujinx.Common;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
@@ -13,10 +14,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
public static void Declare(CodeGenContext context, StructuredProgramInfo info)
{
context.AppendLine(context.Config.Options.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core");
context.AppendLine(context.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core");
context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable");
if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
if (context.HostCapabilities.SupportsShaderBallot)
{
context.AppendLine("#extension GL_ARB_shader_ballot : enable");
}
@@ -30,24 +31,24 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine("#extension GL_EXT_shader_image_load_formatted : enable");
context.AppendLine("#extension GL_EXT_texture_shadow_lod : enable");
if (context.Config.Stage == ShaderStage.Compute)
if (context.Definitions.Stage == ShaderStage.Compute)
{
context.AppendLine("#extension GL_ARB_compute_shader : enable");
}
else if (context.Config.Stage == ShaderStage.Fragment)
else if (context.Definitions.Stage == ShaderStage.Fragment)
{
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
context.AppendLine("#extension GL_ARB_fragment_shader_interlock : enable");
}
else if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderOrderingIntel())
else if (context.HostCapabilities.SupportsFragmentShaderOrderingIntel)
{
context.AppendLine("#extension GL_INTEL_fragment_shader_ordering : enable");
}
}
else
{
if (context.Config.Stage == ShaderStage.Vertex)
if (context.Definitions.Stage == ShaderStage.Vertex)
{
context.AppendLine("#extension GL_ARB_shader_draw_parameters : enable");
}
@@ -55,12 +56,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine("#extension GL_ARB_shader_viewport_layer_array : enable");
}
if (context.Config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
{
context.AppendLine("#extension GL_NV_geometry_shader_passthrough : enable");
}
if (context.Config.GpuAccessor.QueryHostSupportsViewportMask())
if (context.HostCapabilities.SupportsViewportMask)
{
context.AppendLine("#extension GL_NV_viewport_array2 : enable");
}
@@ -71,23 +72,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;");
context.AppendLine();
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Config.Properties.StorageBuffers.Values);
DeclareMemories(context, context.Config.Properties.LocalMemories.Values, isShared: false);
DeclareMemories(context, context.Config.Properties.SharedMemories.Values, isShared: true);
DeclareSamplers(context, context.Config.Properties.Textures.Values);
DeclareImages(context, context.Config.Properties.Images.Values);
DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Properties.StorageBuffers.Values);
DeclareMemories(context, context.Properties.LocalMemories.Values, isShared: false);
DeclareMemories(context, context.Properties.SharedMemories.Values, isShared: true);
DeclareSamplers(context, context.Properties.Textures.Values);
DeclareImages(context, context.Properties.Images.Values);
if (context.Config.Stage != ShaderStage.Compute)
if (context.Definitions.Stage != ShaderStage.Compute)
{
if (context.Config.Stage == ShaderStage.Geometry)
if (context.Definitions.Stage == ShaderStage.Geometry)
{
InputTopology inputTopology = context.Config.GpuAccessor.QueryPrimitiveTopology();
string inPrimitive = inputTopology.ToGlslString();
string inPrimitive = context.Definitions.InputTopology.ToGlslString();
context.AppendLine($"layout (invocations = {context.Config.ThreadsPerInputPrimitive}, {inPrimitive}) in;");
context.AppendLine($"layout (invocations = {context.Definitions.ThreadsPerInputPrimitive}, {inPrimitive}) in;");
if (context.Config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
{
context.AppendLine($"layout (passthrough) in gl_PerVertex");
context.EnterScope();
@@ -98,87 +98,75 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
else
{
string outPrimitive = context.Config.OutputTopology.ToGlslString();
string outPrimitive = context.Definitions.OutputTopology.ToGlslString();
int maxOutputVertices = context.Config.GpPassthrough
? inputTopology.ToInputVertices()
: context.Config.MaxOutputVertices;
int maxOutputVertices = context.Definitions.GpPassthrough
? context.Definitions.InputTopology.ToInputVertices()
: context.Definitions.MaxOutputVertices;
context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;");
}
context.AppendLine();
}
else if (context.Config.Stage == ShaderStage.TessellationControl)
else if (context.Definitions.Stage == ShaderStage.TessellationControl)
{
int threadsPerInputPrimitive = context.Config.ThreadsPerInputPrimitive;
int threadsPerInputPrimitive = context.Definitions.ThreadsPerInputPrimitive;
context.AppendLine($"layout (vertices = {threadsPerInputPrimitive}) out;");
context.AppendLine();
}
else if (context.Config.Stage == ShaderStage.TessellationEvaluation)
else if (context.Definitions.Stage == ShaderStage.TessellationEvaluation)
{
bool tessCw = context.Config.GpuAccessor.QueryTessCw();
bool tessCw = context.Definitions.TessCw;
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
// We invert the front face on Vulkan backend, so we need to do that here aswell.
tessCw = !tessCw;
}
string patchType = context.Config.GpuAccessor.QueryTessPatchType().ToGlsl();
string spacing = context.Config.GpuAccessor.QueryTessSpacing().ToGlsl();
string patchType = context.Definitions.TessPatchType.ToGlsl();
string spacing = context.Definitions.TessSpacing.ToGlsl();
string windingOrder = tessCw ? "cw" : "ccw";
context.AppendLine($"layout ({patchType}, {spacing}, {windingOrder}) in;");
context.AppendLine();
}
if (context.Config.UsedInputAttributes != 0 || context.Config.GpPassthrough)
static bool IsUserDefined(IoDefinition ioDefinition, StorageKind storageKind)
{
DeclareInputAttributes(context, info);
context.AppendLine();
return ioDefinition.StorageKind == storageKind && ioDefinition.IoVariable == IoVariable.UserDefined;
}
if (context.Config.UsedOutputAttributes != 0 || context.Config.Stage != ShaderStage.Fragment)
static bool IsUserDefinedOutput(ShaderStage stage, IoDefinition ioDefinition)
{
DeclareOutputAttributes(context, info);
context.AppendLine();
IoVariable ioVariable = stage == ShaderStage.Fragment ? IoVariable.FragmentOutputColor : IoVariable.UserDefined;
return ioDefinition.StorageKind == StorageKind.Output && ioDefinition.IoVariable == ioVariable;
}
if (context.Config.UsedInputAttributesPerPatch.Count != 0)
DeclareInputAttributes(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.Input)));
DeclareOutputAttributes(context, info.IoDefinitions.Where(x => IsUserDefinedOutput(context.Definitions.Stage, x)));
DeclareInputAttributesPerPatch(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.InputPerPatch)));
DeclareOutputAttributesPerPatch(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.OutputPerPatch)));
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline)
{
DeclareInputAttributesPerPatch(context, context.Config.UsedInputAttributesPerPatch);
context.AppendLine();
}
if (context.Config.UsedOutputAttributesPerPatch.Count != 0)
{
DeclareUsedOutputAttributesPerPatch(context, context.Config.UsedOutputAttributesPerPatch);
context.AppendLine();
}
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline)
{
var tfOutput = context.Config.GetTransformFeedbackOutput(AttributeConsts.PositionX);
var tfOutput = context.Definitions.GetTransformFeedbackOutput(AttributeConsts.PositionX);
if (tfOutput.Valid)
{
context.AppendLine($"layout (xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}) out gl_PerVertex");
context.EnterScope();
context.AppendLine("vec4 gl_Position;");
context.LeaveScope(context.Config.Stage == ShaderStage.TessellationControl ? " gl_out[];" : ";");
context.LeaveScope(context.Definitions.Stage == ShaderStage.TessellationControl ? " gl_out[];" : ";");
}
}
}
else
{
string localSizeX = NumberFormatter.FormatInt(context.Config.GpuAccessor.QueryComputeLocalSizeX());
string localSizeY = NumberFormatter.FormatInt(context.Config.GpuAccessor.QueryComputeLocalSizeY());
string localSizeZ = NumberFormatter.FormatInt(context.Config.GpuAccessor.QueryComputeLocalSizeZ());
string localSizeX = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeX);
string localSizeY = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeY);
string localSizeZ = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeZ);
context.AppendLine(
"layout (" +
@@ -188,15 +176,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine();
}
if (context.Config.Stage == ShaderStage.Fragment)
if (context.Definitions.Stage == ShaderStage.Fragment)
{
if (context.Config.GpuAccessor.QueryEarlyZForce())
if (context.Definitions.EarlyZForce)
{
context.AppendLine("layout (early_fragment_tests) in;");
context.AppendLine();
}
if (context.Config.Properties.OriginUpperLeft)
if (context.Definitions.OriginUpperLeft)
{
context.AppendLine("layout (origin_upper_left) in vec4 gl_FragCoord;");
context.AppendLine();
@@ -251,7 +239,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public static string GetVarTypeName(CodeGenContext context, AggregateType type, bool precise = true)
{
if (context.Config.GpuAccessor.QueryHostReducedPrecision())
if (context.HostCapabilities.ReducedPrecision)
{
precise = false;
}
@@ -305,7 +293,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string set = string.Empty;
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
set = $"set = {buffer.Set}, ";
}
@@ -390,7 +378,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string layout = string.Empty;
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
layout = $", set = {definition.Set}";
}
@@ -435,7 +423,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
layout = ", " + layout;
}
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
layout = $", set = {definition.Set}{layout}";
}
@@ -444,42 +432,50 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
}
private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info)
private static void DeclareInputAttributes(CodeGenContext context, IEnumerable<IoDefinition> inputs)
{
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.IaIndexing))
if (context.Definitions.IaIndexing)
{
string suffix = context.Config.Stage == ShaderStage.Geometry ? "[]" : string.Empty;
string suffix = context.Definitions.Stage == ShaderStage.Geometry ? "[]" : string.Empty;
context.AppendLine($"layout (location = 0) in vec4 {DefaultNames.IAttributePrefix}{suffix}[{Constants.MaxAttributes}];");
context.AppendLine();
}
else
{
int usedAttributes = context.Config.UsedInputAttributes | context.Config.PassthroughAttributes;
while (usedAttributes != 0)
foreach (var ioDefinition in inputs.OrderBy(x => x.Location))
{
int index = BitOperations.TrailingZeroCount(usedAttributes);
DeclareInputAttribute(context, info, index);
usedAttributes &= ~(1 << index);
DeclareInputAttribute(context, ioDefinition.Location, ioDefinition.Component);
}
if (inputs.Any())
{
context.AppendLine();
}
}
}
private static void DeclareInputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs)
private static void DeclareInputAttributesPerPatch(CodeGenContext context, IEnumerable<IoDefinition> inputs)
{
foreach (int attr in attrs.Order())
foreach (var ioDefinition in inputs.OrderBy(x => x.Location))
{
DeclareInputAttributePerPatch(context, attr);
DeclareInputAttributePerPatch(context, ioDefinition.Location);
}
if (inputs.Any())
{
context.AppendLine();
}
}
private static void DeclareInputAttribute(CodeGenContext context, StructuredProgramInfo info, int attr)
private static void DeclareInputAttribute(CodeGenContext context, int location, int component)
{
string suffix = IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: false) ? "[]" : string.Empty;
string suffix = IsArrayAttributeGlsl(context.Definitions.Stage, isOutAttr: false) ? "[]" : string.Empty;
string iq = string.Empty;
if (context.Config.Stage == ShaderStage.Fragment)
if (context.Definitions.Stage == ShaderStage.Fragment)
{
iq = context.Config.ImapTypes[attr].GetFirstUsedType() switch
iq = context.Definitions.ImapTypes[location].GetFirstUsedType() switch
{
PixelImap.Constant => "flat ",
PixelImap.ScreenLinear => "noperspective ",
@@ -487,14 +483,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
};
}
string name = $"{DefaultNames.IAttributePrefix}{attr}";
string name = $"{DefaultNames.IAttributePrefix}{location}";
if (context.Config.TransformFeedbackEnabled && context.Config.Stage == ShaderStage.Fragment)
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.Stage == ShaderStage.Fragment)
{
int components = context.Config.GetTransformFeedbackOutputComponents(attr, 0);
bool hasComponent = context.Definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput: false);
if (components > 1)
if (hasComponent)
{
char swzMask = "xyzw"[component];
context.AppendLine($"layout (location = {location}, component = {component}) {iq}in float {name}_{swzMask}{suffix};");
}
else
{
int components = context.Definitions.GetTransformFeedbackOutputComponents(location, 0);
string type = components switch
{
2 => "vec2",
@@ -503,85 +507,89 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
_ => "float",
};
context.AppendLine($"layout (location = {attr}) in {type} {name};");
}
for (int c = components > 1 ? components : 0; c < 4; c++)
{
char swzMask = "xyzw"[c];
context.AppendLine($"layout (location = {attr}, component = {c}) {iq}in float {name}_{swzMask}{suffix};");
context.AppendLine($"layout (location = {location}) in {type} {name};");
}
}
else
{
bool passthrough = (context.Config.PassthroughAttributes & (1 << attr)) != 0;
string pass = passthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough() ? "passthrough, " : string.Empty;
string type;
bool passthrough = (context.AttributeUsage.PassthroughAttributes & (1 << location)) != 0;
string pass = passthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough ? "passthrough, " : string.Empty;
string type = GetVarTypeName(context, context.Definitions.GetUserDefinedType(location, isOutput: false), false);
if (context.Config.Stage == ShaderStage.Vertex)
{
type = context.Config.GpuAccessor.QueryAttributeType(attr).ToVec4Type();
}
else
{
type = AttributeType.Float.ToVec4Type();
}
context.AppendLine($"layout ({pass}location = {attr}) {iq}in {type} {name}{suffix};");
context.AppendLine($"layout ({pass}location = {location}) {iq}in {type} {name}{suffix};");
}
}
private static void DeclareInputAttributePerPatch(CodeGenContext context, int attr)
private static void DeclareInputAttributePerPatch(CodeGenContext context, int patchLocation)
{
int location = context.Config.GetPerPatchAttributeLocation(attr);
string name = $"{DefaultNames.PerPatchAttributePrefix}{attr}";
int location = context.AttributeUsage.GetPerPatchAttributeLocation(patchLocation);
string name = $"{DefaultNames.PerPatchAttributePrefix}{patchLocation}";
context.AppendLine($"layout (location = {location}) patch in vec4 {name};");
}
private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info)
private static void DeclareOutputAttributes(CodeGenContext context, IEnumerable<IoDefinition> outputs)
{
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing))
if (context.Definitions.OaIndexing)
{
context.AppendLine($"layout (location = 0) out vec4 {DefaultNames.OAttributePrefix}[{Constants.MaxAttributes}];");
context.AppendLine();
}
else
{
int usedAttributes = context.Config.UsedOutputAttributes;
outputs = outputs.OrderBy(x => x.Location);
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
if (context.Definitions.Stage == ShaderStage.Fragment && context.Definitions.DualSourceBlend)
{
int firstOutput = BitOperations.TrailingZeroCount(usedAttributes);
int mask = 3 << firstOutput;
IoDefinition firstOutput = outputs.ElementAtOrDefault(0);
IoDefinition secondOutput = outputs.ElementAtOrDefault(1);
if ((usedAttributes & mask) == mask)
if (firstOutput.Location + 1 == secondOutput.Location)
{
usedAttributes &= ~mask;
DeclareOutputDualSourceBlendAttribute(context, firstOutput);
DeclareOutputDualSourceBlendAttribute(context, firstOutput.Location);
outputs = outputs.Skip(2);
}
}
while (usedAttributes != 0)
foreach (var ioDefinition in outputs)
{
int index = BitOperations.TrailingZeroCount(usedAttributes);
DeclareOutputAttribute(context, index);
usedAttributes &= ~(1 << index);
DeclareOutputAttribute(context, ioDefinition.Location, ioDefinition.Component);
}
if (outputs.Any())
{
context.AppendLine();
}
}
}
private static void DeclareOutputAttribute(CodeGenContext context, int attr)
private static void DeclareOutputAttribute(CodeGenContext context, int location, int component)
{
string suffix = IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: true) ? "[]" : string.Empty;
string name = $"{DefaultNames.OAttributePrefix}{attr}{suffix}";
string suffix = IsArrayAttributeGlsl(context.Definitions.Stage, isOutAttr: true) ? "[]" : string.Empty;
string name = $"{DefaultNames.OAttributePrefix}{location}{suffix}";
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline)
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline)
{
int components = context.Config.GetTransformFeedbackOutputComponents(attr, 0);
bool hasComponent = context.Definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput: true);
if (components > 1)
if (hasComponent)
{
char swzMask = "xyzw"[component];
string xfb = string.Empty;
var tfOutput = context.Definitions.GetTransformFeedbackOutput(location, component);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {location}, component = {component}{xfb}) out float {name}_{swzMask};");
}
else
{
int components = context.Definitions.GetTransformFeedbackOutputComponents(location, 0);
string type = components switch
{
2 => "vec2",
@@ -592,58 +600,59 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string xfb = string.Empty;
var tfOutput = context.Config.GetTransformFeedbackOutput(attr, 0);
var tfOutput = context.Definitions.GetTransformFeedbackOutput(location, 0);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {attr}{xfb}) out {type} {name};");
}
for (int c = components > 1 ? components : 0; c < 4; c++)
{
char swzMask = "xyzw"[c];
string xfb = string.Empty;
var tfOutput = context.Config.GetTransformFeedbackOutput(attr, c);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {attr}, component = {c}{xfb}) out float {name}_{swzMask};");
context.AppendLine($"layout (location = {location}{xfb}) out {type} {name};");
}
}
else
{
string type = context.Config.Stage != ShaderStage.Fragment ? "vec4" :
context.Config.GpuAccessor.QueryFragmentOutputType(attr) switch
{
AttributeType.Sint => "ivec4",
AttributeType.Uint => "uvec4",
_ => "vec4",
};
string type = context.Definitions.Stage != ShaderStage.Fragment ? "vec4" :
GetVarTypeName(context, context.Definitions.GetFragmentOutputColorType(location), false);
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && context.Config.Stage == ShaderStage.Vertex && attr == 0)
if (context.HostCapabilities.ReducedPrecision && context.Definitions.Stage == ShaderStage.Vertex && location == 0)
{
context.AppendLine($"layout (location = {attr}) invariant out {type} {name};");
context.AppendLine($"layout (location = {location}) invariant out {type} {name};");
}
else
{
context.AppendLine($"layout (location = {attr}) out {type} {name};");
context.AppendLine($"layout (location = {location}) out {type} {name};");
}
}
}
private static void DeclareOutputDualSourceBlendAttribute(CodeGenContext context, int attr)
private static void DeclareOutputDualSourceBlendAttribute(CodeGenContext context, int location)
{
string name = $"{DefaultNames.OAttributePrefix}{attr}";
string name2 = $"{DefaultNames.OAttributePrefix}{(attr + 1)}";
string name = $"{DefaultNames.OAttributePrefix}{location}";
string name2 = $"{DefaultNames.OAttributePrefix}{(location + 1)}";
context.AppendLine($"layout (location = {attr}, index = 0) out vec4 {name};");
context.AppendLine($"layout (location = {attr}, index = 1) out vec4 {name2};");
context.AppendLine($"layout (location = {location}, index = 0) out vec4 {name};");
context.AppendLine($"layout (location = {location}, index = 1) out vec4 {name2};");
}
private static void DeclareOutputAttributesPerPatch(CodeGenContext context, IEnumerable<IoDefinition> outputs)
{
foreach (var ioDefinition in outputs)
{
DeclareOutputAttributePerPatch(context, ioDefinition.Location);
}
if (outputs.Any())
{
context.AppendLine();
}
}
private static void DeclareOutputAttributePerPatch(CodeGenContext context, int patchLocation)
{
int location = context.AttributeUsage.GetPerPatchAttributeLocation(patchLocation);
string name = $"{DefaultNames.PerPatchAttributePrefix}{patchLocation}";
context.AppendLine($"layout (location = {location}) patch out vec4 {name};");
}
private static bool IsArrayAttributeGlsl(ShaderStage stage, bool isOutAttr)
@@ -660,29 +669,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
}
private static void DeclareUsedOutputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs)
{
foreach (int attr in attrs.Order())
{
DeclareOutputAttributePerPatch(context, attr);
}
}
private static void DeclareOutputAttributePerPatch(CodeGenContext context, int attr)
{
int location = context.Config.GetPerPatchAttributeLocation(attr);
string name = $"{DefaultNames.PerPatchAttributePrefix}{attr}";
context.AppendLine($"layout (location = {location}) patch out vec4 {name};");
}
private static void AppendHelperFunction(CodeGenContext context, string filename)
{
string code = EmbeddedResources.ReadAllText(filename);
code = code.Replace("\t", CodeGenContext.Tab);
if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
if (context.HostCapabilities.SupportsShaderBallot)
{
code = code.Replace("$SUBGROUP_INVOCATION$", "gl_SubGroupInvocationARB");
code = code.Replace("$SUBGROUP_BROADCAST$", "readInvocationARB");

View File

@@ -11,9 +11,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
private const string MainFunctionName = "main";
public static string Generate(StructuredProgramInfo info, ShaderConfig config)
public static string Generate(StructuredProgramInfo info, CodeGenParameters parameters)
{
CodeGenContext context = new(info, config);
CodeGenContext context = new(info, parameters);
Declarations.Declare(context, info);
@@ -113,7 +113,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
};
bool supportsBarrierDivergence = context.Config.GpuAccessor.QueryHostSupportsShaderBarrierDivergence();
bool supportsBarrierDivergence = context.HostCapabilities.SupportsShaderBarrierDivergence;
bool mayHaveReturned = false;
foreach (IAstNode node in visitor.Visit())
@@ -128,7 +128,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
// so skip emitting the barrier for those cases.
if (visitor.Block.Type != AstBlockType.Main || mayHaveReturned || !isMainFunction)
{
context.Config.GpuAccessor.Log($"Shader has barrier on potentially divergent block, the barrier will be removed.");
context.Logger.Log("Shader has barrier on potentially divergent block, the barrier will be removed.");
continue;
}

View File

@@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
string arg = GetSoureExpr(context, operation.GetSource(0), dstType);
if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
if (context.HostCapabilities.SupportsShaderBallot)
{
return $"unpackUint2x32(ballotARB({arg})).x";
}

View File

@@ -4,11 +4,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{
public static string FSIBegin(CodeGenContext context)
{
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
return "beginInvocationInterlockARB()";
}
else if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderOrderingIntel())
else if (context.HostCapabilities.SupportsFragmentShaderOrderingIntel)
{
return "beginFragmentShaderOrderingINTEL()";
}
@@ -18,7 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
public static string FSIEnd(CodeGenContext context)
{
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
return "endInvocationInterlockARB()";
}

View File

@@ -84,7 +84,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
indexExpr = Src(AggregateType.S32);
}
string imageName = GetImageName(context.Config, texOp, indexExpr);
string imageName = GetImageName(context.Properties, texOp, indexExpr);
texCallBuilder.Append('(');
texCallBuilder.Append(imageName);
@@ -216,7 +216,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Config, texOp, indexExpr);
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
int coordsIndex = isBindless || isIndexed ? 1 : 0;
@@ -273,7 +273,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
// 2D Array and Cube shadow samplers with LOD level or bias requires an extension.
// If the extension is not supported, just remove the LOD parameter.
if (isArray && isShadow && (is2D || isCube) && !context.Config.GpuAccessor.QueryHostSupportsTextureShadowLod())
if (isArray && isShadow && (is2D || isCube) && !context.HostCapabilities.SupportsTextureShadowLod)
{
hasLodBias = false;
hasLodLevel = false;
@@ -281,7 +281,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
// Cube shadow samplers with LOD level requires an extension.
// If the extension is not supported, just remove the LOD level parameter.
if (isShadow && isCube && !context.Config.GpuAccessor.QueryHostSupportsTextureShadowLod())
if (isShadow && isCube && !context.HostCapabilities.SupportsTextureShadowLod)
{
hasLodLevel = false;
}
@@ -342,7 +342,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
indexExpr = Src(AggregateType.S32);
}
string samplerName = GetSamplerName(context.Config, texOp, indexExpr);
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
texCall += "(" + samplerName;
@@ -538,7 +538,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Config, texOp, indexExpr);
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
if (texOp.Index == 3)
{
@@ -546,7 +546,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
else
{
context.Config.Properties.Textures.TryGetValue(texOp.Binding, out TextureDefinition definition);
context.Properties.Textures.TryGetValue(texOp.Binding, out TextureDefinition definition);
bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer;
string texCall;
@@ -593,8 +593,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
int binding = bindingIndex.Value;
BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
? context.Config.Properties.ConstantBuffers[binding]
: context.Config.Properties.StorageBuffers[binding];
? context.Properties.ConstantBuffers[binding]
: context.Properties.StorageBuffers[binding];
if (operation.GetSource(srcIndex++) is not AstOperand fieldIndex || fieldIndex.Type != OperandType.Constant)
{
@@ -614,8 +614,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
MemoryDefinition memory = storageKind == StorageKind.LocalMemory
? context.Config.Properties.LocalMemories[bindingId.Value]
: context.Config.Properties.SharedMemories[bindingId.Value];
? context.Properties.LocalMemories[bindingId.Value]
: context.Properties.SharedMemories[bindingId.Value];
varName = memory.Name;
varType = memory.Type;
@@ -636,7 +636,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
int location = -1;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (operation.GetSource(srcIndex++) is not AstOperand vecIndex || vecIndex.Type != OperandType.Constant)
{
@@ -648,16 +648,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (operation.SourcesCount > srcIndex &&
operation.GetSource(srcIndex) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
srcIndex++;
}
}
(varName, varType) = IoMap.GetGlslVariable(context.Config, ioVariable, location, component, isOutput, isPerPatch);
(varName, varType) = IoMap.GetGlslVariable(
context.Definitions,
context.HostCapabilities,
ioVariable,
location,
component,
isOutput,
isPerPatch);
if (IoMap.IsPerVertexBuiltIn(context.Config.Stage, ioVariable, isOutput))
if (IoMap.IsPerVertexBuiltIn(context.Definitions.Stage, ioVariable, isOutput))
{
// Since those exist both as input and output on geometry and tessellation shaders,
// we need the gl_in and gl_out prefixes to disambiguate.
@@ -692,7 +699,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{
varName += "." + "xyzw"[elementIndex.Value & 3];
}
else if (srcIndex == firstSrcIndex && context.Config.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
else if (srcIndex == firstSrcIndex && context.Definitions.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
{
// GLSL requires that for tessellation control shader outputs,
// that the index expression must be *exactly* "gl_InvocationID",
@@ -715,9 +722,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return varName;
}
private static string GetSamplerName(ShaderConfig config, AstTextureOperation texOp, string indexExpr)
private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr)
{
string name = config.Properties.Textures[texOp.Binding].Name;
string name = resourceDefinitions.Textures[texOp.Binding].Name;
if (texOp.Type.HasFlag(SamplerType.Indexed))
{
@@ -727,9 +734,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return name;
}
private static string GetImageName(ShaderConfig config, AstTextureOperation texOp, string indexExpr)
private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr)
{
string name = config.Properties.Images[texOp.Binding].Name;
string name = resourceDefinitions.Images[texOp.Binding].Name;
if (texOp.Type.HasFlag(SamplerType.Indexed))
{

View File

@@ -7,7 +7,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
static class IoMap
{
public static (string, AggregateType) GetGlslVariable(
ShaderConfig config,
ShaderDefinitions definitions,
HostCapabilities hostCapabilities,
IoVariable ioVariable,
int location,
int component,
@@ -25,7 +26,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IoVariable.DrawIndex => ("gl_DrawIDARB", AggregateType.S32),
IoVariable.FogCoord => ("gl_FogFragCoord", AggregateType.FP32), // Deprecated.
IoVariable.FragmentCoord => ("gl_FragCoord", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.FragmentOutputColor => GetFragmentOutputColorVariableName(config, location),
IoVariable.FragmentOutputColor => GetFragmentOutputColorVariableName(definitions, location),
IoVariable.FragmentOutputDepth => ("gl_FragDepth", AggregateType.FP32),
IoVariable.FrontColorDiffuse => ("gl_FrontColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.FrontColorSpecular => ("gl_FrontSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
@@ -38,20 +39,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IoVariable.PointCoord => ("gl_PointCoord", AggregateType.Vector2 | AggregateType.FP32),
IoVariable.PointSize => ("gl_PointSize", AggregateType.FP32),
IoVariable.Position => ("gl_Position", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.PrimitiveId => GetPrimitiveIdVariableName(config.Stage, isOutput),
IoVariable.SubgroupEqMask => GetSubgroupMaskVariableName(config, "Eq"),
IoVariable.SubgroupGeMask => GetSubgroupMaskVariableName(config, "Ge"),
IoVariable.SubgroupGtMask => GetSubgroupMaskVariableName(config, "Gt"),
IoVariable.SubgroupLaneId => GetSubgroupInvocationIdVariableName(config),
IoVariable.SubgroupLeMask => GetSubgroupMaskVariableName(config, "Le"),
IoVariable.SubgroupLtMask => GetSubgroupMaskVariableName(config, "Lt"),
IoVariable.PrimitiveId => GetPrimitiveIdVariableName(definitions.Stage, isOutput),
IoVariable.SubgroupEqMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Eq"),
IoVariable.SubgroupGeMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Ge"),
IoVariable.SubgroupGtMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Gt"),
IoVariable.SubgroupLaneId => GetSubgroupInvocationIdVariableName(hostCapabilities.SupportsShaderBallot),
IoVariable.SubgroupLeMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Le"),
IoVariable.SubgroupLtMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Lt"),
IoVariable.TessellationCoord => ("gl_TessCoord", AggregateType.Vector3 | AggregateType.FP32),
IoVariable.TessellationLevelInner => ("gl_TessLevelInner", AggregateType.Array | AggregateType.FP32),
IoVariable.TessellationLevelOuter => ("gl_TessLevelOuter", AggregateType.Array | AggregateType.FP32),
IoVariable.TextureCoord => ("gl_TexCoord", AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.ThreadId => ("gl_LocalInvocationID", AggregateType.Vector3 | AggregateType.U32),
IoVariable.ThreadKill => ("gl_HelperInvocation", AggregateType.Bool),
IoVariable.UserDefined => GetUserDefinedVariableName(config, location, component, isOutput, isPerPatch),
IoVariable.UserDefined => GetUserDefinedVariableName(definitions, location, component, isOutput, isPerPatch),
IoVariable.VertexId => ("gl_VertexID", AggregateType.S32),
IoVariable.VertexIndex => ("gl_VertexIndex", AggregateType.S32),
IoVariable.ViewportIndex => ("gl_ViewportIndex", AggregateType.S32),
@@ -86,16 +87,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return false;
}
private static (string, AggregateType) GetFragmentOutputColorVariableName(ShaderConfig config, int location)
private static (string, AggregateType) GetFragmentOutputColorVariableName(ShaderDefinitions definitions, int location)
{
if (location < 0)
{
return (DefaultNames.OAttributePrefix, config.GetFragmentOutputColorType(0));
return (DefaultNames.OAttributePrefix, definitions.GetFragmentOutputColorType(0));
}
string name = DefaultNames.OAttributePrefix + location.ToString(CultureInfo.InvariantCulture);
return (name, config.GetFragmentOutputColorType(location));
return (name, definitions.GetFragmentOutputColorType(location));
}
private static (string, AggregateType) GetPrimitiveIdVariableName(ShaderStage stage, bool isOutput)
@@ -104,21 +105,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return (isOutput || stage != ShaderStage.Geometry ? "gl_PrimitiveID" : "gl_PrimitiveIDIn", AggregateType.S32);
}
private static (string, AggregateType) GetSubgroupMaskVariableName(ShaderConfig config, string cc)
private static (string, AggregateType) GetSubgroupMaskVariableName(bool supportsShaderBallot, string cc)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
return supportsShaderBallot
? ($"unpackUint2x32(gl_SubGroup{cc}MaskARB)", AggregateType.Vector2 | AggregateType.U32)
: ($"gl_Subgroup{cc}Mask", AggregateType.Vector4 | AggregateType.U32);
}
private static (string, AggregateType) GetSubgroupInvocationIdVariableName(ShaderConfig config)
private static (string, AggregateType) GetSubgroupInvocationIdVariableName(bool supportsShaderBallot)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
return supportsShaderBallot
? ("gl_SubGroupInvocationARB", AggregateType.U32)
: ("gl_SubgroupInvocationID", AggregateType.U32);
}
private static (string, AggregateType) GetUserDefinedVariableName(ShaderConfig config, int location, int component, bool isOutput, bool isPerPatch)
private static (string, AggregateType) GetUserDefinedVariableName(ShaderDefinitions definitions, int location, int component, bool isOutput, bool isPerPatch)
{
string name = isPerPatch
? DefaultNames.PerPatchAttributePrefix
@@ -126,17 +127,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (location < 0)
{
return (name, config.GetUserDefinedType(0, isOutput));
return (name, definitions.GetUserDefinedType(0, isOutput));
}
name += location.ToString(CultureInfo.InvariantCulture);
if (config.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput))
if (definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput))
{
name += "_" + "xyzw"[component & 3];
}
return (name, config.GetUserDefinedType(location, isOutput));
return (name, definitions.GetUserDefinedType(location, isOutput));
}
}
}

View File

@@ -68,8 +68,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
BufferDefinition buffer = operation.StorageKind == StorageKind.ConstantBuffer
? context.Config.Properties.ConstantBuffers[bindingIndex.Value]
: context.Config.Properties.StorageBuffers[bindingIndex.Value];
? context.Properties.ConstantBuffers[bindingIndex.Value]
: context.Properties.StorageBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
return field.Type & AggregateType.ElementTypeMask;
@@ -82,8 +82,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
MemoryDefinition memory = operation.StorageKind == StorageKind.LocalMemory
? context.Config.Properties.LocalMemories[bindingId.Value]
: context.Config.Properties.SharedMemories[bindingId.Value];
? context.Properties.LocalMemories[bindingId.Value]
: context.Properties.SharedMemories[bindingId.Value];
return memory.Type & AggregateType.ElementTypeMask;
@@ -102,7 +102,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
int location = 0;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (operation.GetSource(1) is not AstOperand vecIndex || vecIndex.Type != OperandType.Constant)
{
@@ -114,13 +114,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
if (operation.SourcesCount > 2 &&
operation.GetSource(2) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
}
}
(_, AggregateType varType) = IoMap.GetGlslVariable(context.Config, ioVariable, location, component, isOutput, isPerPatch);
(_, AggregateType varType) = IoMap.GetGlslVariable(
context.Definitions,
context.HostCapabilities,
ioVariable,
location,
component,
isOutput,
isPerPatch);
return varType & AggregateType.ElementTypeMask;
}

View File

@@ -20,21 +20,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public StructuredProgramInfo Info { get; }
public ShaderConfig Config { get; }
public AttributeUsage AttributeUsage { get; }
public ShaderDefinitions Definitions { get; }
public ShaderProperties Properties { get; }
public HostCapabilities HostCapabilities { get; }
public ILogger Logger { get; }
public TargetApi TargetApi { get; }
public int InputVertices { get; }
public Dictionary<int, Instruction> ConstantBuffers { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> StorageBuffers { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> LocalMemories { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> SharedMemories { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, SamplerType> SamplersTypes { get; } = new Dictionary<int, SamplerType>();
public Dictionary<int, (Instruction, Instruction, Instruction)> Samplers { get; } = new Dictionary<int, (Instruction, Instruction, Instruction)>();
public Dictionary<int, (Instruction, Instruction)> Images { get; } = new Dictionary<int, (Instruction, Instruction)>();
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<IoDefinition, Instruction> InputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<int, Instruction> ConstantBuffers { get; } = new();
public Dictionary<int, Instruction> StorageBuffers { get; } = new();
public Dictionary<int, Instruction> LocalMemories { get; } = new();
public Dictionary<int, Instruction> SharedMemories { get; } = new();
public Dictionary<int, SamplerType> SamplersTypes { get; } = new();
public Dictionary<int, (Instruction, Instruction, Instruction)> Samplers { get; } = new();
public Dictionary<int, (Instruction, Instruction)> Images { get; } = new();
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new();
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new();
public Dictionary<IoDefinition, Instruction> InputsPerPatch { get; } = new();
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { get; } = new();
public StructuredFunction CurrentFunction { get; set; }
private readonly Dictionary<AstOperand, Instruction> _locals = new();
@@ -81,25 +89,28 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public CodeGenContext(
StructuredProgramInfo info,
ShaderConfig config,
CodeGenParameters parameters,
GeneratorPool<Instruction> instPool,
GeneratorPool<LiteralInteger> integerPool) : base(SpirvVersionPacked, instPool, integerPool)
{
Info = info;
Config = config;
AttributeUsage = parameters.AttributeUsage;
Definitions = parameters.Definitions;
Properties = parameters.Properties;
HostCapabilities = parameters.HostCapabilities;
Logger = parameters.Logger;
TargetApi = parameters.TargetApi;
if (config.Stage == ShaderStage.Geometry)
if (parameters.Definitions.Stage == ShaderStage.Geometry)
{
InputTopology inPrimitive = config.GpuAccessor.QueryPrimitiveTopology();
InputVertices = inPrimitive switch
InputVertices = parameters.Definitions.InputTopology switch
{
InputTopology.Points => 1,
InputTopology.Lines => 2,
InputTopology.LinesAdjacency => 2,
InputTopology.Triangles => 3,
InputTopology.TrianglesAdjacency => 3,
_ => throw new InvalidOperationException($"Invalid input topology \"{inPrimitive}\"."),
_ => throw new InvalidOperationException($"Invalid input topology \"{parameters.Definitions.InputTopology}\"."),
};
}

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