Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
5c3cfb84c0 | |||
55557525b1 | |||
7e6342e44d | |||
c3555cb5d6 | |||
815819767c | |||
623604c391 | |||
617c5700ca | |||
7b62f7475e | |||
841dd56f4c | |||
a16d582a10 | |||
9ef0be477b | |||
c14ce4d2a5 | |||
171b46ef49 | |||
56fe2ff535 | |||
b1f8f868f6 | |||
d773d5152e | |||
33ba170315 | |||
638be5f296 | |||
49b37550ca | |||
a42f0bbb87 | |||
b4bb22ba06 | |||
6fdf774845 | |||
76b53e018a | |||
28dd7d80af | |||
1e06b28b22 | |||
e768a54f17 | |||
4e2bb13080 | |||
ac4f2c1e70 | |||
e40470bbe1 | |||
f460ecc182 | |||
086564c3c8 | |||
b6ac45d36d | |||
7afae8c699 | |||
7835968214 | |||
0aceb534cb | |||
a0af6e4d07 | |||
f61b7818c3 | |||
a2a97e1b11 | |||
8b2625b0be | |||
651e24fed9 |
@ -3,13 +3,13 @@
|
|||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia" Version="11.0.4" />
|
<PackageVersion Include="Avalonia" Version="11.0.5" />
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.4" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.5" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.0.4" />
|
<PackageVersion Include="Avalonia.Desktop" Version="11.0.5" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.4" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.5" />
|
||||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.4" />
|
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.5" />
|
||||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.2" />
|
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.3" />
|
||||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.2" />
|
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.3" />
|
||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
@ -18,12 +18,13 @@
|
|||||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||||
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
|
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
|
||||||
<PackageVersion Include="LibHac" Version="0.18.0" />
|
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
|
<PackageVersion Include="NetCoreServer" Version="7.0.0" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
<PackageVersion Include="OpenTK.Core" Version="4.7.7" />
|
<PackageVersion Include="OpenTK.Core" Version="4.7.7" />
|
||||||
@ -35,6 +36,7 @@
|
|||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
|
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
|
||||||
|
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||||
|
@ -141,3 +141,5 @@ See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY
|
|||||||
|
|
||||||
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
|
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
|
||||||
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
|
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
|
||||||
|
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
|
||||||
|
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
|
||||||
|
@ -682,3 +682,32 @@
|
|||||||
END OF TERMS AND CONDITIONS
|
END OF TERMS AND CONDITIONS
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
# ShellLink (MIT)
|
||||||
|
<details>
|
||||||
|
<summary>See License</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Yorick Koster, Securify B.V.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
@ -3,8 +3,8 @@ Version=1.0
|
|||||||
Name=Ryujinx
|
Name=Ryujinx
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=Ryujinx
|
Icon=Ryujinx
|
||||||
Exec=env DOTNET_EnableAlternateStackCheck=1 Ryujinx %f
|
Exec=Ryujinx.sh %f
|
||||||
Comment=A Nintendo Switch Emulator
|
Comment=Plays Nintendo Switch applications
|
||||||
GenericName=Nintendo Switch Emulator
|
GenericName=Nintendo Switch Emulator
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Game;Emulator;
|
Categories=Game;Emulator;
|
||||||
|
13
distribution/linux/shortcut-template.desktop
Normal file
13
distribution/linux/shortcut-template.desktop
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Name={0}
|
||||||
|
Type=Application
|
||||||
|
Icon={1}
|
||||||
|
Exec={2} %f
|
||||||
|
Comment=Nintendo Switch application
|
||||||
|
GenericName=Nintendo Switch Emulator
|
||||||
|
Terminal=false
|
||||||
|
Categories=Game;Emulator;
|
||||||
|
Keywords=Switch;Nintendo;Emulator;
|
||||||
|
StartupWMClass=Ryujinx
|
||||||
|
PrefersNonDefaultGPU=true
|
35
distribution/macos/shortcut-template.plist
Normal file
35
distribution/macos/shortcut-template.plist
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>{0}</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>{1}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>{2}</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<true/>
|
||||||
|
<key>CSResourcesFileMapped</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>Copyright © 2018 - 2023 Ryujinx Team and Contributors.</string>
|
||||||
|
<key>LSApplicationCategoryType</key>
|
||||||
|
<string>public.app-category.games</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>11.0</string>
|
||||||
|
<key>UIPrerenderedIcon</key>
|
||||||
|
<true/>
|
||||||
|
<key>LSEnvironment</key>
|
||||||
|
<dict>
|
||||||
|
<key>DOTNET_DefaultStackSize</key>
|
||||||
|
<string>200000</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -38,7 +38,9 @@ namespace ARMeilleure.Decoders
|
|||||||
{
|
{
|
||||||
block = new Block(blkAddress);
|
block = new Block(blkAddress);
|
||||||
|
|
||||||
if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress))
|
if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) ||
|
||||||
|
opsCount > instructionLimit ||
|
||||||
|
(visited.Count > 0 && !memory.IsMapped(blkAddress)))
|
||||||
{
|
{
|
||||||
block.Exit = true;
|
block.Exit = true;
|
||||||
block.EndAddress = blkAddress;
|
block.EndAddress = blkAddress;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace ARMeilleure.Diagnostics
|
namespace ARMeilleure.Diagnostics
|
||||||
{
|
{
|
||||||
@ -33,7 +34,6 @@ namespace ARMeilleure.Diagnostics
|
|||||||
|
|
||||||
public static string Get(ulong address)
|
public static string Get(ulong address)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (_symbols.TryGetValue(address, out string result))
|
if (_symbols.TryGetValue(address, out string result))
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
@ -48,13 +48,15 @@ namespace ARMeilleure.Diagnostics
|
|||||||
ulong diff = address - symbol.Start;
|
ulong diff = address - symbol.Start;
|
||||||
ulong rem = diff % symbol.ElementSize;
|
ulong rem = diff % symbol.ElementSize;
|
||||||
|
|
||||||
result = symbol.Name + "_" + diff / symbol.ElementSize;
|
StringBuilder resultBuilder = new();
|
||||||
|
resultBuilder.Append($"{symbol.Name}_{diff / symbol.ElementSize}");
|
||||||
|
|
||||||
if (rem != 0)
|
if (rem != 0)
|
||||||
{
|
{
|
||||||
result += "+" + rem;
|
resultBuilder.Append($"+{rem}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result = resultBuilder.ToString();
|
||||||
_symbols.TryAdd(address, result);
|
_symbols.TryAdd(address, result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -7,14 +7,14 @@ namespace ARMeilleure.Translation
|
|||||||
internal class TranslatorCache<T>
|
internal class TranslatorCache<T>
|
||||||
{
|
{
|
||||||
private readonly IntervalTree<ulong, T> _tree;
|
private readonly IntervalTree<ulong, T> _tree;
|
||||||
private readonly ReaderWriterLock _treeLock;
|
private readonly ReaderWriterLockSlim _treeLock;
|
||||||
|
|
||||||
public int Count => _tree.Count;
|
public int Count => _tree.Count;
|
||||||
|
|
||||||
public TranslatorCache()
|
public TranslatorCache()
|
||||||
{
|
{
|
||||||
_tree = new IntervalTree<ulong, T>();
|
_tree = new IntervalTree<ulong, T>();
|
||||||
_treeLock = new ReaderWriterLock();
|
_treeLock = new ReaderWriterLockSlim();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryAdd(ulong address, ulong size, T value)
|
public bool TryAdd(ulong address, ulong size, T value)
|
||||||
@ -24,70 +24,70 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
public bool AddOrUpdate(ulong address, ulong size, T value, Func<ulong, T, T> updateFactoryCallback)
|
public bool AddOrUpdate(ulong address, ulong size, T value, Func<ulong, T, T> updateFactoryCallback)
|
||||||
{
|
{
|
||||||
_treeLock.AcquireWriterLock(Timeout.Infinite);
|
_treeLock.EnterWriteLock();
|
||||||
bool result = _tree.AddOrUpdate(address, address + size, value, updateFactoryCallback);
|
bool result = _tree.AddOrUpdate(address, address + size, value, updateFactoryCallback);
|
||||||
_treeLock.ReleaseWriterLock();
|
_treeLock.ExitWriteLock();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T GetOrAdd(ulong address, ulong size, T value)
|
public T GetOrAdd(ulong address, ulong size, T value)
|
||||||
{
|
{
|
||||||
_treeLock.AcquireWriterLock(Timeout.Infinite);
|
_treeLock.EnterWriteLock();
|
||||||
value = _tree.GetOrAdd(address, address + size, value);
|
value = _tree.GetOrAdd(address, address + size, value);
|
||||||
_treeLock.ReleaseWriterLock();
|
_treeLock.ExitWriteLock();
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Remove(ulong address)
|
public bool Remove(ulong address)
|
||||||
{
|
{
|
||||||
_treeLock.AcquireWriterLock(Timeout.Infinite);
|
_treeLock.EnterWriteLock();
|
||||||
bool removed = _tree.Remove(address) != 0;
|
bool removed = _tree.Remove(address) != 0;
|
||||||
_treeLock.ReleaseWriterLock();
|
_treeLock.ExitWriteLock();
|
||||||
|
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
_treeLock.AcquireWriterLock(Timeout.Infinite);
|
_treeLock.EnterWriteLock();
|
||||||
_tree.Clear();
|
_tree.Clear();
|
||||||
_treeLock.ReleaseWriterLock();
|
_treeLock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ContainsKey(ulong address)
|
public bool ContainsKey(ulong address)
|
||||||
{
|
{
|
||||||
_treeLock.AcquireReaderLock(Timeout.Infinite);
|
_treeLock.EnterReadLock();
|
||||||
bool result = _tree.ContainsKey(address);
|
bool result = _tree.ContainsKey(address);
|
||||||
_treeLock.ReleaseReaderLock();
|
_treeLock.ExitReadLock();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetValue(ulong address, out T value)
|
public bool TryGetValue(ulong address, out T value)
|
||||||
{
|
{
|
||||||
_treeLock.AcquireReaderLock(Timeout.Infinite);
|
_treeLock.EnterReadLock();
|
||||||
bool result = _tree.TryGet(address, out value);
|
bool result = _tree.TryGet(address, out value);
|
||||||
_treeLock.ReleaseReaderLock();
|
_treeLock.ExitReadLock();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetOverlaps(ulong address, ulong size, ref ulong[] overlaps)
|
public int GetOverlaps(ulong address, ulong size, ref ulong[] overlaps)
|
||||||
{
|
{
|
||||||
_treeLock.AcquireReaderLock(Timeout.Infinite);
|
_treeLock.EnterReadLock();
|
||||||
int count = _tree.Get(address, address + size, ref overlaps);
|
int count = _tree.Get(address, address + size, ref overlaps);
|
||||||
_treeLock.ReleaseReaderLock();
|
_treeLock.ExitReadLock();
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<T> AsList()
|
public List<T> AsList()
|
||||||
{
|
{
|
||||||
_treeLock.AcquireReaderLock(Timeout.Infinite);
|
_treeLock.EnterReadLock();
|
||||||
List<T> list = _tree.AsList();
|
List<T> list = _tree.AsList();
|
||||||
_treeLock.ReleaseReaderLock();
|
_treeLock.ExitReadLock();
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,6 @@ using System.Threading.Tasks;
|
|||||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||||
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
using InputManager = Ryujinx.Input.HLE.InputManager;
|
|
||||||
using IRenderer = Ryujinx.Graphics.GAL.IRenderer;
|
|
||||||
using Key = Ryujinx.Input.Key;
|
using Key = Ryujinx.Input.Key;
|
||||||
using MouseButton = Ryujinx.Input.MouseButton;
|
using MouseButton = Ryujinx.Input.MouseButton;
|
||||||
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
|
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
|
||||||
@ -123,12 +121,14 @@ namespace Ryujinx.Ava
|
|||||||
public int Width { get; private set; }
|
public int Width { get; private set; }
|
||||||
public int Height { get; private set; }
|
public int Height { get; private set; }
|
||||||
public string ApplicationPath { get; private set; }
|
public string ApplicationPath { get; private set; }
|
||||||
|
public ulong ApplicationId { get; private set; }
|
||||||
public bool ScreenshotRequested { get; set; }
|
public bool ScreenshotRequested { get; set; }
|
||||||
|
|
||||||
public AppHost(
|
public AppHost(
|
||||||
RendererHost renderer,
|
RendererHost renderer,
|
||||||
InputManager inputManager,
|
InputManager inputManager,
|
||||||
string applicationPath,
|
string applicationPath,
|
||||||
|
ulong applicationId,
|
||||||
VirtualFileSystem virtualFileSystem,
|
VirtualFileSystem virtualFileSystem,
|
||||||
ContentManager contentManager,
|
ContentManager contentManager,
|
||||||
AccountManager accountManager,
|
AccountManager accountManager,
|
||||||
@ -152,6 +152,7 @@ namespace Ryujinx.Ava
|
|||||||
NpadManager = _inputManager.CreateNpadManager();
|
NpadManager = _inputManager.CreateNpadManager();
|
||||||
TouchScreenManager = _inputManager.CreateTouchScreenManager();
|
TouchScreenManager = _inputManager.CreateTouchScreenManager();
|
||||||
ApplicationPath = applicationPath;
|
ApplicationPath = applicationPath;
|
||||||
|
ApplicationId = applicationId;
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
ContentManager = contentManager;
|
ContentManager = contentManager;
|
||||||
|
|
||||||
@ -190,6 +191,7 @@ namespace Ryujinx.Ava
|
|||||||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
|
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
|
||||||
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough;
|
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough;
|
||||||
|
|
||||||
|
ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState;
|
||||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
|
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
|
||||||
ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState;
|
ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState;
|
||||||
|
|
||||||
@ -408,6 +410,11 @@ namespace Ryujinx.Ava
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateEnableInternetAccessState(object sender, ReactiveEventArgs<bool> e)
|
||||||
|
{
|
||||||
|
Device.Configuration.EnableInternetAccess = e.NewValue;
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e)
|
private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e)
|
||||||
{
|
{
|
||||||
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
|
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
|
||||||
@ -635,7 +642,7 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
||||||
|
|
||||||
if (!Device.LoadXci(ApplicationPath))
|
if (!Device.LoadXci(ApplicationPath, ApplicationId))
|
||||||
{
|
{
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
@ -662,7 +669,7 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
||||||
|
|
||||||
if (!Device.LoadNsp(ApplicationPath))
|
if (!Device.LoadNsp(ApplicationPath, ApplicationId))
|
||||||
{
|
{
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
@ -710,7 +717,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
|
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
|
||||||
{
|
{
|
||||||
appMetadata.LastPlayed = DateTime.UtcNow;
|
appMetadata.UpdatePreGame();
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"MenuBarFileOpenEmuFolder": "Open Ryujinx Folder",
|
"MenuBarFileOpenEmuFolder": "Open Ryujinx Folder",
|
||||||
"MenuBarFileOpenLogsFolder": "Open Logs Folder",
|
"MenuBarFileOpenLogsFolder": "Open Logs Folder",
|
||||||
"MenuBarFileExit": "_Exit",
|
"MenuBarFileExit": "_Exit",
|
||||||
"MenuBarOptions": "Options",
|
"MenuBarOptions": "_Options",
|
||||||
"MenuBarOptionsToggleFullscreen": "Toggle Fullscreen",
|
"MenuBarOptionsToggleFullscreen": "Toggle Fullscreen",
|
||||||
"MenuBarOptionsStartGamesInFullscreen": "Start Games in Fullscreen Mode",
|
"MenuBarOptionsStartGamesInFullscreen": "Start Games in Fullscreen Mode",
|
||||||
"MenuBarOptionsStopEmulation": "Stop Emulation",
|
"MenuBarOptionsStopEmulation": "Stop Emulation",
|
||||||
@ -30,7 +30,7 @@
|
|||||||
"MenuBarToolsManageFileTypes": "Manage file types",
|
"MenuBarToolsManageFileTypes": "Manage file types",
|
||||||
"MenuBarToolsInstallFileTypes": "Install file types",
|
"MenuBarToolsInstallFileTypes": "Install file types",
|
||||||
"MenuBarToolsUninstallFileTypes": "Uninstall file types",
|
"MenuBarToolsUninstallFileTypes": "Uninstall file types",
|
||||||
"MenuBarHelp": "Help",
|
"MenuBarHelp": "_Help",
|
||||||
"MenuBarHelpCheckForUpdates": "Check for Updates",
|
"MenuBarHelpCheckForUpdates": "Check for Updates",
|
||||||
"MenuBarHelpAbout": "About",
|
"MenuBarHelpAbout": "About",
|
||||||
"MenuSearch": "Search...",
|
"MenuSearch": "Search...",
|
||||||
@ -72,6 +72,8 @@
|
|||||||
"GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)",
|
"GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)",
|
||||||
"GameListContextMenuExtractDataLogo": "Logo",
|
"GameListContextMenuExtractDataLogo": "Logo",
|
||||||
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
|
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
|
||||||
|
"GameListContextMenuCreateShortcut": "Create Application Shortcut",
|
||||||
|
"GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application",
|
||||||
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
|
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
|
||||||
"StatusBarSystemVersion": "System Version: {0}",
|
"StatusBarSystemVersion": "System Version: {0}",
|
||||||
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
|
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
|
||||||
@ -537,6 +539,8 @@
|
|||||||
"OpenSetupGuideMessage": "Open the Setup Guide",
|
"OpenSetupGuideMessage": "Open the Setup Guide",
|
||||||
"NoUpdate": "No Update",
|
"NoUpdate": "No Update",
|
||||||
"TitleUpdateVersionLabel": "Version {0}",
|
"TitleUpdateVersionLabel": "Version {0}",
|
||||||
|
"TitleBundledUpdateVersionLabel": "Bundled: Version {0}",
|
||||||
|
"TitleBundledDlcLabel": "Bundled:",
|
||||||
"RyujinxInfo": "Ryujinx - Info",
|
"RyujinxInfo": "Ryujinx - Info",
|
||||||
"RyujinxConfirm": "Ryujinx - Confirmation",
|
"RyujinxConfirm": "Ryujinx - Confirmation",
|
||||||
"FileDialogAllTypes": "All types",
|
"FileDialogAllTypes": "All types",
|
||||||
@ -648,7 +652,7 @@
|
|||||||
"UserEditorTitle": "Edit User",
|
"UserEditorTitle": "Edit User",
|
||||||
"UserEditorTitleCreate": "Create User",
|
"UserEditorTitleCreate": "Create User",
|
||||||
"SettingsTabNetworkInterface": "Network Interface:",
|
"SettingsTabNetworkInterface": "Network Interface:",
|
||||||
"NetworkInterfaceTooltip": "The network interface used for LAN features",
|
"NetworkInterfaceTooltip": "The network interface used for LAN/LDN features",
|
||||||
"NetworkInterfaceDefault": "Default",
|
"NetworkInterfaceDefault": "Default",
|
||||||
"PackagingShaders": "Packaging Shaders",
|
"PackagingShaders": "Packaging Shaders",
|
||||||
"AboutChangelogButton": "View Changelog on GitHub",
|
"AboutChangelogButton": "View Changelog on GitHub",
|
||||||
|
@ -18,7 +18,8 @@ using Ryujinx.Ava.UI.Helpers;
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
@ -173,7 +174,7 @@ namespace Ryujinx.Ava.Common
|
|||||||
string extension = Path.GetExtension(titleFilePath).ToLower();
|
string extension = Path.GetExtension(titleFilePath).ToLower();
|
||||||
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
|
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
|
||||||
{
|
{
|
||||||
PartitionFileSystem pfs;
|
IFileSystem pfs;
|
||||||
|
|
||||||
if (extension == ".xci")
|
if (extension == ".xci")
|
||||||
{
|
{
|
||||||
@ -181,7 +182,9 @@ namespace Ryujinx.Ava.Common
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pfs = new PartitionFileSystem(file.AsStorage());
|
var pfsTemp = new PartitionFileSystem();
|
||||||
|
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||||
|
pfs = pfsTemp;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||||
@ -224,7 +227,11 @@ namespace Ryujinx.Ava.Common
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
|
(Nca updatePatchNca, _) = mainNca.GetUpdateData(_virtualFileSystem, checkLevel, programIndex, out _);
|
||||||
if (updatePatchNca != null)
|
if (updatePatchNca != null)
|
||||||
{
|
{
|
||||||
patchNca = updatePatchNca;
|
patchNca = updatePatchNca;
|
||||||
|
@ -6,13 +6,13 @@ using Ryujinx.Common;
|
|||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.GraphicsDriver;
|
using Ryujinx.Common.GraphicsDriver;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.SystemInfo;
|
|
||||||
using Ryujinx.Common.SystemInterop;
|
using Ryujinx.Common.SystemInterop;
|
||||||
using Ryujinx.Modules;
|
using Ryujinx.Modules;
|
||||||
using Ryujinx.SDL2.Common;
|
using Ryujinx.SDL2.Common;
|
||||||
using Ryujinx.Ui.Common;
|
using Ryujinx.Ui.Common;
|
||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
|
using Ryujinx.Ui.Common.SystemInfo;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
@ -12,6 +12,11 @@
|
|||||||
Click="ToggleFavorite_Click"
|
Click="ToggleFavorite_Click"
|
||||||
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
||||||
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
|
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
|
||||||
|
<MenuItem
|
||||||
|
Click="CreateApplicationShortcut_Click"
|
||||||
|
Header="{locale:Locale GameListContextMenuCreateShortcut}"
|
||||||
|
IsEnabled="{Binding CreateShortcutEnabled}"
|
||||||
|
ToolTip.Tip="{locale:Locale GameListContextMenuCreateShortcutToolTip}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Click="OpenUserSaveDirectory_Click"
|
Click="OpenUserSaveDirectory_Click"
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Threading;
|
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Ava.Common;
|
using Ryujinx.Ava.Common;
|
||||||
@ -15,7 +14,6 @@ using Ryujinx.Ui.App.Common;
|
|||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
@ -41,7 +39,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
{
|
{
|
||||||
viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
|
viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
|
||||||
|
|
||||||
ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.TitleId, appMetadata =>
|
ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata =>
|
||||||
{
|
{
|
||||||
appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
|
appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
|
||||||
});
|
});
|
||||||
@ -76,19 +74,9 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
{
|
{
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
if (!ulong.TryParse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
|
var saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default);
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name);
|
||||||
}
|
|
||||||
|
|
||||||
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveDataType, userId, saveDataId: default, index: default);
|
|
||||||
|
|
||||||
ApplicationHelper.OpenSaveDir(in saveDataFilter, titleIdNumber, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.TitleName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +86,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName);
|
await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, viewModel.SelectedApplication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +96,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName);
|
await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, viewModel.SelectedApplication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,8 +108,8 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
{
|
{
|
||||||
await new CheatWindow(
|
await new CheatWindow(
|
||||||
viewModel.VirtualFileSystem,
|
viewModel.VirtualFileSystem,
|
||||||
viewModel.SelectedApplication.TitleId,
|
viewModel.SelectedApplication.IdString,
|
||||||
viewModel.SelectedApplication.TitleName,
|
viewModel.SelectedApplication.Name,
|
||||||
viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window);
|
viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,7 +121,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
string modsBasePath = ModLoader.GetModsBasePath();
|
string modsBasePath = ModLoader.GetModsBasePath();
|
||||||
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, viewModel.SelectedApplication.TitleId);
|
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, viewModel.SelectedApplication.IdString);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
@ -146,7 +134,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
||||||
string titleModsPath = ModLoader.GetTitleDir(sdModsBasePath, viewModel.SelectedApplication.TitleId);
|
string titleModsPath = ModLoader.GetTitleDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
@ -160,15 +148,15 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
{
|
{
|
||||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||||
LocaleManager.Instance[LocaleKeys.DialogWarning],
|
LocaleManager.Instance[LocaleKeys.DialogWarning],
|
||||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName),
|
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name),
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
||||||
|
|
||||||
if (result == UserResult.Yes)
|
if (result == UserResult.Yes)
|
||||||
{
|
{
|
||||||
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "0"));
|
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
|
||||||
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu", "1"));
|
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
|
||||||
|
|
||||||
List<FileInfo> cacheFiles = new();
|
List<FileInfo> cacheFiles = new();
|
||||||
|
|
||||||
@ -208,14 +196,14 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
{
|
{
|
||||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||||
LocaleManager.Instance[LocaleKeys.DialogWarning],
|
LocaleManager.Instance[LocaleKeys.DialogWarning],
|
||||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName),
|
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name),
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
||||||
|
|
||||||
if (result == UserResult.Yes)
|
if (result == UserResult.Yes)
|
||||||
{
|
{
|
||||||
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "shader"));
|
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader"));
|
||||||
|
|
||||||
List<DirectoryInfo> oldCacheDirectories = new();
|
List<DirectoryInfo> oldCacheDirectories = new();
|
||||||
List<FileInfo> newCacheFiles = new();
|
List<FileInfo> newCacheFiles = new();
|
||||||
@ -263,7 +251,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "cpu");
|
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu");
|
||||||
string mainDir = Path.Combine(ptcDir, "0");
|
string mainDir = Path.Combine(ptcDir, "0");
|
||||||
string backupDir = Path.Combine(ptcDir, "1");
|
string backupDir = Path.Combine(ptcDir, "1");
|
||||||
|
|
||||||
@ -284,7 +272,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.TitleId, "cache", "shader");
|
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader");
|
||||||
|
|
||||||
if (!Directory.Exists(shaderCacheDir))
|
if (!Directory.Exists(shaderCacheDir))
|
||||||
{
|
{
|
||||||
@ -305,7 +293,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
viewModel.StorageProvider,
|
viewModel.StorageProvider,
|
||||||
NcaSectionType.Code,
|
NcaSectionType.Code,
|
||||||
viewModel.SelectedApplication.Path,
|
viewModel.SelectedApplication.Path,
|
||||||
viewModel.SelectedApplication.TitleName);
|
viewModel.SelectedApplication.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,7 +307,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
viewModel.StorageProvider,
|
viewModel.StorageProvider,
|
||||||
NcaSectionType.Data,
|
NcaSectionType.Data,
|
||||||
viewModel.SelectedApplication.Path,
|
viewModel.SelectedApplication.Path,
|
||||||
viewModel.SelectedApplication.TitleName);
|
viewModel.SelectedApplication.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,7 +321,18 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
viewModel.StorageProvider,
|
viewModel.StorageProvider,
|
||||||
NcaSectionType.Logo,
|
NcaSectionType.Logo,
|
||||||
viewModel.SelectedApplication.Path,
|
viewModel.SelectedApplication.Path,
|
||||||
viewModel.SelectedApplication.TitleName);
|
viewModel.SelectedApplication.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
|
||||||
|
|
||||||
|
if (viewModel?.SelectedApplication != null)
|
||||||
|
{
|
||||||
|
ApplicationData selectedApplication = viewModel.SelectedApplication;
|
||||||
|
ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.Name, selectedApplication.IdString, selectedApplication.Icon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,7 +342,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
|
|
||||||
if (viewModel?.SelectedApplication != null)
|
if (viewModel?.SelectedApplication != null)
|
||||||
{
|
{
|
||||||
await viewModel.LoadApplication(viewModel.SelectedApplication.Path);
|
await viewModel.LoadApplication(viewModel.SelectedApplication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@
|
|||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{Binding TitleName}"
|
Text="{Binding Name}"
|
||||||
TextAlignment="Center"
|
TextAlignment="Center"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</Panel>
|
</Panel>
|
||||||
|
@ -85,7 +85,7 @@
|
|||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
FontWeight="Bold"
|
FontWeight="Bold"
|
||||||
Text="{Binding TitleName}"
|
Text="{Binding Name}"
|
||||||
TextAlignment="Left"
|
TextAlignment="Left"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@ -109,7 +109,7 @@
|
|||||||
Spacing="5">
|
Spacing="5">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Text="{Binding TitleId}"
|
Text="{Binding Id, StringFormat=X16}"
|
||||||
TextAlignment="Left"
|
TextAlignment="Left"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@ -126,17 +126,17 @@
|
|||||||
Spacing="5">
|
Spacing="5">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Text="{Binding TimePlayed}"
|
Text="{Binding TimePlayedString}"
|
||||||
TextAlignment="Right"
|
TextAlignment="Right"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Text="{Binding LastPlayed, Converter={helpers:NullableDateTimeConverter}}"
|
Text="{Binding LastPlayedString, Converter={helpers:LocalizedNeverConverter}}"
|
||||||
TextAlignment="Right"
|
TextAlignment="Right"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Text="{Binding FileSize}"
|
Text="{Binding FileSizeString}"
|
||||||
TextAlignment="Right"
|
TextAlignment="Right"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
31
src/Ryujinx.Ava/UI/Controls/SliderScroll.axaml.cs
Normal file
31
src/Ryujinx.Ava/UI/Controls/SliderScroll.axaml.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Controls
|
||||||
|
{
|
||||||
|
public class SliderScroll : Slider
|
||||||
|
{
|
||||||
|
protected override Type StyleKeyOverride => typeof(Slider);
|
||||||
|
|
||||||
|
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
||||||
|
{
|
||||||
|
var newValue = Value + e.Delta.Y * TickFrequency;
|
||||||
|
|
||||||
|
if (newValue < Minimum)
|
||||||
|
{
|
||||||
|
Value = Minimum;
|
||||||
|
}
|
||||||
|
else if (newValue > Maximum)
|
||||||
|
{
|
||||||
|
Value = Maximum;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Value = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs
Normal file
43
src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ui.Common.Helper;
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Helpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This <see cref="IValueConverter"/> makes sure that the string "Never" that's returned by <see cref="ValueFormatUtils.FormatDateTime"/> is properly localized in the Avalonia UI.
|
||||||
|
/// After the Avalonia UI has been made the default and the GTK UI is removed, <see cref="ValueFormatUtils"/> should be updated to directly return a localized string.
|
||||||
|
/// </summary>
|
||||||
|
internal class LocalizedNeverConverter : MarkupExtension, IValueConverter
|
||||||
|
{
|
||||||
|
private static readonly LocalizedNeverConverter _instance = new();
|
||||||
|
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is not string valStr)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valStr == "Never")
|
||||||
|
{
|
||||||
|
return LocaleManager.Instance[LocaleKeys.Never];
|
||||||
|
}
|
||||||
|
|
||||||
|
return valStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
using Avalonia.Data.Converters;
|
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
|
||||||
{
|
|
||||||
internal class NullableDateTimeConverter : MarkupExtension, IValueConverter
|
|
||||||
{
|
|
||||||
private static readonly NullableDateTimeConverter _instance = new();
|
|
||||||
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
return LocaleManager.Instance[LocaleKeys.Never];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value is DateTime dateTime)
|
|
||||||
{
|
|
||||||
return dateTime.ToLocalTime().ToString(culture);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Models
|
namespace Ryujinx.Ava.UI.Models
|
||||||
@ -24,6 +25,9 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
|
|
||||||
public string FileName => Path.GetFileName(ContainerPath);
|
public string FileName => Path.GetFileName(ContainerPath);
|
||||||
|
|
||||||
|
public string Label =>
|
||||||
|
Path.GetExtension(FileName)?.ToLower() == ".xci" ? $"{LocaleManager.Instance[LocaleKeys.TitleBundledDlcLabel]} {FileName}" : FileName;
|
||||||
|
|
||||||
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
|
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
|
||||||
{
|
{
|
||||||
TitleId = titleId;
|
TitleId = titleId;
|
||||||
|
@ -13,20 +13,19 @@ namespace Ryujinx.Ava.UI.Models.Generic
|
|||||||
|
|
||||||
public int Compare(ApplicationData x, ApplicationData y)
|
public int Compare(ApplicationData x, ApplicationData y)
|
||||||
{
|
{
|
||||||
var aValue = x.LastPlayed;
|
DateTime aValue = DateTime.UnixEpoch, bValue = DateTime.UnixEpoch;
|
||||||
var bValue = y.LastPlayed;
|
|
||||||
|
|
||||||
if (!aValue.HasValue)
|
if (x?.LastPlayed != null)
|
||||||
{
|
{
|
||||||
aValue = DateTime.UnixEpoch;
|
aValue = x.LastPlayed.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bValue.HasValue)
|
if (y?.LastPlayed != null)
|
||||||
{
|
{
|
||||||
bValue = DateTime.UnixEpoch;
|
bValue = y.LastPlayed.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (IsAscending ? 1 : -1) * DateTime.Compare(bValue.Value, aValue.Value);
|
return (IsAscending ? 1 : -1) * DateTime.Compare(aValue, bValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs
Normal file
31
src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Models.Generic
|
||||||
|
{
|
||||||
|
internal class TimePlayedSortComparer : IComparer<ApplicationData>
|
||||||
|
{
|
||||||
|
public TimePlayedSortComparer() { }
|
||||||
|
public TimePlayedSortComparer(bool isAscending) { IsAscending = isAscending; }
|
||||||
|
|
||||||
|
public bool IsAscending { get; }
|
||||||
|
|
||||||
|
public int Compare(ApplicationData x, ApplicationData y)
|
||||||
|
{
|
||||||
|
TimeSpan aValue = TimeSpan.Zero, bValue = TimeSpan.Zero;
|
||||||
|
|
||||||
|
if (x?.TimePlayed != null)
|
||||||
|
{
|
||||||
|
aValue = x.TimePlayed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y?.TimePlayed != null)
|
||||||
|
{
|
||||||
|
bValue = y.TimePlayed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (IsAscending ? 1 : -1) * TimeSpan.Compare(aValue, bValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ using Ryujinx.Ava.UI.ViewModels;
|
|||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.Ui.App.Common;
|
||||||
using System;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -38,26 +38,7 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
|
|
||||||
public bool SizeAvailable { get; set; }
|
public bool SizeAvailable { get; set; }
|
||||||
|
|
||||||
public string SizeString => GetSizeString();
|
public string SizeString => ValueFormatUtils.FormatFileSize(Size);
|
||||||
|
|
||||||
private string GetSizeString()
|
|
||||||
{
|
|
||||||
const int Scale = 1024;
|
|
||||||
string[] orders = { "GiB", "MiB", "KiB" };
|
|
||||||
long max = (long)Math.Pow(Scale, orders.Length);
|
|
||||||
|
|
||||||
foreach (string order in orders)
|
|
||||||
{
|
|
||||||
if (Size > max)
|
|
||||||
{
|
|
||||||
return $"{decimal.Divide(Size, max):##.##} {order}";
|
|
||||||
}
|
|
||||||
|
|
||||||
max /= Scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "0 KiB";
|
|
||||||
}
|
|
||||||
|
|
||||||
public SaveModel(SaveDataInfo info)
|
public SaveModel(SaveDataInfo info)
|
||||||
{
|
{
|
||||||
@ -65,14 +46,14 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
TitleId = info.ProgramId;
|
TitleId = info.ProgramId;
|
||||||
UserId = info.UserId;
|
UserId = info.UserId;
|
||||||
|
|
||||||
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.TitleId.ToUpper() == TitleIdString);
|
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.IdString.ToUpper() == TitleIdString);
|
||||||
|
|
||||||
InGameList = appData != null;
|
InGameList = appData != null;
|
||||||
|
|
||||||
if (InGameList)
|
if (InGameList)
|
||||||
{
|
{
|
||||||
Icon = appData.Icon;
|
Icon = appData.Icon;
|
||||||
Title = appData.TitleName;
|
Title = appData.Name;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -8,7 +8,10 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
public ApplicationControlProperty Control { get; }
|
public ApplicationControlProperty Control { get; }
|
||||||
public string Path { get; }
|
public string Path { get; }
|
||||||
|
|
||||||
public string Label => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleUpdateVersionLabel, Control.DisplayVersionString.ToString());
|
public string Label => LocaleManager.Instance.UpdateAndGetDynamicValue(
|
||||||
|
System.IO.Path.GetExtension(Path)?.ToLower() == ".xci" ? LocaleKeys.TitleBundledUpdateVersionLabel : LocaleKeys.TitleUpdateVersionLabel,
|
||||||
|
Control.DisplayVersionString.ToString()
|
||||||
|
);
|
||||||
|
|
||||||
public TitleUpdateModel(ApplicationControlProperty control, string path)
|
public TitleUpdateModel(ApplicationControlProperty control, string path)
|
||||||
{
|
{
|
||||||
|
@ -327,7 +327,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
string imageUrl = _amiiboList.Find(amiibo => amiibo.Equals(selected)).Image;
|
string imageUrl = _amiiboList.Find(amiibo => amiibo.Equals(selected)).Image;
|
||||||
|
|
||||||
string usageString = "";
|
StringBuilder usageStringBuilder = new();
|
||||||
|
|
||||||
for (int i = 0; i < _amiiboList.Count; i++)
|
for (int i = 0; i < _amiiboList.Count; i++)
|
||||||
{
|
{
|
||||||
@ -341,20 +341,19 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
|
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
|
||||||
{
|
{
|
||||||
usageString += Environment.NewLine +
|
usageStringBuilder.Append($"{Environment.NewLine}- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}");
|
||||||
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
|
|
||||||
|
|
||||||
writable = usageItem.Write;
|
writable = usageItem.Write;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usageString.Length == 0)
|
if (usageStringBuilder.Length == 0)
|
||||||
{
|
{
|
||||||
usageString = LocaleManager.Instance[LocaleKeys.Unknown] + ".";
|
usageStringBuilder.Append($"{LocaleManager.Instance[LocaleKeys.Unknown]}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageString}";
|
Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageStringBuilder}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,11 +17,12 @@ using Ryujinx.Common.Configuration;
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Application = Avalonia.Application;
|
using Application = Avalonia.Application;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
|
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
|
||||||
|
|
||||||
private string _search;
|
private string _search;
|
||||||
private readonly ulong _titleId;
|
private readonly ApplicationData _applicationData;
|
||||||
|
|
||||||
private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
@ -92,18 +93,25 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public IStorageProvider StorageProvider;
|
public IStorageProvider StorageProvider;
|
||||||
|
|
||||||
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
|
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
|
||||||
_titleId = titleId;
|
_applicationData = applicationData;
|
||||||
|
|
||||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
StorageProvider = desktop.MainWindow.StorageProvider;
|
StorageProvider = desktop.MainWindow.StorageProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationData.IdString, "dlc.json");
|
||||||
|
|
||||||
|
if (!File.Exists(_downloadableContentJsonPath))
|
||||||
|
{
|
||||||
|
_downloadableContentContainerList = new List<DownloadableContentContainer>();
|
||||||
|
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -120,13 +128,21 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void LoadDownloadableContents()
|
private void LoadDownloadableContents()
|
||||||
{
|
{
|
||||||
|
// NOTE: Try to load downloadable contents from PFS first.
|
||||||
|
AddDownloadableContent(_applicationData.Path);
|
||||||
|
|
||||||
foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
|
foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
|
||||||
{
|
{
|
||||||
if (File.Exists(downloadableContentContainer.ContainerPath))
|
if (File.Exists(downloadableContentContainer.ContainerPath))
|
||||||
{
|
{
|
||||||
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
||||||
|
|
||||||
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
PartitionFileSystem partitionFileSystem = new();
|
||||||
|
|
||||||
|
if (partitionFileSystem.Initialize(containerFile.AsStorage()).IsFailure())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
@ -219,21 +235,34 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
foreach (var file in result)
|
foreach (var file in result)
|
||||||
{
|
{
|
||||||
await AddDownloadableContent(file.Path.LocalPath);
|
if (!AddDownloadableContent(file.Path.LocalPath))
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddDownloadableContent(string path)
|
private bool AddDownloadableContent(string path)
|
||||||
{
|
{
|
||||||
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
|
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
|
||||||
{
|
{
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
using FileStream containerFile = File.OpenRead(path);
|
using FileStream containerFile = File.OpenRead(path);
|
||||||
|
|
||||||
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
IFileSystem partitionFileSystem;
|
||||||
bool containsDownloadableContent = false;
|
|
||||||
|
if (Path.GetExtension(path).ToLower() == ".xci")
|
||||||
|
{
|
||||||
|
partitionFileSystem = new Xci(_virtualFileSystem.KeySet, containerFile.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pfsTemp = new PartitionFileSystem();
|
||||||
|
pfsTemp.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||||
|
partitionFileSystem = pfsTemp;
|
||||||
|
}
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
@ -251,7 +280,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||||
{
|
{
|
||||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
|
if (nca.GetProgramIdBase() != _applicationData.IdBase)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -263,14 +292,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
OnPropertyChanged(nameof(UpdateCount));
|
OnPropertyChanged(nameof(UpdateCount));
|
||||||
Sort();
|
Sort();
|
||||||
|
|
||||||
containsDownloadableContent = true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!containsDownloadableContent)
|
return false;
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(DownloadableContentModel model)
|
public void Remove(DownloadableContentModel model)
|
||||||
|
@ -95,7 +95,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private bool _canUpdate = true;
|
private bool _canUpdate = true;
|
||||||
private Cursor _cursor;
|
private Cursor _cursor;
|
||||||
private string _title;
|
private string _title;
|
||||||
private string _currentEmulatedGamePath;
|
private ApplicationData _currentApplicationData;
|
||||||
private readonly AutoResetEvent _rendererWaitEvent;
|
private readonly AutoResetEvent _rendererWaitEvent;
|
||||||
private WindowState _windowState;
|
private WindowState _windowState;
|
||||||
private double _windowWidth;
|
private double _windowWidth;
|
||||||
@ -106,7 +106,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public ApplicationData ListSelectedApplication;
|
public ApplicationData ListSelectedApplication;
|
||||||
public ApplicationData GridSelectedApplication;
|
public ApplicationData GridSelectedApplication;
|
||||||
|
|
||||||
private string TitleName { get; set; }
|
|
||||||
internal AppHost AppHost { get; set; }
|
internal AppHost AppHost { get; set; }
|
||||||
|
|
||||||
public MainWindowViewModel()
|
public MainWindowViewModel()
|
||||||
@ -356,6 +355,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||||
|
|
||||||
|
public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild();
|
||||||
|
|
||||||
public string LoadHeading
|
public string LoadHeading
|
||||||
{
|
{
|
||||||
get => _loadHeading;
|
get => _loadHeading;
|
||||||
@ -928,21 +929,20 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
return SortMode switch
|
return SortMode switch
|
||||||
{
|
{
|
||||||
#pragma warning disable IDE0055 // Disable formatting
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
|
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Name)
|
||||||
ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSizeBytes)
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Name),
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileSizeBytes),
|
|
||||||
ApplicationSort.TotalTimePlayed => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TimePlayedNum)
|
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.TimePlayedNum),
|
|
||||||
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
|
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
|
|
||||||
ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
|
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite),
|
|
||||||
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
|
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
|
||||||
|
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
|
||||||
|
ApplicationSort.TotalTimePlayed => new TimePlayedSortComparer(IsAscending),
|
||||||
ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
|
ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
|
||||||
|
ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSize)
|
||||||
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileSize),
|
||||||
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
|
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
|
||||||
|
ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
|
||||||
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite),
|
||||||
_ => null,
|
_ => null,
|
||||||
#pragma warning restore IDE0055
|
#pragma warning restore IDE0055
|
||||||
};
|
};
|
||||||
@ -967,7 +967,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
if (arg is ApplicationData app)
|
if (arg is ApplicationData app)
|
||||||
{
|
{
|
||||||
return string.IsNullOrWhiteSpace(_searchText) || app.TitleName.ToLower().Contains(_searchText.ToLower());
|
return string.IsNullOrWhiteSpace(_searchText) || app.Name.ToLower().Contains(_searchText.ToLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -1096,7 +1096,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
IsLoadingIndeterminate = false;
|
IsLoadingIndeterminate = false;
|
||||||
break;
|
break;
|
||||||
case LoadState.Loaded:
|
case LoadState.Loaded:
|
||||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
|
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name);
|
||||||
IsLoadingIndeterminate = true;
|
IsLoadingIndeterminate = true;
|
||||||
CacheLoadStatus = "";
|
CacheLoadStatus = "";
|
||||||
break;
|
break;
|
||||||
@ -1116,7 +1116,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
IsLoadingIndeterminate = false;
|
IsLoadingIndeterminate = false;
|
||||||
break;
|
break;
|
||||||
case ShaderCacheLoadingState.Loaded:
|
case ShaderCacheLoadingState.Loaded:
|
||||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
|
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name);
|
||||||
IsLoadingIndeterminate = true;
|
IsLoadingIndeterminate = true;
|
||||||
CacheLoadStatus = "";
|
CacheLoadStatus = "";
|
||||||
break;
|
break;
|
||||||
@ -1167,13 +1167,13 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
UserChannelPersistence.ShouldRestart = false;
|
UserChannelPersistence.ShouldRestart = false;
|
||||||
|
|
||||||
await LoadApplication(_currentEmulatedGamePath);
|
await LoadApplication(_currentApplicationData);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Otherwise, clear state.
|
// Otherwise, clear state.
|
||||||
UserChannelPersistence = new UserChannelPersistence();
|
UserChannelPersistence = new UserChannelPersistence();
|
||||||
_currentEmulatedGamePath = null;
|
_currentApplicationData = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1278,6 +1278,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
Glyph = Glyph.Grid;
|
Glyph = Glyph.Grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetAspectRatio(AspectRatio aspectRatio)
|
||||||
|
{
|
||||||
|
ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task InstallFirmwareFromFile()
|
public async Task InstallFirmwareFromFile()
|
||||||
{
|
{
|
||||||
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||||
@ -1445,7 +1450,12 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
if (result.Count > 0)
|
if (result.Count > 0)
|
||||||
{
|
{
|
||||||
await LoadApplication(result[0].Path.LocalPath);
|
ApplicationData applicationData = new()
|
||||||
|
{
|
||||||
|
Path = result[0].Path.LocalPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
await LoadApplication(applicationData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1459,11 +1469,17 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
if (result.Count > 0)
|
if (result.Count > 0)
|
||||||
{
|
{
|
||||||
await LoadApplication(result[0].Path.LocalPath);
|
ApplicationData applicationData = new()
|
||||||
|
{
|
||||||
|
Name = Path.GetFileNameWithoutExtension(result[0].Path.LocalPath),
|
||||||
|
Path = result[0].Path.LocalPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
await LoadApplication(applicationData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LoadApplication(string path, bool startFullscreen = false, string titleName = "")
|
public async Task LoadApplication(ApplicationData application, bool startFullscreen = false)
|
||||||
{
|
{
|
||||||
if (AppHost != null)
|
if (AppHost != null)
|
||||||
{
|
{
|
||||||
@ -1483,7 +1499,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
Logger.RestartTime();
|
Logger.RestartTime();
|
||||||
|
|
||||||
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path);
|
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id);
|
||||||
|
|
||||||
PrepareLoadScreen();
|
PrepareLoadScreen();
|
||||||
|
|
||||||
@ -1492,7 +1508,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
AppHost = new AppHost(
|
AppHost = new AppHost(
|
||||||
RendererHostControl,
|
RendererHostControl,
|
||||||
InputManager,
|
InputManager,
|
||||||
path,
|
application.Path,
|
||||||
|
application.Id,
|
||||||
VirtualFileSystem,
|
VirtualFileSystem,
|
||||||
ContentManager,
|
ContentManager,
|
||||||
AccountManager,
|
AccountManager,
|
||||||
@ -1510,17 +1527,17 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
CanUpdate = false;
|
CanUpdate = false;
|
||||||
|
|
||||||
LoadHeading = TitleName = titleName;
|
LoadHeading = application.Name;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(titleName))
|
if (string.IsNullOrWhiteSpace(application.Name))
|
||||||
{
|
{
|
||||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
|
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
|
||||||
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
|
application.Name = AppHost.Device.Processes.ActiveApplication.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
SwitchToRenderer(startFullscreen);
|
SwitchToRenderer(startFullscreen);
|
||||||
|
|
||||||
_currentEmulatedGamePath = path;
|
_currentApplicationData = application;
|
||||||
|
|
||||||
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
|
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
|
||||||
gameThread.Start();
|
gameThread.Start();
|
||||||
@ -1542,13 +1559,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
|
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
|
||||||
{
|
{
|
||||||
if (appMetadata.LastPlayed.HasValue)
|
appMetadata.UpdatePostGame();
|
||||||
{
|
|
||||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
|
|
||||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
|
||||||
}
|
|
||||||
|
|
||||||
appMetadata.LastPlayed = DateTime.UtcNow;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1691,7 +1702,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,6 +147,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool EnableTextureRecompression { get; set; }
|
public bool EnableTextureRecompression { get; set; }
|
||||||
public bool EnableMacroHLE { get; set; }
|
public bool EnableMacroHLE { get; set; }
|
||||||
public bool EnableColorSpacePassthrough { get; set; }
|
public bool EnableColorSpacePassthrough { get; set; }
|
||||||
|
public bool ColorSpacePassthroughAvailable => IsMacOS;
|
||||||
public bool EnableFileLog { get; set; }
|
public bool EnableFileLog { get; set; }
|
||||||
public bool EnableStub { get; set; }
|
public bool EnableStub { get; set; }
|
||||||
public bool EnableInfo { get; set; }
|
public bool EnableInfo { get; set; }
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
@ -8,6 +7,7 @@ using LibHac.Fs;
|
|||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
|
using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
@ -17,12 +17,16 @@ using Ryujinx.Common.Configuration;
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.Ui.App.Common;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Application = Avalonia.Application;
|
||||||
|
using ContentType = LibHac.Ncm.ContentType;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
using SpanHelpers = LibHac.Common.SpanHelpers;
|
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||||
|
|
||||||
@ -33,7 +37,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public TitleUpdateMetadata TitleUpdateWindowData;
|
public TitleUpdateMetadata TitleUpdateWindowData;
|
||||||
public readonly string TitleUpdateJsonPath;
|
public readonly string TitleUpdateJsonPath;
|
||||||
private VirtualFileSystem VirtualFileSystem { get; }
|
private VirtualFileSystem VirtualFileSystem { get; }
|
||||||
private ulong TitleId { get; }
|
private ApplicationData ApplicationData { get; }
|
||||||
|
|
||||||
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
||||||
private AvaloniaList<object> _views = new();
|
private AvaloniaList<object> _views = new();
|
||||||
@ -73,18 +77,18 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public IStorageProvider StorageProvider;
|
public IStorageProvider StorageProvider;
|
||||||
|
|
||||||
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
|
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
|
|
||||||
TitleId = titleId;
|
ApplicationData = applicationData;
|
||||||
|
|
||||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
StorageProvider = desktop.MainWindow.StorageProvider;
|
StorageProvider = desktop.MainWindow.StorageProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, ApplicationData.IdString, "updates.json");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -92,7 +96,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {TitleId} at {TitleUpdateJsonPath}");
|
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {ApplicationData.IdString} at {TitleUpdateJsonPath}");
|
||||||
|
|
||||||
TitleUpdateWindowData = new TitleUpdateMetadata
|
TitleUpdateWindowData = new TitleUpdateMetadata
|
||||||
{
|
{
|
||||||
@ -108,6 +112,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void LoadUpdates()
|
private void LoadUpdates()
|
||||||
{
|
{
|
||||||
|
// Try to load updates from PFS first
|
||||||
|
AddUpdate(ApplicationData.Path, true);
|
||||||
|
|
||||||
foreach (string path in TitleUpdateWindowData.Paths)
|
foreach (string path in TitleUpdateWindowData.Paths)
|
||||||
{
|
{
|
||||||
AddUpdate(path);
|
AddUpdate(path);
|
||||||
@ -162,15 +169,41 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddUpdate(string path)
|
private void AddUpdate(string path, bool ignoreNotFound = false)
|
||||||
{
|
{
|
||||||
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
||||||
{
|
{
|
||||||
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
|
IFileSystem pfs;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, new PartitionFileSystem(file.AsStorage()), TitleId.ToString("x16"), 0);
|
if (Path.GetExtension(path).ToLower() == ".xci")
|
||||||
|
{
|
||||||
|
pfs = new Xci(VirtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pfsTemp = new PartitionFileSystem();
|
||||||
|
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||||
|
pfs = pfsTemp;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<ulong, ContentCollection> updates = pfs.GetUpdateData(VirtualFileSystem, checkLevel);
|
||||||
|
|
||||||
|
Nca patchNca = null;
|
||||||
|
Nca controlNca = null;
|
||||||
|
|
||||||
|
if (updates.TryGetValue(ApplicationData.Id, out ContentCollection content))
|
||||||
|
{
|
||||||
|
patchNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Program);
|
||||||
|
controlNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Control);
|
||||||
|
}
|
||||||
|
|
||||||
if (controlNca != null && patchNca != null)
|
if (controlNca != null && patchNca != null)
|
||||||
{
|
{
|
||||||
@ -184,10 +217,13 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if (!ignoreNotFound)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]));
|
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path)));
|
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path)));
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
@ -460,7 +461,7 @@
|
|||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
<Slider
|
<controls:SliderScroll
|
||||||
Width="130"
|
Width="130"
|
||||||
Maximum="1"
|
Maximum="1"
|
||||||
TickFrequency="0.01"
|
TickFrequency="0.01"
|
||||||
@ -480,7 +481,7 @@
|
|||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
<Slider
|
<controls:SliderScroll
|
||||||
Width="130"
|
Width="130"
|
||||||
Maximum="2"
|
Maximum="2"
|
||||||
TickFrequency="0.01"
|
TickFrequency="0.01"
|
||||||
@ -604,7 +605,7 @@
|
|||||||
<StackPanel
|
<StackPanel
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
<Slider
|
<controls:SliderScroll
|
||||||
Width="130"
|
Width="130"
|
||||||
Maximum="1"
|
Maximum="1"
|
||||||
TickFrequency="0.01"
|
TickFrequency="0.01"
|
||||||
@ -1083,7 +1084,7 @@
|
|||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
<Slider
|
<controls:SliderScroll
|
||||||
Width="130"
|
Width="130"
|
||||||
Maximum="1"
|
Maximum="1"
|
||||||
TickFrequency="0.01"
|
TickFrequency="0.01"
|
||||||
@ -1105,7 +1106,7 @@
|
|||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
<Slider
|
<controls:SliderScroll
|
||||||
Width="130"
|
Width="130"
|
||||||
Maximum="2"
|
Maximum="2"
|
||||||
TickFrequency="0.01"
|
TickFrequency="0.01"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
@ -23,11 +24,11 @@
|
|||||||
Margin="0"
|
Margin="0"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Text="{locale:Locale ControllerSettingsMotionGyroSensitivity}" />
|
Text="{locale:Locale ControllerSettingsMotionGyroSensitivity}" />
|
||||||
<Slider
|
<controls:SliderScroll
|
||||||
Margin="0,-5,0,-5"
|
Margin="0,-5,0,-5"
|
||||||
Width="150"
|
Width="150"
|
||||||
MaxWidth="150"
|
MaxWidth="150"
|
||||||
TickFrequency="0.01"
|
TickFrequency="1"
|
||||||
IsSnapToTickEnabled="True"
|
IsSnapToTickEnabled="True"
|
||||||
SmallChange="0.01"
|
SmallChange="0.01"
|
||||||
Maximum="100"
|
Maximum="100"
|
||||||
@ -45,11 +46,11 @@
|
|||||||
Margin="0"
|
Margin="0"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Text="{locale:Locale ControllerSettingsMotionGyroDeadzone}" />
|
Text="{locale:Locale ControllerSettingsMotionGyroDeadzone}" />
|
||||||
<Slider
|
<controls:SliderScroll
|
||||||
Margin="0,-5,0,-5"
|
Margin="0,-5,0,-5"
|
||||||
Width="150"
|
Width="150"
|
||||||
MaxWidth="150"
|
MaxWidth="150"
|
||||||
TickFrequency="0.01"
|
TickFrequency="1"
|
||||||
IsSnapToTickEnabled="True"
|
IsSnapToTickEnabled="True"
|
||||||
SmallChange="0.01"
|
SmallChange="0.01"
|
||||||
Maximum="100"
|
Maximum="100"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
@ -21,7 +22,7 @@
|
|||||||
TextWrapping="WrapWithOverflow"
|
TextWrapping="WrapWithOverflow"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Text="{locale:Locale ControllerSettingsRumbleStrongMultiplier}" />
|
Text="{locale:Locale ControllerSettingsRumbleStrongMultiplier}" />
|
||||||
<Slider
|
<controls:SliderScroll
|
||||||
Margin="0,-5,0,-5"
|
Margin="0,-5,0,-5"
|
||||||
Width="200"
|
Width="200"
|
||||||
TickFrequency="0.01"
|
TickFrequency="0.01"
|
||||||
@ -41,7 +42,7 @@
|
|||||||
TextWrapping="WrapWithOverflow"
|
TextWrapping="WrapWithOverflow"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Text="{locale:Locale ControllerSettingsRumbleWeakMultiplier}" />
|
Text="{locale:Locale ControllerSettingsRumbleWeakMultiplier}" />
|
||||||
<Slider
|
<controls:SliderScroll
|
||||||
Margin="0,-5,0,-5"
|
Margin="0,-5,0,-5"
|
||||||
Width="200"
|
Width="200"
|
||||||
MaxWidth="200"
|
MaxWidth="200"
|
||||||
|
@ -10,6 +10,7 @@ using Ryujinx.Ava.UI.Windows;
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.Modules;
|
using Ryujinx.Modules;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
using Ryujinx.Ui.Common;
|
using Ryujinx.Ui.Common;
|
||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
@ -131,7 +132,14 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(contentPath))
|
if (!string.IsNullOrEmpty(contentPath))
|
||||||
{
|
{
|
||||||
await ViewModel.LoadApplication(contentPath, false, "Mii Applet");
|
ApplicationData applicationData = new()
|
||||||
|
{
|
||||||
|
Name = "miiEdit",
|
||||||
|
Id = 0x0100000000001009,
|
||||||
|
Path = contentPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,9 +3,11 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
|
xmlns:config="clr-namespace:Ryujinx.Common.Configuration;assembly=Ryujinx.Common"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Ryujinx.Ava.UI.Views.Main.MainStatusBarView"
|
x:Class="Ryujinx.Ava.UI.Views.Main.MainStatusBarView"
|
||||||
x:DataType="viewModels:MainWindowViewModel">
|
x:DataType="viewModels:MainWindowViewModel">
|
||||||
@ -112,15 +114,52 @@
|
|||||||
Background="Gray"
|
Background="Gray"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
IsVisible="{Binding !ShowLoadProgress}" />
|
IsVisible="{Binding !ShowLoadProgress}" />
|
||||||
<TextBlock
|
<SplitButton
|
||||||
Name="AspectRatioStatus"
|
Name="AspectRatioStatus"
|
||||||
Margin="5,0,5,0"
|
Padding="5,0,5,0"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
CornerRadius="0"
|
||||||
IsVisible="{Binding !ShowLoadProgress}"
|
IsVisible="{Binding !ShowLoadProgress}"
|
||||||
PointerReleased="AspectRatioStatus_PointerReleased"
|
Content="{Binding AspectRatioStatusText}"
|
||||||
Text="{Binding AspectRatioStatusText}"
|
Click="AspectRatioStatus_OnClick"
|
||||||
TextAlignment="Left" />
|
ToolTip.Tip="{locale:Locale AspectRatioTooltip}">
|
||||||
|
<SplitButton.Styles>
|
||||||
|
<Style Selector="Border#SeparatorBorder">
|
||||||
|
<Setter Property="Opacity" Value="0" />
|
||||||
|
</Style>
|
||||||
|
</SplitButton.Styles>
|
||||||
|
<SplitButton.Flyout>
|
||||||
|
<MenuFlyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
|
||||||
|
<MenuItem
|
||||||
|
Header="{locale:Locale SettingsTabGraphicsAspectRatio4x3}"
|
||||||
|
Command="{Binding SetAspectRatio}"
|
||||||
|
CommandParameter="{x:Static config:AspectRatio.Fixed4x3}"/>
|
||||||
|
<MenuItem
|
||||||
|
Header="{locale:Locale SettingsTabGraphicsAspectRatio16x9}"
|
||||||
|
Command="{Binding SetAspectRatio}"
|
||||||
|
CommandParameter="{x:Static config:AspectRatio.Fixed16x9}"/>
|
||||||
|
<MenuItem
|
||||||
|
Header="{locale:Locale SettingsTabGraphicsAspectRatio16x10}"
|
||||||
|
Command="{Binding SetAspectRatio}"
|
||||||
|
CommandParameter="{x:Static config:AspectRatio.Fixed16x10}"/>
|
||||||
|
<MenuItem
|
||||||
|
Header="{locale:Locale SettingsTabGraphicsAspectRatio21x9}"
|
||||||
|
Command="{Binding SetAspectRatio}"
|
||||||
|
CommandParameter="{x:Static config:AspectRatio.Fixed21x9}"/>
|
||||||
|
<MenuItem
|
||||||
|
Header="{locale:Locale SettingsTabGraphicsAspectRatio32x9}"
|
||||||
|
Command="{Binding SetAspectRatio}"
|
||||||
|
CommandParameter="{x:Static config:AspectRatio.Fixed32x9}"/>
|
||||||
|
<MenuItem
|
||||||
|
Header="{locale:Locale SettingsTabGraphicsAspectRatioStretch}"
|
||||||
|
Command="{Binding SetAspectRatio}"
|
||||||
|
CommandParameter="{x:Static config:AspectRatio.Stretched}"/>
|
||||||
|
</MenuFlyout>
|
||||||
|
</SplitButton.Flyout>
|
||||||
|
</SplitButton>
|
||||||
<Border
|
<Border
|
||||||
Width="2"
|
Width="2"
|
||||||
Height="12"
|
Height="12"
|
||||||
@ -138,6 +177,7 @@
|
|||||||
Content="{Binding VolumeStatusText}"
|
Content="{Binding VolumeStatusText}"
|
||||||
IsChecked="{Binding VolumeMuted}"
|
IsChecked="{Binding VolumeMuted}"
|
||||||
IsVisible="{Binding !ShowLoadProgress}"
|
IsVisible="{Binding !ShowLoadProgress}"
|
||||||
|
PointerWheelChanged="VolumeStatus_OnPointerWheelChanged"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderThickness="0"
|
BorderThickness="0"
|
||||||
CornerRadius="0">
|
CornerRadius="0">
|
||||||
@ -154,7 +194,7 @@
|
|||||||
<ToggleSplitButton.Flyout>
|
<ToggleSplitButton.Flyout>
|
||||||
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
|
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
|
||||||
<Grid Margin="0">
|
<Grid Margin="0">
|
||||||
<Slider
|
<controls:SliderScroll
|
||||||
MaxHeight="40"
|
MaxHeight="40"
|
||||||
Width="150"
|
Width="150"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
|
@ -43,10 +43,9 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
|
ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AspectRatioStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
|
private void AspectRatioStatus_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value;
|
AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value;
|
||||||
|
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1;
|
ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,5 +53,20 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
{
|
{
|
||||||
Window.LoadApplications();
|
Window.LoadApplications();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void VolumeStatus_OnPointerWheelChanged(object sender, PointerWheelEventArgs e)
|
||||||
|
{
|
||||||
|
// Change the volume by 5% at a time
|
||||||
|
float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f;
|
||||||
|
|
||||||
|
Window.ViewModel.Volume = newValue switch
|
||||||
|
{
|
||||||
|
< 0 => 0,
|
||||||
|
> 1 => 1,
|
||||||
|
_ => newValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
@ -50,7 +51,7 @@
|
|||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{locale:Locale IconSize}"
|
Text="{locale:Locale IconSize}"
|
||||||
ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
|
ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
|
||||||
<Slider
|
<controls:SliderScroll
|
||||||
Width="150"
|
Width="150"
|
||||||
Height="35"
|
Height="35"
|
||||||
Margin="5,-10,5,0"
|
Margin="5,-10,5,0"
|
||||||
@ -103,7 +104,7 @@
|
|||||||
Content="{locale:Locale GameListHeaderApplication}"
|
Content="{locale:Locale GameListHeaderApplication}"
|
||||||
GroupName="Sort"
|
GroupName="Sort"
|
||||||
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
|
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
|
||||||
Tag="Title" />
|
Tag="Application" />
|
||||||
<RadioButton
|
<RadioButton
|
||||||
Checked="Sort_Checked"
|
Checked="Sort_Checked"
|
||||||
Content="{locale:Locale GameListHeaderDeveloper}"
|
Content="{locale:Locale GameListHeaderDeveloper}"
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
@ -63,13 +64,13 @@
|
|||||||
Maximum="100" />
|
Maximum="100" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||||
<Slider Value="{Binding Volume}"
|
<controls:SliderScroll Value="{Binding Volume}"
|
||||||
Margin="250,0,0,0"
|
Margin="250,0,0,0"
|
||||||
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
|
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
|
||||||
Minimum="0"
|
Minimum="0"
|
||||||
Maximum="100"
|
Maximum="100"
|
||||||
SmallChange="5"
|
SmallChange="1"
|
||||||
TickFrequency="5"
|
TickFrequency="1"
|
||||||
IsSnapToTickEnabled="True"
|
IsSnapToTickEnabled="True"
|
||||||
LargeChange="10"
|
LargeChange="10"
|
||||||
Width="350" />
|
Width="350" />
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
@ -73,6 +74,7 @@
|
|||||||
<TextBlock Text="{locale:Locale SettingsEnableMacroHLE}" />
|
<TextBlock Text="{locale:Locale SettingsEnableMacroHLE}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding EnableColorSpacePassthrough}"
|
<CheckBox IsChecked="{Binding EnableColorSpacePassthrough}"
|
||||||
|
IsVisible="{Binding ColorSpacePassthroughAvailable}"
|
||||||
ToolTip.Tip="{locale:Locale SettingsEnableColorSpacePassthroughTooltip}">
|
ToolTip.Tip="{locale:Locale SettingsEnableColorSpacePassthroughTooltip}">
|
||||||
<TextBlock Text="{locale:Locale SettingsEnableColorSpacePassthrough}" />
|
<TextBlock Text="{locale:Locale SettingsEnableColorSpacePassthrough}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
@ -172,7 +174,7 @@
|
|||||||
<TextBlock Text="FSR" />
|
<TextBlock Text="FSR" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<Slider Value="{Binding ScalingFilterLevel}"
|
<controls:SliderScroll Value="{Binding ScalingFilterLevel}"
|
||||||
ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}"
|
ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}"
|
||||||
MinWidth="150"
|
MinWidth="150"
|
||||||
Margin="10,-3,0,0"
|
Margin="10,-3,0,0"
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.Ui.App.Common;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -34,9 +36,12 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
|
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
|
||||||
{
|
{
|
||||||
LoadedCheats = new AvaloniaList<CheatsList>();
|
LoadedCheats = new AvaloniaList<CheatsList>();
|
||||||
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper());
|
Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper());
|
||||||
BuildId = ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath);
|
BuildId = ApplicationData.GetBuildId(virtualFileSystem, checkLevel, titlePath);
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@
|
|||||||
MaxLines="2"
|
MaxLines="2"
|
||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
TextTrimming="CharacterEllipsis"
|
TextTrimming="CharacterEllipsis"
|
||||||
Text="{Binding FileName}" />
|
Text="{Binding Label}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="10 0"
|
Margin="10 0"
|
||||||
|
@ -7,9 +7,9 @@ using Ryujinx.Ava.UI.Helpers;
|
|||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Button = Avalonia.Controls.Button;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Windows
|
namespace Ryujinx.Ava.UI.Windows
|
||||||
{
|
{
|
||||||
@ -24,22 +24,22 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId)
|
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, titleId);
|
DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, applicationData);
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
ContentDialog contentDialog = new()
|
ContentDialog contentDialog = new()
|
||||||
{
|
{
|
||||||
PrimaryButtonText = "",
|
PrimaryButtonText = "",
|
||||||
SecondaryButtonText = "",
|
SecondaryButtonText = "",
|
||||||
CloseButtonText = "",
|
CloseButtonText = "",
|
||||||
Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId),
|
Content = new DownloadableContentManagerWindow(virtualFileSystem, applicationData),
|
||||||
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16")),
|
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], applicationData.Name, applicationData.IdString),
|
||||||
};
|
};
|
||||||
|
|
||||||
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
||||||
|
@ -4,6 +4,7 @@ using Avalonia.Controls.Primitives;
|
|||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
using Ryujinx.Ava.Common;
|
using Ryujinx.Ava.Common;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Input;
|
using Ryujinx.Ava.Input;
|
||||||
@ -23,7 +24,6 @@ using Ryujinx.Ui.Common;
|
|||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -139,9 +139,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
{
|
{
|
||||||
ViewModel.SelectedIcon = args.Application.Icon;
|
ViewModel.SelectedIcon = args.Application.Icon;
|
||||||
|
|
||||||
string path = new FileInfo(args.Application.Path).FullName;
|
ViewModel.LoadApplication(args.Application).Wait();
|
||||||
|
|
||||||
ViewModel.LoadApplication(path).Wait();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
@ -190,7 +188,11 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
LibHacHorizonManager.InitializeBcatServer();
|
LibHacHorizonManager.InitializeBcatServer();
|
||||||
LibHacHorizonManager.InitializeSystemClients();
|
LibHacHorizonManager.InitializeSystemClients();
|
||||||
|
|
||||||
ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem);
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
|
ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel);
|
||||||
|
|
||||||
// Save data created before we supported extra data in directory save data will not work properly if
|
// Save data created before we supported extra data in directory save data will not work properly if
|
||||||
// given empty extra data. Luckily some of that extra data can be created using the data from the
|
// given empty extra data. Luckily some of that extra data can be created using the data from the
|
||||||
@ -297,7 +299,12 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
{
|
{
|
||||||
_deferLoad = false;
|
_deferLoad = false;
|
||||||
|
|
||||||
ViewModel.LoadApplication(_launchPath, _startFullscreen).Wait();
|
ApplicationData applicationData = new()
|
||||||
|
{
|
||||||
|
Path = _launchPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
ViewModel.LoadApplication(applicationData, _startFullscreen).Wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -7,15 +7,15 @@ using Ryujinx.Ava.UI.Helpers;
|
|||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Button = Avalonia.Controls.Button;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Windows
|
namespace Ryujinx.Ava.UI.Windows
|
||||||
{
|
{
|
||||||
public partial class TitleUpdateWindow : UserControl
|
public partial class TitleUpdateWindow : UserControl
|
||||||
{
|
{
|
||||||
public TitleUpdateViewModel ViewModel;
|
public readonly TitleUpdateViewModel ViewModel;
|
||||||
|
|
||||||
public TitleUpdateWindow()
|
public TitleUpdateWindow()
|
||||||
{
|
{
|
||||||
@ -24,22 +24,22 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId)
|
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, titleId);
|
DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, applicationData);
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
ContentDialog contentDialog = new()
|
ContentDialog contentDialog = new()
|
||||||
{
|
{
|
||||||
PrimaryButtonText = "",
|
PrimaryButtonText = "",
|
||||||
SecondaryButtonText = "",
|
SecondaryButtonText = "",
|
||||||
CloseButtonText = "",
|
CloseButtonText = "",
|
||||||
Content = new TitleUpdateWindow(virtualFileSystem, titleId),
|
Content = new TitleUpdateWindow(virtualFileSystem, applicationData),
|
||||||
Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, titleName, titleId.ToString("X16")),
|
Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, applicationData.Name, applicationData.IdString),
|
||||||
};
|
};
|
||||||
|
|
||||||
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
||||||
|
@ -3,5 +3,6 @@
|
|||||||
public enum MultiplayerMode
|
public enum MultiplayerMode
|
||||||
{
|
{
|
||||||
Disabled,
|
Disabled,
|
||||||
|
LdnMitm,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -756,6 +756,18 @@ namespace Ryujinx.Common.Memory
|
|||||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct Array96<T> : IArray<T> where T : unmanaged
|
||||||
|
{
|
||||||
|
T _e0;
|
||||||
|
Array64<T> _other;
|
||||||
|
Array31<T> _other2;
|
||||||
|
public readonly int Length => 96;
|
||||||
|
public ref T this[int index] => ref AsSpan()[index];
|
||||||
|
|
||||||
|
[Pure]
|
||||||
|
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||||
|
}
|
||||||
|
|
||||||
public struct Array127<T> : IArray<T> where T : unmanaged
|
public struct Array127<T> : IArray<T> where T : unmanaged
|
||||||
{
|
{
|
||||||
T _e0;
|
T _e0;
|
||||||
|
@ -5,7 +5,7 @@ namespace Ryujinx.Common
|
|||||||
{
|
{
|
||||||
public class ReactiveObject<T>
|
public class ReactiveObject<T>
|
||||||
{
|
{
|
||||||
private readonly ReaderWriterLock _readerWriterLock = new();
|
private readonly ReaderWriterLockSlim _readerWriterLock = new();
|
||||||
private bool _isInitialized;
|
private bool _isInitialized;
|
||||||
private T _value;
|
private T _value;
|
||||||
|
|
||||||
@ -15,15 +15,15 @@ namespace Ryujinx.Common
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
_readerWriterLock.AcquireReaderLock(Timeout.Infinite);
|
_readerWriterLock.EnterReadLock();
|
||||||
T value = _value;
|
T value = _value;
|
||||||
_readerWriterLock.ReleaseReaderLock();
|
_readerWriterLock.ExitReadLock();
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_readerWriterLock.AcquireWriterLock(Timeout.Infinite);
|
_readerWriterLock.EnterWriteLock();
|
||||||
|
|
||||||
T oldValue = _value;
|
T oldValue = _value;
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ namespace Ryujinx.Common
|
|||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
_value = value;
|
_value = value;
|
||||||
|
|
||||||
_readerWriterLock.ReleaseWriterLock();
|
_readerWriterLock.ExitWriteLock();
|
||||||
|
|
||||||
if (!oldIsInitialized || oldValue == null || !oldValue.Equals(_value))
|
if (!oldIsInitialized || oldValue == null || !oldValue.Equals(_value))
|
||||||
{
|
{
|
||||||
|
@ -74,5 +74,10 @@ namespace Ryujinx.Common.Utilities
|
|||||||
{
|
{
|
||||||
return ConvertIpv4Address(IPAddress.Parse(ipAddress));
|
return ConvertIpv4Address(IPAddress.Parse(ipAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IPAddress ConvertUint(uint ipAddress)
|
||||||
|
{
|
||||||
|
return new IPAddress(new byte[] { (byte)((ipAddress >> 24) & 0xFF), (byte)((ipAddress >> 16) & 0xFF), (byte)((ipAddress >> 8) & 0xFF), (byte)(ipAddress & 0xFF) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
62
src/Ryujinx.Cpu/AppleHv/HvCodePatcher.cs
Normal file
62
src/Ryujinx.Cpu/AppleHv/HvCodePatcher.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Intrinsics;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
|
{
|
||||||
|
static class HvCodePatcher
|
||||||
|
{
|
||||||
|
private const uint XMask = 0x3f808000u;
|
||||||
|
private const uint XValue = 0x8000000u;
|
||||||
|
|
||||||
|
private const uint ZrIndex = 31u;
|
||||||
|
|
||||||
|
public static void RewriteUnorderedExclusiveInstructions(Span<byte> code)
|
||||||
|
{
|
||||||
|
Span<uint> codeUint = MemoryMarshal.Cast<byte, uint>(code);
|
||||||
|
Span<Vector128<uint>> codeVector = MemoryMarshal.Cast<byte, Vector128<uint>>(code);
|
||||||
|
|
||||||
|
Vector128<uint> mask = Vector128.Create(XMask);
|
||||||
|
Vector128<uint> value = Vector128.Create(XValue);
|
||||||
|
|
||||||
|
for (int index = 0; index < codeVector.Length; index++)
|
||||||
|
{
|
||||||
|
Vector128<uint> v = codeVector[index];
|
||||||
|
|
||||||
|
if (Vector128.EqualsAny(Vector128.BitwiseAnd(v, mask), value))
|
||||||
|
{
|
||||||
|
int baseIndex = index * 4;
|
||||||
|
|
||||||
|
for (int instIndex = baseIndex; instIndex < baseIndex + 4; instIndex++)
|
||||||
|
{
|
||||||
|
ref uint inst = ref codeUint[instIndex];
|
||||||
|
|
||||||
|
if ((inst & XMask) != XValue)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isPair = (inst & (1u << 21)) != 0;
|
||||||
|
bool isLoad = (inst & (1u << 22)) != 0;
|
||||||
|
|
||||||
|
uint rt2 = (inst >> 10) & 0x1fu;
|
||||||
|
uint rs = (inst >> 16) & 0x1fu;
|
||||||
|
|
||||||
|
if (isLoad && rs != ZrIndex)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPair && rt2 != ZrIndex)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the ordered flag.
|
||||||
|
inst |= 1u << 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -128,21 +128,6 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable IDE0051 // Remove unused private member
|
|
||||||
/// <summary>
|
|
||||||
/// Ensures the combination of virtual address and size is part of the addressable space and fully mapped.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="va">Virtual address of the range</param>
|
|
||||||
/// <param name="size">Size of the range in bytes</param>
|
|
||||||
private void AssertMapped(ulong va, ulong size)
|
|
||||||
{
|
|
||||||
if (!ValidateAddressAndSize(va, size) || !IsRangeMappedImpl(va, size))
|
|
||||||
{
|
|
||||||
throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#pragma warning restore IDE0051
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||||
{
|
{
|
||||||
@ -736,6 +721,24 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
return (int)(vaSpan / PageSize);
|
return (int)(vaSpan / PageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
|
||||||
|
{
|
||||||
|
if (protection.HasFlag(MemoryPermission.Execute))
|
||||||
|
{
|
||||||
|
// Some applications use unordered exclusive memory access instructions
|
||||||
|
// where it is not valid to do so, leading to memory re-ordering that
|
||||||
|
// makes the code behave incorrectly on some CPUs.
|
||||||
|
// To work around this, we force all such accesses to be ordered.
|
||||||
|
|
||||||
|
using WritableRegion writableRegion = GetWritableRegion(va, (int)size);
|
||||||
|
|
||||||
|
HvCodePatcher.RewriteUnorderedExclusiveInstructions(writableRegion.Memory.Span);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||||
{
|
{
|
||||||
|
@ -575,24 +575,17 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable IDE0051 // Remove unused private member
|
|
||||||
private ulong GetPhysicalAddress(ulong va)
|
|
||||||
{
|
|
||||||
// We return -1L if the virtual address is invalid or unmapped.
|
|
||||||
if (!ValidateAddress(va) || !IsMapped(va))
|
|
||||||
{
|
|
||||||
return ulong.MaxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetPhysicalAddressInternal(va);
|
|
||||||
}
|
|
||||||
#pragma warning restore IDE0051
|
|
||||||
|
|
||||||
private ulong GetPhysicalAddressInternal(ulong va)
|
private ulong GetPhysicalAddressInternal(ulong va)
|
||||||
{
|
{
|
||||||
return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
|
return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||||
{
|
{
|
||||||
@ -698,9 +691,5 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
/// Disposes of resources used by the memory manager.
|
/// Disposes of resources used by the memory manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void Destroy() => _pageTable.Dispose();
|
protected override void Destroy() => _pageTable.Dispose();
|
||||||
|
|
||||||
#pragma warning disable IDE0051 // Remove unused private member
|
|
||||||
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
|
|
||||||
#pragma warning restore IDE0051
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -615,6 +615,12 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
return (int)(vaSpan / PageSize);
|
return (int)(vaSpan / PageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||||
{
|
{
|
||||||
|
@ -38,6 +38,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
public readonly bool SupportsShaderBallot;
|
public readonly bool SupportsShaderBallot;
|
||||||
public readonly bool SupportsShaderBarrierDivergence;
|
public readonly bool SupportsShaderBarrierDivergence;
|
||||||
public readonly bool SupportsShaderFloat64;
|
public readonly bool SupportsShaderFloat64;
|
||||||
|
public readonly bool SupportsTextureGatherOffsets;
|
||||||
public readonly bool SupportsTextureShadowLod;
|
public readonly bool SupportsTextureShadowLod;
|
||||||
public readonly bool SupportsVertexStoreAndAtomics;
|
public readonly bool SupportsVertexStoreAndAtomics;
|
||||||
public readonly bool SupportsViewportIndexVertexTessellation;
|
public readonly bool SupportsViewportIndexVertexTessellation;
|
||||||
@ -92,6 +93,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
bool supportsShaderBallot,
|
bool supportsShaderBallot,
|
||||||
bool supportsShaderBarrierDivergence,
|
bool supportsShaderBarrierDivergence,
|
||||||
bool supportsShaderFloat64,
|
bool supportsShaderFloat64,
|
||||||
|
bool supportsTextureGatherOffsets,
|
||||||
bool supportsTextureShadowLod,
|
bool supportsTextureShadowLod,
|
||||||
bool supportsVertexStoreAndAtomics,
|
bool supportsVertexStoreAndAtomics,
|
||||||
bool supportsViewportIndexVertexTessellation,
|
bool supportsViewportIndexVertexTessellation,
|
||||||
@ -142,6 +144,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
SupportsShaderBallot = supportsShaderBallot;
|
SupportsShaderBallot = supportsShaderBallot;
|
||||||
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
|
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
|
||||||
SupportsShaderFloat64 = supportsShaderFloat64;
|
SupportsShaderFloat64 = supportsShaderFloat64;
|
||||||
|
SupportsTextureGatherOffsets = supportsTextureGatherOffsets;
|
||||||
SupportsTextureShadowLod = supportsTextureShadowLod;
|
SupportsTextureShadowLod = supportsTextureShadowLod;
|
||||||
SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics;
|
SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics;
|
||||||
SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;
|
SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;
|
||||||
|
@ -32,6 +32,11 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ref TState State => ref _state.State;
|
public ref TState State => ref _state.State;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current shadow state.
|
||||||
|
/// </summary>
|
||||||
|
public ref TState ShadowState => ref _shadowState.State;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the device state, with shadow state.
|
/// Creates a new instance of the device state, with shadow state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.Device;
|
using Ryujinx.Graphics.Device;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||||
|
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||||
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
@ -15,9 +18,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
|||||||
private const int ColorLayerCountOffset = 0x818;
|
private const int ColorLayerCountOffset = 0x818;
|
||||||
private const int ColorStructSize = 0x40;
|
private const int ColorStructSize = 0x40;
|
||||||
private const int ZetaLayerCountOffset = 0x1230;
|
private const int ZetaLayerCountOffset = 0x1230;
|
||||||
|
private const int UniformBufferBindVertexOffset = 0x2410;
|
||||||
|
private const int FirstVertexOffset = 0x1434;
|
||||||
|
|
||||||
private const int IndirectIndexedDataEntrySize = 0x14;
|
private const int IndirectIndexedDataEntrySize = 0x14;
|
||||||
|
|
||||||
|
private const int LogicOpOffset = 0x19c4;
|
||||||
|
private const int ShaderIdScratchOffset = 0x3470;
|
||||||
|
private const int ShaderAddressScratchOffset = 0x3488;
|
||||||
|
private const int UpdateConstantBufferAddressesBase = 0x34a8;
|
||||||
|
private const int UpdateConstantBufferSizesBase = 0x34bc;
|
||||||
|
private const int UpdateConstantBufferAddressCbu = 0x3460;
|
||||||
|
|
||||||
private readonly GPFifoProcessor _processor;
|
private readonly GPFifoProcessor _processor;
|
||||||
private readonly MacroHLEFunctionName _functionName;
|
private readonly MacroHLEFunctionName _functionName;
|
||||||
|
|
||||||
@ -49,6 +61,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
|||||||
{
|
{
|
||||||
switch (_functionName)
|
switch (_functionName)
|
||||||
{
|
{
|
||||||
|
case MacroHLEFunctionName.BindShaderProgram:
|
||||||
|
BindShaderProgram(state, arg0);
|
||||||
|
break;
|
||||||
case MacroHLEFunctionName.ClearColor:
|
case MacroHLEFunctionName.ClearColor:
|
||||||
ClearColor(state, arg0);
|
ClearColor(state, arg0);
|
||||||
break;
|
break;
|
||||||
@ -58,6 +73,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
|||||||
case MacroHLEFunctionName.DrawArraysInstanced:
|
case MacroHLEFunctionName.DrawArraysInstanced:
|
||||||
DrawArraysInstanced(state, arg0);
|
DrawArraysInstanced(state, arg0);
|
||||||
break;
|
break;
|
||||||
|
case MacroHLEFunctionName.DrawElements:
|
||||||
|
DrawElements(state, arg0);
|
||||||
|
break;
|
||||||
case MacroHLEFunctionName.DrawElementsInstanced:
|
case MacroHLEFunctionName.DrawElementsInstanced:
|
||||||
DrawElementsInstanced(state, arg0);
|
DrawElementsInstanced(state, arg0);
|
||||||
break;
|
break;
|
||||||
@ -67,6 +85,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
|||||||
case MacroHLEFunctionName.MultiDrawElementsIndirectCount:
|
case MacroHLEFunctionName.MultiDrawElementsIndirectCount:
|
||||||
MultiDrawElementsIndirectCount(state, arg0);
|
MultiDrawElementsIndirectCount(state, arg0);
|
||||||
break;
|
break;
|
||||||
|
case MacroHLEFunctionName.UpdateBlendState:
|
||||||
|
UpdateBlendState(state, arg0);
|
||||||
|
break;
|
||||||
|
case MacroHLEFunctionName.UpdateColorMasks:
|
||||||
|
UpdateColorMasks(state, arg0);
|
||||||
|
break;
|
||||||
|
case MacroHLEFunctionName.UpdateUniformBufferState:
|
||||||
|
UpdateUniformBufferState(state, arg0);
|
||||||
|
break;
|
||||||
|
case MacroHLEFunctionName.UpdateUniformBufferStateCbu:
|
||||||
|
UpdateUniformBufferStateCbu(state, arg0);
|
||||||
|
break;
|
||||||
|
case MacroHLEFunctionName.UpdateUniformBufferStateCbuV2:
|
||||||
|
UpdateUniformBufferStateCbuV2(state, arg0);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException(_functionName.ToString());
|
throw new NotImplementedException(_functionName.ToString());
|
||||||
}
|
}
|
||||||
@ -75,6 +108,149 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
|||||||
Fifo.Clear();
|
Fifo.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binds a shader program with the index in arg0.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">GPU state at the time of the call</param>
|
||||||
|
/// <param name="arg0">First argument of the call</param>
|
||||||
|
private void BindShaderProgram(IDeviceState state, int arg0)
|
||||||
|
{
|
||||||
|
int scratchOffset = ShaderIdScratchOffset + arg0 * 4;
|
||||||
|
|
||||||
|
int lastId = state.Read(scratchOffset);
|
||||||
|
int id = FetchParam().Word;
|
||||||
|
int offset = FetchParam().Word;
|
||||||
|
|
||||||
|
if (lastId == id)
|
||||||
|
{
|
||||||
|
FetchParam();
|
||||||
|
FetchParam();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_processor.ThreedClass.SetShaderOffset(arg0, (uint)offset);
|
||||||
|
|
||||||
|
// Removes overflow on the method address into the increment portion.
|
||||||
|
// Present in the original macro.
|
||||||
|
int addrMask = unchecked((int)0xfffc0fff) << 2;
|
||||||
|
|
||||||
|
state.Write(scratchOffset & addrMask, id);
|
||||||
|
state.Write((ShaderAddressScratchOffset + arg0 * 4) & addrMask, offset);
|
||||||
|
|
||||||
|
int stage = FetchParam().Word;
|
||||||
|
uint cbAddress = (uint)FetchParam().Word;
|
||||||
|
|
||||||
|
_processor.ThreedClass.UpdateUniformBufferState(65536, cbAddress >> 24, cbAddress << 8);
|
||||||
|
|
||||||
|
int stageOffset = (stage & 0x7f) << 3;
|
||||||
|
|
||||||
|
state.Write((UniformBufferBindVertexOffset + stageOffset * 4) & addrMask, 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates uniform buffer state for update or bind.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">GPU state at the time of the call</param>
|
||||||
|
/// <param name="arg0">First argument of the call</param>
|
||||||
|
private void UpdateUniformBufferState(IDeviceState state, int arg0)
|
||||||
|
{
|
||||||
|
uint address = (uint)state.Read(UpdateConstantBufferAddressesBase + arg0 * 4);
|
||||||
|
int size = state.Read(UpdateConstantBufferSizesBase + arg0 * 4);
|
||||||
|
|
||||||
|
_processor.ThreedClass.UpdateUniformBufferState(size, address >> 24, address << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates uniform buffer state for update.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">GPU state at the time of the call</param>
|
||||||
|
/// <param name="arg0">First argument of the call</param>
|
||||||
|
private void UpdateUniformBufferStateCbu(IDeviceState state, int arg0)
|
||||||
|
{
|
||||||
|
uint address = (uint)state.Read(UpdateConstantBufferAddressCbu);
|
||||||
|
|
||||||
|
UniformBufferState ubState = new()
|
||||||
|
{
|
||||||
|
Address = new()
|
||||||
|
{
|
||||||
|
High = address >> 24,
|
||||||
|
Low = address << 8
|
||||||
|
},
|
||||||
|
Size = 24320,
|
||||||
|
Offset = arg0 << 2
|
||||||
|
};
|
||||||
|
|
||||||
|
_processor.ThreedClass.UpdateUniformBufferState(ubState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates uniform buffer state for update.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">GPU state at the time of the call</param>
|
||||||
|
/// <param name="arg0">First argument of the call</param>
|
||||||
|
private void UpdateUniformBufferStateCbuV2(IDeviceState state, int arg0)
|
||||||
|
{
|
||||||
|
uint address = (uint)state.Read(UpdateConstantBufferAddressCbu);
|
||||||
|
|
||||||
|
UniformBufferState ubState = new()
|
||||||
|
{
|
||||||
|
Address = new()
|
||||||
|
{
|
||||||
|
High = address >> 24,
|
||||||
|
Low = address << 8
|
||||||
|
},
|
||||||
|
Size = 28672,
|
||||||
|
Offset = arg0 << 2
|
||||||
|
};
|
||||||
|
|
||||||
|
_processor.ThreedClass.UpdateUniformBufferState(ubState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates blend enable using the given argument.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">GPU state at the time of the call</param>
|
||||||
|
/// <param name="arg0">First argument of the call</param>
|
||||||
|
private void UpdateBlendState(IDeviceState state, int arg0)
|
||||||
|
{
|
||||||
|
state.Write(LogicOpOffset, 0);
|
||||||
|
|
||||||
|
Array8<Boolean32> enable = new();
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
enable[i] = new Boolean32((uint)(arg0 >> (i + 8)) & 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
_processor.ThreedClass.UpdateBlendEnable(ref enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates color masks using the given argument and three pushed arguments.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">GPU state at the time of the call</param>
|
||||||
|
/// <param name="arg0">First argument of the call</param>
|
||||||
|
private void UpdateColorMasks(IDeviceState state, int arg0)
|
||||||
|
{
|
||||||
|
Array8<RtColorMask> masks = new();
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
masks[index++] = new RtColorMask((uint)arg0 & 0x1fff);
|
||||||
|
masks[index++] = new RtColorMask(((uint)arg0 >> 16) & 0x1fff);
|
||||||
|
|
||||||
|
if (i != 3)
|
||||||
|
{
|
||||||
|
arg0 = FetchParam().Word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_processor.ThreedClass.UpdateColorMasks(ref masks);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears one bound color target.
|
/// Clears one bound color target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -129,6 +305,36 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
|||||||
indexed: false);
|
indexed: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a indexed draw.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">GPU state at the time of the call</param>
|
||||||
|
/// <param name="arg0">First argument of the call</param>
|
||||||
|
private void DrawElements(IDeviceState state, int arg0)
|
||||||
|
{
|
||||||
|
var topology = (PrimitiveTopology)arg0;
|
||||||
|
|
||||||
|
var indexAddressHigh = FetchParam();
|
||||||
|
var indexAddressLow = FetchParam();
|
||||||
|
var indexType = FetchParam();
|
||||||
|
var firstIndex = 0;
|
||||||
|
var indexCount = FetchParam();
|
||||||
|
|
||||||
|
_processor.ThreedClass.UpdateIndexBuffer(
|
||||||
|
(uint)indexAddressHigh.Word,
|
||||||
|
(uint)indexAddressLow.Word,
|
||||||
|
(IndexType)indexType.Word);
|
||||||
|
|
||||||
|
_processor.ThreedClass.Draw(
|
||||||
|
topology,
|
||||||
|
indexCount.Word,
|
||||||
|
1,
|
||||||
|
firstIndex,
|
||||||
|
state.Read(FirstVertexOffset),
|
||||||
|
0,
|
||||||
|
indexed: true);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs a indexed draw.
|
/// Performs a indexed draw.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -6,11 +6,19 @@
|
|||||||
enum MacroHLEFunctionName
|
enum MacroHLEFunctionName
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
|
BindShaderProgram,
|
||||||
ClearColor,
|
ClearColor,
|
||||||
ClearDepthStencil,
|
ClearDepthStencil,
|
||||||
DrawArraysInstanced,
|
DrawArraysInstanced,
|
||||||
|
DrawElements,
|
||||||
DrawElementsInstanced,
|
DrawElementsInstanced,
|
||||||
DrawElementsIndirect,
|
DrawElementsIndirect,
|
||||||
MultiDrawElementsIndirectCount,
|
MultiDrawElementsIndirectCount,
|
||||||
|
|
||||||
|
UpdateBlendState,
|
||||||
|
UpdateColorMasks,
|
||||||
|
UpdateUniformBufferState,
|
||||||
|
UpdateUniformBufferStateCbu,
|
||||||
|
UpdateUniformBufferStateCbuV2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,12 +46,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
|||||||
|
|
||||||
private static readonly TableEntry[] _table = new TableEntry[]
|
private static readonly TableEntry[] _table = new TableEntry[]
|
||||||
{
|
{
|
||||||
|
new(MacroHLEFunctionName.BindShaderProgram, new Hash128(0x5d5efb912369f60b, 0x69131ed5019f08ef), 0x68),
|
||||||
new(MacroHLEFunctionName.ClearColor, new Hash128(0xA9FB28D1DC43645A, 0xB177E5D2EAE67FB0), 0x28),
|
new(MacroHLEFunctionName.ClearColor, new Hash128(0xA9FB28D1DC43645A, 0xB177E5D2EAE67FB0), 0x28),
|
||||||
new(MacroHLEFunctionName.ClearDepthStencil, new Hash128(0x1B96CB77D4879F4F, 0x8557032FE0C965FB), 0x24),
|
new(MacroHLEFunctionName.ClearDepthStencil, new Hash128(0x1B96CB77D4879F4F, 0x8557032FE0C965FB), 0x24),
|
||||||
new(MacroHLEFunctionName.DrawArraysInstanced, new Hash128(0x197FB416269DBC26, 0x34288C01DDA82202), 0x48),
|
new(MacroHLEFunctionName.DrawArraysInstanced, new Hash128(0x197FB416269DBC26, 0x34288C01DDA82202), 0x48),
|
||||||
|
new(MacroHLEFunctionName.DrawElements, new Hash128(0x3D7F32AE6C2702A7, 0x9353C9F41C1A244D), 0x20),
|
||||||
new(MacroHLEFunctionName.DrawElementsInstanced, new Hash128(0x1A501FD3D54EC8E0, 0x6CF570CF79DA74D6), 0x5c),
|
new(MacroHLEFunctionName.DrawElementsInstanced, new Hash128(0x1A501FD3D54EC8E0, 0x6CF570CF79DA74D6), 0x5c),
|
||||||
new(MacroHLEFunctionName.DrawElementsIndirect, new Hash128(0x86A3E8E903AF8F45, 0xD35BBA07C23860A4), 0x7c),
|
new(MacroHLEFunctionName.DrawElementsIndirect, new Hash128(0x86A3E8E903AF8F45, 0xD35BBA07C23860A4), 0x7c),
|
||||||
new(MacroHLEFunctionName.MultiDrawElementsIndirectCount, new Hash128(0x890AF57ED3FB1C37, 0x35D0C95C61F5386F), 0x19C),
|
new(MacroHLEFunctionName.MultiDrawElementsIndirectCount, new Hash128(0x890AF57ED3FB1C37, 0x35D0C95C61F5386F), 0x19C),
|
||||||
|
new(MacroHLEFunctionName.UpdateBlendState, new Hash128(0x40F6D4E7B08D7640, 0x82167BEEAECB959F), 0x28),
|
||||||
|
new(MacroHLEFunctionName.UpdateColorMasks, new Hash128(0x9EE32420B8441DFD, 0x6E7724759A57333E), 0x24),
|
||||||
|
new(MacroHLEFunctionName.UpdateUniformBufferState, new Hash128(0x8EE66706049CB0B0, 0x51C1CF906EC86F7C), 0x20),
|
||||||
|
new(MacroHLEFunctionName.UpdateUniformBufferStateCbu, new Hash128(0xA4592676A3E581A0, 0xA39E77FE19FE04AC), 0x18),
|
||||||
|
new(MacroHLEFunctionName.UpdateUniformBufferStateCbuV2, new Hash128(0x392FA750489983D4, 0x35BACE455155D2C3), 0x18)
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -62,18 +69,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
|||||||
/// <returns>True if the host supports the HLE macro, false otherwise</returns>
|
/// <returns>True if the host supports the HLE macro, false otherwise</returns>
|
||||||
private static bool IsMacroHLESupported(Capabilities caps, MacroHLEFunctionName name)
|
private static bool IsMacroHLESupported(Capabilities caps, MacroHLEFunctionName name)
|
||||||
{
|
{
|
||||||
if (name == MacroHLEFunctionName.ClearColor ||
|
if (name == MacroHLEFunctionName.MultiDrawElementsIndirectCount)
|
||||||
name == MacroHLEFunctionName.ClearDepthStencil ||
|
|
||||||
name == MacroHLEFunctionName.DrawArraysInstanced ||
|
|
||||||
name == MacroHLEFunctionName.DrawElementsInstanced ||
|
|
||||||
name == MacroHLEFunctionName.DrawElementsIndirect)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (name == MacroHLEFunctionName.MultiDrawElementsIndirectCount)
|
|
||||||
{
|
{
|
||||||
return caps.SupportsIndirectParameters;
|
return caps.SupportsIndirectParameters;
|
||||||
}
|
}
|
||||||
|
else if (name != MacroHLEFunctionName.None)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -10,4 +10,22 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||||||
MethodPassthrough = 2,
|
MethodPassthrough = 2,
|
||||||
MethodReplay = 3,
|
MethodReplay = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class SetMmeShadowRamControlModeExtensions
|
||||||
|
{
|
||||||
|
public static bool IsTrack(this SetMmeShadowRamControlMode mode)
|
||||||
|
{
|
||||||
|
return mode == SetMmeShadowRamControlMode.MethodTrack || mode == SetMmeShadowRamControlMode.MethodTrackWithFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsPassthrough(this SetMmeShadowRamControlMode mode)
|
||||||
|
{
|
||||||
|
return mode == SetMmeShadowRamControlMode.MethodPassthrough;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsReplay(this SetMmeShadowRamControlMode mode)
|
||||||
|
{
|
||||||
|
return mode == SetMmeShadowRamControlMode.MethodReplay;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
class StateUpdater
|
class StateUpdater
|
||||||
{
|
{
|
||||||
public const int ShaderStateIndex = 26;
|
public const int ShaderStateIndex = 26;
|
||||||
|
public const int RtColorMaskIndex = 14;
|
||||||
public const int RasterizerStateIndex = 15;
|
public const int RasterizerStateIndex = 15;
|
||||||
public const int ScissorStateIndex = 16;
|
public const int ScissorStateIndex = 16;
|
||||||
public const int VertexBufferStateIndex = 0;
|
public const int VertexBufferStateIndex = 0;
|
||||||
|
public const int BlendStateIndex = 2;
|
||||||
public const int IndexBufferStateIndex = 23;
|
public const int IndexBufferStateIndex = 23;
|
||||||
public const int PrimitiveRestartStateIndex = 12;
|
public const int PrimitiveRestartStateIndex = 12;
|
||||||
public const int RenderTargetStateIndex = 27;
|
public const int RenderTargetStateIndex = 27;
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
using Ryujinx.Graphics.Device;
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Graphics.Device;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
|
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
|
||||||
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
using Ryujinx.Graphics.Gpu.Synchronization;
|
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.Intrinsics;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
{
|
{
|
||||||
@ -26,6 +29,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
private readonly ConstantBufferUpdater _cbUpdater;
|
private readonly ConstantBufferUpdater _cbUpdater;
|
||||||
private readonly StateUpdater _stateUpdater;
|
private readonly StateUpdater _stateUpdater;
|
||||||
|
|
||||||
|
private SetMmeShadowRamControlMode ShadowMode => _state.State.SetMmeShadowRamControlMode;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the 3D engine class.
|
/// Creates a new instance of the 3D engine class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -228,6 +233,206 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
_cbUpdater.Update(data);
|
_cbUpdater.Update(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test if two 32 byte structs are equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of the 32-byte struct</typeparam>
|
||||||
|
/// <param name="lhs">First struct</param>
|
||||||
|
/// <param name="rhs">Second struct</param>
|
||||||
|
/// <returns>True if equal, false otherwise</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool UnsafeEquals32Byte<T>(ref T lhs, ref T rhs) where T : unmanaged
|
||||||
|
{
|
||||||
|
if (Vector256.IsHardwareAccelerated)
|
||||||
|
{
|
||||||
|
return Vector256.EqualsAll(
|
||||||
|
Unsafe.As<T, Vector256<uint>>(ref lhs),
|
||||||
|
Unsafe.As<T, Vector256<uint>>(ref rhs)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ref var lhsVec = ref Unsafe.As<T, Vector128<uint>>(ref lhs);
|
||||||
|
ref var rhsVec = ref Unsafe.As<T, Vector128<uint>>(ref rhs);
|
||||||
|
|
||||||
|
return Vector128.EqualsAll(lhsVec, rhsVec) &&
|
||||||
|
Vector128.EqualsAll(Unsafe.Add(ref lhsVec, 1), Unsafe.Add(ref rhsVec, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates blend enable. Respects current shadow mode.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="masks">Blend enable</param>
|
||||||
|
public void UpdateBlendEnable(ref Array8<Boolean32> enable)
|
||||||
|
{
|
||||||
|
var shadow = ShadowMode;
|
||||||
|
ref var state = ref _state.State.BlendEnable;
|
||||||
|
|
||||||
|
if (shadow.IsReplay())
|
||||||
|
{
|
||||||
|
enable = _state.ShadowState.BlendEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!UnsafeEquals32Byte(ref enable, ref state))
|
||||||
|
{
|
||||||
|
state = enable;
|
||||||
|
|
||||||
|
_stateUpdater.ForceDirty(StateUpdater.BlendStateIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shadow.IsTrack())
|
||||||
|
{
|
||||||
|
_state.ShadowState.BlendEnable = enable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates color masks. Respects current shadow mode.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="masks">Color masks</param>
|
||||||
|
public void UpdateColorMasks(ref Array8<RtColorMask> masks)
|
||||||
|
{
|
||||||
|
var shadow = ShadowMode;
|
||||||
|
ref var state = ref _state.State.RtColorMask;
|
||||||
|
|
||||||
|
if (shadow.IsReplay())
|
||||||
|
{
|
||||||
|
masks = _state.ShadowState.RtColorMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!UnsafeEquals32Byte(ref masks, ref state))
|
||||||
|
{
|
||||||
|
state = masks;
|
||||||
|
|
||||||
|
_stateUpdater.ForceDirty(StateUpdater.RtColorMaskIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shadow.IsTrack())
|
||||||
|
{
|
||||||
|
_state.ShadowState.RtColorMask = masks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates index buffer state for an indexed draw. Respects current shadow mode.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addrHigh">High part of the address</param>
|
||||||
|
/// <param name="addrLow">Low part of the address</param>
|
||||||
|
/// <param name="type">Type of the binding</param>
|
||||||
|
public void UpdateIndexBuffer(uint addrHigh, uint addrLow, IndexType type)
|
||||||
|
{
|
||||||
|
var shadow = ShadowMode;
|
||||||
|
ref var state = ref _state.State.IndexBufferState;
|
||||||
|
|
||||||
|
if (shadow.IsReplay())
|
||||||
|
{
|
||||||
|
ref var shadowState = ref _state.ShadowState.IndexBufferState;
|
||||||
|
addrHigh = shadowState.Address.High;
|
||||||
|
addrLow = shadowState.Address.Low;
|
||||||
|
type = shadowState.Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.Address.High != addrHigh || state.Address.Low != addrLow || state.Type != type)
|
||||||
|
{
|
||||||
|
state.Address.High = addrHigh;
|
||||||
|
state.Address.Low = addrLow;
|
||||||
|
state.Type = type;
|
||||||
|
|
||||||
|
_stateUpdater.ForceDirty(StateUpdater.IndexBufferStateIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shadow.IsTrack())
|
||||||
|
{
|
||||||
|
ref var shadowState = ref _state.ShadowState.IndexBufferState;
|
||||||
|
shadowState.Address.High = addrHigh;
|
||||||
|
shadowState.Address.Low = addrLow;
|
||||||
|
shadowState.Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates uniform buffer state for update or bind. Respects current shadow mode.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">Size of the binding</param>
|
||||||
|
/// <param name="addrHigh">High part of the addrsss</param>
|
||||||
|
/// <param name="addrLow">Low part of the address</param>
|
||||||
|
public void UpdateUniformBufferState(int size, uint addrHigh, uint addrLow)
|
||||||
|
{
|
||||||
|
var shadow = ShadowMode;
|
||||||
|
ref var state = ref _state.State.UniformBufferState;
|
||||||
|
|
||||||
|
if (shadow.IsReplay())
|
||||||
|
{
|
||||||
|
ref var shadowState = ref _state.ShadowState.UniformBufferState;
|
||||||
|
size = shadowState.Size;
|
||||||
|
addrHigh = shadowState.Address.High;
|
||||||
|
addrLow = shadowState.Address.Low;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Size = size;
|
||||||
|
state.Address.High = addrHigh;
|
||||||
|
state.Address.Low = addrLow;
|
||||||
|
|
||||||
|
if (shadow.IsTrack())
|
||||||
|
{
|
||||||
|
ref var shadowState = ref _state.ShadowState.UniformBufferState;
|
||||||
|
shadowState.Size = size;
|
||||||
|
shadowState.Address.High = addrHigh;
|
||||||
|
shadowState.Address.Low = addrLow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a shader offset. Respects current shadow mode.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of the shader to update</param>
|
||||||
|
/// <param name="offset">Offset to update with</param>
|
||||||
|
public void SetShaderOffset(int index, uint offset)
|
||||||
|
{
|
||||||
|
var shadow = ShadowMode;
|
||||||
|
ref var shaderState = ref _state.State.ShaderState[index];
|
||||||
|
|
||||||
|
if (shadow.IsReplay())
|
||||||
|
{
|
||||||
|
offset = _state.ShadowState.ShaderState[index].Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shaderState.Offset != offset)
|
||||||
|
{
|
||||||
|
shaderState.Offset = offset;
|
||||||
|
|
||||||
|
_stateUpdater.ForceDirty(StateUpdater.ShaderStateIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shadow.IsTrack())
|
||||||
|
{
|
||||||
|
_state.ShadowState.ShaderState[index].Offset = offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates uniform buffer state for update. Respects current shadow mode.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ubState">Uniform buffer state</param>
|
||||||
|
public void UpdateUniformBufferState(UniformBufferState ubState)
|
||||||
|
{
|
||||||
|
var shadow = ShadowMode;
|
||||||
|
ref var state = ref _state.State.UniformBufferState;
|
||||||
|
|
||||||
|
if (shadow.IsReplay())
|
||||||
|
{
|
||||||
|
ubState = _state.ShadowState.UniformBufferState;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = ubState;
|
||||||
|
|
||||||
|
if (shadow.IsTrack())
|
||||||
|
{
|
||||||
|
_state.ShadowState.UniformBufferState = ubState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Launches the Inline-to-Memory DMA copy operation.
|
/// Launches the Inline-to-Memory DMA copy operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -590,9 +590,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
struct RtColorMask
|
struct RtColorMask
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0649 // Field is never assigned to
|
|
||||||
public uint Packed;
|
public uint Packed;
|
||||||
#pragma warning restore CS0649
|
|
||||||
|
public RtColorMask(uint packed)
|
||||||
|
{
|
||||||
|
Packed = packed;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unpacks red channel enable.
|
/// Unpacks red channel enable.
|
||||||
|
@ -5,9 +5,12 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
readonly struct Boolean32
|
readonly struct Boolean32
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0649 // Field is never assigned to
|
|
||||||
private readonly uint _value;
|
private readonly uint _value;
|
||||||
#pragma warning restore CS0649
|
|
||||||
|
public Boolean32(uint value)
|
||||||
|
{
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
public static implicit operator bool(Boolean32 value)
|
public static implicit operator bool(Boolean32 value)
|
||||||
{
|
{
|
||||||
|
@ -101,6 +101,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AlwaysFlushOnOverlap { get; private set; }
|
public bool AlwaysFlushOnOverlap { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the texture was fully unmapped since the modified flag was set, and flushes should be ignored until it is modified again.
|
||||||
|
/// </summary>
|
||||||
|
public bool FlushStale { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Increments when the host texture is swapped, or when the texture is removed from all pools.
|
/// Increments when the host texture is swapped, or when the texture is removed from all pools.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -149,6 +154,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HadPoolOwner { get; private set; }
|
public bool HadPoolOwner { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
/// Physical memory ranges where the texture data is located.
|
/// Physical memory ranges where the texture data is located.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MultiRange Range { get; private set; }
|
public MultiRange Range { get; private set; }
|
||||||
@ -1411,6 +1417,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SignalModified()
|
public void SignalModified()
|
||||||
{
|
{
|
||||||
|
FlushStale = false;
|
||||||
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
||||||
|
|
||||||
if (_modifiedStale || Group.HasCopyDependencies)
|
if (_modifiedStale || Group.HasCopyDependencies)
|
||||||
@ -1431,6 +1438,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
if (bound)
|
if (bound)
|
||||||
{
|
{
|
||||||
|
FlushStale = false;
|
||||||
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1695,12 +1703,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <param name="unmapRange">The range of memory being unmapped</param>
|
/// <param name="unmapRange">The range of memory being unmapped</param>
|
||||||
public void Unmapped(MultiRange unmapRange)
|
public void Unmapped(MultiRange unmapRange)
|
||||||
{
|
{
|
||||||
|
if (unmapRange.Contains(Range))
|
||||||
|
{
|
||||||
|
// If this is a full unmap, prevent flushes until the texture is mapped again.
|
||||||
|
FlushStale = true;
|
||||||
|
}
|
||||||
|
|
||||||
ChangedMapping = true;
|
ChangedMapping = true;
|
||||||
|
|
||||||
if (Group.Storage == this)
|
if (Group.Storage == this)
|
||||||
{
|
{
|
||||||
Group.Unmapped();
|
Group.Unmapped();
|
||||||
|
|
||||||
Group.ClearModified(unmapRange);
|
Group.ClearModified(unmapRange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,8 +107,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
// Any texture that has been unmapped at any point or is partially unmapped
|
// Any texture that has been unmapped at any point or is partially unmapped
|
||||||
// should update their pool references after the remap completes.
|
// should update their pool references after the remap completes.
|
||||||
|
|
||||||
MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
|
|
||||||
|
|
||||||
foreach (var texture in _partiallyMappedTextures)
|
foreach (var texture in _partiallyMappedTextures)
|
||||||
{
|
{
|
||||||
texture.UpdatePoolMappings();
|
texture.UpdatePoolMappings();
|
||||||
@ -735,9 +733,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
if (overlap.IsView)
|
if (overlap.IsView)
|
||||||
{
|
{
|
||||||
overlapCompatibility = overlapCompatibility == TextureViewCompatibility.FormatAlias ?
|
overlapCompatibility = TextureViewCompatibility.CopyOnly;
|
||||||
TextureViewCompatibility.Incompatible :
|
|
||||||
TextureViewCompatibility.CopyOnly;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -815,7 +811,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
Texture overlap = _textureOverlaps[index];
|
Texture overlap = _textureOverlaps[index];
|
||||||
OverlapInfo oInfo = _overlapInfo[index];
|
OverlapInfo oInfo = _overlapInfo[index];
|
||||||
|
|
||||||
if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible || oInfo.Compatibility == TextureViewCompatibility.FormatAlias)
|
if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible)
|
||||||
{
|
{
|
||||||
if (!overlap.IsView && texture.DataOverlaps(overlap, oInfo.Compatibility))
|
if (!overlap.IsView && texture.DataOverlaps(overlap, oInfo.Compatibility))
|
||||||
{
|
{
|
||||||
|
@ -226,7 +226,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
// D32F and R32F texture have the same representation internally,
|
// D32F and R32F texture have the same representation internally,
|
||||||
// however the R32F format is used to sample from depth textures.
|
// however the R32F format is used to sample from depth textures.
|
||||||
if (lhs.FormatInfo.Format == Format.D32Float && rhs.FormatInfo.Format == Format.R32Float && (forSampler || depthAlias))
|
if (IsValidDepthAsColorAlias(lhs.FormatInfo.Format, rhs.FormatInfo.Format) && (forSampler || depthAlias))
|
||||||
{
|
{
|
||||||
return TextureMatchQuality.FormatAlias;
|
return TextureMatchQuality.FormatAlias;
|
||||||
}
|
}
|
||||||
@ -239,13 +239,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
return TextureMatchQuality.FormatAlias;
|
return TextureMatchQuality.FormatAlias;
|
||||||
}
|
}
|
||||||
|
else if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
|
||||||
if (lhs.FormatInfo.Format == Format.D16Unorm && rhs.FormatInfo.Format == Format.R16Unorm)
|
|
||||||
{
|
|
||||||
return TextureMatchQuality.FormatAlias;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
|
|
||||||
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
|
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
|
||||||
{
|
{
|
||||||
return TextureMatchQuality.FormatAlias;
|
return TextureMatchQuality.FormatAlias;
|
||||||
@ -374,6 +368,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
return stride == rhs.Stride ? TextureViewCompatibility.CopyOnly : TextureViewCompatibility.LayoutIncompatible;
|
return stride == rhs.Stride ? TextureViewCompatibility.CopyOnly : TextureViewCompatibility.LayoutIncompatible;
|
||||||
}
|
}
|
||||||
|
else if (lhs.Target.IsMultisample() != rhs.Target.IsMultisample() && alignedWidthMatches && lhsAlignedSize.Height == rhsAlignedSize.Height)
|
||||||
|
{
|
||||||
|
// Copy between multisample and non-multisample textures with mismatching size is allowed,
|
||||||
|
// as long aligned size matches.
|
||||||
|
|
||||||
|
return TextureViewCompatibility.CopyOnly;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return TextureViewCompatibility.LayoutIncompatible;
|
return TextureViewCompatibility.LayoutIncompatible;
|
||||||
@ -625,12 +626,27 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil())
|
if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil())
|
||||||
{
|
{
|
||||||
return FormatMatches(lhs, rhs, flags.HasFlag(TextureSearchFlags.ForSampler), flags.HasFlag(TextureSearchFlags.DepthAlias)) switch
|
bool forSampler = flags.HasFlag(TextureSearchFlags.ForSampler);
|
||||||
|
bool depthAlias = flags.HasFlag(TextureSearchFlags.DepthAlias);
|
||||||
|
|
||||||
|
TextureMatchQuality matchQuality = FormatMatches(lhs, rhs, forSampler, depthAlias);
|
||||||
|
|
||||||
|
if (matchQuality == TextureMatchQuality.Perfect)
|
||||||
{
|
{
|
||||||
TextureMatchQuality.Perfect => TextureViewCompatibility.Full,
|
return TextureViewCompatibility.Full;
|
||||||
TextureMatchQuality.FormatAlias => TextureViewCompatibility.FormatAlias,
|
}
|
||||||
_ => TextureViewCompatibility.Incompatible,
|
else if (matchQuality == TextureMatchQuality.FormatAlias)
|
||||||
};
|
{
|
||||||
|
return TextureViewCompatibility.FormatAlias;
|
||||||
|
}
|
||||||
|
else if (IsValidColorAsDepthAlias(lhsFormat.Format, rhsFormat.Format) || IsValidDepthAsColorAlias(lhsFormat.Format, rhsFormat.Format))
|
||||||
|
{
|
||||||
|
return TextureViewCompatibility.CopyOnly;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return TextureViewCompatibility.Incompatible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps))
|
if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps))
|
||||||
@ -659,6 +675,30 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
return TextureViewCompatibility.Incompatible;
|
return TextureViewCompatibility.Incompatible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if it's valid to alias a color format as a depth format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lhsFormat">Source format to be checked</param>
|
||||||
|
/// <param name="rhsFormat">Target format to be checked</param>
|
||||||
|
/// <returns>True if it's valid to alias the formats</returns>
|
||||||
|
private static bool IsValidColorAsDepthAlias(Format lhsFormat, Format rhsFormat)
|
||||||
|
{
|
||||||
|
return (lhsFormat == Format.R32Float && rhsFormat == Format.D32Float) ||
|
||||||
|
(lhsFormat == Format.R16Unorm && rhsFormat == Format.D16Unorm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if it's valid to alias a depth format as a color format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lhsFormat">Source format to be checked</param>
|
||||||
|
/// <param name="rhsFormat">Target format to be checked</param>
|
||||||
|
/// <returns>True if it's valid to alias the formats</returns>
|
||||||
|
private static bool IsValidDepthAsColorAlias(Format lhsFormat, Format rhsFormat)
|
||||||
|
{
|
||||||
|
return (lhsFormat == Format.D32Float && rhsFormat == Format.R32Float) ||
|
||||||
|
(lhsFormat == Format.D16Unorm && rhsFormat == Format.R16Unorm);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if aliasing of two formats that would normally be considered incompatible be allowed,
|
/// Checks if aliasing of two formats that would normally be considered incompatible be allowed,
|
||||||
/// using copy dependencies.
|
/// using copy dependencies.
|
||||||
|
@ -1659,6 +1659,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If size is zero, we have nothing to flush.
|
||||||
|
// If the flush is stale, we should ignore it because the texture was unmapped since the modified
|
||||||
|
// flag was set, and flushing it is not safe anymore as the GPU might no longer own the memory.
|
||||||
|
if (size == 0 || Storage.FlushStale)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// There is a small gap here where the action is removed but _actionRegistered is still 1.
|
// There is a small gap here where the action is removed but _actionRegistered is still 1.
|
||||||
// In this case it will skip registering the action, but here we are already handling it,
|
// In this case it will skip registering the action, but here we are already handling it,
|
||||||
// so there shouldn't be any issue as it's the same handler for all actions.
|
// so there shouldn't be any issue as it's the same handler for all actions.
|
||||||
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||||||
private const ushort FileFormatVersionMajor = 1;
|
private const ushort FileFormatVersionMajor = 1;
|
||||||
private const ushort FileFormatVersionMinor = 2;
|
private const ushort FileFormatVersionMinor = 2;
|
||||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||||
private const uint CodeGenVersion = 5682;
|
private const uint CodeGenVersion = 5791;
|
||||||
|
|
||||||
private const string SharedTocFileName = "shared.toc";
|
private const string SharedTocFileName = "shared.toc";
|
||||||
private const string SharedDataFileName = "shared.data";
|
private const string SharedDataFileName = "shared.data";
|
||||||
|
@ -186,6 +186,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
|
|
||||||
public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat;
|
public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat;
|
||||||
|
|
||||||
|
public bool QueryHostSupportsTextureGatherOffsets() => _context.Capabilities.SupportsTextureGatherOffsets;
|
||||||
|
|
||||||
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
|
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
|
||||||
|
|
||||||
public bool QueryHostSupportsTransformFeedback() => _context.Capabilities.SupportsTransformFeedback;
|
public bool QueryHostSupportsTransformFeedback() => _context.Capabilities.SupportsTransformFeedback;
|
||||||
|
@ -367,7 +367,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
return to;
|
return to;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextureView PboCopy(TextureView from, TextureView to, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int width, int height)
|
public void PboCopy(TextureView from, TextureView to, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int width, int height)
|
||||||
{
|
{
|
||||||
int dstWidth = width;
|
int dstWidth = width;
|
||||||
int dstHeight = height;
|
int dstHeight = height;
|
||||||
@ -445,8 +445,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
}
|
}
|
||||||
|
|
||||||
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
|
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
|
||||||
|
|
||||||
return to;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsurePbo(TextureView view)
|
private void EnsurePbo(TextureView view)
|
||||||
|
@ -140,6 +140,28 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
|
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
|
||||||
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, 0, firstLayer, 0, firstLevel, layers, levels);
|
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, 0, firstLayer, 0, firstLevel, layers, levels);
|
||||||
}
|
}
|
||||||
|
else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil())
|
||||||
|
{
|
||||||
|
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
|
||||||
|
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
|
||||||
|
|
||||||
|
for (int level = 0; level < levels; level++)
|
||||||
|
{
|
||||||
|
int srcWidth = Math.Max(1, Width >> level);
|
||||||
|
int srcHeight = Math.Max(1, Height >> level);
|
||||||
|
|
||||||
|
int dstWidth = Math.Max(1, destinationView.Width >> (firstLevel + level));
|
||||||
|
int dstHeight = Math.Max(1, destinationView.Height >> (firstLevel + level));
|
||||||
|
|
||||||
|
int minWidth = Math.Min(srcWidth, dstWidth);
|
||||||
|
int minHeight = Math.Min(srcHeight, dstHeight);
|
||||||
|
|
||||||
|
for (int layer = 0; layer < layers; layer++)
|
||||||
|
{
|
||||||
|
_renderer.TextureCopy.PboCopy(this, destinationView, 0, firstLayer + layer, 0, firstLevel + level, minWidth, minHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
|
_renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
|
||||||
@ -169,6 +191,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
{
|
{
|
||||||
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
||||||
}
|
}
|
||||||
|
else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil())
|
||||||
|
{
|
||||||
|
int minWidth = Math.Min(Width, destinationView.Width);
|
||||||
|
int minHeight = Math.Min(Height, destinationView.Height);
|
||||||
|
|
||||||
|
_renderer.TextureCopy.PboCopy(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, minWidth, minHeight);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
_renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
||||||
|
@ -163,6 +163,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
|
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
|
||||||
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
|
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
|
||||||
supportsShaderFloat64: true,
|
supportsShaderFloat64: true,
|
||||||
|
supportsTextureGatherOffsets: true,
|
||||||
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
||||||
supportsVertexStoreAndAtomics: true,
|
supportsVertexStoreAndAtomics: true,
|
||||||
supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
|
supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
|
||||||
|
@ -92,14 +92,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
|
|
||||||
private static string GetIndentation(int level)
|
private static string GetIndentation(int level)
|
||||||
{
|
{
|
||||||
string indentation = string.Empty;
|
StringBuilder indentationBuilder = new();
|
||||||
|
|
||||||
for (int index = 0; index < level; index++)
|
for (int index = 0; index < level; index++)
|
||||||
{
|
{
|
||||||
indentation += Tab;
|
indentationBuilder.Append(Tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
return indentation;
|
return indentationBuilder.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
|||||||
using Ryujinx.Graphics.Shader.StructuredIr;
|
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||||
using Ryujinx.Graphics.Shader.Translation;
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenBallot;
|
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenBallot;
|
||||||
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenCall;
|
using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenCall;
|
||||||
@ -67,11 +68,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
|
|
||||||
int arity = (int)(info.Type & InstType.ArityMask);
|
int arity = (int)(info.Type & InstType.ArityMask);
|
||||||
|
|
||||||
string args = string.Empty;
|
StringBuilder builder = new();
|
||||||
|
|
||||||
if (atomic && (operation.StorageKind == StorageKind.StorageBuffer || operation.StorageKind == StorageKind.SharedMemory))
|
if (atomic && (operation.StorageKind == StorageKind.StorageBuffer || operation.StorageKind == StorageKind.SharedMemory))
|
||||||
{
|
{
|
||||||
args = GenerateLoadOrStore(context, operation, isStore: false);
|
builder.Append(GenerateLoadOrStore(context, operation, isStore: false));
|
||||||
|
|
||||||
AggregateType dstType = operation.Inst == Instruction.AtomicMaxS32 || operation.Inst == Instruction.AtomicMinS32
|
AggregateType dstType = operation.Inst == Instruction.AtomicMaxS32 || operation.Inst == Instruction.AtomicMinS32
|
||||||
? AggregateType.S32
|
? AggregateType.S32
|
||||||
@ -79,7 +80,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
|
|
||||||
for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++)
|
for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++)
|
||||||
{
|
{
|
||||||
args += ", " + GetSoureExpr(context, operation.GetSource(argIndex), dstType);
|
builder.Append($", {GetSoureExpr(context, operation.GetSource(argIndex), dstType)}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -88,16 +89,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
{
|
{
|
||||||
if (argIndex != 0)
|
if (argIndex != 0)
|
||||||
{
|
{
|
||||||
args += ", ";
|
builder.Append(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
AggregateType dstType = GetSrcVarType(inst, argIndex);
|
AggregateType dstType = GetSrcVarType(inst, argIndex);
|
||||||
|
|
||||||
args += GetSoureExpr(context, operation.GetSource(argIndex), dstType);
|
builder.Append(GetSoureExpr(context, operation.GetSource(argIndex), dstType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return info.OpName + '(' + args + ')';
|
return $"{info.OpName}({builder})";
|
||||||
}
|
}
|
||||||
else if ((info.Type & InstType.Op) != 0)
|
else if ((info.Type & InstType.Op) != 0)
|
||||||
{
|
{
|
||||||
@ -184,8 +185,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
case Instruction.TextureSample:
|
case Instruction.TextureSample:
|
||||||
return TextureSample(context, operation);
|
return TextureSample(context, operation);
|
||||||
|
|
||||||
case Instruction.TextureSize:
|
case Instruction.TextureQuerySamples:
|
||||||
return TextureSize(context, operation);
|
return TextureQuerySamples(context, operation);
|
||||||
|
|
||||||
|
case Instruction.TextureQuerySize:
|
||||||
|
return TextureQuerySize(context, operation);
|
||||||
|
|
||||||
case Instruction.UnpackDouble2x32:
|
case Instruction.UnpackDouble2x32:
|
||||||
return UnpackDouble2x32(context, operation);
|
return UnpackDouble2x32(context, operation);
|
||||||
|
@ -118,7 +118,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
Add(Instruction.Subtract, InstType.OpBinary, "-", 2);
|
Add(Instruction.Subtract, InstType.OpBinary, "-", 2);
|
||||||
Add(Instruction.SwizzleAdd, InstType.CallTernary, HelperFunctionNames.SwizzleAdd);
|
Add(Instruction.SwizzleAdd, InstType.CallTernary, HelperFunctionNames.SwizzleAdd);
|
||||||
Add(Instruction.TextureSample, InstType.Special);
|
Add(Instruction.TextureSample, InstType.Special);
|
||||||
Add(Instruction.TextureSize, InstType.Special);
|
Add(Instruction.TextureQuerySamples, InstType.Special);
|
||||||
|
Add(Instruction.TextureQuerySize, InstType.Special);
|
||||||
Add(Instruction.Truncate, InstType.CallUnary, "trunc");
|
Add(Instruction.Truncate, InstType.CallUnary, "trunc");
|
||||||
Add(Instruction.UnpackDouble2x32, InstType.Special);
|
Add(Instruction.UnpackDouble2x32, InstType.Special);
|
||||||
Add(Instruction.UnpackHalf2x16, InstType.Special);
|
Add(Instruction.UnpackHalf2x16, InstType.Special);
|
||||||
|
@ -517,7 +517,33 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
return texCall;
|
return texCall;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string TextureSize(CodeGenContext context, AstOperation operation)
|
public static string TextureQuerySamples(CodeGenContext context, AstOperation operation)
|
||||||
|
{
|
||||||
|
AstTextureOperation texOp = (AstTextureOperation)operation;
|
||||||
|
|
||||||
|
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||||
|
|
||||||
|
// TODO: Bindless texture support. For now we just return 0.
|
||||||
|
if (isBindless)
|
||||||
|
{
|
||||||
|
return NumberFormatter.FormatInt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||||
|
|
||||||
|
string indexExpr = null;
|
||||||
|
|
||||||
|
if (isIndexed)
|
||||||
|
{
|
||||||
|
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
|
||||||
|
}
|
||||||
|
|
||||||
|
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
|
||||||
|
|
||||||
|
return $"textureSamples({samplerName})";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string TextureQuerySize(CodeGenContext context, AstOperation operation)
|
||||||
{
|
{
|
||||||
AstTextureOperation texOp = (AstTextureOperation)operation;
|
AstTextureOperation texOp = (AstTextureOperation)operation;
|
||||||
|
|
||||||
@ -753,17 +779,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
|
|
||||||
private static string GetMaskMultiDest(int mask)
|
private static string GetMaskMultiDest(int mask)
|
||||||
{
|
{
|
||||||
string swizzle = ".";
|
StringBuilder swizzleBuilder = new();
|
||||||
|
swizzleBuilder.Append('.');
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
if ((mask & (1 << i)) != 0)
|
if ((mask & (1 << i)) != 0)
|
||||||
{
|
{
|
||||||
swizzle += "xyzw"[i];
|
swizzleBuilder.Append("xyzw"[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return swizzle;
|
return swizzleBuilder.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
|
|
||||||
public StructuredFunction CurrentFunction { get; set; }
|
public StructuredFunction CurrentFunction { get; set; }
|
||||||
private readonly Dictionary<AstOperand, Instruction> _locals = new();
|
private readonly Dictionary<AstOperand, Instruction> _locals = new();
|
||||||
private readonly Dictionary<int, Instruction[]> _localForArgs = new();
|
|
||||||
private readonly Dictionary<int, Instruction> _funcArgs = new();
|
private readonly Dictionary<int, Instruction> _funcArgs = new();
|
||||||
private readonly Dictionary<int, (StructuredFunction, Instruction)> _functions = new();
|
private readonly Dictionary<int, (StructuredFunction, Instruction)> _functions = new();
|
||||||
|
|
||||||
@ -112,7 +111,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
IsMainFunction = isMainFunction;
|
IsMainFunction = isMainFunction;
|
||||||
MayHaveReturned = false;
|
MayHaveReturned = false;
|
||||||
_locals.Clear();
|
_locals.Clear();
|
||||||
_localForArgs.Clear();
|
|
||||||
_funcArgs.Clear();
|
_funcArgs.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,11 +167,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
_locals.Add(local, spvLocal);
|
_locals.Add(local, spvLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeclareLocalForArgs(int funcIndex, Instruction[] spvLocals)
|
|
||||||
{
|
|
||||||
_localForArgs.Add(funcIndex, spvLocals);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeclareArgument(int argIndex, Instruction spvLocal)
|
public void DeclareArgument(int argIndex, Instruction spvLocal)
|
||||||
{
|
{
|
||||||
_funcArgs.Add(argIndex, spvLocal);
|
_funcArgs.Add(argIndex, spvLocal);
|
||||||
@ -278,11 +271,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
return _locals[local];
|
return _locals[local];
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instruction[] GetLocalForArgsPointers(int funcIndex)
|
|
||||||
{
|
|
||||||
return _localForArgs[funcIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instruction GetArgumentPointer(AstOperand funcArg)
|
public Instruction GetArgumentPointer(AstOperand funcArg)
|
||||||
{
|
{
|
||||||
return _funcArgs[funcArg.Value];
|
return _funcArgs[funcArg.Value];
|
||||||
|
@ -41,28 +41,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DeclareLocalForArgs(CodeGenContext context, List<StructuredFunction> functions)
|
|
||||||
{
|
|
||||||
for (int funcIndex = 0; funcIndex < functions.Count; funcIndex++)
|
|
||||||
{
|
|
||||||
StructuredFunction function = functions[funcIndex];
|
|
||||||
SpvInstruction[] locals = new SpvInstruction[function.InArguments.Length];
|
|
||||||
|
|
||||||
for (int i = 0; i < function.InArguments.Length; i++)
|
|
||||||
{
|
|
||||||
var type = function.GetArgumentType(i);
|
|
||||||
var localPointerType = context.TypePointer(StorageClass.Function, context.GetType(type));
|
|
||||||
var spvLocal = context.Variable(localPointerType, StorageClass.Function);
|
|
||||||
|
|
||||||
context.AddLocalVariable(spvLocal);
|
|
||||||
|
|
||||||
locals[i] = spvLocal;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.DeclareLocalForArgs(funcIndex, locals);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DeclareAll(CodeGenContext context, StructuredProgramInfo info)
|
public static void DeclareAll(CodeGenContext context, StructuredProgramInfo info)
|
||||||
{
|
{
|
||||||
DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values);
|
DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values);
|
||||||
|
@ -134,7 +134,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
Add(Instruction.Subtract, GenerateSubtract);
|
Add(Instruction.Subtract, GenerateSubtract);
|
||||||
Add(Instruction.SwizzleAdd, GenerateSwizzleAdd);
|
Add(Instruction.SwizzleAdd, GenerateSwizzleAdd);
|
||||||
Add(Instruction.TextureSample, GenerateTextureSample);
|
Add(Instruction.TextureSample, GenerateTextureSample);
|
||||||
Add(Instruction.TextureSize, GenerateTextureSize);
|
Add(Instruction.TextureQuerySamples, GenerateTextureQuerySamples);
|
||||||
|
Add(Instruction.TextureQuerySize, GenerateTextureQuerySize);
|
||||||
Add(Instruction.Truncate, GenerateTruncate);
|
Add(Instruction.Truncate, GenerateTruncate);
|
||||||
Add(Instruction.UnpackDouble2x32, GenerateUnpackDouble2x32);
|
Add(Instruction.UnpackDouble2x32, GenerateUnpackDouble2x32);
|
||||||
Add(Instruction.UnpackHalf2x16, GenerateUnpackHalf2x16);
|
Add(Instruction.UnpackHalf2x16, GenerateUnpackHalf2x16);
|
||||||
@ -310,26 +311,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
var (function, spvFunc) = context.GetFunction(funcId.Value);
|
var (function, spvFunc) = context.GetFunction(funcId.Value);
|
||||||
|
|
||||||
var args = new SpvInstruction[operation.SourcesCount - 1];
|
var args = new SpvInstruction[operation.SourcesCount - 1];
|
||||||
var spvLocals = context.GetLocalForArgsPointers(funcId.Value);
|
|
||||||
|
|
||||||
for (int i = 0; i < args.Length; i++)
|
for (int i = 0; i < args.Length; i++)
|
||||||
{
|
{
|
||||||
var operand = operation.GetSource(i + 1);
|
var operand = operation.GetSource(i + 1);
|
||||||
|
|
||||||
if (i >= function.InArguments.Length)
|
AstOperand local = (AstOperand)operand;
|
||||||
{
|
Debug.Assert(local.Type == OperandType.LocalVariable);
|
||||||
args[i] = context.GetLocalPointer((AstOperand)operand);
|
args[i] = context.GetLocalPointer(local);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var type = function.GetArgumentType(i);
|
|
||||||
var value = context.Get(type, operand);
|
|
||||||
var spvLocal = spvLocals[i];
|
|
||||||
|
|
||||||
context.Store(spvLocal, value);
|
|
||||||
|
|
||||||
args[i] = spvLocal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var retType = function.ReturnType;
|
var retType = function.ReturnType;
|
||||||
@ -1492,7 +1481,36 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
return new OperationResult(swizzledResultType, result);
|
return new OperationResult(swizzledResultType, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OperationResult GenerateTextureSize(CodeGenContext context, AstOperation operation)
|
private static OperationResult GenerateTextureQuerySamples(CodeGenContext context, AstOperation operation)
|
||||||
|
{
|
||||||
|
AstTextureOperation texOp = (AstTextureOperation)operation;
|
||||||
|
|
||||||
|
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||||
|
|
||||||
|
// TODO: Bindless texture support. For now we just return 0.
|
||||||
|
if (isBindless)
|
||||||
|
{
|
||||||
|
return new OperationResult(AggregateType.S32, context.Constant(context.TypeS32(), 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||||
|
|
||||||
|
if (isIndexed)
|
||||||
|
{
|
||||||
|
context.GetS32(texOp.GetSource(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
(var imageType, var sampledImageType, var sampledImageVariable) = context.Samplers[texOp.Binding];
|
||||||
|
|
||||||
|
var image = context.Load(sampledImageType, sampledImageVariable);
|
||||||
|
image = context.Image(imageType, image);
|
||||||
|
|
||||||
|
SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image);
|
||||||
|
|
||||||
|
return new OperationResult(AggregateType.S32, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OperationResult GenerateTextureQuerySize(CodeGenContext context, AstOperation operation)
|
||||||
{
|
{
|
||||||
AstTextureOperation texOp = (AstTextureOperation)operation;
|
AstTextureOperation texOp = (AstTextureOperation)operation;
|
||||||
|
|
||||||
|
@ -161,7 +161,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
context.EnterBlock(function.MainBlock);
|
context.EnterBlock(function.MainBlock);
|
||||||
|
|
||||||
Declarations.DeclareLocals(context, function);
|
Declarations.DeclareLocals(context, function);
|
||||||
Declarations.DeclareLocalForArgs(context, info.Functions);
|
|
||||||
|
|
||||||
Generate(context, function.MainBlock);
|
Generate(context, function.MainBlock);
|
||||||
|
|
||||||
|
@ -339,6 +339,15 @@ namespace Ryujinx.Graphics.Shader
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries host GPU texture gather with multiple offsets support.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the GPU and driver supports texture gather offsets, false otherwise</returns>
|
||||||
|
bool QueryHostSupportsTextureGatherOffsets()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries host GPU texture shadow LOD support.
|
/// Queries host GPU texture shadow LOD support.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -766,7 +766,10 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
flags |= offset == TexOffset.Ptp ? TextureFlags.Offsets : TextureFlags.Offset;
|
flags |= offset == TexOffset.Ptp ? TextureFlags.Offsets : TextureFlags.Offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasDepthCompare)
|
||||||
|
{
|
||||||
sourcesList.Add(Const((int)component));
|
sourcesList.Add(Const((int)component));
|
||||||
|
}
|
||||||
|
|
||||||
Operand[] sources = sourcesList.ToArray();
|
Operand[] sources = sourcesList.ToArray();
|
||||||
Operand[] dests = new Operand[BitOperations.PopCount((uint)componentMask)];
|
Operand[] dests = new Operand[BitOperations.PopCount((uint)componentMask)];
|
||||||
@ -1093,18 +1096,29 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
SamplerType type;
|
SamplerType type;
|
||||||
|
|
||||||
if (isBindless)
|
if (isBindless)
|
||||||
|
{
|
||||||
|
if (query == TexQuery.TexHeaderTextureType)
|
||||||
|
{
|
||||||
|
type = SamplerType.Texture2D | SamplerType.Multisample;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
type = (componentMask & 4) != 0 ? SamplerType.Texture3D : SamplerType.Texture2D;
|
type = (componentMask & 4) != 0 ? SamplerType.Texture3D : SamplerType.Texture2D;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
type = context.TranslatorContext.GpuAccessor.QuerySamplerType(imm);
|
type = context.TranslatorContext.GpuAccessor.QuerySamplerType(imm);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None;
|
TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None;
|
||||||
|
int binding;
|
||||||
|
|
||||||
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
|
switch (query)
|
||||||
Instruction.TextureSize,
|
{
|
||||||
|
case TexQuery.TexHeaderDimension:
|
||||||
|
binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
|
||||||
|
Instruction.TextureQuerySize,
|
||||||
type,
|
type,
|
||||||
TextureFormat.Unknown,
|
TextureFormat.Unknown,
|
||||||
flags,
|
flags,
|
||||||
@ -1122,12 +1136,46 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Validate and use query parameter.
|
context.Copy(d, context.TextureQuerySize(type, flags, binding, compIndex, sources));
|
||||||
Operand res = context.TextureSize(type, flags, binding, compIndex, sources);
|
|
||||||
|
|
||||||
context.Copy(d, res);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TexQuery.TexHeaderTextureType:
|
||||||
|
binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
|
||||||
|
Instruction.TextureQuerySamples,
|
||||||
|
type,
|
||||||
|
TextureFormat.Unknown,
|
||||||
|
flags,
|
||||||
|
TextureOperation.DefaultCbufSlot,
|
||||||
|
imm);
|
||||||
|
|
||||||
|
if ((componentMask & 4) != 0)
|
||||||
|
{
|
||||||
|
// Skip first 2 components if necessary.
|
||||||
|
if ((componentMask & 1) != 0)
|
||||||
|
{
|
||||||
|
GetDest();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((componentMask & 2) != 0)
|
||||||
|
{
|
||||||
|
GetDest();
|
||||||
|
}
|
||||||
|
|
||||||
|
Operand d = GetDest();
|
||||||
|
|
||||||
|
if (d != null)
|
||||||
|
{
|
||||||
|
context.Copy(d, context.TextureQuerySamples(type, flags, binding, sources));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
context.TranslatorContext.GpuAccessor.Log($"Invalid or unsupported query type \"{query}\".");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitTextureSample(
|
private static void EmitTextureSample(
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||||
{
|
{
|
||||||
[Flags]
|
[Flags]
|
||||||
[SuppressMessage("Design", "CA1069: Enums values should not be duplicated")]
|
|
||||||
enum Instruction
|
enum Instruction
|
||||||
{
|
{
|
||||||
Absolute = 1,
|
Absolute = 1,
|
||||||
@ -118,7 +116,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||||||
Subtract,
|
Subtract,
|
||||||
SwizzleAdd,
|
SwizzleAdd,
|
||||||
TextureSample,
|
TextureSample,
|
||||||
TextureSize,
|
TextureQuerySamples,
|
||||||
|
TextureQuerySize,
|
||||||
Truncate,
|
Truncate,
|
||||||
UnpackDouble2x32,
|
UnpackDouble2x32,
|
||||||
UnpackHalf2x16,
|
UnpackHalf2x16,
|
||||||
@ -160,7 +159,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||||||
public static bool IsTextureQuery(this Instruction inst)
|
public static bool IsTextureQuery(this Instruction inst)
|
||||||
{
|
{
|
||||||
inst &= Instruction.Mask;
|
inst &= Instruction.Mask;
|
||||||
return inst == Instruction.Lod || inst == Instruction.TextureSize;
|
return inst == Instruction.Lod || inst == Instruction.TextureQuerySamples || inst == Instruction.TextureQuerySize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
Add(Instruction.Subtract, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
|
Add(Instruction.Subtract, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
|
||||||
Add(Instruction.SwizzleAdd, AggregateType.FP32, AggregateType.FP32, AggregateType.FP32, AggregateType.S32);
|
Add(Instruction.SwizzleAdd, AggregateType.FP32, AggregateType.FP32, AggregateType.FP32, AggregateType.S32);
|
||||||
Add(Instruction.TextureSample, AggregateType.FP32);
|
Add(Instruction.TextureSample, AggregateType.FP32);
|
||||||
Add(Instruction.TextureSize, AggregateType.S32, AggregateType.S32, AggregateType.S32);
|
Add(Instruction.TextureQuerySamples, AggregateType.S32, AggregateType.S32);
|
||||||
|
Add(Instruction.TextureQuerySize, AggregateType.S32, AggregateType.S32, AggregateType.S32);
|
||||||
Add(Instruction.Truncate, AggregateType.Scalar, AggregateType.Scalar);
|
Add(Instruction.Truncate, AggregateType.Scalar, AggregateType.Scalar);
|
||||||
Add(Instruction.UnpackDouble2x32, AggregateType.U32, AggregateType.FP64);
|
Add(Instruction.UnpackDouble2x32, AggregateType.U32, AggregateType.FP64);
|
||||||
Add(Instruction.UnpackHalf2x16, AggregateType.FP32, AggregateType.U32);
|
Add(Instruction.UnpackHalf2x16, AggregateType.FP32, AggregateType.U32);
|
||||||
|
@ -2,17 +2,22 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
|||||||
using Ryujinx.Graphics.Shader.Translation;
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.StructuredIr
|
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
static class StructuredProgram
|
static class StructuredProgram
|
||||||
{
|
{
|
||||||
|
// TODO: Eventually it should be possible to specify the parameter types for the function instead of using S32 for everything.
|
||||||
|
private const AggregateType FuncParameterType = AggregateType.S32;
|
||||||
|
|
||||||
public static StructuredProgramInfo MakeStructuredProgram(
|
public static StructuredProgramInfo MakeStructuredProgram(
|
||||||
IReadOnlyList<Function> functions,
|
IReadOnlyList<Function> functions,
|
||||||
AttributeUsage attributeUsage,
|
AttributeUsage attributeUsage,
|
||||||
ShaderDefinitions definitions,
|
ShaderDefinitions definitions,
|
||||||
ResourceManager resourceManager,
|
ResourceManager resourceManager,
|
||||||
|
TargetLanguage targetLanguage,
|
||||||
bool debugMode)
|
bool debugMode)
|
||||||
{
|
{
|
||||||
StructuredProgramContext context = new(attributeUsage, definitions, resourceManager, debugMode);
|
StructuredProgramContext context = new(attributeUsage, definitions, resourceManager, debugMode);
|
||||||
@ -23,19 +28,19 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
|
|
||||||
BasicBlock[] blocks = function.Blocks;
|
BasicBlock[] blocks = function.Blocks;
|
||||||
|
|
||||||
AggregateType returnType = function.ReturnsValue ? AggregateType.S32 : AggregateType.Void;
|
AggregateType returnType = function.ReturnsValue ? FuncParameterType : AggregateType.Void;
|
||||||
|
|
||||||
AggregateType[] inArguments = new AggregateType[function.InArgumentsCount];
|
AggregateType[] inArguments = new AggregateType[function.InArgumentsCount];
|
||||||
AggregateType[] outArguments = new AggregateType[function.OutArgumentsCount];
|
AggregateType[] outArguments = new AggregateType[function.OutArgumentsCount];
|
||||||
|
|
||||||
for (int i = 0; i < inArguments.Length; i++)
|
for (int i = 0; i < inArguments.Length; i++)
|
||||||
{
|
{
|
||||||
inArguments[i] = AggregateType.S32;
|
inArguments[i] = FuncParameterType;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < outArguments.Length; i++)
|
for (int i = 0; i < outArguments.Length; i++)
|
||||||
{
|
{
|
||||||
outArguments[i] = AggregateType.S32;
|
outArguments[i] = FuncParameterType;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.EnterFunction(blocks.Length, function.Name, returnType, inArguments, outArguments);
|
context.EnterFunction(blocks.Length, function.Name, returnType, inArguments, outArguments);
|
||||||
@ -58,7 +63,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddOperation(context, operation);
|
AddOperation(context, operation, targetLanguage, functions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,7 +78,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
return context.Info;
|
return context.Info;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddOperation(StructuredProgramContext context, Operation operation)
|
private static void AddOperation(StructuredProgramContext context, Operation operation, TargetLanguage targetLanguage, IReadOnlyList<Function> functions)
|
||||||
{
|
{
|
||||||
Instruction inst = operation.Inst;
|
Instruction inst = operation.Inst;
|
||||||
StorageKind storageKind = operation.StorageKind;
|
StorageKind storageKind = operation.StorageKind;
|
||||||
@ -114,10 +119,44 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
|
|
||||||
IAstNode[] sources = new IAstNode[sourcesCount + outDestsCount];
|
IAstNode[] sources = new IAstNode[sourcesCount + outDestsCount];
|
||||||
|
|
||||||
|
if (inst == Instruction.Call && targetLanguage == TargetLanguage.Spirv)
|
||||||
|
{
|
||||||
|
// SPIR-V requires that all function parameters are copied to a local variable before the call
|
||||||
|
// (or at least that's what the Khronos compiler does).
|
||||||
|
|
||||||
|
// First one is the function index.
|
||||||
|
Operand funcIndexOperand = operation.GetSource(0);
|
||||||
|
Debug.Assert(funcIndexOperand.Type == OperandType.Constant);
|
||||||
|
int funcIndex = funcIndexOperand.Value;
|
||||||
|
|
||||||
|
sources[0] = new AstOperand(OperandType.Constant, funcIndex);
|
||||||
|
|
||||||
|
int inArgsCount = functions[funcIndex].InArgumentsCount;
|
||||||
|
|
||||||
|
// Remaining ones are parameters, copy them to a temp local variable.
|
||||||
|
for (int index = 1; index < operation.SourcesCount; index++)
|
||||||
|
{
|
||||||
|
IAstNode source = context.GetOperandOrCbLoad(operation.GetSource(index));
|
||||||
|
|
||||||
|
if (index - 1 < inArgsCount)
|
||||||
|
{
|
||||||
|
AstOperand argTemp = context.NewTemp(FuncParameterType);
|
||||||
|
context.AddNode(new AstAssignment(argTemp, source));
|
||||||
|
sources[index] = argTemp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sources[index] = source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
for (int index = 0; index < operation.SourcesCount; index++)
|
for (int index = 0; index < operation.SourcesCount; index++)
|
||||||
{
|
{
|
||||||
sources[index] = context.GetOperandOrCbLoad(operation.GetSource(index));
|
sources[index] = context.GetOperandOrCbLoad(operation.GetSource(index));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int index = 0; index < outDestsCount; index++)
|
for (int index = 0; index < outDestsCount; index++)
|
||||||
{
|
{
|
||||||
|
@ -897,7 +897,21 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
context.Add(new TextureOperation(Instruction.TextureSample, type, TextureFormat.Unknown, flags, binding, compMask, dests, sources));
|
context.Add(new TextureOperation(Instruction.TextureSample, type, TextureFormat.Unknown, flags, binding, compMask, dests, sources));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand TextureSize(
|
public static Operand TextureQuerySamples(
|
||||||
|
this EmitterContext context,
|
||||||
|
SamplerType type,
|
||||||
|
TextureFlags flags,
|
||||||
|
int binding,
|
||||||
|
Operand[] sources)
|
||||||
|
{
|
||||||
|
Operand dest = Local();
|
||||||
|
|
||||||
|
context.Add(new TextureOperation(Instruction.TextureQuerySamples, type, TextureFormat.Unknown, flags, binding, 0, new[] { dest }, sources));
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operand TextureQuerySize(
|
||||||
this EmitterContext context,
|
this EmitterContext context,
|
||||||
SamplerType type,
|
SamplerType type,
|
||||||
TextureFlags flags,
|
TextureFlags flags,
|
||||||
@ -907,7 +921,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
{
|
{
|
||||||
Operand dest = Local();
|
Operand dest = Local();
|
||||||
|
|
||||||
context.Add(new TextureOperation(Instruction.TextureSize, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
|
context.Add(new TextureOperation(Instruction.TextureQuerySize, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
|
||||||
|
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
@ -27,9 +27,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (texOp.Inst == Instruction.Lod ||
|
if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery())
|
||||||
texOp.Inst == Instruction.TextureSample ||
|
|
||||||
texOp.Inst == Instruction.TextureSize)
|
|
||||||
{
|
{
|
||||||
Operand bindlessHandle = Utils.FindLastOperation(texOp.GetSource(0), block);
|
Operand bindlessHandle = Utils.FindLastOperation(texOp.GetSource(0), block);
|
||||||
|
|
||||||
@ -40,7 +38,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
// as long bindless elimination is successful and we know where the texture descriptor is located.
|
// as long bindless elimination is successful and we know where the texture descriptor is located.
|
||||||
bool rewriteSamplerType =
|
bool rewriteSamplerType =
|
||||||
texOp.Type == SamplerType.TextureBuffer ||
|
texOp.Type == SamplerType.TextureBuffer ||
|
||||||
texOp.Inst == Instruction.TextureSize;
|
texOp.Inst == Instruction.TextureQuerySamples ||
|
||||||
|
texOp.Inst == Instruction.TextureQuerySize;
|
||||||
|
|
||||||
if (bindlessHandle.Type == OperandType.ConstantBuffer)
|
if (bindlessHandle.Type == OperandType.ConstantBuffer)
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||||
@ -785,30 +786,31 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
|
|
||||||
private static string GetFunctionName(Operation baseOp, bool isMultiTarget, IReadOnlyList<uint> targetCbs)
|
private static string GetFunctionName(Operation baseOp, bool isMultiTarget, IReadOnlyList<uint> targetCbs)
|
||||||
{
|
{
|
||||||
string name = baseOp.Inst.ToString();
|
StringBuilder nameBuilder = new();
|
||||||
|
nameBuilder.Append(baseOp.Inst.ToString());
|
||||||
|
|
||||||
name += baseOp.StorageKind switch
|
nameBuilder.Append(baseOp.StorageKind switch
|
||||||
{
|
{
|
||||||
StorageKind.GlobalMemoryS8 => "S8",
|
StorageKind.GlobalMemoryS8 => "S8",
|
||||||
StorageKind.GlobalMemoryS16 => "S16",
|
StorageKind.GlobalMemoryS16 => "S16",
|
||||||
StorageKind.GlobalMemoryU8 => "U8",
|
StorageKind.GlobalMemoryU8 => "U8",
|
||||||
StorageKind.GlobalMemoryU16 => "U16",
|
StorageKind.GlobalMemoryU16 => "U16",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
});
|
||||||
|
|
||||||
if (isMultiTarget)
|
if (isMultiTarget)
|
||||||
{
|
{
|
||||||
name += "Multi";
|
nameBuilder.Append("Multi");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (uint targetCb in targetCbs)
|
foreach (uint targetCb in targetCbs)
|
||||||
{
|
{
|
||||||
(int sbCbSlot, int sbCbOffset) = UnpackCbSlotAndOffset(targetCb);
|
(int sbCbSlot, int sbCbOffset) = UnpackCbSlotAndOffset(targetCb);
|
||||||
|
|
||||||
name += $"_c{sbCbSlot}o{sbCbOffset}";
|
nameBuilder.Append($"_c{sbCbSlot}o{sbCbOffset}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return name;
|
return nameBuilder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryGenerateStorageOp(
|
private static bool TryGenerateStorageOp(
|
||||||
|
@ -232,8 +232,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
inst &= Instruction.Mask;
|
inst &= Instruction.Mask;
|
||||||
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
|
bool isImage = inst == Instruction.ImageLoad || inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
|
||||||
bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
|
bool isWrite = inst == Instruction.ImageStore || inst == Instruction.ImageAtomic;
|
||||||
bool accurateType = inst != Instruction.Lod && inst != Instruction.TextureSize;
|
bool accurateType = !inst.IsTextureQuery();
|
||||||
bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureSize;
|
bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureQuerySize;
|
||||||
bool coherent = flags.HasFlag(TextureFlags.Coherent);
|
bool coherent = flags.HasFlag(TextureFlags.Coherent);
|
||||||
|
|
||||||
if (!isImage)
|
if (!isImage)
|
||||||
|
@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
{
|
{
|
||||||
node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage);
|
node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage);
|
||||||
node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor);
|
node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor);
|
||||||
node = InsertConstOffsets(node, context.ResourceManager, context.GpuAccessor);
|
node = InsertConstOffsets(node, context.GpuAccessor, context.Stage);
|
||||||
|
|
||||||
if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat())
|
if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat())
|
||||||
{
|
{
|
||||||
@ -99,7 +99,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||||
|
|
||||||
if (texOp.Inst == Instruction.TextureSize &&
|
if (texOp.Inst == Instruction.TextureQuerySize &&
|
||||||
texOp.Index < 2 &&
|
texOp.Index < 2 &&
|
||||||
!isBindless &&
|
!isBindless &&
|
||||||
!isIndexed &&
|
!isIndexed &&
|
||||||
@ -190,7 +190,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
}
|
}
|
||||||
|
|
||||||
LinkedListNode<INode> textureSizeNode = node.List.AddBefore(node, new TextureOperation(
|
LinkedListNode<INode> textureSizeNode = node.List.AddBefore(node, new TextureOperation(
|
||||||
Instruction.TextureSize,
|
Instruction.TextureQuerySize,
|
||||||
texOp.Type,
|
texOp.Type,
|
||||||
texOp.Format,
|
texOp.Format,
|
||||||
texOp.Flags,
|
texOp.Flags,
|
||||||
@ -259,7 +259,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.List.AddBefore(node, new TextureOperation(
|
node.List.AddBefore(node, new TextureOperation(
|
||||||
Instruction.TextureSize,
|
Instruction.TextureQuerySize,
|
||||||
texOp.Type,
|
texOp.Type,
|
||||||
texOp.Format,
|
texOp.Format,
|
||||||
texOp.Flags,
|
texOp.Flags,
|
||||||
@ -287,7 +287,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
|
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, IGpuAccessor gpuAccessor, ShaderStage stage)
|
||||||
{
|
{
|
||||||
// Non-constant texture offsets are not allowed (according to the spec),
|
// Non-constant texture offsets are not allowed (according to the spec),
|
||||||
// however some GPUs does support that.
|
// however some GPUs does support that.
|
||||||
@ -303,7 +303,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0;
|
bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0;
|
||||||
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
|
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
|
||||||
|
|
||||||
bool hasInvalidOffset = (hasOffset || hasOffsets) && !gpuAccessor.QueryHostSupportsNonConstantTextureOffset();
|
bool needsOffsetsEmulation = hasOffsets && !gpuAccessor.QueryHostSupportsTextureGatherOffsets();
|
||||||
|
|
||||||
|
bool hasInvalidOffset = needsOffsetsEmulation || ((hasOffset || hasOffsets) && !gpuAccessor.QueryHostSupportsNonConstantTextureOffset());
|
||||||
|
|
||||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||||
|
|
||||||
@ -402,12 +404,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
offsets[index] = offset;
|
offsets[index] = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!needsOffsetsEmulation)
|
||||||
|
{
|
||||||
hasInvalidOffset &= !areAllOffsetsConstant;
|
hasInvalidOffset &= !areAllOffsetsConstant;
|
||||||
|
|
||||||
if (!hasInvalidOffset)
|
if (!hasInvalidOffset)
|
||||||
{
|
{
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hasLodBias)
|
if (hasLodBias)
|
||||||
{
|
{
|
||||||
@ -434,13 +439,13 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
|
|
||||||
LinkedListNode<INode> oldNode = node;
|
LinkedListNode<INode> oldNode = node;
|
||||||
|
|
||||||
if (isGather && !isShadow)
|
if (isGather && !isShadow && hasOffsets)
|
||||||
{
|
{
|
||||||
Operand[] newSources = new Operand[sources.Length];
|
Operand[] newSources = new Operand[sources.Length];
|
||||||
|
|
||||||
sources.CopyTo(newSources, 0);
|
sources.CopyTo(newSources, 0);
|
||||||
|
|
||||||
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount);
|
Operand[] texSizes = InsertTextureBaseSize(node, texOp, bindlessHandle, coordsCount);
|
||||||
|
|
||||||
int destIndex = 0;
|
int destIndex = 0;
|
||||||
|
|
||||||
@ -455,7 +460,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
{
|
{
|
||||||
Operand offset = Local();
|
Operand offset = Local();
|
||||||
|
|
||||||
Operand intOffset = offsets[index + (hasOffsets ? compIndex * coordsCount : 0)];
|
Operand intOffset = offsets[index + compIndex * coordsCount];
|
||||||
|
|
||||||
node.List.AddBefore(node, new Operation(
|
node.List.AddBefore(node, new Operation(
|
||||||
Instruction.FP32 | Instruction.Divide,
|
Instruction.FP32 | Instruction.Divide,
|
||||||
@ -478,7 +483,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
texOp.Format,
|
texOp.Format,
|
||||||
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
|
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
|
||||||
texOp.Binding,
|
texOp.Binding,
|
||||||
1,
|
1 << 3, // W component: i=0, j=0
|
||||||
new[] { dests[destIndex++] },
|
new[] { dests[destIndex++] },
|
||||||
newSources);
|
newSources);
|
||||||
|
|
||||||
@ -502,7 +507,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount);
|
Operand[] texSizes = isGather
|
||||||
|
? InsertTextureBaseSize(node, texOp, bindlessHandle, coordsCount)
|
||||||
|
: InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount, stage);
|
||||||
|
|
||||||
for (int index = 0; index < coordsCount; index++)
|
for (int index = 0; index < coordsCount; index++)
|
||||||
{
|
{
|
||||||
@ -549,16 +556,58 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Operand[] InsertTextureLod(
|
private static Operand[] InsertTextureBaseSize(
|
||||||
LinkedListNode<INode> node,
|
LinkedListNode<INode> node,
|
||||||
TextureOperation texOp,
|
TextureOperation texOp,
|
||||||
Operand[] lodSources,
|
|
||||||
Operand bindlessHandle,
|
Operand bindlessHandle,
|
||||||
int coordsCount)
|
int coordsCount)
|
||||||
{
|
{
|
||||||
Operand[] texSizes = new Operand[coordsCount];
|
Operand[] texSizes = new Operand[coordsCount];
|
||||||
|
|
||||||
Operand lod = Local();
|
for (int index = 0; index < coordsCount; index++)
|
||||||
|
{
|
||||||
|
texSizes[index] = Local();
|
||||||
|
|
||||||
|
Operand[] texSizeSources;
|
||||||
|
|
||||||
|
if (bindlessHandle != null)
|
||||||
|
{
|
||||||
|
texSizeSources = new Operand[] { bindlessHandle, Const(0) };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
texSizeSources = new Operand[] { Const(0) };
|
||||||
|
}
|
||||||
|
|
||||||
|
node.List.AddBefore(node, new TextureOperation(
|
||||||
|
Instruction.TextureQuerySize,
|
||||||
|
texOp.Type,
|
||||||
|
texOp.Format,
|
||||||
|
texOp.Flags,
|
||||||
|
texOp.Binding,
|
||||||
|
index,
|
||||||
|
new[] { texSizes[index] },
|
||||||
|
texSizeSources));
|
||||||
|
}
|
||||||
|
|
||||||
|
return texSizes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand[] InsertTextureLod(
|
||||||
|
LinkedListNode<INode> node,
|
||||||
|
TextureOperation texOp,
|
||||||
|
Operand[] lodSources,
|
||||||
|
Operand bindlessHandle,
|
||||||
|
int coordsCount,
|
||||||
|
ShaderStage stage)
|
||||||
|
{
|
||||||
|
Operand[] texSizes = new Operand[coordsCount];
|
||||||
|
|
||||||
|
Operand lod;
|
||||||
|
|
||||||
|
if (stage == ShaderStage.Fragment)
|
||||||
|
{
|
||||||
|
lod = Local();
|
||||||
|
|
||||||
node.List.AddBefore(node, new TextureOperation(
|
node.List.AddBefore(node, new TextureOperation(
|
||||||
Instruction.Lod,
|
Instruction.Lod,
|
||||||
@ -569,6 +618,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
0,
|
0,
|
||||||
new[] { lod },
|
new[] { lod },
|
||||||
lodSources));
|
lodSources));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lod = Const(0);
|
||||||
|
}
|
||||||
|
|
||||||
for (int index = 0; index < coordsCount; index++)
|
for (int index = 0; index < coordsCount; index++)
|
||||||
{
|
{
|
||||||
@ -586,7 +640,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.List.AddBefore(node, new TextureOperation(
|
node.List.AddBefore(node, new TextureOperation(
|
||||||
Instruction.TextureSize,
|
Instruction.TextureQuerySize,
|
||||||
texOp.Type,
|
texOp.Type,
|
||||||
texOp.Format,
|
texOp.Format,
|
||||||
texOp.Flags,
|
texOp.Flags,
|
||||||
|
@ -329,6 +329,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
attributeUsage,
|
attributeUsage,
|
||||||
definitions,
|
definitions,
|
||||||
resourceManager,
|
resourceManager,
|
||||||
|
Options.TargetLanguage,
|
||||||
Options.Flags.HasFlag(TranslationFlags.DebugMode));
|
Options.Flags.HasFlag(TranslationFlags.DebugMode));
|
||||||
|
|
||||||
int geometryVerticesPerPrimitive = Definitions.OutputTopology switch
|
int geometryVerticesPerPrimitive = Definitions.OutputTopology switch
|
||||||
|
@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private int _flushTemp;
|
private int _flushTemp;
|
||||||
private int _lastFlushWrite = -1;
|
private int _lastFlushWrite = -1;
|
||||||
|
|
||||||
private readonly ReaderWriterLock _flushLock;
|
private readonly ReaderWriterLockSlim _flushLock;
|
||||||
private FenceHolder _flushFence;
|
private FenceHolder _flushFence;
|
||||||
private int _flushWaiting;
|
private int _flushWaiting;
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_currentType = currentType;
|
_currentType = currentType;
|
||||||
DesiredType = currentType;
|
DesiredType = currentType;
|
||||||
|
|
||||||
_flushLock = new ReaderWriterLock();
|
_flushLock = new ReaderWriterLockSlim();
|
||||||
_useMirrors = gd.IsTBDR;
|
_useMirrors = gd.IsTBDR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_currentType = currentType;
|
_currentType = currentType;
|
||||||
DesiredType = currentType;
|
DesiredType = currentType;
|
||||||
|
|
||||||
_flushLock = new ReaderWriterLock();
|
_flushLock = new ReaderWriterLockSlim();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryBackingSwap(ref CommandBufferScoped? cbs)
|
public bool TryBackingSwap(ref CommandBufferScoped? cbs)
|
||||||
@ -116,7 +116,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
// Only swap if the buffer is not used in any queued command buffer.
|
// Only swap if the buffer is not used in any queued command buffer.
|
||||||
bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
|
bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
|
||||||
|
|
||||||
if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReaderLockHeld && (_pendingData == null || cbs != null))
|
if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReadLockHeld && (_pendingData == null || cbs != null))
|
||||||
{
|
{
|
||||||
var currentAllocation = _allocationAuto;
|
var currentAllocation = _allocationAuto;
|
||||||
var currentBuffer = _buffer;
|
var currentBuffer = _buffer;
|
||||||
@ -131,7 +131,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
ClearMirrors(cbs.Value, 0, Size);
|
ClearMirrors(cbs.Value, 0, Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
_flushLock.AcquireWriterLock(Timeout.Infinite);
|
_flushLock.EnterWriteLock();
|
||||||
|
|
||||||
ClearFlushFence();
|
ClearFlushFence();
|
||||||
|
|
||||||
@ -185,7 +185,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
_gd.PipelineInternal.SwapBuffer(currentBuffer, _buffer);
|
_gd.PipelineInternal.SwapBuffer(currentBuffer, _buffer);
|
||||||
|
|
||||||
_flushLock.ReleaseWriterLock();
|
_flushLock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
_swapQueued = false;
|
_swapQueued = false;
|
||||||
@ -548,13 +548,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
private void WaitForFlushFence()
|
private void WaitForFlushFence()
|
||||||
{
|
{
|
||||||
// Assumes the _flushLock is held as reader, returns in same state.
|
if (_flushFence == null)
|
||||||
|
|
||||||
if (_flushFence != null)
|
|
||||||
{
|
{
|
||||||
// If storage has changed, make sure the fence has been reached so that the data is in place.
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var cookie = _flushLock.UpgradeToWriterLock(Timeout.Infinite);
|
// If storage has changed, make sure the fence has been reached so that the data is in place.
|
||||||
|
_flushLock.ExitReadLock();
|
||||||
|
_flushLock.EnterWriteLock();
|
||||||
|
|
||||||
if (_flushFence != null)
|
if (_flushFence != null)
|
||||||
{
|
{
|
||||||
@ -563,11 +564,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
// Don't wait in the lock.
|
// Don't wait in the lock.
|
||||||
|
|
||||||
var restoreCookie = _flushLock.ReleaseLock();
|
_flushLock.ExitWriteLock();
|
||||||
|
|
||||||
fence.Wait();
|
fence.Wait();
|
||||||
|
|
||||||
_flushLock.RestoreLock(ref restoreCookie);
|
_flushLock.EnterWriteLock();
|
||||||
|
|
||||||
if (Interlocked.Decrement(ref _flushWaiting) == 0)
|
if (Interlocked.Decrement(ref _flushWaiting) == 0)
|
||||||
{
|
{
|
||||||
@ -577,13 +578,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_flushFence = null;
|
_flushFence = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_flushLock.DowngradeFromWriterLock(ref cookie);
|
// Assumes the _flushLock is held as reader, returns in same state.
|
||||||
}
|
_flushLock.ExitWriteLock();
|
||||||
|
_flushLock.EnterReadLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PinnedSpan<byte> GetData(int offset, int size)
|
public PinnedSpan<byte> GetData(int offset, int size)
|
||||||
{
|
{
|
||||||
_flushLock.AcquireReaderLock(Timeout.Infinite);
|
_flushLock.EnterReadLock();
|
||||||
|
|
||||||
WaitForFlushFence();
|
WaitForFlushFence();
|
||||||
|
|
||||||
@ -603,7 +605,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
// Need to be careful here, the buffer can't be unmapped while the data is being used.
|
// Need to be careful here, the buffer can't be unmapped while the data is being used.
|
||||||
_buffer.IncrementReferenceCount();
|
_buffer.IncrementReferenceCount();
|
||||||
|
|
||||||
_flushLock.ReleaseReaderLock();
|
_flushLock.ExitReadLock();
|
||||||
|
|
||||||
return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
|
return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
|
||||||
}
|
}
|
||||||
@ -621,7 +623,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
result = resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size);
|
result = resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
_flushLock.ReleaseReaderLock();
|
_flushLock.ExitReadLock();
|
||||||
|
|
||||||
// Flush buffer is pinned until the next GetBufferData on the thread, which is fine for current uses.
|
// Flush buffer is pinned until the next GetBufferData on the thread, which is fine for current uses.
|
||||||
return PinnedSpan<byte>.UnsafeFromSpan(result);
|
return PinnedSpan<byte>.UnsafeFromSpan(result);
|
||||||
@ -1073,11 +1075,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_allocationAuto.Dispose();
|
_allocationAuto.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_flushLock.AcquireWriterLock(Timeout.Infinite);
|
_flushLock.EnterWriteLock();
|
||||||
|
|
||||||
ClearFlushFence();
|
ClearFlushFence();
|
||||||
|
|
||||||
_flushLock.ReleaseWriterLock();
|
_flushLock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,6 +211,13 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
|
int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
|
||||||
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, 0, firstLayer, 0, firstLevel, layers, levels);
|
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, 0, firstLayer, 0, firstLevel, layers, levels);
|
||||||
}
|
}
|
||||||
|
else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
|
||||||
|
{
|
||||||
|
int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
|
||||||
|
int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
|
||||||
|
|
||||||
|
_gd.HelperShader.CopyColor(_gd, cbs, src, dst, 0, firstLayer, 0, FirstLevel, layers, levels);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TextureCopy.Copy(
|
TextureCopy.Copy(
|
||||||
@ -260,6 +267,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
||||||
}
|
}
|
||||||
|
else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
|
||||||
|
{
|
||||||
|
_gd.HelperShader.CopyColor(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TextureCopy.Copy(
|
TextureCopy.Copy(
|
||||||
|
@ -605,6 +605,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
supportsShaderBallot: false,
|
supportsShaderBallot: false,
|
||||||
supportsShaderBarrierDivergence: Vendor != Vendor.Intel,
|
supportsShaderBarrierDivergence: Vendor != Vendor.Intel,
|
||||||
supportsShaderFloat64: Capabilities.SupportsShaderFloat64,
|
supportsShaderFloat64: Capabilities.SupportsShaderFloat64,
|
||||||
|
supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended && !IsMoltenVk,
|
||||||
supportsTextureShadowLod: false,
|
supportsTextureShadowLod: false,
|
||||||
supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics,
|
supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics,
|
||||||
supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex,
|
supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex,
|
||||||
|
61
src/Ryujinx.HLE/FileSystem/ContentCollection.cs
Normal file
61
src/Ryujinx.HLE/FileSystem/ContentCollection.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
using LibHac.Common.Keys;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.Ncm;
|
||||||
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using LibHac.Tools.Ncm;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.FileSystem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Thin wrapper around <see cref="Cnmt"/>
|
||||||
|
/// </summary>
|
||||||
|
public class ContentCollection
|
||||||
|
{
|
||||||
|
private readonly IFileSystem _pfs;
|
||||||
|
private readonly Cnmt _cnmt;
|
||||||
|
|
||||||
|
public ulong Id => _cnmt.TitleId;
|
||||||
|
public TitleVersion Version => _cnmt.TitleVersion;
|
||||||
|
public ContentMetaType Type => _cnmt.Type;
|
||||||
|
public ulong ApplicationId => _cnmt.ApplicationTitleId;
|
||||||
|
public ulong PatchId => _cnmt.PatchTitleId;
|
||||||
|
public TitleVersion RequiredSystemVersion => _cnmt.MinimumSystemVersion;
|
||||||
|
public TitleVersion RequiredApplicationVersion => _cnmt.MinimumApplicationVersion;
|
||||||
|
public byte[] Digest => _cnmt.Hash;
|
||||||
|
|
||||||
|
public ulong ProgramBaseId => Id & ~0x1FFFUL;
|
||||||
|
public bool IsSystemTitle => _cnmt.Type < ContentMetaType.Application;
|
||||||
|
|
||||||
|
public ContentCollection(IFileSystem pfs, Cnmt cnmt)
|
||||||
|
{
|
||||||
|
_pfs = pfs;
|
||||||
|
_cnmt = cnmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Nca GetNcaByType(KeySet keySet, ContentType type, int programIndex = 0)
|
||||||
|
{
|
||||||
|
// TODO: Replace this with a check for IdOffset as soon as LibHac supports it:
|
||||||
|
// && entry.IdOffset == programIndex
|
||||||
|
|
||||||
|
foreach (var entry in _cnmt.ContentEntries)
|
||||||
|
{
|
||||||
|
if (entry.Type != type)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ncaId = BitConverter.ToString(entry.NcaId).Replace("-", null).ToLower();
|
||||||
|
Nca nca = _pfs.GetNca(keySet, $"/{ncaId}.nca");
|
||||||
|
|
||||||
|
if (nca.GetProgramIndex() == programIndex)
|
||||||
|
{
|
||||||
|
return nca;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.FileSystem
|
namespace Ryujinx.HLE.FileSystem
|
||||||
@ -197,7 +198,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
{
|
{
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
fs.OpenFile(ref ncaFile.Ref, ncaPath.FullPath.ToU8Span(), OpenMode.Read);
|
fs.OpenFile(ref ncaFile.Ref, ncaPath.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||||
if (nca.Header.ContentType != NcaContentType.Meta)
|
if (nca.Header.ContentType != NcaContentType.Meta)
|
||||||
{
|
{
|
||||||
@ -209,7 +210,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel);
|
using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel);
|
||||||
using var cnmtFile = new UniqueRef<IFile>();
|
using var cnmtFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
pfs0.OpenFile(ref cnmtFile.Ref, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read);
|
pfs0.OpenFile(ref cnmtFile.Ref, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
var cnmt = new Cnmt(cnmtFile.Get.AsStream());
|
var cnmt = new Cnmt(cnmtFile.Get.AsStream());
|
||||||
if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
|
if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
|
||||||
@ -219,7 +220,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
string ncaId = Convert.ToHexString(cnmt.ContentEntries[0].NcaId).ToLower();
|
string ncaId = Convert.ToHexString(cnmt.ContentEntries[0].NcaId).ToLower();
|
||||||
|
|
||||||
AddAocItem(cnmt.TitleId, containerPath, $"{ncaId}.nca", true);
|
AddAocItem(cnmt.TitleId, containerPath, $"/{ncaId}.nca", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +238,8 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
if (!mergedToContainer)
|
if (!mergedToContainer)
|
||||||
{
|
{
|
||||||
using FileStream fileStream = File.OpenRead(containerPath);
|
using FileStream fileStream = File.OpenRead(containerPath);
|
||||||
using PartitionFileSystem partitionFileSystem = new(fileStream.AsStorage());
|
using PartitionFileSystem partitionFileSystem = new();
|
||||||
|
partitionFileSystem.Initialize(fileStream.AsStorage()).ThrowIfFailure();
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||||
}
|
}
|
||||||
@ -258,17 +260,17 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
{
|
{
|
||||||
var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read);
|
var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read);
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
PartitionFileSystem pfs;
|
|
||||||
|
|
||||||
switch (Path.GetExtension(aoc.ContainerPath))
|
switch (Path.GetExtension(aoc.ContainerPath))
|
||||||
{
|
{
|
||||||
case ".xci":
|
case ".xci":
|
||||||
pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
var xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
||||||
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read);
|
xci.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
break;
|
break;
|
||||||
case ".nsp":
|
case ".nsp":
|
||||||
pfs = new PartitionFileSystem(file.AsStorage());
|
var pfs = new PartitionFileSystem();
|
||||||
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read);
|
pfs.Initialize(file.AsStorage());
|
||||||
|
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return false; // Print error?
|
return false; // Print error?
|
||||||
@ -605,11 +607,11 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
if (filesystem.FileExists($"{path}/00"))
|
if (filesystem.FileExists($"{path}/00"))
|
||||||
{
|
{
|
||||||
filesystem.OpenFile(ref file.Ref, $"{path}/00".ToU8Span(), mode);
|
filesystem.OpenFile(ref file.Ref, $"{path}/00".ToU8Span(), mode).ThrowIfFailure();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
filesystem.OpenFile(ref file.Ref, path.ToU8Span(), mode);
|
filesystem.OpenFile(ref file.Ref, path.ToU8Span(), mode).ThrowIfFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
return file.Release();
|
return file.Release();
|
||||||
@ -817,13 +819,13 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
if (updateNcas.Count > 0)
|
if (updateNcas.Count > 0)
|
||||||
{
|
{
|
||||||
string extraNcas = string.Empty;
|
StringBuilder extraNcas = new();
|
||||||
|
|
||||||
foreach (var entry in updateNcas)
|
foreach (var entry in updateNcas)
|
||||||
{
|
{
|
||||||
foreach (var (type, path) in entry.Value)
|
foreach (var (type, path) in entry.Value)
|
||||||
{
|
{
|
||||||
extraNcas += path + Environment.NewLine;
|
extraNcas.AppendLine(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -954,13 +956,13 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
if (updateNcas.Count > 0)
|
if (updateNcas.Count > 0)
|
||||||
{
|
{
|
||||||
string extraNcas = string.Empty;
|
StringBuilder extraNcas = new();
|
||||||
|
|
||||||
foreach (var entry in updateNcas)
|
foreach (var entry in updateNcas)
|
||||||
{
|
{
|
||||||
foreach (var (type, path) in entry.Value)
|
foreach (var (type, path) in entry.Value)
|
||||||
{
|
{
|
||||||
extraNcas += path + Environment.NewLine;
|
extraNcas.AppendLine(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ using LibHac.Fs.Shim;
|
|||||||
using LibHac.FsSrv;
|
using LibHac.FsSrv;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
|
using LibHac.Sdmmc;
|
||||||
using LibHac.Spl;
|
using LibHac.Spl;
|
||||||
using LibHac.Tools.Es;
|
using LibHac.Tools.Es;
|
||||||
using LibHac.Tools.Fs;
|
using LibHac.Tools.Fs;
|
||||||
@ -32,7 +33,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
public KeySet KeySet { get; private set; }
|
public KeySet KeySet { get; private set; }
|
||||||
public EmulatedGameCard GameCard { get; private set; }
|
public EmulatedGameCard GameCard { get; private set; }
|
||||||
public EmulatedSdCard SdCard { get; private set; }
|
public SdmmcApi SdCard { get; private set; }
|
||||||
public ModLoader ModLoader { get; private set; }
|
public ModLoader ModLoader { get; private set; }
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<ulong, Stream> _romFsByPid;
|
private readonly ConcurrentDictionary<ulong, Stream> _romFsByPid;
|
||||||
@ -198,15 +199,15 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator();
|
fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator();
|
||||||
|
|
||||||
GameCard = fsServerObjects.GameCard;
|
GameCard = fsServerObjects.GameCard;
|
||||||
SdCard = fsServerObjects.SdCard;
|
SdCard = fsServerObjects.Sdmmc;
|
||||||
|
|
||||||
SdCard.SetSdCardInsertionStatus(true);
|
SdCard.SetSdCardInserted(true);
|
||||||
|
|
||||||
var fsServerConfig = new FileSystemServerConfig
|
var fsServerConfig = new FileSystemServerConfig
|
||||||
{
|
{
|
||||||
DeviceOperator = fsServerObjects.DeviceOperator,
|
|
||||||
ExternalKeySet = KeySet.ExternalKeySet,
|
ExternalKeySet = KeySet.ExternalKeySet,
|
||||||
FsCreators = fsServerObjects.FsCreators,
|
FsCreators = fsServerObjects.FsCreators,
|
||||||
|
StorageDeviceManagerFactory = fsServerObjects.StorageDeviceManagerFactory,
|
||||||
RandomGenerator = randomGenerator,
|
RandomGenerator = randomGenerator,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -263,7 +264,16 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
if (result.IsSuccess())
|
if (result.IsSuccess())
|
||||||
{
|
{
|
||||||
Ticket ticket = new(ticketFile.Get.AsStream());
|
// When reading a file from a Sha256PartitionFileSystem, you can't start a read in the middle
|
||||||
|
// of the hashed portion (usually the first 0x200 bytes) of the file and end the read after
|
||||||
|
// the end of the hashed portion, so we read the ticket file using a single read.
|
||||||
|
byte[] ticketData = new byte[0x2C0];
|
||||||
|
result = ticketFile.Get.Read(out long bytesRead, 0, ticketData);
|
||||||
|
|
||||||
|
if (result.IsFailure() || bytesRead != ticketData.Length)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Ticket ticket = new(new MemoryStream(ticketData));
|
||||||
var titleKey = ticket.GetTitleKey(KeySet);
|
var titleKey = ticket.GetTitleKey(KeySet);
|
||||||
|
|
||||||
if (titleKey != null)
|
if (titleKey != null)
|
||||||
|
@ -101,7 +101,7 @@ namespace Ryujinx.HLE
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control if the guest application should be told that there is a Internet connection available.
|
/// Control if the guest application should be told that there is a Internet connection available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal readonly bool EnableInternetAccess;
|
public bool EnableInternetAccess { internal get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control LibHac's integrity check level.
|
/// Control LibHac's integrity check level.
|
||||||
|
46
src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs
Normal file
46
src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
enum KMemoryPermission : uint
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
UserMask = Read | Write | Execute,
|
||||||
|
Mask = uint.MaxValue,
|
||||||
|
|
||||||
|
Read = 1 << 0,
|
||||||
|
Write = 1 << 1,
|
||||||
|
Execute = 1 << 2,
|
||||||
|
DontCare = 1 << 28,
|
||||||
|
|
||||||
|
ReadAndWrite = Read | Write,
|
||||||
|
ReadAndExecute = Read | Execute,
|
||||||
|
}
|
||||||
|
|
||||||
|
static class KMemoryPermissionExtensions
|
||||||
|
{
|
||||||
|
public static MemoryPermission Convert(this KMemoryPermission permission)
|
||||||
|
{
|
||||||
|
MemoryPermission output = MemoryPermission.None;
|
||||||
|
|
||||||
|
if (permission.HasFlag(KMemoryPermission.Read))
|
||||||
|
{
|
||||||
|
output = MemoryPermission.Read;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permission.HasFlag(KMemoryPermission.Write))
|
||||||
|
{
|
||||||
|
output |= MemoryPermission.Write;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permission.HasFlag(KMemoryPermission.Execute))
|
||||||
|
{
|
||||||
|
output |= MemoryPermission.Execute;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -203,15 +203,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission)
|
protected override Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission)
|
||||||
{
|
{
|
||||||
// TODO.
|
_cpuMemory.Reprotect(address, pagesCount * PageSize, permission.Convert());
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override Result ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission)
|
protected override Result ReprotectAndFlush(ulong address, ulong pagesCount, KMemoryPermission permission)
|
||||||
{
|
{
|
||||||
// TODO.
|
// TODO: Flush JIT cache.
|
||||||
return Result.Success;
|
|
||||||
|
return Reprotect(address, pagesCount, permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
@ -1255,7 +1255,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
|
|
||||||
if ((oldPermission & KMemoryPermission.Execute) != 0)
|
if ((oldPermission & KMemoryPermission.Execute) != 0)
|
||||||
{
|
{
|
||||||
result = ReprotectWithAttributes(address, pagesCount, permission);
|
result = ReprotectAndFlush(address, pagesCount, permission);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -3036,13 +3036,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
protected abstract Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission);
|
protected abstract Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Changes the permissions of a given virtual memory region.
|
/// Changes the permissions of a given virtual memory region, while also flushing the cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Virtual address of the region to have the permission changes</param>
|
/// <param name="address">Virtual address of the region to have the permission changes</param>
|
||||||
/// <param name="pagesCount">Number of pages to have their permissions changed</param>
|
/// <param name="pagesCount">Number of pages to have their permissions changed</param>
|
||||||
/// <param name="permission">New permission</param>
|
/// <param name="permission">New permission</param>
|
||||||
/// <returns>Result of the permission change operation</returns>
|
/// <returns>Result of the permission change operation</returns>
|
||||||
protected abstract Result ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission);
|
protected abstract Result ReprotectAndFlush(ulong address, ulong pagesCount, KMemoryPermission permission);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Alerts the memory tracking that a given region has been read from or written to.
|
/// Alerts the memory tracking that a given region has been read from or written to.
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user