Compare commits

..

30 Commits

Author SHA1 Message Date
470a8031a4 time: Update for 15.0.0 changes and fixes long standing issues (#4822)
* time: Update for 15.0.0 changes

Last time we did an upgrade on the time service was during 9.x era, it was about time to take back that reverse again!

15.0.0 added a new structure on the shared memory to get steady clock raw timepoints with a granularity in nanoseconds.

This commit implements this new part.

I plan to write a follow up with a bit of refactoring of this ancient part of the emulator.

As always, reverse and work done by your truly.

PS: As a reminder, if this change is reused anywhere else, work should be credited as Ryujinx and not my person.

* time: Do not set setup value to posix time

This should fix local and network clock returning 0 under usage with
shared memory.

This probably fix #2430.

* Address gdkchan's comment

* Fix internal offset not working since changes and ensure that user clock have a valid clock id

* time: Report auto correcting clock and hardcode steady clock unique id

Fix Pokemon Sword Pokejobs for real.

* Address gdkchan's comment
2023-05-08 00:15:58 +02:00
5440d4ad5c misc: Switch ProcessResult to a class (#4846)
This avoid giant copies being performed when being returned or passed.
2023-05-07 20:50:45 +00:00
dde208b480 UI: Expose games build ID for cheat management (#4340)
* Ava UI: Expose games build ID for cheat management

* Fix bad merge

* Change integrity check level to error on invalid

* Add support for GDK

* Remove whitespace

* Add BID identifier

* PR Comments fix

* Restore title id in cheats GTK window

* use halign center instead of margin_left

* Merge

* fix after merge

* PR comments fix - design AVA

* PR fix - Move GetApplicationBuildId to ApplicationData class

* PR comment fix - Add empty line before method

* Align with PR #4755

* PR comments fix

* Change BuildId label to support translation

* Comments fix

* Remove unused BuildIdLabel property
2023-05-07 14:36:44 +00:00
4c3d2d5d75 UI: Add progress bar for re-packaging shaders (#4805)
* feat: introduce new shader loading state for progress tracking when writing shaders to disk

* fix: move translation to bottom of locale file

* fix: change back to foreach and add requested spacing between lines

* style: fix formatting

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-05-06 15:35:46 +02:00
fab11ba3f1 AM: Stub some service calls (#4825)
* AM: Stub some service call

Some IPC I have stubbed during private testing and I don't want to deal with them anymore. Nothing more.

* ICommonStateGetter disposable
2023-05-06 03:33:50 +02:00
332891b5ff Use correct offset for storage constant buffer elimination (#4821) 2023-05-05 23:59:36 +02:00
7df4fcada7 GPU: Remove CPU region handle containers (#4817)
* GPU: Remove CPU region handle containers.

Another one for the "I don't know why I didn't do this earlier" pile.

This removes the "Cpu" prefixed region handle classes, which each mirror a region handle type from Ryujinx.Memory.

Originally, not all projects had a reference to Ryujinx.Memory, so these classes were introduced to bridge the gap. Someone else crossed that bridge since, so these classes don't have much of a purpose anymore.

This PR replaces all uses of CpuRegionHandle etc to their direct Ryujinx.Memory versions.

RegionHandle methods (specifically QueryModified) are about the hottest path there is in the entire emulator, so there is a nice boost from doing this.

* Add docs
2023-05-05 23:40:46 +02:00
d6698680be 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
2023-05-05 21:24:35 +00:00
e5c9838b0b Correct tooltips for add,remove,removeall buttons (#4819) 2023-05-05 22:38:57 +02:00
f8ec878796 Fix typo in TextureBindingsManager.cs (#4798)
accomodate -> accommodate
2023-05-05 22:17:36 +02:00
9ff21f9ab6 Use ToLowerInvariant when detecting GPU vendor. (#4815) 2023-05-05 16:35:59 +00:00
aa021085cf Allow any shader SSBO constant buffer slot and offset (#2237)
* Allow any shader SSBO constant buffer slot and offset

* Fix slot value passed to SetUsedStorageBuffer on fallback case

* Shader cache version

* Ensure that the storage buffer source constant buffer offset is word aligned

* Fix FirstBinding on GetUniformBufferDescriptors
2023-05-05 14:20:20 +00:00
1f5d881860 GPU: Allow granular buffer updates from the constant buffer updater (#4749)
* GPU: Allow granular buffer updates from the constant buffer updater

Sometimes, constant buffer updates can't be avoided, either due to a cb0 access that cannot be eliminated, or the game updating a buffer between draws to the detriment of everyone.

To avoid uploading the full 4096 bytes each time, this PR remembers the offset and size containing all constant buffer updates since the last sync. It will then upload that range after sync.

* Allow clearing the dirty range

* Always use precise

Might want to not do this if distance between the existing range and new one is too high.

* Use old force dirty mechanism when distance between regions is too great

* Update src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Fix inheritance of _dirtyStart and _dirtyEnd

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-05-05 13:47:15 +00:00
1f664100bd ModLoader: Fix case sensitivy issues without breaking cheats (#4783)
* Fix case sensitivity for mod subdirectories

* Small refactoring of ModLoader

* Don't share instruction list between all cheats

Co-authored-by: riperiperi <rhy3756547@hotmail.com>

---------

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
2023-05-05 09:39:08 +02:00
1f5e1ffa80 fix: linux launcher breaks when there are spaces in the directory path (#4795)
* fix: linux launcher breaks when there are spaces in the directory path

* Add quotes around $0 as well

---------

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-05-05 09:06:15 +02:00
264438ff19 Revert "bcat ipc (#4446)" (#4801)
This reverts commit 4250732353.
2023-05-04 18:16:51 +02:00
3b8ac1641a UI: Move ApplicationContextMenu in a separated class (#4755)
* UI: Move ApplicationContextMenu in a separated class

This PR remove duplicated code related to the context menu on the Application list/grid by create a control for the menu which include related handler.

I've renamed "GameList/GameGrid" by "Application" for consistencies. And I've removed all uneeded field from the project file too.

While I cleaned up things, I've found an issue about purging Ptc/Shader cache, both methods list files even if the user say "No", shader cache is purged even if the user say "No". It's fixed.

* Adresses feedbacks
2023-05-04 14:41:06 +00:00
4250732353 bcat ipc (#4446) 2023-05-04 16:26:10 +02:00
4d1579acbf Fix some invalid blits involving depth textures (#4723) 2023-05-03 21:20:12 -03:00
6279f5e430 Update SettingsWindow.cs (#4785)
fix saving if directory path directly pasted in to the text field instead of using FileChooser.
2023-05-03 16:04:40 +02:00
b7d2bff6aa Revert "ModLoader: Fix case sensitivy issues (#4720)" (#4781)
This reverts commit cc1a933a2f.
2023-05-03 11:20:05 +02:00
7c327fecb3 Vulkan: Record modifications after changing the framebuffer (#4775)
Our Vulkan backend inserts image barriers when a texture is sampled after it is rendered. This is done via a "modification flag" which is set when a render target is unbound (presuming that a texture has finished drawing to it).

Imagine the following scenario:
- Game sets render target to texture A
- Game renders to texture A
- (render pass ends)
- Game binds texture A to a sampler
- Game sets render target to texture B
- Renders to texture B using texture A (barrier required)

Because of the previous behaviour, the check to add a barrier for sampling a texture actually happens before it is registered as modified, meaning no barrier was added at all. This isn't always the case, but it was definitely causing issues in Xenoblade 2.

This doesn't fix any more complicated issues where a texture is repeatedly sampled while it is currently being rendered.

Fixes visual glitches at lower resolutions in Xenoblade 2. May fix other cases.
2023-05-03 10:42:21 +02:00
cc1a933a2f ModLoader: Fix case sensitivy issues (#4720)
* Fix case sensitivity for mod subdirectories

* Small refactoring of ModLoader
2023-05-03 02:07:16 +02:00
dd574146fb Add hide-cursor command line argument & always hide cursor option (#4613)
* Add hide-cursor command line argument

* gtk: Adjust SettingsWindow for hide cursor options

* ava: Adjust SettingsWindow for hide cursor options

* ava: Add override check for HideCursor arg

* Remove copy&paste sins

* ava: Leave a little more room between the options

* gtk: Fix hide cursor issues

* ava: Only hide cursor if it's within the embedded window
2023-05-02 03:29:47 +02:00
2c94ac455e GPU: Keep rendered textures without any pool references alive (#4662)
* GPU: Keep sampled textures without any pool references alive

Occasionally games are very wasteful and clear/write to a texture without ever sampling it. As rendered textures in NVN games seem to all have overlapping memory ranges, the texture will eventually get overwritten.

Normally, this would trigger a removal from the auto delete cache, but a pool entry would keep the texture alive. However, with these textures that are never used, they will get deleted immediately and recreated on the next frame.

This change makes it so the ShortTextureCache can keep textures that have naver had a pool reference alive for a few frames, so they're not constantly being created and deleted.

This improves performance in Zelda BOTW a little.

* Cleanup
2023-05-01 16:27:51 -03:00
e18d258fa0 GPU: Pre-emptively flush textures that are flushed often (to imported memory when available) (#4711)
* WIP texture pre-flush

Improve performance of TextureView GetData to buffer

Fix copy/sync ordering

Fix minor bug

Make this actually work

WIP host mapping stuff

* Fix usage flags

* message

* Cleanup 1

* Fix rebase

* Fix

* Improve pre-flush rules

* Fix pre-flush

* A lot of cleanup

* Use the host memory bits

* Select the correct memory type

* Cleanup TextureGroupHandle

* Missing comment

* Remove debugging logs

* Revert BufferHandle _value access modifier

* One interrupt action at a time.

* Support D32S8 to D24S8 conversion, safeguards

* Interrupt cannot happen in sync handle's lock

Waitable needs to be checked twice now, but this should stop it from deadlocking.

* Remove unused using

* Address some feedback

* Address feedback

* Address more feedback

* Address more feedback

* Improve sync rules

Should allow for faster sync in some cases.
2023-05-01 16:05:12 -03:00
36f10df775 GPU: Fix errors handling texture remapping (#4745)
* GPU: Fix errors handling texture remapping

- Fixes an error where a pool entry and memory mapping changing at the same time could cause a texture to rebind its data from the wrong GPU VA (data swaps)
- Fixes an error where the texture pool could act on a mapping change before the mapping has actually been changed ("Unmapped" event happens before change, we need to signal it changed _after_ it completes)

TODO: remove textures from partially mapped list... if they aren't.

* Add Remap actions for handling post-mapping behaviours

* Remove unused code.

* Address feedback

* Nit
2023-05-01 15:32:32 -03:00
680e548022 Uneven frame pacing with vsync (#4744)
fixes issue #3906
2023-04-29 21:54:41 +01:00
21c4176157 Allow window to remember its size, position and state (GTK + Avalonia) (#4657)
* Update ConfigurationState.cs

* Update ConfigurationFileFormat.cs

* Update MainWindow.cs

* Update ConfigurationFileFormat.cs

* Update ConfigurationState.cs

* Update MainWindow.cs

* Update MainWindow.cs

* Update Ryujinx.Ui.Common/Configuration/ConfigurationState.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Update MainWindow.cs

* Update Ryujinx/Ui/MainWindow.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Initial properties

* Viewmodel adjustments and additions

* abstract and monitor dimension changes

* Remove position from ViewModel and simplify methods

* Remove unused dep

* Update configuration and fix typo from AA

* review changes

* Review changes

* Screensize checks - Ava

* Review changes 2

* basic review changes

* Standardise GTK/Ava functions

* Actually call function

---------

Co-authored-by: HaizenTrist <123991082+HaizenTrist@users.noreply.github.com>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-04-28 22:59:53 +02:00
3b4ff2d6d9 nuget: bump System.IdentityModel.Tokens.Jwt from 6.29.0 to 6.30.0 (#4736)
Bumps [System.IdentityModel.Tokens.Jwt](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.29.0 to 6.30.0.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/6.29.0...6.30.0)

---
updated-dependencies:
- dependency-name: System.IdentityModel.Tokens.Jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 11:54:19 +02:00
130 changed files with 3432 additions and 1213 deletions

View File

@ -44,7 +44,7 @@
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.29.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.30.0" />
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
<PackageVersion Include="System.Management" Version="7.0.1" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />

View File

@ -1,6 +1,6 @@
#!/bin/sh
SCRIPT_DIR=$(dirname $(realpath $0))
SCRIPT_DIR=$(dirname "$(realpath "$0")")
RYUJINX_BIN="Ryujinx"
if [ -f "$SCRIPT_DIR/Ryujinx.Ava" ]; then

View File

@ -157,7 +157,7 @@ namespace Ryujinx.Ava
_isFirmwareTitle = true;
}
ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed;
ConfigurationState.Instance.HideCursor.Event += HideCursorState_Changed;
_topLevel.PointerMoved += TopLevel_PointerMoved;
@ -468,9 +468,9 @@ namespace Ryujinx.Ava
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null);
}
private void HideCursorState_Changed(object sender, ReactiveEventArgs<bool> state)
private void HideCursorState_Changed(object sender, ReactiveEventArgs<HideCursorMode> state)
{
if (state.NewValue)
if (state.NewValue == HideCursorMode.OnIdle)
{
_lastCursorMoveTime = Stopwatch.GetTimestamp();
}
@ -965,30 +965,38 @@ namespace Ryujinx.Ava
if (_viewModel.IsActive)
{
if (ConfigurationState.Instance.Hid.EnableMouse)
if (_isCursorInRenderer)
{
if (_isCursorInRenderer)
if (ConfigurationState.Instance.Hid.EnableMouse)
{
HideCursor();
}
else
{
ShowCursor();
switch (ConfigurationState.Instance.HideCursor.Value)
{
case HideCursorMode.Never:
ShowCursor();
break;
case HideCursorMode.OnIdle:
if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency)
{
HideCursor();
}
else
{
ShowCursor();
}
break;
case HideCursorMode.Always:
HideCursor();
break;
}
}
}
else
{
if (ConfigurationState.Instance.HideCursorOnIdle)
{
if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency)
{
HideCursor();
}
else
{
ShowCursor();
}
}
ShowCursor();
}
Dispatcher.UIThread.Post(() =>
@ -1133,4 +1141,4 @@ namespace Ryujinx.Ava
return state;
}
}
}
}

View File

@ -80,7 +80,10 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch",
"SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog",
"SettingsTabGeneralHideCursorOnIdle": "Hide Cursor on Idle",
"SettingsTabGeneralHideCursor": "Hide Cursor:",
"SettingsTabGeneralHideCursorNever": "Never",
"SettingsTabGeneralHideCursorOnIdle": "On Idle",
"SettingsTabGeneralHideCursorAlways": "Always",
"SettingsTabGeneralGameDirectories": "Game Directories",
"SettingsTabGeneralAdd": "Add",
"SettingsTabGeneralRemove": "Remove",
@ -587,6 +590,7 @@
"DlcWindowTitle": "Manage Downloadable Content for {0} ({1})",
"UpdateWindowTitle": "Title Update Manager",
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
"BuildId": "BuildId:",
"DlcWindowHeading": "{0} Downloadable Content(s)",
"UserProfilesEditProfile": "Edit Selected",
"Cancel": "Cancel",
@ -641,5 +645,6 @@
"UserEditorTitleCreate" : "Create User",
"SettingsTabNetworkInterface": "Network Interface:",
"NetworkInterfaceTooltip": "The network interface used for LAN features",
"NetworkInterfaceDefault": "Default"
"NetworkInterfaceDefault": "Default",
"PackagingShaders": "Packaging Shaders"
}

View File

@ -233,9 +233,14 @@ namespace Ryujinx.Ava.Common
try
{
IFileSystem ncaFileSystem = patchNca != null
? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid)
: mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid);
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;

View File

@ -183,6 +183,18 @@ namespace Ryujinx.Ava
{
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
}
// Check if HideCursor was overridden.
if (CommandLineState.OverrideHideCursor is not null)
{
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch
{
"never" => HideCursorMode.Never,
"onidle" => HideCursorMode.OnIdle,
"always" => HideCursorMode.Always,
_ => ConfigurationState.Instance.HideCursor.Value
};
}
}
private static void PrintSystemInfo()
@ -226,4 +238,4 @@ namespace Ryujinx.Ava
Logger.Shutdown();
}
}
}
}

View File

@ -103,50 +103,6 @@
<Generator>MSBuild:Compile</Generator>
</AvaloniaResource>
<AvaloniaResource Include="Assets\Styles\Styles.xaml" />
<Compile Update="App.axaml.cs">
<DependentUpon>App.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Ui\Windows\MainWindow.axaml.cs">
<DependentUpon>MainWindow.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Ui\Windows\AboutWindow.axaml.cs">
<DependentUpon>AboutWindow.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Ui\Applet\ErrorAppletWindow.axaml.cs">
<DependentUpon>ProfileWindow.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Ui\Applet\SwkbdAppletWindow.axaml.cs">
<DependentUpon>ProfileWindow.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Ui\Controls\InputDialog.axaml.cs">
<DependentUpon>InputDialog.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Ui\Windows\ContentDialogOverlay.xaml.cs">
<DependentUpon>ContentDialogOverlay.xaml</DependentUpon>
</Compile>
<Compile Update="Ui\Controls\GameListView.axaml.cs">
<DependentUpon>GameListView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="UI\Views\User\UserEditorView.axaml.cs">
<DependentUpon>UserEditor.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="UI\Views\User\UserRecovererView.axaml.cs">
<DependentUpon>UserRecoverer.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="UI\Views\User\UserSelectorView.axaml.cs">
<DependentUpon>UserSelector.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,80 @@
<MenuFlyout
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">
<MenuItem
Click="ToggleFavorite_Click"
Header="{locale:Locale GameListContextMenuToggleFavorite}"
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
<Separator />
<MenuItem
Click="OpenUserSaveDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
IsEnabled="{Binding OpenUserSaveDirectoryEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
<MenuItem
Click="OpenDeviceSaveDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}"
IsEnabled="{Binding OpenDeviceSaveDirectoryEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
<MenuItem
Click="OpenBcatSaveDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}"
IsEnabled="{Binding OpenBcatSaveDirectoryEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
<Separator />
<MenuItem
Click="OpenTitleUpdateManager_Click"
Header="{locale:Locale GameListContextMenuManageTitleUpdates}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
<MenuItem
Click="OpenDownloadableContentManager_Click"
Header="{locale:Locale GameListContextMenuManageDlc}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" />
<MenuItem
Click="OpenCheatManager_Click"
Header="{locale:Locale GameListContextMenuManageCheat}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" />
<MenuItem
Click="OpenModsDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
<MenuItem
Click="OpenSdModsDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
<Separator />
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
<MenuItem
Click="PurgePtcCache_Click"
Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
<MenuItem
Click="PurgeShaderCache_Click"
Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" />
<MenuItem
Click="OpenPtcDirectory_Click"
Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" />
<MenuItem
Click="OpenShaderCacheDirectory_Click"
Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" />
</MenuItem>
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}">
<MenuItem
Click="ExtractApplicationExeFs_Click"
Header="{locale:Locale GameListContextMenuExtractDataExeFS}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" />
<MenuItem
Click="ExtractApplicationRomFs_Click"
Header="{locale:Locale GameListContextMenuExtractDataRomFS}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" />
<MenuItem
Click="ExtractApplicationLogo_Click"
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
</MenuItem>
</MenuFlyout>

View File

@ -0,0 +1,327 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using LibHac.Fs;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Configuration;
using Ryujinx.Ui.App.Common;
using Ryujinx.HLE.HOS;
using Ryujinx.Ui.Common.Helper;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Path = System.IO.Path;
using UserId = LibHac.Fs.UserId;
namespace Ryujinx.Ava.UI.Controls
{
public class ApplicationContextMenu : MenuFlyout
{
public ApplicationContextMenu()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void ToggleFavorite_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
viewModel.ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.TitleId, appMetadata =>
{
appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
});
viewModel.RefreshView();
}
}
public void OpenUserSaveDirectory_Click(object sender, RoutedEventArgs args)
{
if ((sender as MenuItem)?.DataContext is MainWindowViewModel viewModel)
{
OpenSaveDirectory(viewModel, SaveDataType.Account, userId: new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low));
}
}
public void OpenDeviceSaveDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
OpenSaveDirectory(viewModel, SaveDataType.Device, userId: default);
}
public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
OpenSaveDirectory(viewModel, SaveDataType.Bcat, userId: default);
}
private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId)
{
if (viewModel?.SelectedApplication != null)
{
if (!ulong.TryParse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
});
return;
}
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveDataType, userId, saveDataId: default, index: default);
ApplicationHelper.OpenSaveDir(in saveDataFilter, titleIdNumber, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.TitleName);
}
}
public async void OpenTitleUpdateManager_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName);
}
}
public async void OpenDownloadableContentManager_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName);
}
}
public async void OpenCheatManager_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
await new CheatWindow(
viewModel.VirtualFileSystem,
viewModel.SelectedApplication.TitleId,
viewModel.SelectedApplication.TitleName,
viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window);
}
}
public void OpenModsDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
string modsBasePath = ModLoader.GetModsBasePath();
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, viewModel.SelectedApplication.TitleId);
OpenHelper.OpenFolder(titleModsPath);
}
}
public void OpenSdModsDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
string sdModsBasePath = ModLoader.GetSdModsBasePath();
string titleModsPath = ModLoader.GetTitleDir(sdModsBasePath, viewModel.SelectedApplication.TitleId);
OpenHelper.OpenFolder(titleModsPath);
}
}
public async void PurgePtcCache_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes)
{
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "0"));
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "1"));
List<FileInfo> cacheFiles = new();
if (mainDir.Exists)
{
cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
}
if (backupDir.Exists)
{
cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
}
if (cacheFiles.Count > 0)
{
foreach (FileInfo file in cacheFiles)
{
try
{
file.Delete();
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex));
}
}
}
}
}
}
public async void PurgeShaderCache_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes)
{
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "shader"));
List<DirectoryInfo> oldCacheDirectories = new();
List<FileInfo> newCacheFiles = new();
if (shaderCacheDir.Exists)
{
oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*"));
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc"));
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data"));
}
if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0))
{
foreach (DirectoryInfo directory in oldCacheDirectories)
{
try
{
directory.Delete(true);
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, ex));
}
}
foreach (FileInfo file in newCacheFiles)
{
try
{
file.Delete();
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, ex));
}
}
}
}
}
}
public void OpenPtcDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu");
string mainDir = Path.Combine(ptcDir, "0");
string backupDir = Path.Combine(ptcDir, "1");
if (!Directory.Exists(ptcDir))
{
Directory.CreateDirectory(ptcDir);
Directory.CreateDirectory(mainDir);
Directory.CreateDirectory(backupDir);
}
OpenHelper.OpenFolder(ptcDir);
}
}
public void OpenShaderCacheDirectory_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "shader");
if (!Directory.Exists(shaderCacheDir))
{
Directory.CreateDirectory(shaderCacheDir);
}
OpenHelper.OpenFolder(shaderCacheDir);
}
}
public async void ExtractApplicationExeFs_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
await ApplicationHelper.ExtractSection(NcaSectionType.Code, viewModel.SelectedApplication.Path, viewModel.SelectedApplication.TitleName);
}
}
public async void ExtractApplicationRomFs_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
await ApplicationHelper.ExtractSection(NcaSectionType.Data, viewModel.SelectedApplication.Path, viewModel.SelectedApplication.TitleName);
}
}
public async void ExtractApplicationLogo_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
await ApplicationHelper.ExtractSection(NcaSectionType.Logo, viewModel.SelectedApplication.Path, viewModel.SelectedApplication.TitleName);
}
}
}
}

View File

@ -0,0 +1,102 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Controls.ApplicationGridView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
d:DesignHeight="450"
d:DesignWidth="800"
Focusable="True"
mc:Ignorable="d">
<UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
<controls:ApplicationContextMenu x:Key="ApplicationContextMenu" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox
Grid.Row="0"
Padding="8"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ContextFlyout="{StaticResource ApplicationContextMenu}"
DoubleTapped="GameList_DoubleTapped"
Items="{Binding AppsObservableList}"
SelectionChanged="GameList_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<flex:FlexPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AlignContent="FlexStart"
JustifyContent="Center" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="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>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Border
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}"
ClipToBounds="True"
CornerRadius="4">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
<Panel
Grid.Row="1"
Height="50"
Margin="0,10,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}">
<TextBlock
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding TitleName}"
TextAlignment="Center"
TextWrapping="Wrap" />
</Panel>
</Grid>
</Border>
<ui:SymbolIcon
Margin="5,5,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontSize="16"
Foreground="{DynamicResource SystemAccentColor}"
IsVisible="{Binding Favorite}"
Symbol="StarFilled" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>

View File

@ -9,10 +9,10 @@ using System;
namespace Ryujinx.Ava.UI.Controls
{
public partial class GameGridView : UserControl
public partial class ApplicationGridView : UserControl
{
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
RoutedEvent.Register<ApplicationGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
{
@ -20,7 +20,7 @@ namespace Ryujinx.Ava.UI.Controls
remove { RemoveHandler(ApplicationOpenedEvent, value); }
}
public GameGridView()
public ApplicationGridView()
{
InitializeComponent();
}
@ -49,7 +49,7 @@ namespace Ryujinx.Ava.UI.Controls
}
}
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
private void SearchBox_OnKeyUp(object sender, KeyEventArgs args)
{
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
}

View File

@ -1,10 +1,10 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Controls.GameListView"
x:Class="Ryujinx.Ava.UI.Controls.ApplicationListView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
d:DesignHeight="450"
@ -13,82 +13,7 @@
mc:Ignorable="d">
<UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
<MenuFlyout x:Key="GameContextMenu">
<MenuItem
Command="{Binding ToggleFavorite}"
Header="{locale:Locale GameListContextMenuToggleFavorite}"
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
<Separator />
<MenuItem
Command="{Binding OpenUserSaveDirectory}"
IsEnabled="{Binding EnabledUserSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
<MenuItem
Command="{Binding OpenDeviceSaveDirectory}"
IsEnabled="{Binding EnabledDeviceSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
<MenuItem
Command="{Binding OpenBcatSaveDirectory}"
IsEnabled="{Binding EnabledBcatSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
<Separator />
<MenuItem
Command="{Binding OpenTitleUpdateManager}"
Header="{locale:Locale GameListContextMenuManageTitleUpdates}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
<MenuItem
Command="{Binding OpenDownloadableContentManager}"
Header="{locale:Locale GameListContextMenuManageDlc}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" />
<MenuItem
Command="{Binding OpenCheatManager}"
Header="{locale:Locale GameListContextMenuManageCheat}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" />
<MenuItem
Command="{Binding OpenModsDirectory}"
Header="{locale:Locale GameListContextMenuOpenModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
<MenuItem
Command="{Binding OpenSdModsDirectory}"
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
<Separator />
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
<MenuItem
Command="{Binding PurgePtcCache}"
Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
<MenuItem
Command="{Binding PurgeShaderCache}"
Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" />
<MenuItem
Command="{Binding OpenPtcDirectory}"
Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" />
<MenuItem
Command="{Binding OpenShaderCacheDirectory}"
Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" />
</MenuItem>
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}">
<MenuItem
Command="{Binding ExtractExeFs}"
Header="{locale:Locale GameListContextMenuExtractDataExeFS}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" />
<MenuItem
Command="{Binding ExtractRomFs}"
Header="{locale:Locale GameListContextMenuExtractDataRomFS}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" />
<MenuItem
Command="{Binding ExtractLogo}"
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
</MenuItem>
</MenuFlyout>
<controls:ApplicationContextMenu x:Key="ApplicationContextMenu" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
@ -100,7 +25,7 @@
Padding="8"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ContextFlyout="{StaticResource GameContextMenu}"
ContextFlyout="{StaticResource ApplicationContextMenu}"
DoubleTapped="GameList_DoubleTapped"
Items="{Binding AppsObservableList}"
SelectionChanged="GameList_SelectionChanged">

View File

@ -9,10 +9,10 @@ using System;
namespace Ryujinx.Ava.UI.Controls
{
public partial class GameListView : UserControl
public partial class ApplicationListView : UserControl
{
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
RoutedEvent.Register<ApplicationListView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
{
@ -20,7 +20,7 @@ namespace Ryujinx.Ava.UI.Controls
remove { RemoveHandler(ApplicationOpenedEvent, value); }
}
public GameListView()
public ApplicationListView()
{
InitializeComponent();
}
@ -49,7 +49,7 @@ namespace Ryujinx.Ava.UI.Controls
}
}
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
private void SearchBox_OnKeyUp(object sender, KeyEventArgs args)
{
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
}

View File

@ -1,177 +0,0 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Controls.GameGridView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d"
Focusable="True">
<UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
<MenuFlyout x:Key="GameContextMenu">
<MenuItem
Command="{Binding ToggleFavorite}"
Header="{locale:Locale GameListContextMenuToggleFavorite}"
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
<Separator />
<MenuItem
Command="{Binding OpenUserSaveDirectory}"
IsEnabled="{Binding EnabledUserSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
<MenuItem
Command="{Binding OpenDeviceSaveDirectory}"
IsEnabled="{Binding EnabledDeviceSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
<MenuItem
Command="{Binding OpenBcatSaveDirectory}"
IsEnabled="{Binding EnabledBcatSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
<Separator />
<MenuItem
Command="{Binding OpenTitleUpdateManager}"
Header="{locale:Locale GameListContextMenuManageTitleUpdates}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
<MenuItem
Command="{Binding OpenDownloadableContentManager}"
Header="{locale:Locale GameListContextMenuManageDlc}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" />
<MenuItem
Command="{Binding OpenCheatManager}"
Header="{locale:Locale GameListContextMenuManageCheat}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" />
<MenuItem
Command="{Binding OpenModsDirectory}"
Header="{locale:Locale GameListContextMenuOpenModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
<MenuItem
Command="{Binding OpenSdModsDirectory}"
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
<Separator />
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
<MenuItem
Command="{Binding PurgePtcCache}"
Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
<MenuItem
Command="{Binding PurgeShaderCache}"
Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" />
<MenuItem
Command="{Binding OpenPtcDirectory}"
Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" />
<MenuItem
Command="{Binding OpenShaderCacheDirectory}"
Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" />
</MenuItem>
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}">
<MenuItem
Command="{Binding ExtractExeFs}"
Header="{locale:Locale GameListContextMenuExtractDataExeFS}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" />
<MenuItem
Command="{Binding ExtractRomFs}"
Header="{locale:Locale GameListContextMenuExtractDataRomFS}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" />
<MenuItem
Command="{Binding ExtractLogo}"
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
</MenuItem>
</MenuFlyout>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox
Grid.Row="0"
Padding="8"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ContextFlyout="{StaticResource GameContextMenu}"
DoubleTapped="GameList_DoubleTapped"
Items="{Binding AppsObservableList}"
SelectionChanged="GameList_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<flex:FlexPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AlignContent="FlexStart"
JustifyContent="Center" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="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>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Border
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}"
ClipToBounds="True"
CornerRadius="4">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
<Panel
Grid.Row="1"
Height="50"
Margin="0 10 0 0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}">
<TextBlock
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding TitleName}"
TextAlignment="Center"
TextWrapping="Wrap" />
</Panel>
</Grid>
</Border>
<ui:SymbolIcon
Margin="5,5,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontSize="16"
Foreground="{DynamicResource SystemAccentColor}"
IsVisible="{Binding Favorite}"
Symbol="StarFilled" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>

View File

@ -6,8 +6,6 @@ using Avalonia.Threading;
using DynamicData;
using DynamicData.Binding;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
@ -33,13 +31,11 @@ using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Path = System.IO.Path;
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
using UserId = LibHac.Fs.UserId;
namespace Ryujinx.Ava.UI.ViewModels
{
@ -95,6 +91,9 @@ namespace Ryujinx.Ava.UI.ViewModels
private string _currentEmulatedGamePath;
private AutoResetEvent _rendererWaitEvent;
private WindowState _windowState;
private double _windowWidth;
private double _windowHeight;
private bool _isActive;
public ApplicationData ListSelectedApplication;
@ -343,11 +342,11 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public bool EnabledUserSaveDirectory => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0;
public bool OpenUserSaveDirectoryEnabled => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0;
public bool EnabledDeviceSaveDirectory => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
public bool OpenDeviceSaveDirectoryEnabled => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
public bool EnabledBcatSaveDirectory => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
public bool OpenBcatSaveDirectoryEnabled => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
public string LoadHeading
{
@ -622,6 +621,28 @@ namespace Ryujinx.Ava.UI.ViewModels
OnPropertyChanged();
}
}
public double WindowWidth
{
get => _windowWidth;
set
{
_windowWidth = value;
OnPropertyChanged();
}
}
public double WindowHeight
{
get => _windowHeight;
set
{
_windowHeight = value;
OnPropertyChanged();
}
}
public bool IsGrid => Glyph == Glyph.Grid;
public bool IsList => Glyph == Glyph.List;
@ -916,7 +937,7 @@ namespace Ryujinx.Ava.UI.ViewModels
};
}
private void RefreshView()
public void RefreshView()
{
RefreshGrid();
}
@ -1078,6 +1099,10 @@ namespace Ryujinx.Ava.UI.ViewModels
LoadHeading = LocaleManager.Instance[LocaleKeys.CompilingShaders];
IsLoadingIndeterminate = false;
break;
case ShaderCacheLoadingState.Packaging:
LoadHeading = LocaleManager.Instance[LocaleKeys.PackagingShaders];
IsLoadingIndeterminate = false;
break;
case ShaderCacheLoadingState.Loaded:
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
IsLoadingIndeterminate = true;
@ -1091,30 +1116,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}));
}
private async void ExtractLogo()
{
if (SelectedApplication != null)
{
await ApplicationHelper.ExtractSection(NcaSectionType.Logo, SelectedApplication.Path, SelectedApplication.TitleName);
}
}
private async void ExtractRomFs()
{
if (SelectedApplication != null)
{
await ApplicationHelper.ExtractSection(NcaSectionType.Data, SelectedApplication.Path, SelectedApplication.TitleName);
}
}
private async void ExtractExeFs()
{
if (SelectedApplication != null)
{
await ApplicationHelper.ExtractSection(NcaSectionType.Code, SelectedApplication.Path, SelectedApplication.TitleName);
}
}
private void PrepareLoadScreen()
{
using MemoryStream stream = new(SelectedIcon);
@ -1358,241 +1359,11 @@ namespace Ryujinx.Ava.UI.ViewModels
await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient);
}
public void OpenPtcDirectory()
{
ApplicationData selection = SelectedApplication;
if (selection != null)
{
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu");
string mainPath = Path.Combine(ptcDir, "0");
string backupPath = Path.Combine(ptcDir, "1");
if (!Directory.Exists(ptcDir))
{
Directory.CreateDirectory(ptcDir);
Directory.CreateDirectory(mainPath);
Directory.CreateDirectory(backupPath);
}
OpenHelper.OpenFolder(ptcDir);
}
}
public async void PurgePtcCache()
{
ApplicationData selection = SelectedApplication;
if (selection != null)
{
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0"));
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, selection.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
List<FileInfo> cacheFiles = new();
if (mainDir.Exists)
{
cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
}
if (backupDir.Exists)
{
cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
}
if (cacheFiles.Count > 0 && result == UserResult.Yes)
{
foreach (FileInfo file in cacheFiles)
{
try
{
file.Delete();
}
catch (Exception e)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, e));
}
}
}
}
}
public void OpenShaderCacheDirectory()
{
ApplicationData selection = SelectedApplication;
if (selection != null)
{
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader");
if (!Directory.Exists(shaderCacheDir))
{
Directory.CreateDirectory(shaderCacheDir);
}
OpenHelper.OpenFolder(shaderCacheDir);
}
}
public void SimulateWakeUpMessage()
{
AppHost.Device.System.SimulateWakeUpMessage();
}
public async void PurgeShaderCache()
{
ApplicationData selection = SelectedApplication;
if (selection != null)
{
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, selection.TitleName),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
List<DirectoryInfo> oldCacheDirectories = new();
List<FileInfo> newCacheFiles = new();
if (shaderCacheDir.Exists)
{
oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*"));
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc"));
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data"));
}
if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0) && result == UserResult.Yes)
{
foreach (DirectoryInfo directory in oldCacheDirectories)
{
try
{
directory.Delete(true);
}
catch (Exception e)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, e));
}
}
}
foreach (FileInfo file in newCacheFiles)
{
try
{
file.Delete();
}
catch (Exception e)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, e));
}
}
}
}
public void ToggleFavorite()
{
ApplicationData selection = SelectedApplication;
if (selection != null)
{
selection.Favorite = !selection.Favorite;
ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata =>
{
appMetadata.Favorite = selection.Favorite;
});
RefreshView();
}
}
public void OpenUserSaveDirectory()
{
OpenSaveDirectory(SaveDataType.Account, userId: new UserId((ulong)AccountManager.LastOpenedUser.UserId.High, (ulong)AccountManager.LastOpenedUser.UserId.Low));
}
public void OpenDeviceSaveDirectory()
{
OpenSaveDirectory(SaveDataType.Device, userId: default);
}
public void OpenBcatSaveDirectory()
{
OpenSaveDirectory(SaveDataType.Bcat, userId: default);
}
private void OpenSaveDirectory(SaveDataType saveDataType, UserId userId)
{
if (SelectedApplication != null)
{
if (!ulong.TryParse(SelectedApplication.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
});
return;
}
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveDataType, userId, saveDataId: default, index: default);
ApplicationHelper.OpenSaveDir(in saveDataFilter, titleIdNumber, SelectedApplication.ControlHolder, SelectedApplication.TitleName);
}
}
public void OpenModsDirectory()
{
if (SelectedApplication != null)
{
string modsBasePath = VirtualFileSystem.ModLoader.GetModsBasePath();
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, SelectedApplication.TitleId);
OpenHelper.OpenFolder(titleModsPath);
}
}
public void OpenSdModsDirectory()
{
if (SelectedApplication != null)
{
string sdModsBasePath = VirtualFileSystem.ModLoader.GetSdModsBasePath();
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, SelectedApplication.TitleId);
OpenHelper.OpenFolder(titleModsPath);
}
}
public async void OpenTitleUpdateManager()
{
if (SelectedApplication != null)
{
await TitleUpdateWindow.Show(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName);
}
}
public async void OpenDownloadableContentManager()
{
if (SelectedApplication != null)
{
await DownloadableContentManagerWindow.Show(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName);
}
}
public async void OpenCheatManager()
{
if (SelectedApplication != null)
{
await new CheatWindow(VirtualFileSystem, SelectedApplication.TitleId, SelectedApplication.TitleName).ShowDialog(TopLevel as Window);
}
}
public async void LoadApplications()
{
await Dispatcher.UIThread.InvokeAsync(() =>

View File

@ -132,7 +132,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableDiscordIntegration { get; set; }
public bool CheckUpdatesOnStart { get; set; }
public bool ShowConfirmExit { get; set; }
public bool HideCursorOnIdle { get; set; }
public int HideCursor { get; set; }
public bool EnableDockedMode { get; set; }
public bool EnableKeyboard { get; set; }
public bool EnableMouse { get; set; }
@ -375,7 +375,7 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableDiscordIntegration = config.EnableDiscordIntegration;
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
ShowConfirmExit = config.ShowConfirmExit;
HideCursorOnIdle = config.HideCursorOnIdle;
HideCursor = (int)config.HideCursor.Value;
GameDirectories.Clear();
GameDirectories.AddRange(config.Ui.GameDirs.Value);
@ -458,7 +458,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
config.ShowConfirmExit.Value = ShowConfirmExit;
config.HideCursorOnIdle.Value = HideCursorOnIdle;
config.HideCursor.Value = (HideCursorMode)HideCursor;
if (_directoryChanged)
{

View File

@ -11,6 +11,7 @@ using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS;
using Ryujinx.Modules;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
@ -176,7 +177,11 @@ namespace Ryujinx.Ava.UI.Views.Main
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
await new CheatWindow(Window.VirtualFileSystem, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, name).ShowDialog(Window);
await new CheatWindow(
Window.VirtualFileSystem,
ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText,
name,
Window.ViewModel.SelectedApplication.Path).ShowDialog(Window);
ViewModel.AppHost.Device.EnableCheats();
}

View File

@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsUIView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@ -12,7 +12,7 @@
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<ScrollViewer
<ScrollViewer
Name="UiPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
@ -37,9 +37,24 @@
<CheckBox IsChecked="{Binding ShowConfirmExit}">
<TextBlock Text="{locale:Locale SettingsTabGeneralShowConfirmExitDialog}" />
</CheckBox>
<CheckBox IsChecked="{Binding HideCursorOnIdle}">
<TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorOnIdle}" />
</CheckBox>
<StackPanel Margin="0, 15, 0, 10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabGeneralHideCursor}"
Width="150" />
<ComboBox SelectedIndex="{Binding HideCursor}"
HorizontalContentAlignment="Left"
MinWidth="100">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorNever}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorOnIdle}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorAlways}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
<Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGameDirectories}" />
@ -105,7 +120,7 @@
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<CheckBox
<CheckBox
IsChecked="{Binding EnableCustomTheme}"
ToolTip.Tip="{locale:Locale CustomThemeCheckTooltip}">
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeEnableCustomTheme}" />
@ -122,7 +137,7 @@
Grid.Column="1"
Margin="0,10,0,0"
Text="{Binding CustomThemePath}" />
<Button
<Button
Grid.Row="1"
Grid.Column="2"
Margin="10,10,0,0"
@ -153,4 +168,4 @@
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>
</UserControl>

View File

@ -21,23 +21,52 @@
</Window.Styles>
<Grid Name="CheatGrid" Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
MaxWidth="500"
Margin="20,15,20,20"
Margin="20,15,20,5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
LineHeight="18"
Text="{Binding Heading}"
TextAlignment="Center"
TextWrapping="Wrap" />
<Border
<TextBlock
Grid.Row="2"
Grid.Column="0"
MaxWidth="500"
Margin="140,15,20,5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
LineHeight="30"
Text="{locale:Locale BuildId}"
TextAlignment="Center"
TextWrapping="Wrap" />
<TextBox
Grid.Row="2"
Grid.Column="1"
Margin="0,5,110,5"
MinWidth="160"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding BuildId}"
IsReadOnly="True" />
<Border
Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
@ -81,7 +110,9 @@
</TreeView>
</Border>
<DockPanel
Grid.Row="3"
Grid.Row="4"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0"
HorizontalAlignment="Stretch">
<DockPanel Margin="0" HorizontalAlignment="Right">

View File

@ -1,8 +1,10 @@
using Avalonia.Collections;
using Avalonia;
using Avalonia.Collections;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.Ui.App.Common;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -17,6 +19,7 @@ namespace Ryujinx.Ava.UI.Windows
private AvaloniaList<CheatsList> LoadedCheats { get; }
public string Heading { get; }
public string BuildId { get; }
public CheatWindow()
{
@ -27,16 +30,17 @@ namespace Ryujinx.Ava.UI.Windows
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.CheatWindowTitle];
}
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName)
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
{
LoadedCheats = new AvaloniaList<CheatsList>();
Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper());
BuildId = ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath);
InitializeComponent();
string modsBasePath = virtualFileSystem.ModLoader.GetModsBasePath();
string titleModsPath = virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, titleId);
string modsBasePath = ModLoader.GetModsBasePath();
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, titleId);
ulong titleIdValue = ulong.Parse(titleId, System.Globalization.NumberStyles.HexNumber);
_enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt");

View File

@ -12,15 +12,14 @@
Cursor="{Binding Cursor}"
Title="{Binding Title}"
WindowState="{Binding WindowState}"
Width="1280"
Height="777"
Width="{Binding WindowWidth}"
Height="{Binding WindowHeight}"
MinWidth="1092"
MinHeight="672"
d:DesignHeight="720"
d:DesignWidth="1280"
x:CompileBindings="True"
x:DataType="viewModels:MainWindowViewModel"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d"
Focusable="True">
<Window.Styles>
@ -89,16 +88,16 @@
<main:MainViewControls
Name="ViewControls"
Grid.Row="0"/>
<controls:GameListView
x:Name="GameList"
<controls:ApplicationListView
x:Name="ApplicationList"
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
IsVisible="{Binding IsList}" />
<controls:GameGridView
x:Name="GameGrid"
<controls:ApplicationGridView
x:Name="ApplicationGrid"
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"

View File

@ -62,6 +62,8 @@ namespace Ryujinx.Ava.UI.Windows
DataContext = ViewModel;
SetWindowSizePosition();
InitializeComponent();
Load();
@ -286,17 +288,62 @@ namespace Ryujinx.Ava.UI.Windows
{
StatusBarView.VolumeStatus.Click += VolumeStatus_CheckedChanged;
GameGrid.ApplicationOpened += Application_Opened;
ApplicationGrid.ApplicationOpened += Application_Opened;
GameGrid.DataContext = ViewModel;
ApplicationGrid.DataContext = ViewModel;
GameList.ApplicationOpened += Application_Opened;
ApplicationList.ApplicationOpened += Application_Opened;
GameList.DataContext = ViewModel;
ApplicationList.DataContext = ViewModel;
LoadHotKeys();
}
private void SetWindowSizePosition()
{
PixelPoint SavedPoint = new PixelPoint(ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX,
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY);
ViewModel.WindowHeight = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
ViewModel.WindowWidth = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor;
ViewModel.WindowState = ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value is true ? WindowState.Maximized : WindowState.Normal;
if (CheckScreenBounds(SavedPoint))
{
Position = SavedPoint;
}
else WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
private bool CheckScreenBounds(PixelPoint configPoint)
{
for (int i = 0; i < Screens.ScreenCount; i++)
{
if (Screens.All[i].Bounds.Contains(configPoint))
{
return true;
}
}
Logger.Warning?.Print(LogClass.Application, $"Failed to find valid start-up coordinates. Defaulting to primary monitor center.");
return false;
}
private void SaveWindowSizePosition()
{
ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight.Value = (int)Height;
ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth.Value = (int)Width;
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX.Value = Position.X;
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY.Value = Position.Y;
ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized;
MainWindowViewModel.SaveConfig();
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
@ -388,6 +435,8 @@ namespace Ryujinx.Ava.UI.Windows
return;
}
SaveWindowSizePosition();
ApplicationLibrary.CancelLoading();
InputManager.Dispose();
Program.Exit();

View File

@ -0,0 +1,9 @@
namespace Ryujinx.Common.Configuration
{
public enum HideCursorMode
{
Never,
OnIdle,
Always
}
}

View File

@ -1,5 +1,4 @@
using ARMeilleure.Memory;
using Ryujinx.Cpu.Tracking;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking;
@ -822,21 +821,21 @@ namespace Ryujinx.Cpu.AppleHv
}
/// <inheritdoc/>
public CpuRegionHandle BeginTracking(ulong address, ulong size, int id)
public RegionHandle BeginTracking(ulong address, ulong size, int id)
{
return new CpuRegionHandle(Tracking.BeginTracking(address, size, id));
return Tracking.BeginTracking(address, size, id);
}
/// <inheritdoc/>
public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
{
return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity, id));
return Tracking.BeginGranularTracking(address, size, handles, granularity, id);
}
/// <inheritdoc/>
public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
{
return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity, id));
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
}
/// <summary>

View File

@ -1,5 +1,4 @@
using Ryujinx.Cpu.Tracking;
using Ryujinx.Memory;
using Ryujinx.Memory;
using Ryujinx.Memory.Tracking;
using System;
using System.Collections.Generic;
@ -30,7 +29,7 @@ namespace Ryujinx.Cpu
/// <param name="size">Size of the region</param>
/// <param name="id">Handle ID</param>
/// <returns>The memory tracking handle</returns>
CpuRegionHandle BeginTracking(ulong address, ulong size, int id);
RegionHandle BeginTracking(ulong address, ulong size, int id);
/// <summary>
/// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
@ -41,7 +40,7 @@ namespace Ryujinx.Cpu
/// <param name="granularity">Desired granularity of write tracking</param>
/// <param name="id">Handle ID</param>
/// <returns>The memory tracking handle</returns>
CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id);
MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id);
/// <summary>
/// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
@ -51,6 +50,6 @@ namespace Ryujinx.Cpu
/// <param name="granularity">Desired granularity of write tracking</param>
/// <param name="id">Handle ID</param>
/// <returns>The memory tracking handle</returns>
CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id);
SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id);
}
}

View File

@ -1,5 +1,4 @@
using ARMeilleure.Memory;
using Ryujinx.Cpu.Tracking;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking;
@ -629,21 +628,21 @@ namespace Ryujinx.Cpu.Jit
}
/// <inheritdoc/>
public CpuRegionHandle BeginTracking(ulong address, ulong size, int id)
public RegionHandle BeginTracking(ulong address, ulong size, int id)
{
return new CpuRegionHandle(Tracking.BeginTracking(address, size, id));
return Tracking.BeginTracking(address, size, id);
}
/// <inheritdoc/>
public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
{
return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity, id));
return Tracking.BeginGranularTracking(address, size, handles, granularity, id);
}
/// <inheritdoc/>
public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
{
return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity, id));
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
}
/// <inheritdoc/>

View File

@ -1,5 +1,4 @@
using ARMeilleure.Memory;
using Ryujinx.Cpu.Tracking;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking;
@ -706,21 +705,21 @@ namespace Ryujinx.Cpu.Jit
}
/// <inheritdoc/>
public CpuRegionHandle BeginTracking(ulong address, ulong size, int id)
public RegionHandle BeginTracking(ulong address, ulong size, int id)
{
return new CpuRegionHandle(Tracking.BeginTracking(address, size, id));
return Tracking.BeginTracking(address, size, id);
}
/// <inheritdoc/>
public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
{
return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity, id));
return Tracking.BeginGranularTracking(address, size, handles, granularity, id);
}
/// <inheritdoc/>
public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
{
return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity, id));
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
}
/// <summary>

View File

@ -1,28 +0,0 @@
using Ryujinx.Memory.Tracking;
using System;
using System.Collections.Generic;
namespace Ryujinx.Cpu.Tracking
{
public class CpuMultiRegionHandle : IMultiRegionHandle
{
private readonly MultiRegionHandle _impl;
public bool Dirty => _impl.Dirty;
internal CpuMultiRegionHandle(MultiRegionHandle impl)
{
_impl = impl;
}
public void Dispose() => _impl.Dispose();
public void ForceDirty(ulong address, ulong size) => _impl.ForceDirty(address, size);
public IEnumerable<IRegionHandle> GetHandles() => _impl.GetHandles();
public void QueryModified(Action<ulong, ulong> modifiedAction) => _impl.QueryModified(modifiedAction);
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction) => _impl.QueryModified(address, size, modifiedAction);
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber) => _impl.QueryModified(address, size, modifiedAction, sequenceNumber);
public void RegisterAction(ulong address, ulong size, RegionSignal action) => _impl.RegisterAction(address, size, action);
public void RegisterPreciseAction(ulong address, ulong size, PreciseRegionSignal action) => _impl.RegisterPreciseAction(address, size, action);
public void SignalWrite() => _impl.SignalWrite();
}
}

View File

@ -1,37 +0,0 @@
using Ryujinx.Memory.Tracking;
using System;
namespace Ryujinx.Cpu.Tracking
{
public class CpuRegionHandle : IRegionHandle
{
private readonly RegionHandle _impl;
public bool Dirty => _impl.Dirty;
public bool Unmapped => _impl.Unmapped;
public ulong Address => _impl.Address;
public ulong Size => _impl.Size;
public ulong EndAddress => _impl.EndAddress;
internal CpuRegionHandle(RegionHandle impl)
{
_impl = impl;
}
public void Dispose() => _impl.Dispose();
public bool DirtyOrVolatile() => _impl.DirtyOrVolatile();
public void ForceDirty() => _impl.ForceDirty();
public IRegionHandle GetHandle() => _impl;
public void RegisterAction(RegionSignal action) => _impl.RegisterAction(action);
public void RegisterPreciseAction(PreciseRegionSignal action) => _impl.RegisterPreciseAction(action);
public void RegisterDirtyEvent(Action action) => _impl.RegisterDirtyEvent(action);
public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty);
public bool OverlapsWith(ulong address, ulong size) => _impl.OverlapsWith(address, size);
public bool RangeEquals(CpuRegionHandle other)
{
return _impl.RealAddress == other._impl.RealAddress && _impl.RealSize == other._impl.RealSize;
}
}
}

View File

@ -1,26 +0,0 @@
using Ryujinx.Memory.Tracking;
using System;
namespace Ryujinx.Cpu.Tracking
{
public class CpuSmartMultiRegionHandle : IMultiRegionHandle
{
private readonly SmartMultiRegionHandle _impl;
public bool Dirty => _impl.Dirty;
internal CpuSmartMultiRegionHandle(SmartMultiRegionHandle impl)
{
_impl = impl;
}
public void Dispose() => _impl.Dispose();
public void ForceDirty(ulong address, ulong size) => _impl.ForceDirty(address, size);
public void RegisterAction(RegionSignal action) => _impl.RegisterAction(action);
public void RegisterPreciseAction(PreciseRegionSignal action) => _impl.RegisterPreciseAction(action);
public void QueryModified(Action<ulong, ulong> modifiedAction) => _impl.QueryModified(modifiedAction);
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction) => _impl.QueryModified(address, size, modifiedAction);
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber) => _impl.QueryModified(address, size, modifiedAction, sequenceNumber);
public void SignalWrite() => _impl.SignalWrite();
}
}

View File

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.GAL
{
public enum BufferAccess
{
Default,
FlushPersistent
}
}

View File

@ -21,11 +21,14 @@ namespace Ryujinx.Graphics.GAL
{
return CreateBuffer(size, BufferHandle.Null);
}
BufferHandle CreateBuffer(nint pointer, int size);
BufferHandle CreateBuffer(int size, BufferAccess access);
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
ISampler CreateSampler(SamplerCreateInfo info);
ITexture CreateTexture(TextureCreateInfo info, float scale);
bool PrepareHostMapping(nint address, ulong size);
void CreateSync(ulong id, bool strict);

View File

@ -12,6 +12,7 @@ namespace Ryujinx.Graphics.GAL
void CopyTo(ITexture destination, int firstLayer, int firstLevel);
void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel);
void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter);
void CopyTo(BufferRange range, int layer, int level, int stride);
ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);

View File

@ -43,6 +43,8 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<ActionCommand>(CommandType.Action);
Register<CreateBufferCommand>(CommandType.CreateBuffer);
Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess);
Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer);
Register<CreateProgramCommand>(CommandType.CreateProgram);
Register<CreateSamplerCommand>(CommandType.CreateSampler);
Register<CreateSyncCommand>(CommandType.CreateSync);
@ -69,6 +71,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<TextureCopyToCommand>(CommandType.TextureCopyTo);
Register<TextureCopyToScaledCommand>(CommandType.TextureCopyToScaled);
Register<TextureCopyToSliceCommand>(CommandType.TextureCopyToSlice);
Register<TextureCopyToBufferCommand>(CommandType.TextureCopyToBuffer);
Register<TextureCreateViewCommand>(CommandType.TextureCreateView);
Register<TextureGetDataCommand>(CommandType.TextureGetData);
Register<TextureGetDataSliceCommand>(CommandType.TextureGetDataSlice);

View File

@ -4,6 +4,8 @@
{
Action,
CreateBuffer,
CreateBufferAccess,
CreateHostBuffer,
CreateProgram,
CreateSampler,
CreateSync,
@ -29,6 +31,7 @@
SamplerDispose,
TextureCopyTo,
TextureCopyToBuffer,
TextureCopyToScaled,
TextureCopyToSlice,
TextureCreateView,

View File

@ -0,0 +1,22 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{
struct CreateBufferAccessCommand : IGALCommand, IGALCommand<CreateBufferAccessCommand>
{
public CommandType CommandType => CommandType.CreateBufferAccess;
private BufferHandle _threadedHandle;
private int _size;
private BufferAccess _access;
public void Set(BufferHandle threadedHandle, int size, BufferAccess access)
{
_threadedHandle = threadedHandle;
_size = size;
_access = access;
}
public static void Run(ref CreateBufferAccessCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, command._access));
}
}
}

View File

@ -0,0 +1,22 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{
struct CreateHostBufferCommand : IGALCommand, IGALCommand<CreateHostBufferCommand>
{
public CommandType CommandType => CommandType.CreateHostBuffer;
private BufferHandle _threadedHandle;
private nint _pointer;
private int _size;
public void Set(BufferHandle threadedHandle, nint pointer, int size)
{
_threadedHandle = threadedHandle;
_pointer = pointer;
_size = size;
}
public static void Run(ref CreateHostBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._pointer, command._size));
}
}
}

View File

@ -0,0 +1,29 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{
struct TextureCopyToBufferCommand : IGALCommand, IGALCommand<TextureCopyToBufferCommand>
{
public CommandType CommandType => CommandType.TextureCopyToBuffer;
private TableRef<ThreadedTexture> _texture;
private BufferRange _range;
private int _layer;
private int _level;
private int _stride;
public void Set(TableRef<ThreadedTexture> texture, BufferRange range, int layer, int level, int stride)
{
_texture = texture;
_range = range;
_layer = layer;
_level = level;
_stride = stride;
}
public static void Run(ref TextureCopyToBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
command._texture.Get(threaded).Base.CopyTo(threaded.Buffers.MapBufferRange(command._range), command._layer, command._level, command._stride);
}
}
}

View File

@ -108,6 +108,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
}
}
public void CopyTo(BufferRange range, int layer, int level, int stride)
{
_renderer.New<TextureCopyToBufferCommand>().Set(Ref(this), range, layer, level, stride);
_renderer.QueueCommand();
}
public void SetData(SpanOrArray<byte> data)
{
_renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data.ToArray()));

View File

@ -57,6 +57,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
private int _refConsumerPtr;
private Action _interruptAction;
private object _interruptLock = new();
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
@ -274,6 +275,24 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return handle;
}
public BufferHandle CreateBuffer(nint pointer, int size)
{
BufferHandle handle = Buffers.CreateBufferHandle();
New<CreateHostBufferCommand>().Set(handle, pointer, size);
QueueCommand();
return handle;
}
public BufferHandle CreateBuffer(int size, BufferAccess access)
{
BufferHandle handle = Buffers.CreateBufferHandle();
New<CreateBufferAccessCommand>().Set(handle, size, access);
QueueCommand();
return handle;
}
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
{
var program = new ThreadedProgram(this);
@ -448,11 +467,14 @@ namespace Ryujinx.Graphics.GAL.Multithreading
}
else
{
while (Interlocked.CompareExchange(ref _interruptAction, action, null) != null) { }
lock (_interruptLock)
{
while (Interlocked.CompareExchange(ref _interruptAction, action, null) != null) { }
_galWorkAvailable.Set();
_galWorkAvailable.Set();
_interruptRun.WaitOne();
_interruptRun.WaitOne();
}
}
}
@ -461,6 +483,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
// Threaded renderer ignores given interrupt action, as it provides its own to the child renderer.
}
public bool PrepareHostMapping(nint address, ulong size)
{
return _baseRenderer.PrepareHostMapping(address, size);
}
public void Dispose()
{
// Dispose must happen from the render thread, after all commands have completed.

View File

@ -157,11 +157,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
{
BufferDescriptor sb = info.SBuffers[index];
ulong sbDescAddress = _channel.BufferManager.GetComputeUniformBufferAddress(0);
int sbDescOffset = 0x310 + sb.Slot * 0x10;
sbDescAddress += (ulong)sbDescOffset;
ulong sbDescAddress = _channel.BufferManager.GetComputeUniformBufferAddress(sb.SbCbSlot);
sbDescAddress += (ulong)sb.SbCbOffset * 4;
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);

View File

@ -1,5 +1,6 @@
using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.Gpu.Engine.MME;
using Ryujinx.Graphics.Gpu.Synchronization;
using System;
using System.Collections.Generic;
using System.Threading;
@ -59,7 +60,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
if (_createSyncPending)
{
_createSyncPending = false;
_context.CreateHostSyncIfNeeded(false, false);
_context.CreateHostSyncIfNeeded(HostSyncFlags.None);
}
}
@ -157,7 +158,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
}
else if (operation == SyncpointbOperation.Incr)
{
_context.CreateHostSyncIfNeeded(true, true);
_context.CreateHostSyncIfNeeded(HostSyncFlags.StrictSyncpoint);
_context.Synchronization.IncrementSyncpoint(syncpointId);
}
@ -184,7 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
{
_context.Renderer.Pipeline.CommandBufferBarrier();
_context.CreateHostSyncIfNeeded(false, true);
_context.CreateHostSyncIfNeeded(HostSyncFlags.Strict);
}
/// <summary>

View File

@ -252,6 +252,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
public void Interrupt()
{
_interrupt = true;
_event.Set();
}
/// <summary>

View File

@ -351,11 +351,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
BufferDescriptor sb = info.SBuffers[index];
ulong sbDescAddress = _channel.BufferManager.GetGraphicsUniformBufferAddress(stage, 0);
int sbDescOffset = 0x110 + stage * 0x100 + sb.Slot * 0x10;
sbDescAddress += (ulong)sbDescOffset;
ulong sbDescAddress = _channel.BufferManager.GetGraphicsUniformBufferAddress(stage, sb.SbCbSlot);
sbDescAddress += (ulong)sb.SbCbOffset * 4;
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);

View File

@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
using Ryujinx.Graphics.Gpu.Synchronization;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@ -254,7 +255,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
uint syncpointId = (uint)argument & 0xFFFF;
_context.AdvanceSequence();
_context.CreateHostSyncIfNeeded(true, true);
_context.CreateHostSyncIfNeeded(HostSyncFlags.StrictSyncpoint);
_context.Renderer.UpdateCounters(); // Poll the query counters, the game may want an updated result.
_context.Synchronization.IncrementSyncpoint(syncpointId);
}

View File

@ -300,11 +300,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
IsCopyRegionComplete(srcCopyTexture, srcCopyTextureFormat, srcX1, srcY1, srcX2, srcY2) &&
IsCopyRegionComplete(dstCopyTexture, dstCopyTextureFormat, dstX1, dstY1, dstX2, dstY2);
// We can only allow aliasing of color formats as depth if the source and destination textures
// are the same, as we can't blit between different depth formats.
bool srcDepthAlias = srcCopyTexture.Format == dstCopyTexture.Format;
var srcTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
memoryManager,
srcCopyTexture,
offset,
srcCopyTextureFormat,
srcDepthAlias,
!canDirectCopy,
false,
srcHint);
@ -325,6 +330,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
// When the source texture that was found has a depth format,
// we must enforce the target texture also has a depth format,
// as copies between depth and color formats are not allowed.
// For depth blit, the destination texture format should always match exactly.
if (srcTexture.Format.IsDepthOrStencil())
{
@ -340,7 +346,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod
dstCopyTexture,
0,
dstCopyTextureFormat,
true,
depthAlias: false,
shouldCreate: true,
srcTexture.ScaleMode == TextureScaleMode.Scaled,
dstHint);

View File

@ -60,14 +60,14 @@ namespace Ryujinx.Graphics.Gpu
/// If there are more than 0 items when this happens, a host sync object will be generated for the given <see cref="SyncNumber"/>,
/// and the SyncNumber will be incremented.
/// </summary>
internal List<Action> SyncActions { get; }
internal List<ISyncActionHandler> SyncActions { get; }
/// <summary>
/// Actions to be performed when a CPU waiting syncpoint is triggered.
/// If there are more than 0 items when this happens, a host sync object will be generated for the given <see cref="SyncNumber"/>,
/// and the SyncNumber will be incremented.
/// </summary>
internal List<Action> SyncpointActions { get; }
internal List<ISyncActionHandler> SyncpointActions { get; }
/// <summary>
/// Buffer migrations that are currently in-flight. These are checked whenever sync is created to determine if buffer migration
@ -114,8 +114,8 @@ namespace Ryujinx.Graphics.Gpu
HostInitalized = new ManualResetEvent(false);
SyncActions = new List<Action>();
SyncpointActions = new List<Action>();
SyncActions = new List<ISyncActionHandler>();
SyncpointActions = new List<ISyncActionHandler>();
BufferMigrations = new List<BufferMigration>();
DeferredActions = new Queue<Action>();
@ -296,9 +296,9 @@ namespace Ryujinx.Graphics.Gpu
/// Registers an action to be performed the next time a syncpoint is incremented.
/// This will also ensure a host sync object is created, and <see cref="SyncNumber"/> is incremented.
/// </summary>
/// <param name="action">The action to be performed on sync object creation</param>
/// <param name="action">The resource with action to be performed on sync object creation</param>
/// <param name="syncpointOnly">True if the sync action should only run when syncpoints are incremented</param>
public void RegisterSyncAction(Action action, bool syncpointOnly = false)
internal void RegisterSyncAction(ISyncActionHandler action, bool syncpointOnly = false)
{
if (syncpointOnly)
{
@ -315,10 +315,13 @@ namespace Ryujinx.Graphics.Gpu
/// Creates a host sync object if there are any pending sync actions. The actions will then be called.
/// If no actions are present, a host sync object is not created.
/// </summary>
/// <param name="syncpoint">True if host sync is being created by a syncpoint</param>
/// <param name="strict">True if the sync should signal as soon as possible</param>
public void CreateHostSyncIfNeeded(bool syncpoint, bool strict)
/// <param name="flags">Modifiers for how host sync should be created</param>
internal void CreateHostSyncIfNeeded(HostSyncFlags flags)
{
bool syncpoint = flags.HasFlag(HostSyncFlags.Syncpoint);
bool strict = flags.HasFlag(HostSyncFlags.Strict);
bool force = flags.HasFlag(HostSyncFlags.Force);
if (BufferMigrations.Count > 0)
{
ulong currentSyncNumber = Renderer.GetCurrentSync();
@ -336,24 +339,17 @@ namespace Ryujinx.Graphics.Gpu
}
}
if (_pendingSync || (syncpoint && SyncpointActions.Count > 0))
if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0))
{
Renderer.CreateSync(SyncNumber, strict);
SyncActions.ForEach(action => action.SyncPreAction(syncpoint));
SyncpointActions.ForEach(action => action.SyncPreAction(syncpoint));
SyncNumber++;
foreach (Action action in SyncActions)
{
action();
}
foreach (Action action in SyncpointActions)
{
action();
}
SyncActions.Clear();
SyncpointActions.Clear();
SyncActions.RemoveAll(action => action.SyncAction(syncpoint));
SyncpointActions.RemoveAll(action => action.SyncAction(syncpoint));
}
_pendingSync = false;

View File

@ -1,5 +1,4 @@
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Image
@ -9,6 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
class ShortTextureCacheEntry
{
public bool IsAutoDelete;
public readonly TextureDescriptor Descriptor;
public readonly int InvalidatedSequence;
public readonly Texture Texture;
@ -24,6 +24,17 @@ namespace Ryujinx.Graphics.Gpu.Image
InvalidatedSequence = texture.InvalidatedSequence;
Texture = texture;
}
/// <summary>
/// Create a new entry on the short duration texture cache from the auto delete cache.
/// </summary>
/// <param name="texture">The texture</param>
public ShortTextureCacheEntry(Texture texture)
{
IsAutoDelete = true;
InvalidatedSequence = texture.InvalidatedSequence;
Texture = texture;
}
}
/// <summary>
@ -199,7 +210,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{
texture.DecrementReferenceCount();
_shortCacheLookup.Remove(texture.ShortCacheEntry.Descriptor);
if (!texture.ShortCacheEntry.IsAutoDelete)
{
_shortCacheLookup.Remove(texture.ShortCacheEntry.Descriptor);
}
texture.ShortCacheEntry = null;
}
}
@ -222,6 +237,25 @@ namespace Ryujinx.Graphics.Gpu.Image
texture.IncrementReferenceCount();
}
/// <summary>
/// Adds a texture to the short duration cache without a descriptor. This typically keeps it alive for two ticks.
/// On expiry, it will be removed from the AutoDeleteCache.
/// </summary>
/// <param name="texture">Texture to add to the short cache</param>
public void AddShortCache(Texture texture)
{
if (texture.ShortCacheEntry != null)
{
var entry = new ShortTextureCacheEntry(texture);
_shortCacheBuilder.Add(entry);
texture.ShortCacheEntry = entry;
texture.IncrementReferenceCount();
}
}
/// <summary>
/// Delete textures from the short duration cache.
/// Moves the builder set to be deleted on next process.
@ -234,7 +268,15 @@ namespace Ryujinx.Graphics.Gpu.Image
{
entry.Texture.DecrementReferenceCount();
_shortCacheLookup.Remove(entry.Descriptor);
if (entry.IsAutoDelete)
{
Remove(entry.Texture, false);
}
else
{
_shortCacheLookup.Remove(entry.Descriptor);
}
entry.Texture.ShortCacheEntry = null;
}

View File

@ -1,5 +1,5 @@
using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Memory.Tracking;
using System;
using System.Runtime.InteropServices;
@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public ulong Size { get; }
private readonly CpuMultiRegionHandle _memoryTracking;
private readonly MultiRegionHandle _memoryTracking;
private readonly Action<ulong, ulong> _modifiedDelegate;
private int _modifiedSequenceOffset;

View File

@ -144,6 +144,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public ShortTextureCacheEntry ShortCacheEntry { get; set; }
/// <summary>
/// Whether this texture has ever been referenced by a pool.
/// </summary>
public bool HadPoolOwner { get; private set; }
/// Physical memory ranges where the texture data is located.
/// </summary>
public MultiRange Range { get; private set; }
@ -1113,7 +1118,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
bool forSampler = (flags & TextureSearchFlags.ForSampler) != 0;
TextureMatchQuality matchQuality = TextureCompatibility.FormatMatches(Info, info, forSampler, (flags & TextureSearchFlags.ForCopy) != 0);
TextureMatchQuality matchQuality = TextureCompatibility.FormatMatches(Info, info, forSampler, (flags & TextureSearchFlags.DepthAlias) != 0);
if (matchQuality == TextureMatchQuality.NoMatch)
{
@ -1423,7 +1428,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
}
if (_modifiedStale || Group.HasCopyDependencies)
if (_modifiedStale || Group.HasCopyDependencies || Group.HasFlushBuffer)
{
_modifiedStale = false;
Group.SignalModifying(this, bound);
@ -1506,10 +1511,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="gpuVa">GPU VA of the pool reference</param>
public void IncrementReferenceCount(TexturePool pool, int id, ulong gpuVa)
{
HadPoolOwner = true;
lock (_poolOwners)
{
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id, GpuAddress = gpuVa });
}
_referenceCount++;
if (ShortCacheEntry != null)
@ -1594,7 +1602,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_poolOwners.Clear();
}
if (ShortCacheEntry != null && _context.IsGpuThread())
if (ShortCacheEntry != null && !ShortCacheEntry.IsAutoDelete && _context.IsGpuThread())
{
// If this is called from another thread (unmapped), the short cache will
// have to remove this texture on a future tick.
@ -1610,6 +1618,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public void UpdatePoolMappings()
{
ChangedMapping = true;
lock (_poolOwners)
{
ulong address = 0;
@ -1683,10 +1693,10 @@ namespace Ryujinx.Graphics.Gpu.Image
if (Group.Storage == this)
{
Group.Unmapped();
Group.ClearModified(unmapRange);
}
UpdatePoolMappings();
}
/// <summary>

View File

@ -537,7 +537,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
@ -666,7 +666,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
Format format = bindingInfo.Format;
@ -879,4 +879,4 @@ namespace Ryujinx.Graphics.Gpu.Image
Array.Clear(_imageState);
}
}
}
}

View File

@ -64,7 +64,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
/// Handles removal of textures written to a memory region being unmapped.
/// Handles marking of textures written to a memory region being (partially) remapped.
/// </summary>
/// <param name="sender">Sender object</param>
/// <param name="e">Event arguments</param>
@ -80,26 +80,41 @@ namespace Ryujinx.Graphics.Gpu.Image
overlapCount = _textures.FindOverlaps(unmapped, ref overlaps);
}
for (int i = 0; i < overlapCount; i++)
if (overlapCount > 0)
{
overlaps[i].Unmapped(unmapped);
for (int i = 0; i < overlapCount; i++)
{
overlaps[i].Unmapped(unmapped);
}
}
// If any range was previously unmapped, we also need to purge
// all partially mapped texture, as they might be fully mapped now.
for (int i = 0; i < unmapped.Count; i++)
lock (_partiallyMappedTextures)
{
if (unmapped.GetSubRange(i).Address == MemoryManager.PteUnmapped)
if (overlapCount > 0 || _partiallyMappedTextures.Count > 0)
{
lock (_partiallyMappedTextures)
e.AddRemapAction(() =>
{
foreach (var texture in _partiallyMappedTextures)
lock (_partiallyMappedTextures)
{
texture.Unmapped(unmapped);
}
}
if (overlapCount > 0)
{
for (int i = 0; i < overlapCount; i++)
{
_partiallyMappedTextures.Add(overlaps[i]);
}
}
break;
// Any texture that has been unmapped at any point or is partially unmapped
// should update their pool references after the remap completes.
MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
foreach (var texture in _partiallyMappedTextures)
{
texture.UpdatePoolMappings();
}
}
});
}
}
}
@ -234,6 +249,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="copyTexture">Copy texture to find or create</param>
/// <param name="offset">Offset to be added to the physical texture address</param>
/// <param name="formatInfo">Format information of the copy texture</param>
/// <param name="depthAlias">Indicates if aliasing between color and depth format should be allowed</param>
/// <param name="shouldCreate">Indicates if a new texture should be created if none is found on the cache</param>
/// <param name="preferScaling">Indicates if the texture should be scaled from the start</param>
/// <param name="sizeHint">A hint indicating the minimum used size for the texture</param>
/// <returns>The texture</returns>
@ -242,6 +259,7 @@ namespace Ryujinx.Graphics.Gpu.Image
TwodTexture copyTexture,
ulong offset,
FormatInfo formatInfo,
bool depthAlias,
bool shouldCreate,
bool preferScaling,
Size sizeHint)
@ -278,6 +296,11 @@ namespace Ryujinx.Graphics.Gpu.Image
TextureSearchFlags flags = TextureSearchFlags.ForCopy;
if (depthAlias)
{
flags |= TextureSearchFlags.DepthAlias;
}
if (preferScaling)
{
flags |= TextureSearchFlags.WithUpscale;
@ -833,7 +856,17 @@ namespace Ryujinx.Graphics.Gpu.Image
if (overlapInCache)
{
_cache.Remove(overlap, flush);
if (flush || overlap.HadPoolOwner || overlap.IsView)
{
_cache.Remove(overlap, flush);
}
else
{
// This texture has only ever been referenced in the AutoDeleteCache.
// Keep this texture alive with the short duration cache, as it may be used often but not sampled.
_cache.AddShortCache(overlap);
}
}
removeOverlap = modified;
@ -1135,6 +1168,44 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
/// <summary>
/// Queries a texture's memory range and marks it as partially mapped or not.
/// Partially mapped textures re-evaluate their memory range after each time GPU memory is mapped.
/// </summary>
/// <param name="memoryManager">GPU memory manager where the texture is mapped</param>
/// <param name="address">The virtual address of the texture</param>
/// <param name="texture">The texture to be marked</param>
/// <returns>The physical regions for the texture, found when evaluating whether the texture was partially mapped</returns>
public MultiRange UpdatePartiallyMapped(MemoryManager memoryManager, ulong address, Texture texture)
{
MultiRange range;
lock (_partiallyMappedTextures)
{
range = memoryManager.GetPhysicalRegions(address, texture.Size);
bool partiallyMapped = false;
for (int i = 0; i < range.Count; i++)
{
if (range.GetSubRange(i).Address == MemoryManager.PteUnmapped)
{
partiallyMapped = true;
break;
}
}
if (partiallyMapped)
{
_partiallyMappedTextures.Add(texture);
}
else
{
_partiallyMappedTextures.Remove(texture);
}
}
return range;
}
/// <summary>
/// Adds a texture to the short duration cache. This typically keeps it alive for two ticks.
/// </summary>
@ -1145,6 +1216,16 @@ namespace Ryujinx.Graphics.Gpu.Image
_cache.AddShortCache(texture, ref descriptor);
}
/// <summary>
/// Adds a texture to the short duration cache without a descriptor. This typically keeps it alive for two ticks.
/// On expiry, it will be removed from the AutoDeleteCache.
/// </summary>
/// <param name="texture">Texture to add to the short cache</param>
public void AddShortCache(Texture texture)
{
_cache.AddShortCache(texture);
}
/// <summary>
/// Removes a texture from the short duration cache.
/// </summary>

View File

@ -220,18 +220,18 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="lhs">Texture information to compare</param>
/// <param name="rhs">Texture information to compare with</param>
/// <param name="forSampler">Indicates that the texture will be used for shader sampling</param>
/// <param name="forCopy">Indicates that the texture will be used as copy source or target</param>
/// <param name="depthAlias">Indicates if aliasing between color and depth format should be allowed</param>
/// <returns>A value indicating how well the formats match</returns>
public static TextureMatchQuality FormatMatches(TextureInfo lhs, TextureInfo rhs, bool forSampler, bool forCopy)
public static TextureMatchQuality FormatMatches(TextureInfo lhs, TextureInfo rhs, bool forSampler, bool depthAlias)
{
// D32F and R32F texture have the same representation internally,
// however the R32F format is used to sample from depth textures.
if (lhs.FormatInfo.Format == Format.D32Float && rhs.FormatInfo.Format == Format.R32Float && (forSampler || forCopy))
if (lhs.FormatInfo.Format == Format.D32Float && rhs.FormatInfo.Format == Format.R32Float && (forSampler || depthAlias))
{
return TextureMatchQuality.FormatAlias;
}
if (forCopy)
if (depthAlias)
{
// The 2D engine does not support depth-stencil formats, so it will instead
// use equivalent color formats. We must also consider them as compatible.

View File

@ -1,10 +1,10 @@
using Ryujinx.Common.Memory;
using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@ -57,6 +57,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public bool HasCopyDependencies { get; set; }
/// <summary>
/// Indicates if the texture group has a pre-emptive flush buffer.
/// When one is present, the group must always be notified on unbind.
/// </summary>
public bool HasFlushBuffer => _flushBuffer != BufferHandle.Null;
/// <summary>
/// Indicates if this texture has any incompatible overlaps alive.
/// </summary>
@ -89,6 +95,10 @@ namespace Ryujinx.Graphics.Gpu.Image
private bool _incompatibleOverlapsDirty = true;
private bool _flushIncompatibleOverlaps;
private BufferHandle _flushBuffer;
private bool _flushBufferImported;
private bool _flushBufferInvalid;
/// <summary>
/// Create a new texture group.
/// </summary>
@ -245,7 +255,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
TextureGroupHandle group = _handles[baseHandle + i];
foreach (CpuRegionHandle handle in group.Handles)
foreach (RegionHandle handle in group.Handles)
{
if (handle.Dirty)
{
@ -286,7 +296,7 @@ namespace Ryujinx.Graphics.Gpu.Image
bool handleDirty = false;
bool handleUnmapped = false;
foreach (CpuRegionHandle handle in group.Handles)
foreach (RegionHandle handle in group.Handles)
{
if (handle.Dirty)
{
@ -464,8 +474,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </remarks>
/// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
/// <param name="sliceIndex">The index of the slice to flush</param>
/// <param name="inBuffer">Whether the flushed texture data is up to date in the flush buffer</param>
/// <param name="texture">The specific host texture to flush. Defaults to the storage texture</param>
private void FlushTextureDataSliceToGuest(bool tracked, int sliceIndex, ITexture texture = null)
private void FlushTextureDataSliceToGuest(bool tracked, int sliceIndex, bool inBuffer, ITexture texture = null)
{
(int layer, int level) = GetLayerLevelForView(sliceIndex);
@ -475,7 +486,16 @@ namespace Ryujinx.Graphics.Gpu.Image
using WritableRegion region = _physicalMemory.GetWritableRegion(Storage.Range.Slice((ulong)offset, (ulong)size), tracked);
Storage.GetTextureDataSliceFromGpu(region.Memory.Span, layer, level, tracked, texture);
if (inBuffer)
{
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(_flushBuffer, offset, size);
Storage.ConvertFromHostCompatibleFormat(region.Memory.Span, data.Get(), level, true);
}
else
{
Storage.GetTextureDataSliceFromGpu(region.Memory.Span, layer, level, tracked, texture);
}
}
/// <summary>
@ -484,12 +504,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
/// <param name="sliceStart">The first slice to flush</param>
/// <param name="sliceEnd">The slice to finish flushing on (exclusive)</param>
/// <param name="inBuffer">Whether the flushed texture data is up to date in the flush buffer</param>
/// <param name="texture">The specific host texture to flush. Defaults to the storage texture</param>
private void FlushSliceRange(bool tracked, int sliceStart, int sliceEnd, ITexture texture = null)
private void FlushSliceRange(bool tracked, int sliceStart, int sliceEnd, bool inBuffer, ITexture texture = null)
{
for (int i = sliceStart; i < sliceEnd; i++)
{
FlushTextureDataSliceToGuest(tracked, i, texture);
FlushTextureDataSliceToGuest(tracked, i, inBuffer, texture);
}
}
@ -520,7 +541,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
if (endSlice > startSlice)
{
FlushSliceRange(tracked, startSlice, endSlice);
FlushSliceRange(tracked, startSlice, endSlice, false);
flushed = true;
}
@ -553,7 +574,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else
{
FlushSliceRange(tracked, startSlice, endSlice);
FlushSliceRange(tracked, startSlice, endSlice, false);
}
flushed = true;
@ -565,6 +586,58 @@ namespace Ryujinx.Graphics.Gpu.Image
return flushed;
}
/// <summary>
/// Flush the texture data into a persistently mapped buffer.
/// If the buffer does not exist, this method will create it.
/// </summary>
/// <param name="handle">Handle of the texture group to flush slices of</param>
public void FlushIntoBuffer(TextureGroupHandle handle)
{
// Ensure that the buffer exists.
if (_flushBufferInvalid && _flushBuffer != BufferHandle.Null)
{
_flushBufferInvalid = false;
_context.Renderer.DeleteBuffer(_flushBuffer);
_flushBuffer = BufferHandle.Null;
}
if (_flushBuffer == BufferHandle.Null)
{
if (!TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities))
{
return;
}
bool canImport = Storage.Info.IsLinear && Storage.Info.Stride >= Storage.Info.Width * Storage.Info.FormatInfo.BytesPerPixel;
var hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0;
if (hostPointer != 0 && _context.Renderer.PrepareHostMapping(hostPointer, Storage.Size))
{
_flushBuffer = _context.Renderer.CreateBuffer(hostPointer, (int)Storage.Size);
_flushBufferImported = true;
}
else
{
_flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.FlushPersistent);
_flushBufferImported = false;
}
Storage.BlacklistScale();
}
int sliceStart = handle.BaseSlice;
int sliceEnd = sliceStart + handle.SliceCount;
for (int i = sliceStart; i < sliceEnd; i++)
{
(int layer, int level) = GetLayerLevelForView(i);
Storage.GetFlushTexture().CopyTo(new BufferRange(_flushBuffer, _allOffsets[i], _sliceSizes[level]), layer, level, _flushBufferImported ? Storage.Info.Stride : 0);
}
}
/// <summary>
/// Clears competing modified flags for all incompatible ranges, if they have possibly been modified.
/// </summary>
@ -630,7 +703,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="group">The group to register an action for</param>
public void RegisterAction(TextureGroupHandle group)
{
foreach (CpuRegionHandle handle in group.Handles)
foreach (RegionHandle handle in group.Handles)
{
handle.RegisterAction((address, size) => FlushAction(group, address, size));
}
@ -912,7 +985,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="address">The start address of the tracked region</param>
/// <param name="size">The size of the tracked region</param>
/// <returns>A CpuRegionHandle covering the given range</returns>
private CpuRegionHandle GenerateHandle(ulong address, ulong size)
private RegionHandle GenerateHandle(ulong address, ulong size)
{
return _physicalMemory.BeginTracking(address, size, ResourceKind.Texture);
}
@ -932,7 +1005,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int endOffset = _allOffsets[viewEnd] + _sliceSizes[lastLevel];
int size = endOffset - offset;
var result = new List<CpuRegionHandle>();
var result = new List<RegionHandle>();
for (int i = 0; i < TextureRange.Count; i++)
{
@ -977,7 +1050,7 @@ namespace Ryujinx.Graphics.Gpu.Image
views,
result.ToArray());
foreach (CpuRegionHandle handle in result)
foreach (RegionHandle handle in result)
{
handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
}
@ -1175,7 +1248,7 @@ namespace Ryujinx.Graphics.Gpu.Image
continue;
}
foreach (CpuRegionHandle handle in groupHandle.Handles)
foreach (RegionHandle handle in groupHandle.Handles)
{
bool hasMatch = false;
@ -1197,7 +1270,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else
{
foreach (CpuRegionHandle handle in groupHandle.Handles)
foreach (RegionHandle handle in groupHandle.Handles)
{
handle.Reprotect();
}
@ -1230,7 +1303,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (!(_hasMipViews || _hasLayerViews))
{
// Single dirty region.
var cpuRegionHandles = new CpuRegionHandle[TextureRange.Count];
var cpuRegionHandles = new RegionHandle[TextureRange.Count];
int count = 0;
for (int i = 0; i < TextureRange.Count; i++)
@ -1249,7 +1322,7 @@ namespace Ryujinx.Graphics.Gpu.Image
var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, 0, _allOffsets.Length, cpuRegionHandles);
foreach (CpuRegionHandle handle in cpuRegionHandles)
foreach (RegionHandle handle in cpuRegionHandles)
{
handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
}
@ -1570,10 +1643,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_context.Renderer.BackgroundContextAction(() =>
{
if (!isGpuThread)
{
handle.Sync(_context);
}
bool inBuffer = !isGpuThread && handle.Sync(_context);
Storage.SignalModifiedDirty();
@ -1585,13 +1655,24 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
if (TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities))
if (TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities) && !(inBuffer && _flushBufferImported))
{
FlushSliceRange(false, handle.BaseSlice, handle.BaseSlice + handle.SliceCount, Storage.GetFlushTexture());
FlushSliceRange(false, handle.BaseSlice, handle.BaseSlice + handle.SliceCount, inBuffer, Storage.GetFlushTexture());
}
});
}
/// <summary>
/// Called if any part of the storage texture is unmapped.
/// </summary>
public void Unmapped()
{
if (_flushBufferImported)
{
_flushBufferInvalid = true;
}
}
/// <summary>
/// Dispose this texture group, disposing all related memory tracking handles.
/// </summary>
@ -1606,6 +1687,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{
incompatible.Group._incompatibleOverlaps.RemoveAll(overlap => overlap.Group == this);
}
if (_flushBuffer != BufferHandle.Null)
{
_context.Renderer.DeleteBuffer(_flushBuffer);
}
}
}
}

View File

@ -1,4 +1,5 @@
using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.Gpu.Synchronization;
using Ryujinx.Memory.Tracking;
using System;
using System.Collections.Generic;
using System.Linq;
@ -13,8 +14,14 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Also tracks copy dependencies for the handle - references to other handles that must be kept
/// in sync with this one before use.
/// </summary>
class TextureGroupHandle : IDisposable
class TextureGroupHandle : ISyncActionHandler, IDisposable
{
private const int FlushBalanceIncrement = 6;
private const int FlushBalanceWriteCost = 1;
private const int FlushBalanceThreshold = 7;
private const int FlushBalanceMax = 60;
private const int FlushBalanceMin = -10;
private TextureGroup _group;
private int _bindCount;
private int _firstLevel;
@ -26,6 +33,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The sync number last registered.
/// </summary>
private ulong _registeredSync;
private ulong _registeredBufferSync = ulong.MaxValue;
private ulong _registeredBufferGuestSync = ulong.MaxValue;
/// <summary>
/// The sync number when the texture was last modified by GPU.
@ -42,6 +51,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
private bool _syncActionRegistered;
/// <summary>
/// Determines the balance of synced writes to flushes.
/// Used to determine if the texture should always write data to a persistent buffer for flush.
/// </summary>
private int _flushBalance;
/// <summary>
/// The byte offset from the start of the storage of this handle.
/// </summary>
@ -70,7 +85,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary>
/// The CPU memory tracking handles that cover this handle.
/// </summary>
public CpuRegionHandle[] Handles { get; }
public RegionHandle[] Handles { get; }
/// <summary>
/// True if a texture overlapping this handle has been modified. Is set false when the flush action is called.
@ -112,7 +127,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int firstLevel,
int baseSlice,
int sliceCount,
CpuRegionHandle[] handles)
RegionHandle[] handles)
{
_group = group;
_firstLayer = firstLayer;
@ -132,6 +147,12 @@ namespace Ryujinx.Graphics.Gpu.Image
}
Handles = handles;
if (group.Storage.Info.IsLinear)
{
// Linear textures are presumed to be used for readback initially.
_flushBalance = FlushBalanceThreshold + FlushBalanceIncrement;
}
}
/// <summary>
@ -159,6 +180,35 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
/// <summary>
/// Determine if the next sync will copy into the flush buffer.
/// </summary>
/// <returns>True if it will copy, false otherwise</returns>
private bool NextSyncCopies()
{
return _flushBalance - FlushBalanceWriteCost > FlushBalanceThreshold;
}
/// <summary>
/// Alters the flush balance by the given value. Should increase significantly with each sync, decrease with each write.
/// A flush balance higher than the threshold will cause a texture to repeatedly copy to a flush buffer on each use.
/// </summary>
/// <param name="modifier">Value to add to the existing flush balance</param>
/// <returns>True if the new balance is over the threshold, false otherwise</returns>
private bool ModifyFlushBalance(int modifier)
{
int result;
int existingBalance;
do
{
existingBalance = _flushBalance;
result = Math.Max(FlushBalanceMin, Math.Min(FlushBalanceMax, existingBalance + modifier));
}
while (Interlocked.CompareExchange(ref _flushBalance, result, existingBalance) != existingBalance);
return result > FlushBalanceThreshold;
}
/// <summary>
/// Adds a single texture view as an overlap if its range overlaps.
/// </summary>
@ -204,7 +254,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (!_syncActionRegistered)
{
_modifiedSync = context.SyncNumber;
context.RegisterSyncAction(SyncAction, true);
context.RegisterSyncAction(this, true);
_syncActionRegistered = true;
}
@ -241,6 +291,13 @@ namespace Ryujinx.Graphics.Gpu.Image
{
SignalModified(context);
if (!bound && _syncActionRegistered && NextSyncCopies())
{
// On unbind, textures that flush often should immediately create sync so their result can be obtained as soon as possible.
context.CreateHostSyncIfNeeded(HostSyncFlags.Force);
}
// Note: Bind count currently resets to 0 on inherit for safety, as the handle <-> view relationship can change.
_bindCount = Math.Max(0, _bindCount + (bound ? 1 : -1));
}
@ -266,25 +323,35 @@ namespace Ryujinx.Graphics.Gpu.Image
/// removing the modified flag if it was reached, or leaving it set if it has not yet been created.
/// </summary>
/// <param name="context">The GPU context used to wait for sync</param>
public void Sync(GpuContext context)
/// <returns>True if the texture data can be read from the flush buffer</returns>
public bool Sync(GpuContext context)
{
ulong registeredSync = _registeredSync;
long diff = (long)(context.SyncNumber - registeredSync);
// Currently assumes the calling thread is a guest thread.
bool inBuffer = _registeredBufferGuestSync != ulong.MaxValue;
ulong sync = inBuffer ? _registeredBufferGuestSync : _registeredSync;
long diff = (long)(context.SyncNumber - sync);
ModifyFlushBalance(FlushBalanceIncrement);
if (diff > 0)
{
context.Renderer.WaitSync(registeredSync);
context.Renderer.WaitSync(sync);
if ((long)(_modifiedSync - registeredSync) > 0)
if ((long)(_modifiedSync - sync) > 0)
{
// Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes.
return;
return inBuffer;
}
Modified = false;
return inBuffer;
}
// If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag.
return false;
}
/// <summary>
@ -296,15 +363,41 @@ namespace Ryujinx.Graphics.Gpu.Image
Interlocked.Exchange(ref _actionRegistered, 0);
}
/// <summary>
/// Action to perform before a sync number is registered after modification.
/// This action will copy the texture data to the flush buffer if this texture
/// flushes often enough, which is determined by the flush balance.
/// </summary>
/// <inheritdoc/>
public void SyncPreAction(bool syncpoint)
{
if (syncpoint || NextSyncCopies())
{
if (ModifyFlushBalance(0) && _registeredBufferSync != _modifiedSync)
{
_group.FlushIntoBuffer(this);
_registeredBufferSync = _modifiedSync;
}
}
}
/// <summary>
/// Action to perform when a sync number is registered after modification.
/// This action will register a read tracking action on the memory tracking handle so that a flush from CPU can happen.
/// </summary>
private void SyncAction()
/// <inheritdoc/>
public bool SyncAction(bool syncpoint)
{
// The storage will need to signal modified again to update the sync number in future.
_group.Storage.SignalModifiedDirty();
bool lastInBuffer = _registeredBufferSync == _modifiedSync;
if (!lastInBuffer)
{
_registeredBufferSync = ulong.MaxValue;
}
lock (Overlaps)
{
foreach (Texture texture in Overlaps)
@ -314,6 +407,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
// Register region tracking for CPU? (again)
_registeredSync = _modifiedSync;
_syncActionRegistered = false;
@ -321,6 +415,14 @@ namespace Ryujinx.Graphics.Gpu.Image
{
_group.RegisterAction(this);
}
if (syncpoint)
{
_registeredBufferGuestSync = _registeredBufferSync;
}
// If the last modification is in the buffer, keep this sync action alive until it sees a syncpoint.
return syncpoint || !lastInBuffer;
}
/// <summary>
@ -540,7 +642,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public void Dispose()
{
foreach (CpuRegionHandle handle in Handles)
foreach (RegionHandle handle in Handles)
{
handle.Dispose();
}

View File

@ -272,7 +272,15 @@ namespace Ryujinx.Graphics.Gpu.Image
ulong address = descriptor.UnpackAddress();
MultiRange range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
if (!descriptor.Equals(ref DescriptorCache[request.ID]))
{
// If the pool entry has already been replaced, just remove the texture.
texture.DecrementReferenceCount();
continue;
}
MultiRange range = _channel.MemoryManager.Physical.TextureCache.UpdatePartiallyMapped(_channel.MemoryManager, address, texture);
// If the texture is not mapped at all, delete its reference.

View File

@ -11,7 +11,8 @@ namespace Ryujinx.Graphics.Gpu.Image
None = 0,
ForSampler = 1 << 1,
ForCopy = 1 << 2,
WithUpscale = 1 << 3,
NoCreate = 1 << 4
DepthAlias = 1 << 3,
WithUpscale = 1 << 4,
NoCreate = 1 << 5
}
}

View File

@ -1,5 +1,5 @@
using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Synchronization;
using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking;
using System;
@ -11,7 +11,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
/// </summary>
class Buffer : IRange, IDisposable
class Buffer : IRange, ISyncActionHandler, IDisposable
{
private const ulong GranularBufferThreshold = 4096;
@ -53,8 +53,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </remarks>
private BufferModifiedRangeList _modifiedRanges = null;
private readonly CpuMultiRegionHandle _memoryTrackingGranular;
private readonly CpuRegionHandle _memoryTracking;
private readonly MultiRegionHandle _memoryTrackingGranular;
private readonly RegionHandle _memoryTracking;
private readonly RegionSignal _externalFlushDelegate;
private readonly Action<ulong, ulong> _loadDelegate;
@ -67,6 +67,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
private int _referenceCount = 1;
private ulong _dirtyStart = ulong.MaxValue;
private ulong _dirtyEnd = ulong.MaxValue;
/// <summary>
/// Creates a new instance of the buffer.
/// </summary>
@ -98,7 +101,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
return Enumerable.Repeat(buffer._memoryTracking.GetHandle(), 1);
return Enumerable.Repeat(buffer._memoryTracking, 1);
}
});
}
@ -220,6 +223,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
_sequenceNumber = _context.SequenceNumber;
_dirtyStart = ulong.MaxValue;
}
}
if (_dirtyStart != ulong.MaxValue)
{
ulong end = address + size;
if (end > _dirtyStart && address < _dirtyEnd)
{
if (_modifiedRanges != null)
{
_modifiedRanges.ExcludeModifiedRegions(_dirtyStart, _dirtyEnd - _dirtyStart, _loadDelegate);
}
else
{
LoadRegion(_dirtyStart, _dirtyEnd - _dirtyStart);
}
_dirtyStart = ulong.MaxValue;
}
}
}
@ -248,7 +271,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (!_syncActionRegistered)
{
_context.RegisterSyncAction(SyncAction);
_context.RegisterSyncAction(this);
_syncActionRegistered = true;
}
}
@ -267,7 +290,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Action to be performed when a syncpoint is reached after modification.
/// This will register read/write tracking to flush the buffer from GPU when its memory is used.
/// </summary>
private void SyncAction()
/// <inheritdoc/>
public bool SyncAction(bool syncpoint)
{
_syncActionRegistered = false;
@ -284,10 +308,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
_memoryTracking.RegisterAction(_externalFlushDelegate);
SynchronizeMemory(Address, Size);
}
return true;
}
/// <summary>
/// Inherit modified ranges from another buffer.
/// Inherit modified and dirty ranges from another buffer.
/// </summary>
/// <param name="from">The buffer to inherit from</param>
public void InheritModifiedRanges(Buffer from)
@ -296,7 +322,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (from._syncActionRegistered && !_syncActionRegistered)
{
_context.RegisterSyncAction(SyncAction);
_context.RegisterSyncAction(this);
_syncActionRegistered = true;
}
@ -316,6 +342,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction);
}
if (from._dirtyStart != ulong.MaxValue)
{
ForceDirty(from._dirtyStart, from._dirtyEnd - from._dirtyStart);
}
}
/// <summary>
@ -334,6 +365,44 @@ namespace Ryujinx.Graphics.Gpu.Memory
return false;
}
/// <summary>
/// Clear the dirty range that overlaps with the given region.
/// </summary>
/// <param name="address">Start address of the modified region</param>
/// <param name="size">Size of the modified region</param>
private void ClearDirty(ulong address, ulong size)
{
if (_dirtyStart != ulong.MaxValue)
{
ulong end = address + size;
if (end > _dirtyStart && address < _dirtyEnd)
{
if (address <= _dirtyStart)
{
// Cut off the start.
if (end < _dirtyEnd)
{
_dirtyStart = end;
}
else
{
_dirtyStart = ulong.MaxValue;
}
}
else if (end >= _dirtyEnd)
{
// Cut off the end.
_dirtyEnd = address;
}
// If fully contained, do nothing.
}
}
}
/// <summary>
/// Indicate that a region of the buffer was modified, and must be loaded from memory.
/// </summary>
@ -353,6 +422,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
mSize = maxSize;
}
ClearDirty(mAddress, mSize);
if (_modifiedRanges != null)
{
_modifiedRanges.ExcludeModifiedRegions(mAddress, mSize, _loadDelegate);
@ -376,14 +447,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
/// Force a region of the buffer to be dirty. Avoids reprotection and nullifies sequence number check.
/// Force a region of the buffer to be dirty within the memory tracking. Avoids reprotection and nullifies sequence number check.
/// </summary>
/// <param name="mAddress">Start address of the modified region</param>
/// <param name="mSize">Size of the region to force dirty</param>
public void ForceDirty(ulong mAddress, ulong mSize)
private void ForceTrackingDirty(ulong mAddress, ulong mSize)
{
_modifiedRanges?.Clear(mAddress, mSize);
if (_useGranular)
{
_memoryTrackingGranular.ForceDirty(mAddress, mSize);
@ -395,6 +464,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
/// <summary>
/// Force a region of the buffer to be dirty. Avoids reprotection and nullifies sequence number check.
/// </summary>
/// <param name="mAddress">Start address of the modified region</param>
/// <param name="mSize">Size of the region to force dirty</param>
public void ForceDirty(ulong mAddress, ulong mSize)
{
_modifiedRanges?.Clear(mAddress, mSize);
ulong end = mAddress + mSize;
if (_dirtyStart == ulong.MaxValue)
{
_dirtyStart = mAddress;
_dirtyEnd = end;
}
else
{
// Is the new range more than a page away from the existing one?
if ((long)(mAddress - _dirtyEnd) >= (long)MemoryManager.PageSize ||
(long)(_dirtyStart - end) >= (long)MemoryManager.PageSize)
{
ForceTrackingDirty(mAddress, mSize);
}
else
{
_dirtyStart = Math.Min(_dirtyStart, mAddress);
_dirtyEnd = Math.Max(_dirtyEnd, end);
}
}
}
/// <summary>
/// Performs copy of all the buffer data from one buffer to another.
/// </summary>

View File

@ -1,5 +1,4 @@
using Ryujinx.Cpu.Tracking;
using Ryujinx.Memory.Tracking;
using Ryujinx.Memory.Tracking;
using System;
namespace Ryujinx.Graphics.Gpu.Memory
@ -9,7 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
class GpuRegionHandle : IRegionHandle
{
private readonly CpuRegionHandle[] _cpuRegionHandles;
private readonly RegionHandle[] _cpuRegionHandles;
public bool Dirty
{
@ -35,7 +34,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Create a new GpuRegionHandle, made up of mulitple CpuRegionHandles.
/// </summary>
/// <param name="cpuRegionHandles">The CpuRegionHandles that make up this handle</param>
public GpuRegionHandle(CpuRegionHandle[] cpuRegionHandles)
public GpuRegionHandle(RegionHandle[] cpuRegionHandles)
{
_cpuRegionHandles = cpuRegionHandles;
}

View File

@ -365,6 +365,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
/// <summary>
/// Runs remap actions that are added to an unmap event.
/// These must run after the mapping completes.
/// </summary>
/// <param name="e">Event with remap actions</param>
private void RunRemapActions(UnmapEventArgs e)
{
if (e.RemapActions != null)
{
foreach (Action action in e.RemapActions)
{
action();
}
}
}
/// <summary>
/// Maps a given range of pages to the specified CPU virtual address.
/// </summary>
@ -379,12 +395,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
lock (_pageTable)
{
MemoryUnmapped?.Invoke(this, new UnmapEventArgs(va, size));
UnmapEventArgs e = new(va, size);
MemoryUnmapped?.Invoke(this, e);
for (ulong offset = 0; offset < size; offset += PageSize)
{
SetPte(va + offset, PackPte(pa + offset, kind));
}
RunRemapActions(e);
}
}
@ -398,12 +417,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
lock (_pageTable)
{
// Event handlers are not expected to be thread safe.
MemoryUnmapped?.Invoke(this, new UnmapEventArgs(va, size));
UnmapEventArgs e = new(va, size);
MemoryUnmapped?.Invoke(this, e);
for (ulong offset = 0; offset < size; offset += PageSize)
{
SetPte(va + offset, PteUnmapped);
}
RunRemapActions(e);
}
}

View File

@ -1,5 +1,4 @@
using Ryujinx.Cpu;
using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Memory;
@ -8,6 +7,7 @@ using Ryujinx.Memory.Tracking;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Linq;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory
@ -82,6 +82,34 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
/// <summary>
/// Gets a host pointer for a given range of application memory.
/// If the memory region is not a single contiguous block, this method returns 0.
/// </summary>
/// <remarks>
/// Getting a host pointer is unsafe. It should be considered invalid immediately if the GPU memory is unmapped.
/// </remarks>
/// <param name="range">Ranges of physical memory where the target data is located</param>
/// <returns>Pointer to the range of memory</returns>
public nint GetHostPointer(MultiRange range)
{
if (range.Count == 1)
{
var singleRange = range.GetSubRange(0);
if (singleRange.Address != MemoryManager.PteUnmapped)
{
var regions = _cpuMemory.GetHostRegions(singleRange.Address, singleRange.Size);
if (regions != null && regions.Count() == 1)
{
return (nint)regions.First().Address;
}
}
}
return 0;
}
/// <summary>
/// Gets a span of data from the application process.
/// </summary>
@ -319,7 +347,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size of the region</param>
/// <param name="kind">Kind of the resource being tracked</param>
/// <returns>The memory tracking handle</returns>
public CpuRegionHandle BeginTracking(ulong address, ulong size, ResourceKind kind)
public RegionHandle BeginTracking(ulong address, ulong size, ResourceKind kind)
{
return _cpuMemory.BeginTracking(address, size, (int)kind);
}
@ -332,7 +360,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <returns>The memory tracking handle</returns>
public GpuRegionHandle BeginTracking(MultiRange range, ResourceKind kind)
{
var cpuRegionHandles = new CpuRegionHandle[range.Count];
var cpuRegionHandles = new RegionHandle[range.Count];
int count = 0;
for (int i = 0; i < range.Count; i++)
@ -361,7 +389,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="handles">Handles to inherit state from or reuse</param>
/// <param name="granularity">Desired granularity of write tracking</param>
/// <returns>The memory tracking handle</returns>
public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, ResourceKind kind, IEnumerable<IRegionHandle> handles = null, ulong granularity = 4096)
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, ResourceKind kind, IEnumerable<IRegionHandle> handles = null, ulong granularity = 4096)
{
return _cpuMemory.BeginGranularTracking(address, size, handles, granularity, (int)kind);
}
@ -374,7 +402,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="kind">Kind of the resource being tracked</param>
/// <param name="granularity">Desired granularity of write tracking</param>
/// <returns>The memory tracking handle</returns>
public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ResourceKind kind, ulong granularity = 4096)
public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ResourceKind kind, ulong granularity = 4096)
{
return _cpuMemory.BeginSmartGranularTracking(address, size, granularity, (int)kind);
}

View File

@ -1,14 +1,24 @@
namespace Ryujinx.Graphics.Gpu.Memory
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Memory
{
public class UnmapEventArgs
{
public ulong Address { get; }
public ulong Size { get; }
public List<Action> RemapActions { get; private set; }
public UnmapEventArgs(ulong address, ulong size)
{
Address = address;
Size = size;
}
public void AddRemapAction(Action action)
{
RemapActions ??= new List<Action>();
RemapActions.Add(action);
}
}
}

View File

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

View File

@ -299,10 +299,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
if (_programList.Count != 0)
{
_stateChangeCallback(ShaderCacheState.Packaging, 0, _programList.Count);
Logger.Info?.Print(LogClass.Gpu, $"Rebuilding {_programList.Count} shaders...");
using var streams = _hostStorage.GetOutputStreams(_context);
int packagedShaders = 0;
foreach (var kv in _programList)
{
if (!Active)
@ -311,7 +314,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
(CachedShaderProgram program, byte[] binaryCode) = kv.Value;
_hostStorage.AddShader(_context, program, binaryCode, streams);
_stateChangeCallback(ShaderCacheState.Packaging, ++packagedShaders, _programList.Count);
}
Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully.");

View File

@ -7,6 +7,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
Start,
/// <summary>Shader cache is loading</summary>
Loading,
/// <summary>Shader cache is written to disk</summary>
Packaging,
/// <summary>Shader cache finished loading</summary>
Loaded
}

View File

@ -0,0 +1,30 @@
using System;
namespace Ryujinx.Graphics.Gpu.Synchronization
{
/// <summary>
/// Modifier flags for creating host sync.
/// </summary>
[Flags]
internal enum HostSyncFlags
{
None = 0,
/// <summary>
/// Present if host sync is being created by a syncpoint.
/// </summary>
Syncpoint = 1 << 0,
/// <summary>
/// Present if the sync should signal as soon as possible.
/// </summary>
Strict = 1 << 1,
/// <summary>
/// Present will force the sync to be created, even if no actions are eligible.
/// </summary>
Force = 1 << 2,
StrictSyncpoint = Strict | Syncpoint
}
}

View File

@ -0,0 +1,22 @@
namespace Ryujinx.Graphics.Gpu.Synchronization
{
/// <summary>
/// This interface indicates that a class can be registered for a sync action.
/// </summary>
interface ISyncActionHandler
{
/// <summary>
/// Action to be performed when some synchronizing action is reached after modification.
/// Generally used to register read/write tracking to flush resources from GPU when their memory is used.
/// </summary>
/// <param name="syncpoint">True if the action is a guest syncpoint</param>
/// <returns>True if the action is to be removed, false otherwise</returns>
bool SyncAction(bool syncpoint);
/// <summary>
/// Action to be performed immediately before sync is created.
/// </summary>
/// <param name="syncpoint">True if the action is a guest syncpoint</param>
void SyncPreAction(bool syncpoint) { }
}
}

View File

@ -42,6 +42,20 @@ namespace Ryujinx.Graphics.OpenGL
return Handle.FromInt32<BufferHandle>(handle);
}
public static BufferHandle CreatePersistent(int size)
{
int handle = GL.GenBuffer();
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle);
GL.BufferStorage(BufferTarget.CopyWriteBuffer, size, IntPtr.Zero,
BufferStorageFlags.MapPersistentBit |
BufferStorageFlags.MapCoherentBit |
BufferStorageFlags.ClientStorageBit |
BufferStorageFlags.MapReadBit);
return Handle.FromInt32<BufferHandle>(handle);
}
public static void Copy(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
{
GL.BindBuffer(BufferTarget.CopyReadBuffer, source.ToInt32());
@ -60,7 +74,11 @@ namespace Ryujinx.Graphics.OpenGL
// Data in the persistent buffer and host array is guaranteed to be available
// until the next time the host thread requests data.
if (HwCapabilities.UsePersistentBufferForFlush)
if (renderer.PersistentBuffers.TryGet(buffer, out IntPtr ptr))
{
return new PinnedSpan<byte>(IntPtr.Add(ptr, offset).ToPointer(), size);
}
else if (HwCapabilities.UsePersistentBufferForFlush)
{
return PinnedSpan<byte>.UnsafeFromSpan(renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size));
}

View File

@ -104,7 +104,7 @@ namespace Ryujinx.Graphics.OpenGL
private static GpuVendor GetGpuVendor()
{
string vendor = GL.GetString(StringName.Vendor).ToLower();
string vendor = GL.GetString(StringName.Vendor).ToLowerInvariant();
if (vendor == "nvidia corporation")
{
@ -112,7 +112,7 @@ namespace Ryujinx.Graphics.OpenGL
}
else if (vendor == "intel")
{
string renderer = GL.GetString(StringName.Renderer).ToLower();
string renderer = GL.GetString(StringName.Renderer).ToLowerInvariant();
return renderer.Contains("mesa") ? GpuVendor.IntelUnix : GpuVendor.IntelWindows;
}

View File

@ -49,6 +49,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
return GetData();
}
public void CopyTo(BufferRange range, int layer, int level, int stride)
{
throw new NotImplementedException();
}
public void SetData(SpanOrArray<byte> data)
{
var dataSpan = data.AsSpan();

View File

@ -3,6 +3,7 @@ using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using System;
using System.Diagnostics;
namespace Ryujinx.Graphics.OpenGL.Image
{
@ -287,6 +288,26 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
public void CopyTo(BufferRange range, int layer, int level, int stride)
{
if (stride != 0 && stride != BitUtils.AlignUp(Info.Width * Info.BytesPerPixel, 4))
{
throw new NotSupportedException("Stride conversion for texture copy to buffer not supported.");
}
GL.BindBuffer(BufferTarget.PixelPackBuffer, range.Handle.ToInt32());
FormatInfo format = FormatTable.GetFormatInfo(Info.Format);
if (format.PixelFormat == PixelFormat.DepthStencil)
{
throw new InvalidOperationException("DepthStencil copy to buffer is not supported for layer/level > 0.");
}
int offset = WriteToPbo2D(range.Offset, layer, level);
Debug.Assert(offset == 0);
}
public void WriteToPbo(int offset, bool forceBgra)
{
WriteTo(IntPtr.Zero + offset, forceBgra);

View File

@ -58,10 +58,31 @@ namespace Ryujinx.Graphics.OpenGL
}
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
{
return CreateBuffer(size, GAL.BufferAccess.Default);
}
public BufferHandle CreateBuffer(int size, GAL.BufferAccess access)
{
BufferCount++;
return Buffer.Create(size);
if (access == GAL.BufferAccess.FlushPersistent)
{
BufferHandle handle = Buffer.CreatePersistent(size);
PersistentBuffers.Map(handle, size);
return handle;
}
else
{
return Buffer.Create(size);
}
}
public BufferHandle CreateBuffer(nint pointer, int size)
{
throw new NotSupportedException();
}
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
@ -88,6 +109,8 @@ namespace Ryujinx.Graphics.OpenGL
public void DeleteBuffer(BufferHandle buffer)
{
PersistentBuffers.Unmap(buffer);
Buffer.Delete(buffer);
}
@ -272,5 +295,10 @@ namespace Ryujinx.Graphics.OpenGL
{
ScreenCaptured?.Invoke(this, bitmap);
}
public bool PrepareHostMapping(nint address, ulong size)
{
return false;
}
}
}

View File

@ -1,8 +1,9 @@
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Image;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -13,6 +14,8 @@ namespace Ryujinx.Graphics.OpenGL
private PersistentBuffer _main = new PersistentBuffer();
private PersistentBuffer _background = new PersistentBuffer();
private Dictionary<BufferHandle, IntPtr> _maps = new Dictionary<BufferHandle, IntPtr>();
public PersistentBuffer Default => BackgroundContextWorker.InBackground ? _background : _main;
public void Dispose()
@ -20,6 +23,30 @@ namespace Ryujinx.Graphics.OpenGL
_main?.Dispose();
_background?.Dispose();
}
public void Map(BufferHandle handle, int size)
{
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32());
IntPtr ptr = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, IntPtr.Zero, size, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit);
_maps[handle] = ptr;
}
public void Unmap(BufferHandle handle)
{
if (_maps.ContainsKey(handle))
{
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32());
GL.UnmapBuffer(BufferTarget.CopyWriteBuffer);
_maps.Remove(handle);
}
}
public bool TryGet(BufferHandle handle, out IntPtr ptr)
{
return _maps.TryGetValue(handle, out ptr);
}
}
class PersistentBuffer : IDisposable

View File

@ -5,13 +5,27 @@ namespace Ryujinx.Graphics.Shader
// New fields should be added to the end of the struct to keep disk shader cache compatibility.
public readonly int Binding;
public readonly int Slot;
public readonly byte Slot;
public readonly byte SbCbSlot;
public readonly ushort SbCbOffset;
public BufferUsageFlags Flags;
public BufferDescriptor(int binding, int slot)
{
Binding = binding;
Slot = slot;
Slot = (byte)slot;
SbCbSlot = 0;
SbCbOffset = 0;
Flags = BufferUsageFlags.None;
}
public BufferDescriptor(int binding, int slot, int sbCbSlot, int sbCbOffset)
{
Binding = binding;
Slot = (byte)slot;
SbCbSlot = (byte)sbCbSlot;
SbCbOffset = (ushort)sbCbOffset;
Flags = BufferUsageFlags.None;
}

View File

@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Shader
/// Flags that indicate how a buffer will be used in a shader.
/// </summary>
[Flags]
public enum BufferUsageFlags
public enum BufferUsageFlags : byte
{
None = 0,

View File

@ -16,6 +16,8 @@ namespace Ryujinx.Graphics.Shader.Translation
public const int UbeDescsSize = StorageDescSize * UbeMaxCount;
public const int UbeFirstCbuf = 8;
public const int DriverReservedCb = 0;
public static bool UsesGlobalMemory(Instruction inst, StorageKind storageKind)
{
return (inst.IsAtomic() && storageKind == StorageKind.GlobalMemory) ||

View File

@ -8,6 +8,20 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class GlobalToStorage
{
private struct SearchResult
{
public static SearchResult NotFound => new SearchResult(-1, 0);
public bool Found => SbCbSlot != -1;
public int SbCbSlot { get; }
public int SbCbOffset { get; }
public SearchResult(int sbCbSlot, int sbCbOffset)
{
SbCbSlot = sbCbSlot;
SbCbOffset = sbCbOffset;
}
}
public static void RunPass(BasicBlock block, ShaderConfig config, ref int sbUseMask, ref int ubeUseMask)
{
int sbStart = GetStorageBaseCbOffset(config.Stage);
@ -49,30 +63,33 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
Operand source = operation.GetSource(0);
int storageIndex = SearchForStorageBase(block, source, sbStart, sbEnd);
if (storageIndex >= 0)
var result = SearchForStorageBase(config, block, source);
if (!result.Found)
{
// Storage buffers are implemented using global memory access.
// If we know from where the base address of the access is loaded,
// we can guess which storage buffer it is accessing.
// We can then replace the global memory access with a storage
// buffer access.
node = ReplaceGlobalWithStorage(block, node, config, storageIndex);
continue;
}
else if (config.Stage == ShaderStage.Compute && operation.Inst == Instruction.LoadGlobal)
if (config.Stage == ShaderStage.Compute &&
operation.Inst == Instruction.LoadGlobal &&
result.SbCbSlot == DriverReservedCb &&
result.SbCbOffset >= UbeBaseOffset &&
result.SbCbOffset < UbeBaseOffset + UbeDescsSize)
{
// Here we effectively try to replace a LDG instruction with LDC.
// The hardware only supports a limited amount of constant buffers
// so NVN "emulates" more constant buffers using global memory access.
// Here we try to replace the global access back to a constant buffer
// load.
storageIndex = SearchForStorageBase(block, source, ubeStart, ubeStart + ubeEnd);
if (storageIndex >= 0)
{
node = ReplaceLdgWithLdc(node, config, storageIndex);
}
node = ReplaceLdgWithLdc(node, config, (result.SbCbOffset - UbeBaseOffset) / StorageDescSize);
}
else
{
// Storage buffers are implemented using global memory access.
// If we know from where the base address of the access is loaded,
// we can guess which storage buffer it is accessing.
// We can then replace the global memory access with a storage
// buffer access.
node = ReplaceGlobalWithStorage(block, node, config, config.GetSbSlot((byte)result.SbCbSlot, (ushort)result.SbCbOffset));
}
}
}
@ -144,12 +161,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand addrLow,
bool isStg16Or8)
{
int baseAddressCbOffset = GetStorageCbOffset(config.Stage, storageIndex);
(int sbCbSlot, int sbCbOffset) = config.GetSbCbInfo(storageIndex);
bool storageAligned = !(config.GpuAccessor.QueryHasUnalignedStorageBuffer() || config.GpuAccessor.QueryHostStorageBufferOffsetAlignment() > Constants.StorageAlignment);
(Operand byteOffset, int constantOffset) = storageAligned ?
GetStorageOffset(block, Utils.FindLastOperation(addrLow, block), baseAddressCbOffset) :
GetStorageOffset(block, Utils.FindLastOperation(addrLow, block), sbCbSlot, sbCbOffset) :
(null, 0);
if (byteOffset != null)
@ -159,7 +176,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (byteOffset == null)
{
Operand baseAddrLow = Cbuf(0, baseAddressCbOffset);
Operand baseAddrLow = Cbuf(sbCbSlot, sbCbOffset);
Operand baseAddrTrunc = Local();
Operand alignMask = Const(-config.GpuAccessor.QueryHostStorageBufferOffsetAlignment());
@ -198,9 +215,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return wordOffset;
}
private static bool IsCb0Offset(Operand operand, int offset)
private static bool IsCbOffset(Operand operand, int slot, int offset)
{
return operand.Type == OperandType.ConstantBuffer && operand.GetCbufSlot() == 0 && operand.GetCbufOffset() == offset;
return operand.Type == OperandType.ConstantBuffer && operand.GetCbufSlot() == slot && operand.GetCbufOffset() == offset;
}
private static void ReplaceAddressAlignment(LinkedList<INode> list, Operand address, Operand byteOffset, int constantOffset)
@ -244,9 +261,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
private static (Operand, int) GetStorageOffset(BasicBlock block, Operand address, int baseAddressCbOffset)
private static (Operand, int) GetStorageOffset(BasicBlock block, Operand address, int cbSlot, int baseAddressCbOffset)
{
if (IsCb0Offset(address, baseAddressCbOffset))
if (IsCbOffset(address, cbSlot, baseAddressCbOffset))
{
// Direct offset: zero.
return (Const(0), 0);
@ -256,7 +273,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
address = Utils.FindLastOperation(address, block);
if (IsCb0Offset(address, baseAddressCbOffset))
if (IsCbOffset(address, cbSlot, baseAddressCbOffset))
{
// Only constant offset
return (Const(0), constantOffset);
@ -270,11 +287,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand src1 = offsetAdd.GetSource(0);
Operand src2 = Utils.FindLastOperation(offsetAdd.GetSource(1), block);
if (IsCb0Offset(src2, baseAddressCbOffset))
if (IsCbOffset(src2, cbSlot, baseAddressCbOffset))
{
return (src1, constantOffset);
}
else if (IsCb0Offset(src1, baseAddressCbOffset))
else if (IsCbOffset(src1, cbSlot, baseAddressCbOffset))
{
return (src2, constantOffset);
}
@ -360,20 +377,20 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return node;
}
private static int SearchForStorageBase(BasicBlock block, Operand globalAddress, int sbStart, int sbEnd)
private static SearchResult SearchForStorageBase(ShaderConfig config, BasicBlock block, Operand globalAddress)
{
globalAddress = Utils.FindLastOperation(globalAddress, block);
if (globalAddress.Type == OperandType.ConstantBuffer)
{
return GetStorageIndex(globalAddress, sbStart, sbEnd);
return GetStorageIndex(config, globalAddress);
}
Operation operation = globalAddress.AsgOp as Operation;
if (operation == null || operation.Inst != Instruction.Add)
{
return -1;
return SearchResult.NotFound;
}
Operand src1 = operation.GetSource(0);
@ -382,34 +399,65 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if ((src1.Type == OperandType.LocalVariable && src2.Type == OperandType.Constant) ||
(src2.Type == OperandType.LocalVariable && src1.Type == OperandType.Constant))
{
Operand baseAddr;
if (src1.Type == OperandType.LocalVariable)
{
operation = Utils.FindLastOperation(src1, block).AsgOp as Operation;
baseAddr = Utils.FindLastOperation(src1, block);
}
else
{
operation = Utils.FindLastOperation(src2, block).AsgOp as Operation;
baseAddr = Utils.FindLastOperation(src2, block);
}
var result = GetStorageIndex(config, baseAddr);
if (result.Found)
{
return result;
}
operation = baseAddr.AsgOp as Operation;
if (operation == null || operation.Inst != Instruction.Add)
{
return -1;
return SearchResult.NotFound;
}
}
var selectedResult = SearchResult.NotFound;
for (int index = 0; index < operation.SourcesCount; index++)
{
Operand source = operation.GetSource(index);
int storageIndex = GetStorageIndex(source, sbStart, sbEnd);
var result = GetStorageIndex(config, source);
if (storageIndex != -1)
// If we already have a result, we give preference to the ones from
// the driver reserved constant buffer, as those are the ones that
// contains the base address.
if (result.Found && (!selectedResult.Found || result.SbCbSlot == GlobalMemory.DriverReservedCb))
{
return storageIndex;
selectedResult = result;
}
}
return -1;
return selectedResult;
}
private static SearchResult GetStorageIndex(ShaderConfig config, Operand operand)
{
if (operand.Type == OperandType.ConstantBuffer)
{
int slot = operand.GetCbufSlot();
int offset = operand.GetCbufOffset();
if ((offset & 3) == 0)
{
return new SearchResult(slot, offset);
}
}
return SearchResult.NotFound;
}
private static int GetStorageIndex(Operand operand, int sbStart, int sbEnd)

View File

@ -68,7 +68,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
ConstantFolding.RunPass(operation);
Simplification.RunPass(operation);
if (DestIsLocalVar(operation))

View File

@ -110,9 +110,9 @@ namespace Ryujinx.Graphics.Shader.Translation
Operand BindingRangeCheck(int cbOffset, out Operand baseAddrLow)
{
baseAddrLow = Cbuf(0, cbOffset);
Operand baseAddrHigh = Cbuf(0, cbOffset + 1);
Operand size = Cbuf(0, cbOffset + 2);
baseAddrLow = Cbuf(DriverReservedCb, cbOffset);
Operand baseAddrHigh = Cbuf(DriverReservedCb, cbOffset + 1);
Operand size = Cbuf(DriverReservedCb, cbOffset + 2);
Operand offset = PrependOperation(Instruction.Subtract, addrLow, baseAddrLow);
Operand borrow = PrependOperation(Instruction.CompareLessU32, addrLow, baseAddrLow);
@ -134,9 +134,10 @@ namespace Ryujinx.Graphics.Shader.Translation
sbUseMask &= ~(1 << slot);
config.SetUsedStorageBuffer(slot, isWrite);
int cbOffset = GetStorageCbOffset(config.Stage, slot);
slot = config.GetSbSlot(DriverReservedCb, (ushort)cbOffset);
config.SetUsedStorageBuffer(slot, isWrite);
Operand inRange = BindingRangeCheck(cbOffset, out Operand baseAddrLow);

View File

@ -125,6 +125,9 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly Dictionary<TextureInfo, TextureMeta> _usedTextures;
private readonly Dictionary<TextureInfo, TextureMeta> _usedImages;
private readonly Dictionary<int, int> _sbSlots;
private readonly Dictionary<int, int> _sbSlotsReverse;
private BufferDescriptor[] _cachedConstantBufferDescriptors;
private BufferDescriptor[] _cachedStorageBufferDescriptors;
private TextureDescriptor[] _cachedTextureDescriptors;
@ -152,6 +155,9 @@ namespace Ryujinx.Graphics.Shader.Translation
_usedTextures = new Dictionary<TextureInfo, TextureMeta>();
_usedImages = new Dictionary<TextureInfo, TextureMeta>();
_sbSlots = new Dictionary<int, int>();
_sbSlotsReverse = new Dictionary<int, int>();
}
public ShaderConfig(
@ -770,9 +776,8 @@ namespace Ryujinx.Graphics.Shader.Translation
usedMask |= (int)GpuAccessor.QueryConstantBufferUse();
}
return _cachedConstantBufferDescriptors = GetBufferDescriptors(
return _cachedConstantBufferDescriptors = GetUniformBufferDescriptors(
usedMask,
0,
UsedFeatures.HasFlag(FeatureFlags.CbIndexing),
out _firstConstantBufferBinding,
GpuAccessor.QueryBindingConstantBuffer);
@ -785,7 +790,7 @@ namespace Ryujinx.Graphics.Shader.Translation
return _cachedStorageBufferDescriptors;
}
return _cachedStorageBufferDescriptors = GetBufferDescriptors(
return _cachedStorageBufferDescriptors = GetStorageBufferDescriptors(
_usedStorageBuffers,
_usedStorageBuffersWrite,
true,
@ -793,7 +798,48 @@ namespace Ryujinx.Graphics.Shader.Translation
GpuAccessor.QueryBindingStorageBuffer);
}
private static BufferDescriptor[] GetBufferDescriptors(
private static BufferDescriptor[] GetUniformBufferDescriptors(int usedMask, bool isArray, out int firstBinding, Func<int, int> getBindingCallback)
{
firstBinding = 0;
int lastSlot = -1;
bool hasFirstBinding = false;
var descriptors = new BufferDescriptor[BitOperations.PopCount((uint)usedMask)];
for (int i = 0; i < descriptors.Length; i++)
{
int slot = BitOperations.TrailingZeroCount(usedMask);
if (isArray)
{
// The next array entries also consumes bindings, even if they are unused.
for (int j = lastSlot + 1; j < slot; j++)
{
int binding = getBindingCallback(j);
if (!hasFirstBinding)
{
firstBinding = binding;
hasFirstBinding = true;
}
}
}
lastSlot = slot;
descriptors[i] = new BufferDescriptor(getBindingCallback(slot), slot);
if (!hasFirstBinding)
{
firstBinding = descriptors[i].Binding;
hasFirstBinding = true;
}
usedMask &= ~(1 << slot);
}
return descriptors;
}
private BufferDescriptor[] GetStorageBufferDescriptors(
int usedMask,
int writtenMask,
bool isArray,
@ -827,7 +873,9 @@ namespace Ryujinx.Graphics.Shader.Translation
lastSlot = slot;
descriptors[i] = new BufferDescriptor(getBindingCallback(slot), slot);
(int sbCbSlot, int sbCbOffset) = GetSbCbInfo(slot);
descriptors[i] = new BufferDescriptor(getBindingCallback(slot), slot, sbCbSlot, sbCbOffset);
if (!hasFirstBinding)
{
@ -924,6 +972,40 @@ namespace Ryujinx.Graphics.Shader.Translation
return FindDescriptorIndex(GetImageDescriptors(), texOp);
}
public int GetSbSlot(byte sbCbSlot, ushort sbCbOffset)
{
int key = PackSbCbInfo(sbCbSlot, sbCbOffset);
if (!_sbSlots.TryGetValue(key, out int slot))
{
slot = _sbSlots.Count;
_sbSlots.Add(key, slot);
_sbSlotsReverse.Add(slot, key);
}
return slot;
}
public (int, int) GetSbCbInfo(int slot)
{
if (_sbSlotsReverse.TryGetValue(slot, out int key))
{
return UnpackSbCbInfo(key);
}
throw new ArgumentException($"Invalid slot {slot}.", nameof(slot));
}
private static int PackSbCbInfo(int sbCbSlot, int sbCbOffset)
{
return sbCbOffset | ((int)sbCbSlot << 16);
}
private static (int, int) UnpackSbCbInfo(int key)
{
return ((byte)(key >> 16), (ushort)key);
}
public ShaderProgramInfo CreateProgramInfo(ShaderIdentification identification = ShaderIdentification.None)
{
return new ShaderProgramInfo(

View File

@ -33,6 +33,7 @@ namespace Ryujinx.Graphics.Vulkan
private MemoryAllocation _allocation;
private Auto<DisposableBuffer> _buffer;
private Auto<MemoryAllocation> _allocationAuto;
private bool _allocationImported;
private ulong _bufferHandle;
private CacheByRange<BufferHolder> _cachedConvertedBuffers;
@ -81,6 +82,26 @@ namespace Ryujinx.Graphics.Vulkan
_flushLock = new ReaderWriterLock();
}
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, Auto<MemoryAllocation> allocation, int size, BufferAllocationType type, BufferAllocationType currentType, int offset)
{
_gd = gd;
_device = device;
_allocation = allocation.GetUnsafe();
_allocationAuto = allocation;
_allocationImported = true;
_waitable = new MultiFenceHolder(size);
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), _waitable, _allocationAuto);
_bufferHandle = buffer.Handle;
Size = size;
_map = _allocation.HostPointer + offset;
_baseType = type;
_currentType = currentType;
DesiredType = currentType;
_flushLock = new ReaderWriterLock();
}
public bool TryBackingSwap(ref CommandBufferScoped? cbs)
{
if (_swapQueued && DesiredType != _currentType)
@ -775,8 +796,15 @@ namespace Ryujinx.Graphics.Vulkan
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
_buffer.Dispose();
_allocationAuto.Dispose();
_cachedConvertedBuffers.Dispose();
if (_allocationImported)
{
_allocationAuto.DecrementReferenceCount();
}
else
{
_allocationAuto.Dispose();
}
_flushLock.AcquireWriterLock(Timeout.Infinite);

View File

@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Vulkan
{
class BufferManager : IDisposable
{
private const MemoryPropertyFlags DefaultBufferMemoryFlags =
public const MemoryPropertyFlags DefaultBufferMemoryFlags =
MemoryPropertyFlags.HostVisibleBit |
MemoryPropertyFlags.HostCoherentBit |
MemoryPropertyFlags.HostCachedBit;
@ -40,6 +40,10 @@ namespace Ryujinx.Graphics.Vulkan
BufferUsageFlags.VertexBufferBit |
BufferUsageFlags.TransformFeedbackBufferBitExt;
private const BufferUsageFlags HostImportedBufferUsageFlags =
BufferUsageFlags.TransferSrcBit |
BufferUsageFlags.TransferDstBit;
private readonly Device _device;
private readonly IdList<BufferHolder> _buffers;
@ -48,11 +52,47 @@ namespace Ryujinx.Graphics.Vulkan
public StagingBuffer StagingBuffer { get; }
public MemoryRequirements HostImportedBufferMemoryRequirements { get; }
public BufferManager(VulkanRenderer gd, Device device)
{
_device = device;
_buffers = new IdList<BufferHolder>();
StagingBuffer = new StagingBuffer(gd, this);
HostImportedBufferMemoryRequirements = GetHostImportedUsageRequirements(gd);
}
public unsafe BufferHandle CreateHostImported(VulkanRenderer gd, nint pointer, int size)
{
var usage = HostImportedBufferUsageFlags;
if (gd.Capabilities.SupportsIndirectParameters)
{
usage |= BufferUsageFlags.IndirectBufferBit;
}
var bufferCreateInfo = new BufferCreateInfo()
{
SType = StructureType.BufferCreateInfo,
Size = (ulong)size,
Usage = usage,
SharingMode = SharingMode.Exclusive
};
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
(Auto<MemoryAllocation> allocation, ulong offset) = gd.HostMemoryAllocator.GetExistingAllocation(pointer, (ulong)size);
gd.Api.BindBufferMemory(_device, buffer, allocation.GetUnsafe().Memory, allocation.GetUnsafe().Offset + offset);
var holder = new BufferHolder(gd, _device, buffer, allocation, size, BufferAllocationType.HostMapped, BufferAllocationType.HostMapped, (int)offset);
BufferCount++;
ulong handle64 = (uint)_buffers.Add(holder);
return Unsafe.As<ulong, BufferHandle>(ref handle64);
}
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default)
@ -75,6 +115,32 @@ namespace Ryujinx.Graphics.Vulkan
return Unsafe.As<ulong, BufferHandle>(ref handle64);
}
public unsafe MemoryRequirements GetHostImportedUsageRequirements(VulkanRenderer gd)
{
var usage = HostImportedBufferUsageFlags;
if (gd.Capabilities.SupportsIndirectParameters)
{
usage |= BufferUsageFlags.IndirectBufferBit;
}
var bufferCreateInfo = new BufferCreateInfo()
{
SType = StructureType.BufferCreateInfo,
Size = (ulong)Environment.SystemPageSize,
Usage = usage,
SharingMode = SharingMode.Exclusive
};
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements);
gd.Api.DestroyBuffer(_device, buffer, null);
return requirements;
}
public unsafe (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) CreateBacking(
VulkanRenderer gd,
int size,

View File

@ -364,6 +364,15 @@ namespace Ryujinx.Graphics.Vulkan
};
}
public static BufferAllocationType Convert(this BufferAccess access)
{
return access switch
{
BufferAccess.FlushPersistent => BufferAllocationType.HostMapped,
_ => BufferAllocationType.Auto
};
}
private static T2 LogInvalidAndReturn<T1, T2>(T1 value, string name, T2 defaultValue = default)
{
Logger.Debug?.Print(LogClass.Gpu, $"Invalid {name} enum value: {value}.");

View File

@ -41,6 +41,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsPipelineStatisticsQuery;
public readonly bool SupportsGeometryShader;
public readonly bool SupportsViewportArray2;
public readonly bool SupportsHostImportedMemory;
public readonly uint MinSubgroupSize;
public readonly uint MaxSubgroupSize;
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
@ -75,6 +76,7 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsPipelineStatisticsQuery,
bool supportsGeometryShader,
bool supportsViewportArray2,
bool supportsHostImportedMemory,
uint minSubgroupSize,
uint maxSubgroupSize,
ShaderStageFlags requiredSubgroupSizeStages,
@ -108,6 +110,7 @@ namespace Ryujinx.Graphics.Vulkan
SupportsPipelineStatisticsQuery = supportsPipelineStatisticsQuery;
SupportsGeometryShader = supportsGeometryShader;
SupportsViewportArray2 = supportsViewportArray2;
SupportsHostImportedMemory = supportsHostImportedMemory;
MinSubgroupSize = minSubgroupSize;
MaxSubgroupSize = maxSubgroupSize;
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;

View File

@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly IProgram _programColorClearSI;
private readonly IProgram _programColorClearUI;
private readonly IProgram _programStrideChange;
private readonly IProgram _programConvertD32S8ToD24S8;
private readonly IProgram _programConvertIndexBuffer;
private readonly IProgram _programConvertIndirectData;
private readonly IProgram _programColorCopyShortening;
@ -158,6 +159,17 @@ namespace Ryujinx.Graphics.Vulkan
new ShaderSource(ShaderBinaries.ColorDrawToMsFragmentShaderSource, colorDrawToMsFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
});
var convertD32S8ToD24S8Bindings = new ShaderBindings(
new[] { 0 },
new[] { 1, 2 },
Array.Empty<int>(),
Array.Empty<int>());
_programConvertD32S8ToD24S8 = gd.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(ShaderBinaries.ConvertD32S8ToD24S8ShaderSource, convertD32S8ToD24S8Bindings, ShaderStage.Compute, TargetLanguage.Spirv),
});
var convertIndexBufferBindings = new ShaderBindings(
new[] { 0 },
new[] { 1, 2 },
@ -1644,6 +1656,82 @@ namespace Ryujinx.Graphics.Vulkan
_pipeline.Finish(gd, cbs);
}
public unsafe void ConvertD32S8ToD24S8(VulkanRenderer gd, CommandBufferScoped cbs, BufferHolder src, Auto<DisposableBuffer> dstBufferAuto, int pixelCount, int dstOffset)
{
int inSize = pixelCount * 2 * sizeof(int);
int outSize = pixelCount * sizeof(int);
var srcBufferAuto = src.GetBuffer();
var srcBuffer = srcBufferAuto.Get(cbs, 0, inSize).Value;
var dstBuffer = dstBufferAuto.Get(cbs, dstOffset, outSize).Value;
var access = AccessFlags.ShaderWriteBit;
var stage = PipelineStageFlags.ComputeShaderBit;
BufferHolder.InsertBufferBarrier(
gd,
cbs.CommandBuffer,
srcBuffer,
BufferHolder.DefaultAccessFlags,
AccessFlags.ShaderReadBit,
PipelineStageFlags.AllCommandsBit,
stage,
0,
outSize);
BufferHolder.InsertBufferBarrier(
gd,
cbs.CommandBuffer,
dstBuffer,
BufferHolder.DefaultAccessFlags,
access,
PipelineStageFlags.AllCommandsBit,
stage,
0,
outSize);
const int ParamsBufferSize = sizeof(int) * 2;
Span<int> shaderParams = stackalloc int[2];
shaderParams[0] = pixelCount;
shaderParams[1] = dstOffset;
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize);
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2];
sbRanges[0] = srcBufferAuto;
sbRanges[1] = dstBufferAuto;
_pipeline.SetStorageBuffers(1, sbRanges);
_pipeline.SetProgram(_programConvertD32S8ToD24S8);
_pipeline.DispatchCompute(1, 1, 1);
gd.BufferManager.Delete(bufferHandle);
_pipeline.Finish(gd, cbs);
BufferHolder.InsertBufferBarrier(
gd,
cbs.CommandBuffer,
dstBuffer,
access,
BufferHolder.DefaultAccessFlags,
stage,
PipelineStageFlags.AllCommandsBit,
0,
outSize);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)

View File

@ -0,0 +1,188 @@
using Ryujinx.Common;
using Ryujinx.Common.Collections;
using Ryujinx.Common.Logging;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.EXT;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Vulkan
{
internal class HostMemoryAllocator
{
private struct HostMemoryAllocation
{
public readonly Auto<MemoryAllocation> Allocation;
public readonly IntPtr Pointer;
public readonly ulong Size;
public ulong Start => (ulong)Pointer;
public ulong End => (ulong)Pointer + Size;
public HostMemoryAllocation(Auto<MemoryAllocation> allocation, IntPtr pointer, ulong size)
{
Allocation = allocation;
Pointer = pointer;
Size = size;
}
}
private readonly MemoryAllocator _allocator;
private readonly Vk _api;
private readonly ExtExternalMemoryHost _hostMemoryApi;
private readonly Device _device;
private readonly object _lock = new();
private List<HostMemoryAllocation> _allocations;
private IntervalTree<ulong, HostMemoryAllocation> _allocationTree;
public HostMemoryAllocator(MemoryAllocator allocator, Vk api, ExtExternalMemoryHost hostMemoryApi, Device device)
{
_allocator = allocator;
_api = api;
_hostMemoryApi = hostMemoryApi;
_device = device;
_allocations = new List<HostMemoryAllocation>();
_allocationTree = new IntervalTree<ulong, HostMemoryAllocation>();
}
public unsafe bool TryImport(
MemoryRequirements requirements,
MemoryPropertyFlags flags,
IntPtr pointer,
ulong size)
{
lock (_lock)
{
// Does a compatible allocation exist in the tree?
var allocations = new HostMemoryAllocation[10];
ulong start = (ulong)pointer;
ulong end = start + size;
int count = _allocationTree.Get(start, end, ref allocations);
// A compatible range is one that where the start and end completely cover the requested range.
for (int i = 0; i < count; i++)
{
HostMemoryAllocation existing = allocations[i];
if (start >= existing.Start && end <= existing.End)
{
try
{
existing.Allocation.IncrementReferenceCount();
return true;
}
catch (InvalidOperationException)
{
// Can throw if the allocation has been disposed.
// Just continue the search if this happens.
}
}
}
nint pageAlignedPointer = BitUtils.AlignDown(pointer, Environment.SystemPageSize);
nint pageAlignedEnd = BitUtils.AlignUp((nint)((ulong)pointer + size), Environment.SystemPageSize);
ulong pageAlignedSize = (ulong)(pageAlignedEnd - pageAlignedPointer);
Result getResult = _hostMemoryApi.GetMemoryHostPointerProperties(_device, ExternalMemoryHandleTypeFlags.HostAllocationBitExt, (void*)pageAlignedPointer, out MemoryHostPointerPropertiesEXT properties);
if (getResult < Result.Success)
{
return false;
}
int memoryTypeIndex = _allocator.FindSuitableMemoryTypeIndex(properties.MemoryTypeBits & requirements.MemoryTypeBits, flags);
if (memoryTypeIndex < 0)
{
return false;
}
ImportMemoryHostPointerInfoEXT importInfo = new ImportMemoryHostPointerInfoEXT()
{
SType = StructureType.ImportMemoryHostPointerInfoExt,
HandleType = ExternalMemoryHandleTypeFlags.HostAllocationBitExt,
PHostPointer = (void*)pageAlignedPointer
};
var memoryAllocateInfo = new MemoryAllocateInfo()
{
SType = StructureType.MemoryAllocateInfo,
AllocationSize = pageAlignedSize,
MemoryTypeIndex = (uint)memoryTypeIndex,
PNext = &importInfo
};
Result result = _api.AllocateMemory(_device, memoryAllocateInfo, null, out var deviceMemory);
if (result < Result.Success)
{
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Host mapping import 0x{pageAlignedPointer:x16} 0x{pageAlignedSize:x8} failed.");
return false;
}
var allocation = new MemoryAllocation(this, deviceMemory, pageAlignedPointer, 0, pageAlignedSize);
var allocAuto = new Auto<MemoryAllocation>(allocation);
var hostAlloc = new HostMemoryAllocation(allocAuto, pageAlignedPointer, pageAlignedSize);
allocAuto.IncrementReferenceCount();
allocAuto.Dispose(); // Kept alive by ref count only.
// Register this mapping for future use.
_allocationTree.Add(hostAlloc.Start, hostAlloc.End, hostAlloc);
_allocations.Add(hostAlloc);
}
return true;
}
public (Auto<MemoryAllocation>, ulong) GetExistingAllocation(IntPtr pointer, ulong size)
{
lock (_lock)
{
// Does a compatible allocation exist in the tree?
var allocations = new HostMemoryAllocation[10];
ulong start = (ulong)pointer;
ulong end = start + size;
int count = _allocationTree.Get(start, end, ref allocations);
// A compatible range is one that where the start and end completely cover the requested range.
for (int i = 0; i < count; i++)
{
HostMemoryAllocation existing = allocations[i];
if (start >= existing.Start && end <= existing.End)
{
return (existing.Allocation, start - existing.Start);
}
}
throw new InvalidOperationException($"No host allocation was prepared for requested range 0x{pointer:x16}:0x{size:x16}.");
}
}
public void Free(DeviceMemory memory, ulong offset, ulong size)
{
lock (_lock)
{
_allocations.RemoveAll(allocation =>
{
if (allocation.Allocation.GetUnsafe().Memory.Handle == memory.Handle)
{
_allocationTree.Remove(allocation.Start, allocation);
return true;
}
return false;
});
}
_api.FreeMemory(_device, memory, ReadOnlySpan<AllocationCallbacks>.Empty);
}
}
}

View File

@ -7,6 +7,7 @@ namespace Ryujinx.Graphics.Vulkan
{
private readonly MemoryAllocatorBlockList _owner;
private readonly MemoryAllocatorBlockList.Block _block;
private readonly HostMemoryAllocator _hostMemory;
public DeviceMemory Memory { get; }
public IntPtr HostPointer { get;}
@ -29,9 +30,30 @@ namespace Ryujinx.Graphics.Vulkan
Size = size;
}
public MemoryAllocation(
HostMemoryAllocator hostMemory,
DeviceMemory memory,
IntPtr hostPointer,
ulong offset,
ulong size)
{
_hostMemory = hostMemory;
Memory = memory;
HostPointer = hostPointer;
Offset = offset;
Size = size;
}
public void Dispose()
{
_owner.Free(_block, Offset, Size);
if (_hostMemory != null)
{
_hostMemory.Free(Memory, Offset, Size);
}
else
{
_owner.Free(_block, Offset, Size);
}
}
}
}

View File

@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.Vulkan
return newBl.Allocate(size, alignment, map);
}
private int FindSuitableMemoryTypeIndex(
internal int FindSuitableMemoryTypeIndex(
uint memoryTypeBits,
MemoryPropertyFlags flags)
{

View File

@ -1015,8 +1015,8 @@ namespace Ryujinx.Graphics.Vulkan
private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
{
FramebufferParams?.UpdateModifications();
CreateFramebuffer(colors, depthStencil, filterWriteMasked);
FramebufferParams?.UpdateModifications();
CreateRenderPass();
SignalStateChange();
SignalAttachmentChange();

View File

@ -0,0 +1,58 @@
#version 450 core
#extension GL_EXT_scalar_block_layout : require
layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout (std430, set = 0, binding = 0) uniform stride_arguments
{
int pixelCount;
int dstStartOffset;
};
layout (std430, set = 1, binding = 1) buffer in_s
{
uint[] in_data;
};
layout (std430, set = 1, binding = 2) buffer out_s
{
uint[] out_data;
};
void main()
{
// Determine what slice of the stride copies this invocation will perform.
int invocations = int(gl_WorkGroupSize.x);
int copiesRequired = pixelCount;
// Find the copies that this invocation should perform.
// - Copies that all invocations perform.
int allInvocationCopies = copiesRequired / invocations;
// - Extra remainder copy that this invocation performs.
int index = int(gl_LocalInvocationID.x);
int extra = (index < (copiesRequired % invocations)) ? 1 : 0;
int copyCount = allInvocationCopies + extra;
// Finally, get the starting offset. Make sure to count extra copies.
int startCopy = allInvocationCopies * index + min(copiesRequired % invocations, index);
int srcOffset = startCopy * 2;
int dstOffset = dstStartOffset + startCopy;
// Perform the conversion for this region.
for (int i = 0; i < copyCount; i++)
{
float depth = uintBitsToFloat(in_data[srcOffset++]);
uint stencil = in_data[srcOffset++];
uint rescaledDepth = uint(clamp(depth, 0.0, 1.0) * 16777215.0);
out_data[dstOffset++] = (rescaledDepth << 8) | (stencil & 0xff);
}
}

View File

@ -1236,6 +1236,213 @@ namespace Ryujinx.Graphics.Vulkan.Shaders
0x38, 0x00, 0x01, 0x00,
};
public static readonly byte[] ConvertD32S8ToD24S8ShaderSource = new byte[]
{
0x03, 0x02, 0x23, 0x07, 0x00, 0x05, 0x01, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x77, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,
0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0F, 0x00, 0x09, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00,
0x67, 0x00, 0x00, 0x00, 0x10, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x47, 0x4C, 0x5F, 0x45,
0x58, 0x54, 0x5F, 0x73, 0x63, 0x61, 0x6C, 0x61, 0x72, 0x5F, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x5F,
0x6C, 0x61, 0x79, 0x6F, 0x75, 0x74, 0x00, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x47, 0x4C, 0x5F, 0x47,
0x4F, 0x4F, 0x47, 0x4C, 0x45, 0x5F, 0x63, 0x70, 0x70, 0x5F, 0x73, 0x74, 0x79, 0x6C, 0x65, 0x5F,
0x6C, 0x69, 0x6E, 0x65, 0x5F, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x00,
0x04, 0x00, 0x08, 0x00, 0x47, 0x4C, 0x5F, 0x47, 0x4F, 0x4F, 0x47, 0x4C, 0x45, 0x5F, 0x69, 0x6E,
0x63, 0x6C, 0x75, 0x64, 0x65, 0x5F, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00,
0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x05, 0x00, 0x08, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x76, 0x6F, 0x63, 0x61, 0x74, 0x69,
0x6F, 0x6E, 0x73, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x70, 0x69,
0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00,
0x0B, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x5F, 0x61, 0x72, 0x67, 0x75, 0x6D,
0x65, 0x6E, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x70, 0x69, 0x78, 0x65, 0x6C, 0x43, 0x6F, 0x75, 0x6E, 0x74, 0x00, 0x00,
0x06, 0x00, 0x07, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x73, 0x74, 0x53,
0x74, 0x61, 0x72, 0x74, 0x4F, 0x66, 0x66, 0x73, 0x65, 0x74, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, 0x12, 0x00, 0x00, 0x00,
0x61, 0x6C, 0x6C, 0x49, 0x6E, 0x76, 0x6F, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x43, 0x6F, 0x70,
0x69, 0x65, 0x73, 0x00, 0x05, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x64, 0x65,
0x78, 0x00, 0x00, 0x00, 0x05, 0x00, 0x08, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x4C,
0x6F, 0x63, 0x61, 0x6C, 0x49, 0x6E, 0x76, 0x6F, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x49, 0x44,
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x65, 0x78, 0x74, 0x72,
0x61, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x29, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x70, 0x79,
0x43, 0x6F, 0x75, 0x6E, 0x74, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x2D, 0x00, 0x00, 0x00,
0x73, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6F, 0x70, 0x79, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,
0x37, 0x00, 0x00, 0x00, 0x73, 0x72, 0x63, 0x4F, 0x66, 0x66, 0x73, 0x65, 0x74, 0x00, 0x00, 0x00,
0x05, 0x00, 0x05, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x64, 0x73, 0x74, 0x4F, 0x66, 0x66, 0x73, 0x65,
0x74, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00,
0x05, 0x00, 0x04, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x64, 0x65, 0x70, 0x74, 0x68, 0x00, 0x00, 0x00,
0x05, 0x00, 0x04, 0x00, 0x4D, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x5F, 0x73, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x05, 0x00, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x5F, 0x64,
0x61, 0x74, 0x61, 0x00, 0x05, 0x00, 0x03, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x04, 0x00, 0x57, 0x00, 0x00, 0x00, 0x73, 0x74, 0x65, 0x6E, 0x63, 0x69, 0x6C, 0x00,
0x05, 0x00, 0x06, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x72, 0x65, 0x73, 0x63, 0x61, 0x6C, 0x65, 0x64,
0x44, 0x65, 0x70, 0x74, 0x68, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x65, 0x00, 0x00, 0x00,
0x6F, 0x75, 0x74, 0x5F, 0x73, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x65, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x6F, 0x75, 0x74, 0x5F, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x03, 0x00, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x4C, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x48, 0x00, 0x05, 0x00, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x4D, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x48, 0x00, 0x05, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x65, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x67, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x67, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x76, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x0B, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x18, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x17, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00, 0x25, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00,
0x49, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x4A, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x03, 0x00, 0x4C, 0x00, 0x00, 0x00,
0x17, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x03, 0x00, 0x4D, 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00,
0x20, 0x00, 0x04, 0x00, 0x4E, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x00, 0x00,
0x3B, 0x00, 0x04, 0x00, 0x4E, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x20, 0x00, 0x04, 0x00, 0x52, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
0x20, 0x00, 0x04, 0x00, 0x56, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
0x2B, 0x00, 0x04, 0x00, 0x49, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2B, 0x00, 0x04, 0x00, 0x49, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F,
0x2B, 0x00, 0x04, 0x00, 0x49, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x7F, 0x4B,
0x1D, 0x00, 0x03, 0x00, 0x64, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x03, 0x00,
0x65, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x66, 0x00, 0x00, 0x00,
0x0C, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x66, 0x00, 0x00, 0x00,
0x67, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x6B, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00,
0x6E, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00,
0x74, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00,
0x75, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x06, 0x00, 0x18, 0x00, 0x00, 0x00,
0x76, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00,
0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x4A, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
0x56, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
0x0A, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x13, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x87, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
0x15, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x1C, 0x00, 0x00, 0x00,
0x1D, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
0x17, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
0x16, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x21, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x22, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x23, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x8B, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
0x24, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0xB1, 0x00, 0x05, 0x00,
0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
0xA9, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
0x27, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00,
0x12, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00,
0x2A, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x29, 0x00, 0x00, 0x00,
0x2C, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00,
0x12, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x2E, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x31, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x32, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x8B, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
0x33, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x07, 0x00,
0x06, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
0x36, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
0x2D, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x38, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
0x3A, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
0x37, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x3C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
0x06, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00,
0x3E, 0x00, 0x03, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
0x40, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0x41, 0x00, 0x00, 0x00,
0xF8, 0x00, 0x02, 0x00, 0x41, 0x00, 0x00, 0x00, 0xF6, 0x00, 0x04, 0x00, 0x43, 0x00, 0x00, 0x00,
0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0x45, 0x00, 0x00, 0x00,
0xF8, 0x00, 0x02, 0x00, 0x45, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x46, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x47, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0xB1, 0x00, 0x05, 0x00, 0x25, 0x00, 0x00, 0x00,
0x48, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x04, 0x00,
0x48, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
0x42, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
0x37, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00,
0x50, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x37, 0x00, 0x00, 0x00,
0x51, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00,
0x4F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
0x17, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x04, 0x00,
0x49, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
0x4B, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x58, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
0x59, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
0x37, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x52, 0x00, 0x00, 0x00,
0x5A, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
0x3D, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00,
0x3E, 0x00, 0x03, 0x00, 0x57, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
0x49, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x08, 0x00,
0x49, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00,
0x5D, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00,
0x49, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
0x6D, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00,
0x3E, 0x00, 0x03, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
0x06, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
0x3E, 0x00, 0x03, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
0x17, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00, 0xC4, 0x00, 0x05, 0x00,
0x17, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00,
0x3D, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00,
0xC7, 0x00, 0x05, 0x00, 0x17, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00,
0x6E, 0x00, 0x00, 0x00, 0xC5, 0x00, 0x05, 0x00, 0x17, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
0x6C, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x52, 0x00, 0x00, 0x00,
0x71, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00,
0x3E, 0x00, 0x03, 0x00, 0x71, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
0x44, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x44, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
0x06, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
0x3E, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
0x41, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x43, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00,
0x38, 0x00, 0x01, 0x00
};
public static readonly byte[] ConvertIndexBufferShaderSource = new byte[]
{
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x91, 0x00, 0x00, 0x00,

View File

@ -125,6 +125,24 @@ namespace Ryujinx.Graphics.Vulkan
if (result != null)
{
if (result.Waitable == null)
{
return;
}
long beforeTicks = Stopwatch.GetTimestamp();
if (result.NeedsFlush(FlushId))
{
_gd.InterruptAction(() =>
{
if (result.NeedsFlush(FlushId))
{
_gd.FlushAllCommands();
}
});
}
lock (result)
{
if (result.Waitable == null)
@ -132,19 +150,6 @@ namespace Ryujinx.Graphics.Vulkan
return;
}
long beforeTicks = Stopwatch.GetTimestamp();
if (result.NeedsFlush(FlushId))
{
_gd.InterruptAction(() =>
{
if (result.NeedsFlush(FlushId))
{
_gd.FlushAllCommands();
}
});
}
bool signaled = result.Signalled || result.Waitable.WaitForFences(_gd.Api, _device, 1000000000);
if (!signaled)

View File

@ -67,6 +67,11 @@ namespace Ryujinx.Graphics.Vulkan
return GetData();
}
public void CopyTo(BufferRange range, int layer, int level, int stride)
{
throw new NotImplementedException();
}
public void Release()
{
if (_gd.Textures.Remove(this))

View File

@ -563,6 +563,34 @@ namespace Ryujinx.Graphics.Vulkan
}
}
public void CopyTo(BufferRange range, int layer, int level, int stride)
{
_gd.PipelineInternal.EndRenderPass();
var cbs = _gd.PipelineInternal.CurrentCommandBuffer;
int outSize = Info.GetMipSize(level);
int hostSize = GetBufferDataLength(outSize);
var image = GetImage().Get(cbs).Value;
int offset = range.Offset;
Auto<DisposableBuffer> autoBuffer = _gd.BufferManager.GetBuffer(cbs.CommandBuffer, range.Handle, true);
VkBuffer buffer = autoBuffer.Get(cbs, range.Offset, outSize).Value;
if (PrepareOutputBuffer(cbs, hostSize, buffer, out VkBuffer copyToBuffer, out BufferHolder tempCopyHolder))
{
offset = 0;
}
CopyFromOrToBuffer(cbs.CommandBuffer, copyToBuffer, image, hostSize, true, layer, level, 1, 1, singleSlice: true, offset, stride);
if (tempCopyHolder != null)
{
CopyDataToOutputBuffer(cbs, tempCopyHolder, autoBuffer, hostSize, range.Offset);
tempCopyHolder.Dispose();
}
}
private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer)
{
int size = 0;
@ -693,6 +721,30 @@ namespace Ryujinx.Graphics.Vulkan
return storage;
}
private bool PrepareOutputBuffer(CommandBufferScoped cbs, int hostSize, VkBuffer target, out VkBuffer copyTarget, out BufferHolder copyTargetHolder)
{
if (NeedsD24S8Conversion())
{
copyTargetHolder = _gd.BufferManager.Create(_gd, hostSize);
copyTarget = copyTargetHolder.GetBuffer().Get(cbs, 0, hostSize).Value;
return true;
}
copyTarget = target;
copyTargetHolder = null;
return false;
}
private void CopyDataToOutputBuffer(CommandBufferScoped cbs, BufferHolder hostData, Auto<DisposableBuffer> copyTarget, int hostSize, int dstOffset)
{
if (NeedsD24S8Conversion())
{
_gd.HelperShader.ConvertD32S8ToD24S8(_gd, cbs, hostData, copyTarget, hostSize / (2 * sizeof(int)), dstOffset);
}
}
private bool NeedsD24S8Conversion()
{
return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
@ -708,7 +760,9 @@ namespace Ryujinx.Graphics.Vulkan
int dstLevel,
int dstLayers,
int dstLevels,
bool singleSlice)
bool singleSlice,
int offset = 0,
int stride = 0)
{
bool is3D = Info.Target == Target.Texture3D;
int width = Math.Max(1, Info.Width >> dstLevel);
@ -718,8 +772,6 @@ namespace Ryujinx.Graphics.Vulkan
int layers = dstLayers;
int levels = dstLevels;
int offset = 0;
for (int level = 0; level < levels; level++)
{
int mipSize = GetBufferDataLength(Info.GetMipSize2D(dstLevel + level) * dstLayers);
@ -731,7 +783,7 @@ namespace Ryujinx.Graphics.Vulkan
break;
}
int rowLength = (Info.GetMipStride(dstLevel + level) / Info.BytesPerPixel) * Info.BlockWidth;
int rowLength = ((stride == 0 ? Info.GetMipStride(dstLevel + level) : stride) / Info.BytesPerPixel) * Info.BlockWidth;
var aspectFlags = Info.Format.ConvertAspectFlags();

View File

@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
ExtTransformFeedback.ExtensionName,
KhrDrawIndirectCount.ExtensionName,
KhrPushDescriptor.ExtensionName,
ExtExternalMemoryHost.ExtensionName,
"VK_EXT_blend_operation_advanced",
"VK_EXT_custom_border_color",
"VK_EXT_descriptor_indexing", // Enabling this works around an issue with disposed buffer bindings on RADV.

View File

@ -44,6 +44,7 @@ namespace Ryujinx.Graphics.Vulkan
internal object QueueLock { get; private set; }
internal MemoryAllocator MemoryAllocator { get; private set; }
internal HostMemoryAllocator HostMemoryAllocator { get; private set; }
internal CommandBufferPool CommandBufferPool { get; private set; }
internal DescriptorSetManager DescriptorSetManager { get; private set; }
internal PipelineLayoutCache PipelineLayoutCache { get; private set; }
@ -307,6 +308,7 @@ namespace Ryujinx.Graphics.Vulkan
_physicalDevice.PhysicalDeviceFeatures.PipelineStatisticsQuery,
_physicalDevice.PhysicalDeviceFeatures.GeometryShader,
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
propertiesSubgroupSizeControl.MinSubgroupSize,
propertiesSubgroupSizeControl.MaxSubgroupSize,
propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
@ -319,6 +321,9 @@ namespace Ryujinx.Graphics.Vulkan
MemoryAllocator = new MemoryAllocator(Api, _physicalDevice, _device);
Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi);
HostMemoryAllocator = new HostMemoryAllocator(MemoryAllocator, Api, hostMemoryApi, _device);
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
DescriptorSetManager = new DescriptorSetManager(_device);
@ -375,11 +380,21 @@ namespace Ryujinx.Graphics.Vulkan
_initialized = true;
}
public BufferHandle CreateBuffer(int size, BufferAccess access)
{
return BufferManager.CreateWithHandle(this, size, access.Convert());
}
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
{
return BufferManager.CreateWithHandle(this, size, BufferAllocationType.Auto, storageHint);
}
public BufferHandle CreateBuffer(nint pointer, int size)
{
return BufferManager.CreateHostImported(this, pointer, size);
}
public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)
{
bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute;
@ -816,5 +831,11 @@ namespace Ryujinx.Graphics.Vulkan
// Last step destroy the instance
_instance.Dispose();
}
public bool PrepareHostMapping(nint address, ulong size)
{
return Capabilities.SupportsHostImportedMemory &&
HostMemoryAllocator.TryImport(BufferManager.HostImportedBufferMemoryRequirements, BufferManager.DefaultBufferMemoryFlags, address, size);
}
}
}

View File

@ -200,9 +200,10 @@ namespace Ryujinx.HLE.HOS
LibHacHorizonManager = device.Configuration.LibHacHorizonManager;
// We hardcode a clock source id to avoid it changing between each start.
// TODO: use set:sys (and get external clock source id from settings)
// TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate.
UInt128 clockSourceId = UInt128Utils.CreateRandom();
UInt128 clockSourceId = new UInt128(0x36a0328702ce8bc1, 0x1608eaba02333284);
IRtcManager.GetExternalRtcValue(out ulong rtcValue);
// We assume the rtc is system time.
@ -222,22 +223,22 @@ namespace Ryujinx.HLE.HOS
internalOffset = internalOffset.AddSeconds(-3600L);
}
internalOffset = new TimeSpanType(-internalOffset.NanoSeconds);
systemTime = new TimeSpanType(systemTime.NanoSeconds + internalOffset.NanoSeconds);
// First init the standard steady clock
TimeServiceManager.Instance.SetupStandardSteadyClock(TickSource, clockSourceId, systemTime, internalOffset, TimeSpanType.Zero, false);
TimeServiceManager.Instance.SetupStandardSteadyClock(TickSource, clockSourceId, TimeSpanType.Zero, TimeSpanType.Zero, TimeSpanType.Zero, false);
TimeServiceManager.Instance.SetupStandardLocalSystemClock(TickSource, new SystemClockContext(), systemTime.ToSeconds());
TimeServiceManager.Instance.StandardLocalSystemClock.GetClockContext(TickSource, out SystemClockContext localSytemClockContext);
if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes))
{
TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000);
// The network system clock needs a valid system clock, as such we setup this system clock using the local system clock.
TimeServiceManager.Instance.StandardLocalSystemClock.GetClockContext(TickSource, out SystemClockContext localSytemClockContext);
TimeServiceManager.Instance.SetupStandardNetworkSystemClock(localSytemClockContext, standardNetworkClockSufficientAccuracy);
}
TimeServiceManager.Instance.SetupStandardUserSystemClock(TickSource, false, SteadyClockTimePoint.GetRandom());
TimeServiceManager.Instance.SetupStandardUserSystemClock(TickSource, true, localSytemClockContext.SteadyTimePoint);
// FIXME: TimeZone should be init here but it's actually done in ContentManager

View File

@ -89,7 +89,7 @@ namespace Ryujinx.HLE.HOS
}
// Title independent mods
public class PatchCache
private class PatchCache
{
public List<Mod<DirectoryInfo>> NsoPatches { get; }
public List<Mod<DirectoryInfo>> NroPatches { get; }
@ -107,14 +107,14 @@ namespace Ryujinx.HLE.HOS
}
}
public Dictionary<ulong, ModCache> AppMods; // key is TitleId
public PatchCache Patches;
private readonly Dictionary<ulong, ModCache> _appMods; // key is TitleId
private PatchCache _patches;
private static readonly EnumerationOptions _dirEnumOptions;
private static readonly EnumerationOptions DirEnumOptions;
static ModLoader()
{
_dirEnumOptions = new EnumerationOptions
DirEnumOptions = new EnumerationOptions
{
MatchCasing = MatchCasing.CaseInsensitive,
MatchType = MatchType.Simple,
@ -125,37 +125,73 @@ namespace Ryujinx.HLE.HOS
public ModLoader()
{
AppMods = new Dictionary<ulong, ModCache>();
Patches = new PatchCache();
_appMods = new Dictionary<ulong, ModCache>();
_patches = new PatchCache();
}
public void Clear()
private void Clear()
{
AppMods.Clear();
Patches = new PatchCache();
_appMods.Clear();
_patches = new PatchCache();
}
private static bool StrEquals(string s1, string s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase);
public string GetModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetModsPath());
public string GetSdModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetSdModsPath());
public static string GetModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetModsPath());
public static string GetSdModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetSdModsPath());
private string EnsureBaseDirStructure(string modsBasePath)
private static string EnsureBaseDirStructure(string modsBasePath)
{
var modsDir = new DirectoryInfo(modsBasePath);
modsDir.CreateSubdirectory(AmsContentsDir);
modsDir.CreateSubdirectory(AmsNsoPatchDir);
modsDir.CreateSubdirectory(AmsNroPatchDir);
// modsDir.CreateSubdirectory(AmsKipPatchDir); // uncomment when KIPs are supported
// TODO: uncomment when KIPs are supported
// modsDir.CreateSubdirectory(AmsKipPatchDir);
return modsDir.FullName;
}
private static DirectoryInfo FindTitleDir(DirectoryInfo contentsDir, string titleId)
=> contentsDir.EnumerateDirectories($"{titleId}*", _dirEnumOptions).FirstOrDefault();
=> contentsDir.EnumerateDirectories($"{titleId}*", DirEnumOptions).FirstOrDefault();
public string GetTitleDir(string modsBasePath, string titleId)
private static void AddModsFromDirectory(ModCache mods, DirectoryInfo dir, string titleId)
{
System.Text.StringBuilder types = new();
foreach (var modDir in dir.EnumerateDirectories())
{
types.Clear();
Mod<DirectoryInfo> mod = new("", null);
if (StrEquals(RomfsDir, modDir.Name))
{
mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleId} RomFs>", modDir));
types.Append('R');
}
else if (StrEquals(ExefsDir, modDir.Name))
{
mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleId} ExeFs>", modDir));
types.Append('E');
}
else if (StrEquals(CheatDir, modDir.Name))
{
types.Append('C', QueryCheatsDir(mods, modDir));
}
else
{
AddModsFromDirectory(mods, modDir, titleId);
}
if (types.Length > 0)
{
Logger.Info?.Print(LogClass.ModLoader, $"Found mod '{mod.Name}' [{types}]");
}
}
}
public static string GetTitleDir(string modsBasePath, string titleId)
{
var contentsDir = new DirectoryInfo(Path.Combine(modsBasePath, AmsContentsDir));
var titleModsPath = FindTitleDir(contentsDir, titleId);
@ -170,17 +206,32 @@ namespace Ryujinx.HLE.HOS
}
// Static Query Methods
public static void QueryPatchDirs(PatchCache cache, DirectoryInfo patchDir)
private static void QueryPatchDirs(PatchCache cache, DirectoryInfo patchDir)
{
if (cache.Initialized || !patchDir.Exists) return;
if (cache.Initialized || !patchDir.Exists)
{
return;
}
var patches = cache.KipPatches;
string type = null;
List<Mod<DirectoryInfo>> patches;
string type;
if (StrEquals(AmsNsoPatchDir, patchDir.Name)) { patches = cache.NsoPatches; type = "NSO"; }
else if (StrEquals(AmsNroPatchDir, patchDir.Name)) { patches = cache.NroPatches; type = "NRO"; }
else if (StrEquals(AmsKipPatchDir, patchDir.Name)) { patches = cache.KipPatches; type = "KIP"; }
else return;
if (StrEquals(AmsNsoPatchDir, patchDir.Name))
{
patches = cache.NsoPatches; type = "NSO";
}
else if (StrEquals(AmsNroPatchDir, patchDir.Name))
{
patches = cache.NroPatches; type = "NRO";
}
else if (StrEquals(AmsKipPatchDir, patchDir.Name))
{
patches = cache.KipPatches; type = "KIP";
}
else
{
return;
}
foreach (var modDir in patchDir.EnumerateDirectories())
{
@ -189,9 +240,12 @@ namespace Ryujinx.HLE.HOS
}
}
public static void QueryTitleDir(ModCache mods, DirectoryInfo titleDir)
private static void QueryTitleDir(ModCache mods, DirectoryInfo titleDir)
{
if (!titleDir.Exists) return;
if (!titleDir.Exists)
{
return;
}
var fsFile = new FileInfo(Path.Combine(titleDir.FullName, RomfsContainer));
if (fsFile.Exists)
@ -205,64 +259,15 @@ namespace Ryujinx.HLE.HOS
mods.ExefsContainers.Add(new Mod<FileInfo>($"<{titleDir.Name} ExeFs>", fsFile));
}
System.Text.StringBuilder types = new System.Text.StringBuilder(5);
foreach (var modDir in titleDir.EnumerateDirectories())
{
types.Clear();
Mod<DirectoryInfo> mod = new Mod<DirectoryInfo>("", null);
if (StrEquals(RomfsDir, modDir.Name))
{
mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleDir.Name} RomFs>", modDir));
types.Append('R');
}
else if (StrEquals(ExefsDir, modDir.Name))
{
mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleDir.Name} ExeFs>", modDir));
types.Append('E');
}
else if (StrEquals(CheatDir, modDir.Name))
{
for (int i = 0; i < QueryCheatsDir(mods, modDir); i++)
{
types.Append('C');
}
}
else
{
var romfs = new DirectoryInfo(Path.Combine(modDir.FullName, RomfsDir));
var exefs = new DirectoryInfo(Path.Combine(modDir.FullName, ExefsDir));
var cheat = new DirectoryInfo(Path.Combine(modDir.FullName, CheatDir));
if (romfs.Exists)
{
mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>(modDir.Name, romfs));
types.Append('R');
}
if (exefs.Exists)
{
mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>(modDir.Name, exefs));
types.Append('E');
}
if (cheat.Exists)
{
for (int i = 0; i < QueryCheatsDir(mods, cheat); i++)
{
types.Append('C');
}
}
}
if (types.Length > 0) Logger.Info?.Print(LogClass.ModLoader, $"Found mod '{mod.Name}' [{types}]");
}
AddModsFromDirectory(mods, titleDir, titleDir.Name);
}
public static void QueryContentsDir(ModCache mods, DirectoryInfo contentsDir, ulong titleId)
{
if (!contentsDir.Exists) return;
if (!contentsDir.Exists)
{
return;
}
Logger.Info?.Print(LogClass.ModLoader, $"Searching mods for {((titleId & 0x1000) != 0 ? "DLC" : "Title")} {titleId:X16}");
@ -302,9 +307,16 @@ namespace Ryujinx.HLE.HOS
continue;
}
int oldCheatsCount = mods.Cheats.Count;
// A cheat file can contain several cheats for the same executable, so the file must be parsed in
// order to properly enumerate them.
mods.Cheats.AddRange(GetCheatsInFile(file));
if (mods.Cheats.Count - oldCheatsCount > 0)
{
numMods++;
}
}
return numMods;
@ -313,57 +325,54 @@ namespace Ryujinx.HLE.HOS
private static IEnumerable<Cheat> GetCheatsInFile(FileInfo cheatFile)
{
string cheatName = DefaultCheatName;
List<string> instructions = new List<string>();
List<Cheat> cheats = new List<Cheat>();
List<string> instructions = new();
List<Cheat> cheats = new();
using (StreamReader cheatData = cheatFile.OpenText())
using StreamReader cheatData = cheatFile.OpenText();
while (cheatData.ReadLine() is { } line)
{
string line;
while ((line = cheatData.ReadLine()) != null)
line = line.Trim();
if (line.StartsWith('['))
{
line = line.Trim();
if (line.StartsWith('['))
// This line starts a new cheat section.
if (!line.EndsWith(']') || line.Length < 3)
{
// This line starts a new cheat section.
if (!line.EndsWith(']') || line.Length < 3)
{
// Skip the entire file if there's any error while parsing the cheat file.
// Skip the entire file if there's any error while parsing the cheat file.
Logger.Warning?.Print(LogClass.ModLoader, $"Ignoring cheat '{cheatFile.FullName}' because it is malformed");
Logger.Warning?.Print(LogClass.ModLoader, $"Ignoring cheat '{cheatFile.FullName}' because it is malformed");
return new List<Cheat>();
}
// Add the previous section to the list.
if (instructions.Count != 0)
{
cheats.Add(new Cheat($"<{cheatName} Cheat>", cheatFile, instructions));
}
// Start a new cheat section.
cheatName = line.Substring(1, line.Length - 2);
instructions = new List<string>();
return Array.Empty<Cheat>();
}
else if (line.Length > 0)
// Add the previous section to the list.
if (instructions.Count > 0)
{
// The line contains an instruction.
instructions.Add(line);
cheats.Add(new Cheat($"<{cheatName} Cheat>", cheatFile, instructions));
}
// Start a new cheat section.
cheatName = line.Substring(1, line.Length - 2);
instructions = new List<string>();
}
// Add the last section being processed.
if (instructions.Count != 0)
else if (line.Length > 0)
{
cheats.Add(new Cheat($"<{cheatName} Cheat>", cheatFile, instructions));
// The line contains an instruction.
instructions.Add(line);
}
}
// Add the last section being processed.
if (instructions.Count > 0)
{
cheats.Add(new Cheat($"<{cheatName} Cheat>", cheatFile, instructions));
}
return cheats;
}
// Assumes searchDirPaths don't overlap
public static void CollectMods(Dictionary<ulong, ModCache> modCaches, PatchCache patches, params string[] searchDirPaths)
private static void CollectMods(Dictionary<ulong, ModCache> modCaches, PatchCache patches, params string[] searchDirPaths)
{
static bool IsPatchesDir(string name) => StrEquals(AmsNsoPatchDir, name) ||
StrEquals(AmsNroPatchDir, name) ||
@ -375,7 +384,7 @@ namespace Ryujinx.HLE.HOS
{
if (IsContentsDir(searchDir.Name))
{
foreach (var (titleId, cache) in modCaches)
foreach ((ulong titleId, ModCache cache) in modCaches)
{
QueryContentsDir(cache, searchDir, titleId);
}
@ -419,15 +428,15 @@ namespace Ryujinx.HLE.HOS
foreach (ulong titleId in titles)
{
AppMods[titleId] = new ModCache();
_appMods[titleId] = new ModCache();
}
CollectMods(AppMods, Patches, searchDirPaths);
CollectMods(_appMods, _patches, searchDirPaths);
}
internal IStorage ApplyRomFsMods(ulong titleId, IStorage baseStorage)
{
if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.RomfsDirs.Count + mods.RomfsContainers.Count == 0)
if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.RomfsDirs.Count + mods.RomfsContainers.Count == 0)
{
return baseStorage;
}
@ -487,7 +496,7 @@ namespace Ryujinx.HLE.HOS
return newStorage;
}
private static void AddFiles(IFileSystem fs, string modName, HashSet<string> fileSet, RomFsBuilder builder)
private static void AddFiles(IFileSystem fs, string modName, ISet<string> fileSet, RomFsBuilder builder)
{
foreach (var entry in fs.EnumerateEntries()
.Where(f => f.Type == DirectoryEntryType.File)
@ -509,7 +518,7 @@ namespace Ryujinx.HLE.HOS
internal bool ReplaceExefsPartition(ulong titleId, ref IFileSystem exefs)
{
if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsContainers.Count == 0)
if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsContainers.Count == 0)
{
return false;
}
@ -537,13 +546,13 @@ namespace Ryujinx.HLE.HOS
internal ModLoadResult ApplyExefsMods(ulong titleId, NsoExecutable[] nsos)
{
ModLoadResult modLoadResult = new ModLoadResult
ModLoadResult modLoadResult = new()
{
Stubs = new BitVector32(),
Replaces = new BitVector32()
};
if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsDirs.Count == 0)
if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsDirs.Count == 0)
{
return modLoadResult;
}
@ -561,7 +570,7 @@ namespace Ryujinx.HLE.HOS
{
var nsoName = ProcessConst.ExeFsPrefixes[i];
FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName));
FileInfo nsoFile = new(Path.Combine(mod.Path.FullName, nsoName));
if (nsoFile.Exists)
{
if (modLoadResult.Replaces[1 << i])
@ -580,7 +589,7 @@ namespace Ryujinx.HLE.HOS
modLoadResult.Stubs[1 << i] |= File.Exists(Path.Combine(mod.Path.FullName, nsoName + StubExtension));
}
FileInfo npdmFile = new FileInfo(Path.Combine(mod.Path.FullName, "main.npdm"));
FileInfo npdmFile = new(Path.Combine(mod.Path.FullName, "main.npdm"));
if (npdmFile.Exists)
{
if (modLoadResult.Npdm != null)
@ -611,7 +620,7 @@ namespace Ryujinx.HLE.HOS
internal void ApplyNroPatches(NroExecutable nro)
{
var nroPatches = Patches.NroPatches;
var nroPatches = _patches.NroPatches;
if (nroPatches.Count == 0) return;
@ -622,9 +631,9 @@ namespace Ryujinx.HLE.HOS
internal bool ApplyNsoPatches(ulong titleId, params IExecutable[] programs)
{
IEnumerable<Mod<DirectoryInfo>> nsoMods = Patches.NsoPatches;
IEnumerable<Mod<DirectoryInfo>> nsoMods = _patches.NsoPatches;
if (AppMods.TryGetValue(titleId, out ModCache mods))
if (_appMods.TryGetValue(titleId, out ModCache mods))
{
nsoMods = nsoMods.Concat(mods.ExefsDirs);
}
@ -636,7 +645,7 @@ namespace Ryujinx.HLE.HOS
internal void LoadCheats(ulong titleId, ProcessTamperInfo tamperInfo, TamperMachine tamperMachine)
{
if (tamperInfo == null || tamperInfo.BuildIds == null || tamperInfo.CodeAddresses == null)
if (tamperInfo?.BuildIds == null || tamperInfo.CodeAddresses == null)
{
Logger.Error?.Print(LogClass.ModLoader, "Unable to install cheat because the associated process is invalid");
@ -645,14 +654,14 @@ namespace Ryujinx.HLE.HOS
Logger.Info?.Print(LogClass.ModLoader, $"Build ids found for title {titleId:X16}:\n {String.Join("\n ", tamperInfo.BuildIds)}");
if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.Cheats.Count == 0)
if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.Cheats.Count == 0)
{
return;
}
var cheats = mods.Cheats;
var processExes = tamperInfo.BuildIds.Zip(tamperInfo.CodeAddresses, (k, v) => new { k, v })
.ToDictionary(x => x.k.Substring(0, Math.Min(Cheat.CheatIdSize, x.k.Length)), x => x.v);
.ToDictionary(x => x.k[..Math.Min(Cheat.CheatIdSize, x.k.Length)], x => x.v);
foreach (var cheat in cheats)
{
@ -758,4 +767,4 @@ namespace Ryujinx.HLE.HOS
return count > 0;
}
}
}
}

View File

@ -92,6 +92,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
return ResultCode.Success;
}
[CommandCmif(23)]
// GetAppletCommonFunctions() -> object<nn::am::service::IAppletCommonFunctions>
public ResultCode GetAppletCommonFunctions(ServiceCtx context)
{
MakeObject(context, new IAppletCommonFunctions());
return ResultCode.Success;
}
[CommandCmif(1000)]
// GetDebugFunctions() -> object<nn::am::service::IDebugFunctions>
public ResultCode GetDebugFunctions(ServiceCtx context)

View File

@ -9,8 +9,10 @@ using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class ICommonStateGetter : IpcService
class ICommonStateGetter : DisposableIpcService
{
private readonly ServiceCtx _context;
private Apm.ManagerServer _apmManagerServer;
private Apm.SystemManagerServer _apmSystemManagerServer;
private Lbl.LblControllerServer _lblControllerServer;
@ -23,11 +25,18 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
private int _messageEventHandle;
private int _displayResolutionChangedEventHandle;
private KEvent _acquiredSleepLockEvent;
private int _acquiredSleepLockEventHandle;
public ICommonStateGetter(ServiceCtx context)
{
_context = context;
_apmManagerServer = new Apm.ManagerServer(context);
_apmSystemManagerServer = new Apm.SystemManagerServer(context);
_lblControllerServer = new Lbl.LblControllerServer(context);
_acquiredSleepLockEvent = new KEvent(context.Device.System.KernelContext);
}
[CommandCmif(0)]
@ -117,6 +126,34 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.Success;
}
[CommandCmif(10)]
// RequestToAcquireSleepLock()
public ResultCode RequestToAcquireSleepLock(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(13)]
// GetAcquiredSleepLockEvent() -> handle<copy>
public ResultCode GetAcquiredSleepLockEvent(ServiceCtx context)
{
if (_acquiredSleepLockEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(_acquiredSleepLockEvent.ReadableEvent, out _acquiredSleepLockEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_acquiredSleepLockEventHandle);
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(50)] // 3.0.0+
// IsVrModeEnabled() -> b8
public ResultCode IsVrModeEnabled(ServiceCtx context)
@ -281,5 +318,17 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.Success;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
if (_acquiredSleepLockEventHandle != 0)
{
_context.Process.HandleTable.CloseHandle(_acquiredSleepLockEventHandle);
_acquiredSleepLockEventHandle = 0;
}
}
}
}
}

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